* Invision Power Services * IP.Board v3.2.3 * Parse Incoming Emails * Last Updated: $Date: 2011-10-03 11:31:57 -0400 (Mon, 03 Oct 2011) $ * * * @author $Author: mark $ * @copyright (c) 2010 Invision Power Services, Inc. * @license This is NULLED! * @package IP.Board * @subpackage Kernel * @link http://hatynka.in * @since 25th June 2010 * @version $Revision: 9573 $ */ class classIncomingEmail { public $ignore = FALSE; /** * Recipient Address * * @var string * @see __construct */ public $to; /** * Sender Address * * @var string * @see __construct */ public $from; /** * Subject * * @var string * @see __construct */ public $subject; /** * Message Body * * @var string * @see __construct */ public $message; /** * Attachments * This is an associative array of attachment data. * The keys are IDs which are associated with taks in $this->body. * The values are an array of data to insert into the attachments database table after attach_member_id and attach_rel_module have been set. * * @var array * @see __construct */ public $attachments; /** * Constructor * Sets $this->to, $this->from, $this->subject, $this->message and $this->attachments * * @param string Raw message with headers */ public function __construct( $email, $override=array() ) { $this->raw = $email; //-------------------------------------- // Get some stuff //-------------------------------------- if ( class_exists( 'ipsRegistry' ) ) { $this->DB = ipsRegistry::DB(); // Fetch upload path $this->upload_dir = ipsRegistry::$settings['upload_dir']; $this->upload_url = ipsRegistry::$settings['upload_url']; } else { $this->initDB(); // Fetch upload path $this->DB->build( array( 'select' => 'conf_key,conf_value', 'from' => 'core_sys_conf_settings', 'where' => "conf_key='upload_dir' OR conf_key='upload_url'" ) ); $this->DB->execute(); while ( $r = $this->DB->fetch() ) { if ( $r['conf_key'] == 'upload_dir' ) { $this->upload_dir = $r['conf_value']; } elseif ( $r['conf_key'] == 'upload_url' ) { $this->upload_url = $r['conf_value']; } } } // And allowed attachment types $this->types = array(); $this->DB->build( array( 'select' => '*', 'from' => 'attachments_type' ) ); $this->DB->execute(); while ( $r = $this->DB->fetch() ) { $this->types[ $r['atype_mimetype'] ][] = $r; } //-------------------------------------- // Pass to PEAR //-------------------------------------- // It raises strict warnings @error_reporting( E_NONE ); @ini_set( 'display_errors', 'off' ); require_once ( IPS_KERNEL_PATH . 'PEAR/Mail/mimeDecode.php' );/*noLibHook*/ $decoder = new Mail_mimeDecode( $email ); $mail = $decoder->decode( array( 'include_bodies' => TRUE, 'decode_bodies' => TRUE, 'decode_headers' => TRUE, ) ); //-------------------------------------- // Parse Headers //-------------------------------------- /* To */ if ( isset( $override['to'] ) ) { $this->to = $override['to']; } else { if ( $mail->headers['delivered-to'] ) { $mail->headers['to'] = $mail->headers['delivered-to']; } $to = array(); if ( strpos( $mail->headers['to'], ',' ) === FALSE ) { $mail->headers['to'] = array( $mail->headers['to'] ); } else { $mail->headers['to'] = explode( ',', $mail->headers['to'] ); } foreach ( $mail->headers['to'] as $_to ) { if ( preg_match( "/.+? <(.+?)>/", $_to, $matches ) ) { $to[] = htmlentities( $matches[1] ); } else { $to[] = htmlentities( trim( $_to, '<>' ) ); } } $this->to = implode( ',', $to ); } /* From */ if ( isset( $override['from'] ) ) { $this->from = $override['from']; } else { if ( preg_match( "/.+? <(.+?)>/", $mail->headers['from'], $matches ) ) { $this->from = htmlentities( $matches[1] ); } else { $this->from = htmlentities( trim( $mail->headers['from'], '<>' ) ); } } /* Subject */ if ( isset( $override['subject'] ) ) { $this->subject = $override['subject']; } else { $this->subject = ( (bool) trim( $mail->headers['subject'] ) ) ? $mail->headers['subject'] : '(No Subject)'; $this->subject = htmlentities( $this->subject ); } /* CC */ if ( strpos( $mail->headers['cc'], ',' ) === FALSE ) { $mail->headers['cc'] = array( $mail->headers['cc'] ); } else { $mail->headers['cc'] = explode( ',', $mail->headers['cc'] ); } foreach ( $mail->headers['cc'] as $_cc ) { if ( preg_match( "/.+? <(.+?)>/", $_cc, $matches ) ) { $cc[] = htmlentities( $matches[1] ); } else { $cc[] = htmlentities( trim( $_cc, '<>' ) ); } } $this->cc = str_replace( array( '>', '<' ), '', implode( ',', $cc ) ); //----------------------------------------- // Ignore? //----------------------------------------- $escapedFrom = $this->DB->addSlashes( $this->from ); $log = $this->DB->buildAndFetch( array( 'select' => '*', 'from' => 'core_incoming_email_log', 'where' => "log_email='{$escapedFrom}'" ) ); if ( $log['log_id'] ) { $oneMinuteAgo = time() - 60; if ( $log['log_time'] > $oneMinuteAgo ) { $this->ignore = TRUE; } $this->DB->update( 'core_incoming_email_log', array( 'log_time' => time() ), "log_id={$log['log_id']}" ); if ( $this->ignore ) { return; } } else { $this->DB->insert( 'core_incoming_email_log', array( 'log_email' => $this->from, 'log_time' => time() ) ); } //-------------------------------------- // Parse Body //-------------------------------------- $this->message = ''; $this->attachments = array(); $akey = 0; /* Normal message */ if ( $mail->ctype_primary == 'text' ) { $this->message = str_replace( array( "\n", "\r", "\r\n" ), '
', $this->cleanMessage( $mail->body ) ); } /* Normal message, sent in both HTML and normal */ elseif ( strtolower( $mail->ctype_primary ) == 'multipart' AND strtolower( $mail->ctype_secondary ) == 'alternative' ) { $this->message = ''; foreach ( $mail->parts as $part ) { if ( in_array( $part->ctype_secondary, array( 'plain', 'html' ) ) ) { $this->message = str_replace( array( "\n", "\r", "\r\n" ), '
', $this->cleanMessage( $part->body ) ); if ( strtolower( $part->ctype_secondary ) == 'plain' ) // We prefer plain text { break; } } } } /* Multipart message (with attachments and stuff) */ else { foreach ( $mail->parts as $part ) { if ( strtolower( $part->ctype_primary ) == 'multipart' AND strtolower( $part->ctype_secondary ) == 'alternative' ) { $add = ''; foreach ( $part->parts as $subpart ) { $add = str_replace( array( "\n", "\r", "\r\n" ), '
', $subpart->body ); if ( strtolower( $subpart->ctype_secondary ) == 'plain' ) { break; } } $this->message .= $add; } elseif ( $part->ctype_primary == 'text' ) { $this->message .= str_replace( array( "\n", "\r", "\r\n" ), '
', $this->cleanMessage( $part->body ) ); } else { $mime = "{$part->ctype_primary}/{$part->ctype_secondary}"; if ( isset( $this->types[ $mime ] ) ) { foreach ( $this->types[ $mime ] as $data ) { $name_parts = explode( '.', $part->ctype_parameters['name'] ); $ext = array_pop( $name_parts ); if ( $data['atype_extension'] == $ext and $data['atype_post'] ) { /* Create the file */ $masked_name = md5( uniqid( 'email' ) ) . "-{$part->ctype_parameters['name']}"; while ( is_file( $this->upload_dir . "/{$masked_name}" ) ) { $masked_name = md5( uniqid( 'email' ) . microtime() ) . "-{$part->ctype_parameters['name']}"; } file_put_contents( $this->upload_dir . "/{$masked_name}", $part->body ); /* Store attachment data */ $akey++; $this->attachments[ $akey ] = array( 'attach_ext' => $ext, 'attach_file' => $part->ctype_parameters['name'], 'attach_location' => $masked_name, 'attach_is_image' => ( $part->ctype_primary == 'image' ) ? 1 : 0, 'attach_date' => time(), 'attach_filesize' => $part->d_parameters['size'], ); $this->message .= ""; break; } } } } } } } /** * Route * Uses incoming Email rules to route Email */ public function route() { $unrouted = TRUE; $this->DB->build( array( 'select' => '*', 'from' => 'core_incoming_emails' ) ); $this->DB->execute(); while ( $row = $this->DB->fetch() ) { switch ( $row['rule_criteria_field'] ) { case 'to': $analyse = $this->to; break; case 'from': $analyse = $this->from; break; case 'sbjt': $analyse = $this->subject; break; case 'body': $analyse = $this->message; break; } $match = false; switch ( $row['rule_criteria_type'] ) { case 'ctns': $match = (bool) ( strpos( $analyse, $row['rule_criteria_value'] ) !== FALSE ); break; case 'eqls': if ( strpos( $analyse, ',' ) !== FALSE ) { $match = (bool) in_array( $analyse, explode( ',', $analyse ) ); } else { $match = (bool) ( $analyse == $row['rule_criteria_value'] ); } break; case 'regx': $match = (bool) ( preg_match( "/{$row['rule_criteria_value']}/", $analyse ) == 1 ); break; } if ( $match ) { $unrouted = FALSE; if ( $row['rule_app'] != '--' ) { $appdir = IPS_ROOT_PATH . 'applications_addon/ips/' . $row['rule_app']; if ( !is_dir( $appdir ) ) { $appdir = IPS_ROOT_PATH . 'applications_addon/other/' . $row['rule_app']; } if ( !is_dir( $appdir ) ) { $appdir = IPS_ROOT_PATH . 'applications/' . $row['rule_app']; } if ( is_file( $appdir . '/extensions/incomingEmails.php' ) ) { $class = 'incomingEmails_' . $row['rule_app']; require_once( $appdir . '/extensions/incomingEmails.php' );/*noLibHook*/ $class = new $class; $class->process( $this->DB, $this->to, $this->from, $this->subject, $this->message, $this->attachments, $this->raw, $this->cc ); } } break; } } if ( $unrouted ) { $apps = array(); foreach ( glob( IPS_ROOT_PATH . 'applications/*' ) as $f ) { if ( is_file( $f . '/extensions/incomingEmails.php' ) ) { $bits = explode( '/', $f ); $_appdir = array_pop( $bits ); $apps[ $_appdir ] = $f; } } foreach ( glob( IPS_ROOT_PATH . 'applications_addon/ips/*' ) as $f ) { if ( is_file( $f . '/extensions/incomingEmails.php' ) ) { $bits = explode( '/', $f ); $_appdir = array_pop( $bits ); $apps[ $_appdir ] = $f; } } foreach ( glob( IPS_ROOT_PATH . 'applications_addon/other/*' ) as $f ) { if ( is_file( $f . '/extensions/incomingEmails.php' ) ) { $bits = explode( '/', $f ); $_appdir = array_pop( $bits ); $apps[ $_appdir ] = $f; } } $routed = FALSE; foreach ( $apps as $_appdir => $appdir ) { require_once( $appdir . '/extensions/incomingEmails.php' );/*noLibHook*/ $class = 'incomingEmails_' . $_appdir; $i = new $class; if ( $routed = $i->handleUnrouted( $this->DB, $this->to, $this->from, $this->subject, $this->message, $this->attachments, $this->raw, $this->cc ) ) { break; } } if ( !$routed ) { $unroutedMessage = @file_get_contents( DOC_IPS_ROOT_PATH . 'interface/email/unrouted.txt' ); if ( $unroutedMessage ) { $unroutedMessage = nl2br( $unroutedMessage ) . '

'; foreach ( explode( "
", $this->message ) as $line ) { $unroutedMessage .= "> {$line}
"; } $this->DB->insert( 'mail_queue', array( 'mail_date' => time(), 'mail_to' => $this->from, 'mail_from' => $this->to, 'mail_subject' => "Re: {$this->subject}", 'mail_content' => $unroutedMessage, 'mail_html_on' => TRUE ) ); } } } } /** * Init DB * Since this class may be being called when ipsRegistry * is not set up, we may need to create a DB object * * @see route() */ protected function initDB() { /* Init */ $INFO = array(); @require( DOC_IPS_ROOT_PATH . 'conf_global.php' );/*noLibHook*/ $this->DB_driver = strtolower( $INFO['sql_driver'] ); require_once( IPS_KERNEL_PATH . 'classDb' . ucwords( $this->DB_driver ) . '.php' );/*noLibHook*/ $classname = "db_driver_" . $this->DB_driver; $this->DB = new $classname; $this->DB->obj['sql_database'] = $INFO['sql_database']; $this->DB->obj['sql_user'] = $INFO['sql_user']; $this->DB->obj['sql_pass'] = $INFO['sql_pass']; $this->DB->obj['sql_host'] = $INFO['sql_host']; $this->DB->obj['sql_charset'] = $INFO['sql_charset']; $this->DB->obj['sql_tbl_prefix'] = $INFO['sql_tbl_prefix'] ? $INFO['sql_tbl_prefix'] : ''; $this->DB->connect(); } /** * Clean message * Parses message and removes any nasty stuff * * @param string Message * @param string Cleaned Message */ protected function cleanMessage( $txt ) { //----------------------------------------- // First try gracfully removing stuff //----------------------------------------- $txt = preg_replace( "//U", '', $txt ); $txt = preg_replace( "/(.+?)<\/head>/ms", '', $txt ); $txt = preg_replace( "//", '', $txt ); $txt = str_replace( array( '', '' ), '', $txt ); $txt = preg_replace( "/(.+?)<\/script>/i", '', $txt ); $txt = preg_replace( "/style=['\"](.+?)['\"]/i", '', $txt ); $txt = str_replace( "