X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;f=class%2Fdeliver%2FDeliver.class.php;h=9952a06e660d731cf773f1e010a3c682e8f0cd0f;hb=7f0dbd10f69bc6e80ae98f86bca0b970e2131f1a;hp=7652bcd36923145e4b748219644adc67c5c98151;hpb=b4316b34bb52a7dfd042e165226b4f4b975be654;p=squirrelmail.git diff --git a/class/deliver/Deliver.class.php b/class/deliver/Deliver.class.php index 7652bcd3..9952a06e 100644 --- a/class/deliver/Deliver.class.php +++ b/class/deliver/Deliver.class.php @@ -1,15 +1,15 @@ rfc822_header; + function mail(&$message, $stream=false, $reply_id=0, $reply_ent_id=0, + $imap_stream=NULL, $extra=NULL) { + + $rfc822_header = &$message->rfc822_header; + if (count($message->entities)) { $boundary = $this->mimeBoundary(); $rfc822_header->content_type->properties['boundary']='"'.$boundary.'"'; @@ -45,16 +84,95 @@ class Deliver { $boundary=''; } $raw_length = 0; + + + // calculate reply header if needed + // + if ($reply_id) { + global $imapConnection, $username, $imapServerAddress, + $imapPort, $mailbox; + + // try our best to use an existing IMAP handle + // + $close_imap_stream = FALSE; + if (is_resource($imap_stream)) { + $my_imap_stream = $imap_stream; + + } else if (is_resource($imapConnection)) { + $my_imap_stream = $imapConnection; + + } else { + $close_imap_stream = TRUE; + $my_imap_stream = sqimap_login($username, FALSE, + $imapServerAddress, $imapPort, 0); + } + + sqimap_mailbox_select($my_imap_stream, $mailbox); + $reply_message = sqimap_get_message($my_imap_stream, $reply_id, $mailbox); + + if ($close_imap_stream) { + sqimap_logout($my_imap_stream); + } + + if ($reply_ent_id) { + /* redefine the messsage in case of message/rfc822 */ + $reply_message = $message->getEntity($reply_ent_id); + /* message is an entity which contains the envelope and type0=message + * and type1=rfc822. The actual entities are childs from + * $reply_message->entities[0]. That's where the encoding and is located + */ + + $orig_header = $reply_message->rfc822_header; /* here is the envelope located */ + + } else { + $orig_header = $reply_message->rfc822_header; + } + $message->reply_rfc822_header = $orig_header; + } + + $reply_rfc822_header = (isset($message->reply_rfc822_header) ? $message->reply_rfc822_header : ''); $header = $this->prepareRFC822_Header($rfc822_header, $reply_rfc822_header, $raw_length); + $this->send_mail($message, $header, $boundary, $stream, $raw_length, $extra); + + return $raw_length; + } + + /** + * function send_mail - send the message parts to the IMAP stream + * + * @param Message $message Message object to send + * @param string $header Headers ready to send + * @param string $boundary Message parts boundary + * @param resource $stream Handle to the SMTP stream + * (when FALSE, nothing will be + * written to the stream; this can + * be used to determine the actual + * number of bytes that will be + * written to the stream) + * @param int &$raw_length The number of bytes written (or that + * would have been written) to the + * output stream - NOTE that this is + * passed by reference + * @param mixed $extra Any implementation-specific variables + * can be passed in here and used in + * an overloaded version of this method + * if needed. + * + * @return void + * + */ + function send_mail($message, $header, $boundary, $stream=false, + &$raw_length, $extra=NULL) { + + if ($stream) { $this->preWriteToStream($header); $this->writeToStream($stream, $header); } $this->writeBody($message, $stream, $raw_length, $boundary); - return $raw_length; } /** @@ -65,6 +183,11 @@ class Deliver { * * @param Message $message Message object to transform * @param resource $stream SMTP output stream + * (when FALSE, nothing will be + * written to the stream; this can + * be used to determine the actual + * number of bytes that will be + * written to the stream) * @param integer &$length_raw raw length of the message (part) * as returned by mail fn * @param string $boundary custom boundary to call, usually for subparts @@ -123,6 +246,11 @@ class Deliver { * * @param Message $message Message object to transform * @param resource $stream SMTP output stream + * (when FALSE, nothing will be + * written to the stream; this can + * be used to determine the actual + * number of bytes that will be + * written to the stream) * @param integer &$length length of the message part * as returned by mail fn * @@ -151,8 +279,10 @@ class Deliver { } $last = $body_part; } elseif ($message->att_local_name) { + global $username, $attachment_dir; + $hashed_attachment_dir = getHashedDir($username, $attachment_dir); $filename = $message->att_local_name; - $file = fopen ($filename, 'rb'); + $file = fopen ($hashed_attachment_dir . '/' . $filename, 'rb'); while ($body_part = fgets($file, 4096)) { // remove NUL characters $body_part = str_replace("\0",'',$body_part); @@ -176,8 +306,10 @@ class Deliver { $this->writeToStream($stream, $body_part); } } elseif ($message->att_local_name) { + global $username, $attachment_dir; + $hashed_attachment_dir = getHashedDir($username, $attachment_dir); $filename = $message->att_local_name; - $file = fopen ($filename, 'rb'); + $file = fopen ($hashed_attachment_dir . '/' . $filename, 'rb'); while ($tmp = fread($file, 570)) { $body_part = chunk_split(base64_encode($tmp)); // Up to 4.3.10 chunk_split always appends a newline, @@ -331,6 +463,8 @@ class Deliver { } else { if ($mime_header->type0 == 'text' || $mime_header->type0 == 'message') { $header[] = 'Content-Transfer-Encoding: 8bit' . $rn; + } else if ($mime_header->type0 == 'multipart' || $mime_header->type0 == 'alternative') { + /* no-op; no encoding needed */ } else { $header[] = 'Content-Transfer-Encoding: base64' . $rn; } @@ -377,11 +511,14 @@ class Deliver { * * @return string $header */ - function prepareRFC822_Header($rfc822_header, $reply_rfc822_header, &$raw_length) { - global $domain, $version, $username, $encode_header_key, $edit_identity, $hide_auth_header; - - /* if server var SERVER_NAME not available, use $domain */ - if(!sqGetGlobalVar('SERVER_NAME', $SERVER_NAME, SQ_SERVER)) { + function prepareRFC822_Header(&$rfc822_header, $reply_rfc822_header, &$raw_length) { + global $domain, $username, $encode_header_key, + $edit_identity, $hide_auth_header; + + /* if server var SERVER_NAME not available, or contains + ":" (e.g. IPv6) which is illegal in a Message-ID, use $domain */ + if(!sqGetGlobalVar('SERVER_NAME', $SERVER_NAME, SQ_SERVER) || + strpos($SERVER_NAME,':') !== FALSE) { $SERVER_NAME = $domain; } @@ -394,16 +531,22 @@ class Deliver { $rn = "\r\n"; /* This creates an RFC 822 date */ - $date = date('D, j M Y H:i:s ', mktime()) . $this->timezone(); + $date = date('D, j M Y H:i:s ', time()) . $this->timezone(); + /* Create a message-id */ - $message_id = '<' . $REMOTE_PORT . '.'; - if (isset($encode_header_key) && trim($encode_header_key)!='') { - // use encrypted form of remote address - $message_id.= OneTimePadEncrypt($this->ip2hex($REMOTE_ADDR),base64_encode($encode_header_key)); - } else { - $message_id.= $REMOTE_ADDR; + $message_id = 'MESSAGE ID GENERATION ERROR! PLEASE CONTACT SQUIRRELMAIL DEVELOPERS'; + if (empty($rfc822_header->message_id)) { + $message_id = '<'; + /* user-specifc data to decrease collision chance */ + $seed_data = $username . '.'; + $seed_data .= (!empty($REMOTE_PORT) ? $REMOTE_PORT . '.' : ''); + $seed_data .= (!empty($REMOTE_ADDR) ? $REMOTE_ADDR . '.' : ''); + /* add the current time in milliseconds and randomness */ + $seed_data .= uniqid(mt_rand(),true); + /* put it through one-way hash and add it to the ID */ + $message_id .= md5($seed_data) . '.squirrel@' . $SERVER_NAME .'>'; } - $message_id .= '.' . time() . '.squirrel@' . $SERVER_NAME .'>'; + /* Make an RFC822 Received: line */ if (isset($REMOTE_HOST)) { $received_from = "$REMOTE_HOST ([$REMOTE_ADDR])"; @@ -426,39 +569,66 @@ class Deliver { * unless you understand all possible forging issues or your * webmail installation does not prevent changes in user's email address. * See SquirrelMail bug tracker #847107 for more details about it. + * + * Add $hide_squirrelmail_header as a candidate for config_local.php + * to allow completely hiding SquirrelMail participation in message + * processing; This is dangerous, especially if users can modify their + * account information, as it makes mapping a sent message back to the + * original sender almost impossible. */ - if (isset($encode_header_key) && + $show_sm_header = ( defined('hide_squirrelmail_header') ? ! hide_squirrelmail_header : 1 ); + + // FIXME: The following headers may generate slightly differently between the message sent to the destination and that stored in the Sent folder because this code will be called before both actions. This is not necessarily a big problem, but other headers such as Message-ID and Date are preserved between both actions + if ( $show_sm_header ) { + if (isset($encode_header_key) && trim($encode_header_key)!='') { // use encoded headers, if encryption key is set and not empty $header[] = 'X-Squirrel-UserHash: '.OneTimePadEncrypt($username,base64_encode($encode_header_key)).$rn; $header[] = 'X-Squirrel-FromHash: '.OneTimePadEncrypt($this->ip2hex($REMOTE_ADDR),base64_encode($encode_header_key)).$rn; if (isset($HTTP_X_FORWARDED_FOR)) $header[] = 'X-Squirrel-ProxyHash:'.OneTimePadEncrypt($this->ip2hex($HTTP_X_FORWARDED_FOR),base64_encode($encode_header_key)).$rn; - } else { + } else { // use default received headers $header[] = "Received: from $received_from" . $rn; - if ($edit_identity || ! isset($hide_auth_header) || ! $hide_auth_header) + if (!isset($hide_auth_header) || !$hide_auth_header) $header[] = " (SquirrelMail authenticated user $username)" . $rn; $header[] = " by $SERVER_NAME with HTTP;" . $rn; $header[] = " $date" . $rn; + } } /* Insert the rest of the header fields */ - $header[] = 'Message-ID: '. $message_id . $rn; - if (is_object($reply_rfc822_header) && + + if (!empty($rfc822_header->message_id)) { + $header[] = 'Message-ID: '. $rfc822_header->message_id . $rn; + } else { + $header[] = 'Message-ID: '. $message_id . $rn; + $rfc822_header->message_id = $message_id; + } + + if (is_object($reply_rfc822_header) && isset($reply_rfc822_header->message_id) && $reply_rfc822_header->message_id) { $rep_message_id = $reply_rfc822_header->message_id; - // $this->strip_crlf($message_id); $header[] = 'In-Reply-To: '.$rep_message_id . $rn; + $rfc822_header->in_reply_to = $rep_message_id; $references = $this->calculate_references($reply_rfc822_header); $header[] = 'References: '.$references . $rn; + $rfc822_header->references = $references; } - $header[] = "Date: $date" . $rn; + + if (!empty($rfc822_header->date) && $rfc822_header->date != -1) { + $header[] = 'Date: '. $rfc822_header->date . $rn; + } else { + $header[] = "Date: $date" . $rn; + $rfc822_header->date = $date; + } + $header[] = 'Subject: '.encodeHeader($rfc822_header->subject) . $rn; $header[] = 'From: '. $rfc822_header->getAddr_s('from',",$rn ",true) . $rn; - // folding address list [From|To|Cc|Bcc] happens by using ",$rn" as delimiter + // folding address list [From|To|Cc|Bcc] happens by using ",$rn" + // as delimiter // Do not use foldLine for that. // RFC2822 if from contains more then 1 address @@ -485,7 +655,7 @@ class Deliver { } } /* Identify SquirrelMail */ - $header[] = 'User-Agent: SquirrelMail/' . $version . $rn; + $header[] = 'User-Agent: SquirrelMail/' . SM_VERSION . $rn; /* Do the MIME-stuff */ $header[] = 'MIME-Version: 1.0' . $rn; $contenttype = 'Content-Type: '. $rfc822_header->content_type->type0 .'/'. @@ -501,7 +671,7 @@ class Deliver { if ($encoding = $rfc822_header->encoding) { $header[] = 'Content-Transfer-Encoding: ' . $encoding . $rn; } - if ($rfc822_header->dnt) { + if (isset($rfc822_header->dnt) && $rfc822_header->dnt) { $dnt = $rfc822_header->getAddr_s('dnt'); /* Pegasus Mail */ $header[] = 'X-Confirm-Reading-To: '.$dnt. $rn; @@ -543,7 +713,9 @@ class Deliver { $aRefs = explode(' ',$sRefs); $sLine = 'References:'; foreach ($aRefs as $sReference) { - if (strlen($sLine)+strlen($sReference) >76) { + if ( trim($sReference) == '' ) { + /* Don't add spaces. */ + } elseif (strlen($sLine)+strlen($sReference) >76) { $hdr_s .= $sLine; $sLine = $rn . ' ' . $sReference; } else { @@ -707,27 +879,36 @@ class Deliver { } /** - * function calculate_references - calculate correct Referer string + * function calculate_references - calculate correct References string + * Adds the current message ID, and makes sure it doesn't grow forever, + * to that extent it drops message-ID's in a smart way until the string + * length is under the recommended value of 1000 ("References: <986>\r\n"). + * It always keeps the first and the last three ID's. * * @param Rfc822Header $hdr message header to calculate from * - * @return string $refer concatenated and trimmed Referer string + * @return string $refer concatenated and trimmed References string */ function calculate_references($hdr) { - $refer = $hdr->references; + $aReferences = preg_split('/\s+/', $hdr->references); $message_id = $hdr->message_id; $in_reply_to = $hdr->in_reply_to; - if (strlen($refer) > 2) { - $refer .= ' ' . $message_id; - } else { - if ($in_reply_to) { - $refer .= $in_reply_to . ' ' . $message_id; - } else { - $refer .= $message_id; - } + + // if References already exists, add the current message ID at the end. + // no References exists; if we know a IRT, add that aswell + if (count($aReferences) == 0 && $in_reply_to) { + $aReferences[] = $in_reply_to; + } + $aReferences[] = $message_id; + + // sanitize the array: trim whitespace, remove dupes + array_walk($aReferences, 'sq_trim_value'); + $aReferences = array_unique($aReferences); + + while ( count($aReferences) > 4 && strlen(implode(' ', $aReferences)) >= 986 ) { + $aReferences = array_merge(array_slice($aReferences,0,1),array_slice($aReferences,2)); } - trim($refer); - return $refer; + return implode(' ', $aReferences); } /** @@ -736,7 +917,7 @@ class Deliver { * Function is used to convert ipv4 and ipv6 addresses to hex strings. * It removes all delimiter symbols from ip addresses, converts decimal * ipv4 numbers to hex and pads strings in order to present full length - * address. ipv4 addresses are represented as 8 byte strings, ipv6 addresses + * address. ipv4 addresses are represented as 8 byte strings, ipv6 addresses * are represented as 32 byte string. * * If function fails to detect address format, it returns unprocessed string. @@ -791,4 +972,3 @@ class Deliver { return $ret; } } -?>