* a delivery backend.
*
* @author Marc Groot Koerkamp
- * @copyright © 1999-2005 The SquirrelMail Project Team
+ * @copyright © 1999-2007 The SquirrelMail Project Team
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
* @version $Id$
* @package squirrelmail
*/
class Deliver {
+ /**
+ * Most recently calculated Message-ID
+ * External code should NEVER access this directly!
+ * @var string
+ */
+ var $message_id;
+
/**
* function mail - send the message parts to the SMTP stream
*
- * @param Message $message Message class to send
- * @param resource $stream file handle to the SMTP stream
+ * @param Message $message Message object to send
+ * @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 string $reply_id Identifies message being replied to
+ * (OPTIONAL; caller should ONLY specify
+ * a value for this when the message
+ * being sent is a reply)
+ * @param string $reply_ent_id Identifies message being replied to
+ * in the case it was an embedded/attached
+ * message inside another (OPTIONAL; caller
+ * should ONLY specify a value for this
+ * when the message being sent is a reply)
+ * @param mixed $extra Any implementation-specific variables
+ * can be passed in here and used in
+ * an overloaded version of this method
+ * if needed.
+ *
+ * @return array An array containing at least these elements in this order:
+ * - The number of bytes written (or that would have been
+ * written) to the output stream
+ * - The message ID (WARNING: if $stream is FALSE, this
+ * may not be supplied, or may not be accurate)
*
- * @return integer $raw_length
*/
- function mail($message, $stream=false) {
+ function mail($message, $stream=false, $reply_id=0, $reply_ent_id=0,
+ $extra=NULL) {
+
$rfc822_header = $message->rfc822_header;
if (count($message->entities)) {
$boundary = $this->mimeBoundary();
$boundary='';
}
$raw_length = 0;
+
+
+ // calculate reply header if needed
+ //
+ if ($reply_id) {
+ global $imapConnection, $username, $imapServerAddress,
+ $imapPort, $mailbox;
+
+ if (!is_resource($imapConnection))
+ $imapConnection = sqimap_login($username, FALSE,
+ $imapServerAddress, $imapPort, 0);
+
+ sqimap_mailbox_select($imapConnection, $mailbox);
+ $reply_message = sqimap_get_message($imapConnection, $reply_id, $mailbox);
+
+ 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 array($raw_length, $this->message_id);
+ }
+
+ /**
+ * 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;
}
/**
*
* @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
*
* @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
*
}
$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);
$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,
* @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;
+ global $domain, $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)) {
$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 . '.';
+ $message_id = '<' . (!empty($REMOTE_PORT) ? $REMOTE_PORT . '.' : '');
+//FIXME: if $REMOTE_ADDR is missing, should we skip this if/else block? or perhaps try to generate it with some different kind of info?
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));
$message_id.= $REMOTE_ADDR;
}
$message_id .= '.' . time() . '.squirrel@' . $SERVER_NAME .'>';
+ $this->message_id = $message_id;
+
/* Make an RFC822 Received: line */
if (isset($REMOTE_HOST)) {
$received_from = "$REMOTE_HOST ([$REMOTE_ADDR])";
* 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 );
+
+ 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)
$header[] = " (SquirrelMail authenticated user $username)" . $rn;
$header[] = " by $SERVER_NAME with HTTP;" . $rn;
$header[] = " $date" . $rn;
+ }
}
/* Insert the rest of the header fields */
}
}
/* 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 .'/'.
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;
$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 {
}
/**
- * 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;
}
- trim($refer);
- return $refer;
+ $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));
+ }
+ return implode(' ', $aReferences);
}
/**
return $ret;
}
}
-
-?>
\ No newline at end of file