From f22ea7ba1875863890b486db3e5f448f99c1debc Mon Sep 17 00:00:00 2001
From: alecpl <alec@alec.pl>
Date: Thu, 07 Oct 2010 04:52:05 -0400
Subject: [PATCH] - Support SMTP Delivery Status Notifications - RFC3461 (#1486142)

---
 program/include/rcube_smtp.php          |   33 +++++++++++-----
 CHANGELOG                               |    1 
 program/steps/mail/compose.inc          |   23 +++++++++++
 config/main.inc.php.dist                |   19 ++++++---
 program/include/rcube_message.php       |    4 +-
 program/steps/mail/sendmail.inc         |    8 +++
 program/steps/settings/save_prefs.inc   |    1 
 program/localization/en_US/messages.inc |    1 
 program/steps/mail/func.inc             |    5 +-
 program/steps/settings/func.inc         |   10 +++++
 program/localization/en_US/labels.inc   |    2 +
 program/localization/pl_PL/labels.inc   |    2 +
 program/localization/pl_PL/messages.inc |    1 
 skins/default/templates/compose.html    |    3 +
 14 files changed, 91 insertions(+), 22 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index e408d48..0de16f2 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -21,6 +21,7 @@
 - Add unique index on users.username+users.mail_host
 - Make htmleditor option more consistent and add option to use HTML on reply to HTML message (#1485840)
 - Use empty envelope sender address for message disposition notifications (RFC2298.3)
+- Support SMTP Delivery Status Notifications - RFC3461 (#1486142)
 
 RELEASE 0.4.2
 -------------
diff --git a/config/main.inc.php.dist b/config/main.inc.php.dist
index 8ec7f08..bbf6149 100644
--- a/config/main.inc.php.dist
+++ b/config/main.inc.php.dist
@@ -334,12 +334,6 @@
 // if in your system 0 quota means no limit set this option to true 
 $rcmail_config['quota_zero_as_unlimited'] = false;
 
-// Behavior if a received message requests a message delivery notification (read receipt)
-// 0 = ask the user, 1 = send automatically, 2 = ignore (never send or ask)
-// 3 = send automatically if sender is in addressbook, otherwise ask the user
-// 4 = send automatically if sender is in addressbook, otherwise ignore
-$rcmail_config['mdn_requests'] = 0;
-
 // Make use of the built-in spell checker. It is based on GoogieSpell.
 // Since Google only accepts connections over https your PHP installatation
 // requires to be compiled with Open SSL support
@@ -571,5 +565,16 @@
 // when user is over quota and Trash is included in the quota.
 $rcmail_config['delete_always'] = false;
 
-// end of config file
+// Behavior if a received message requests a message delivery notification (read receipt)
+// 0 = ask the user, 1 = send automatically, 2 = ignore (never send or ask)
+// 3 = send automatically if sender is in addressbook, otherwise ask the user
+// 4 = send automatically if sender is in addressbook, otherwise ignore
+$rcmail_config['mdn_requests'] = 0;
 
+// Return receipt checkbox default state
+$rcmail_config['mdn_default'] = 0;
+
+// Delivery Status Notification checkbox default state
+$rcmail_config['dsn_default'] = 0;
+
+// end of config file
diff --git a/program/include/rcube_message.php b/program/include/rcube_message.php
index dfccb36..fd42e4a 100644
--- a/program/include/rcube_message.php
+++ b/program/include/rcube_message.php
@@ -399,9 +399,9 @@
                     if ($part_orig_mimetype == 'message/rfc822' && !empty($mail_part->filename))
                         $this->attachments[] = $mail_part;
                 }
-                // part text/[plain|html] OR message/delivery-status
+                // part text/[plain|html] or delivery status
                 else if ((($part_mimetype == 'text/plain' || $part_mimetype == 'text/html') && $mail_part->disposition != 'attachment') ||
-                    $part_mimetype == 'message/delivery-status' || $part_mimetype == 'message/disposition-notification'
+                    in_array($part_mimetype, array('message/delivery-status', 'text/rfc822-headers', 'message/disposition-notification'))
                 ) {
                     // Allow plugins to handle also this part
                     $plugin = $this->app->plugins->exec_hook('message_part_structure',
diff --git a/program/include/rcube_smtp.php b/program/include/rcube_smtp.php
index 61eebf3..5acc156 100644
--- a/program/include/rcube_smtp.php
+++ b/program/include/rcube_smtp.php
@@ -153,17 +153,16 @@
    *               each RFC822 valid. This may contain recipients not
    *               specified in the headers, for Bcc:, resending
    *               messages, etc.
-   *
    * @param mixed  The message headers to send with the mail
    *               Either as an associative array or a finally
    *               formatted string
-   *
    * @param mixed  The full text of the message body, including any Mime parts
    *               or file handle
+   * @param array  Delivery options (e.g. DSN request)
    *
    * @return bool  Returns true on success, or false on error
    */
-  public function send_mail($from, $recipients, &$headers, &$body)
+  public function send_mail($from, $recipients, &$headers, &$body, $opts=null)
   {
     if (!is_object($this->conn))
       return false;
@@ -183,7 +182,7 @@
     else
     {
       $this->reset();
-      $this->response[] .= "Invalid message headers";
+      $this->response[] = "Invalid message headers";
       return false;
     }
 
@@ -191,8 +190,22 @@
     if (!isset($from))
     {
       $this->reset();
-      $this->response[] .= "No From address has been provided";
+      $this->response[] = "No From address has been provided";
       return false;
+    }
+
+    // RFC3461: Delivery Status Notification
+    if ($opts['dsn']) {
+      $exts = $this->conn->getServiceExtensions();
+
+      if (!isset($exts['DSN'])) {
+        $this->error = array('label' => 'smtpdsnerror');
+        $this->response[] = "DSN not supported";
+        return false;
+      }
+
+      $from_params      = 'RET=HDRS';
+      $recipient_params = 'NOTIFY=SUCCESS,FAILURE';
     }
 
     // RFC2298.3: remove envelope sender address
@@ -203,12 +216,12 @@
     }
 
     // set From: address
-    if (PEAR::isError($this->conn->mailFrom($from)))
+    if (PEAR::isError($this->conn->mailFrom($from, $from_params)))
     {
       $err = $this->conn->getResponse();
       $this->error = array('label' => 'smtpfromerror', 'vars' => array(
         'from' => $from, 'code' => $this->conn->_code, 'msg' => $err[1]));
-      $this->response[] .= "Failed to set sender '$from'";
+      $this->response[] = "Failed to set sender '$from'";
       $this->reset();
       return false;
     }
@@ -225,11 +238,11 @@
     // set mail recipients
     foreach ($recipients as $recipient)
     {
-      if (PEAR::isError($this->conn->rcptTo($recipient))) {
+      if (PEAR::isError($this->conn->rcptTo($recipient, $recipient_params))) {
         $err = $this->conn->getResponse();
         $this->error = array('label' => 'smtptoerror', 'vars' => array(
           'to' => $recipient, 'code' => $this->conn->_code, 'msg' => $err[1]));
-        $this->response[] .= "Failed to add recipient '$recipient'";
+        $this->response[] = "Failed to add recipient '$recipient'";
         $this->reset();
         return false;
       }
@@ -261,7 +274,7 @@
         $msg = $result->getMessage();
 
       $this->error = array('label' => 'smtperror', 'vars' => array('msg' => $msg));
-      $this->response[] .= "Failed to send data";
+      $this->response[] = "Failed to send data";
       $this->reset();
       return false;
     }
diff --git a/program/localization/en_US/labels.inc b/program/localization/en_US/labels.inc
index a34be5f..aace461 100644
--- a/program/localization/en_US/labels.inc
+++ b/program/localization/en_US/labels.inc
@@ -209,6 +209,7 @@
 $labels['charset']        = 'Charset';
 $labels['editortype']     = 'Editor type';
 $labels['returnreceipt']  = 'Return receipt';
+$labels['dsn']            = 'Delivery status notification';
 
 $labels['editidents']    = 'Edit identities';
 $labels['checkspelling'] = 'Check spelling';
@@ -374,6 +375,7 @@
 $labels['previewpanemarkread']  = 'Mark previewed messages as read';
 $labels['afternseconds']  = 'after $n seconds';
 $labels['reqmdn'] = 'Always request a return receipt';
+$labels['reqdsn'] = 'Always request a delivery status notification';
 
 $labels['folder']  = 'Folder';
 $labels['folders']  = 'Folders';
diff --git a/program/localization/en_US/messages.inc b/program/localization/en_US/messages.inc
index 900a6db..26fa36a 100644
--- a/program/localization/en_US/messages.inc
+++ b/program/localization/en_US/messages.inc
@@ -109,6 +109,7 @@
 $messages['smtpfromerror'] = 'SMTP Error ($code): Failed to set sender "$from" ($msg)';
 $messages['smtptoerror'] = 'SMTP Error ($code): Failed to add recipient "$to" ($msg)';
 $messages['smtprecipientserror'] = 'SMTP Error: Unable to parse recipients list';
+$messages['smtpdsnerror'] = 'SMTP Error: No support for Delivery Status Notifications';
 $messages['smtperror'] = 'SMTP Error: $msg';
 $messages['emailformaterror'] = 'Invalid e-mail address: $email';
 $messages['toomanyrecipients'] = 'Too many recipients. Reduce the number of recipients to $max.';
diff --git a/program/localization/pl_PL/labels.inc b/program/localization/pl_PL/labels.inc
index b083fb5..804b3d1 100644
--- a/program/localization/pl_PL/labels.inc
+++ b/program/localization/pl_PL/labels.inc
@@ -360,5 +360,7 @@
 $labels['editidents'] = 'Edytuj tożsamości';
 $labels['addmailreplyto'] = 'Dodaj Mail-Reply-To';
 $labels['addmailfollowupto'] = 'Dodaj Mail-Followup-To';
+$labels['dsn'] = 'Status dostarczenia (DSN)';
+$labels['reqdsn'] = 'Zawsze żądaj statusu dostarczenia (DSN)';
 
 ?>
diff --git a/program/localization/pl_PL/messages.inc b/program/localization/pl_PL/messages.inc
index 781ad0b..91b3c90 100644
--- a/program/localization/pl_PL/messages.inc
+++ b/program/localization/pl_PL/messages.inc
@@ -114,6 +114,7 @@
 $messages['smtpfromerror'] = 'Błąd SMTP ($code): Nie można ustawić nadawcy "$from" ($msg)';
 $messages['smtptoerror'] = 'Błąd SMTP ($code): Nie można dodać odbiorcy "$to" ($msg)';
 $messages['smtprecipientserror'] = 'Błąd SMTP: Parsowanie listy odbiorców nie powiodło się';
+$messages['smtpdsnerror'] = 'Błąd SMTP: Statusy dostarczenia (DSN) nie są obsługiwane przez serwer';
 $messages['smtperror'] = 'Błąd SMTP: $msg';
 $messages['emailformaterror'] = 'Błędny adres e-mail: $email';
 $messages['toomanyrecipients'] = 'Zbyt wielu odbiorców. Zmniejsz ich liczbę do $max.';
diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc
index c1f4914..406033c 100644
--- a/program/steps/mail/compose.inc
+++ b/program/steps/mail/compose.inc
@@ -1174,6 +1174,28 @@
 }
 
 
+function rcmail_dsn_checkbox($attrib)
+{
+  global $RCMAIL;
+
+  list($form_start, $form_end) = get_form_tags($attrib);
+  unset($attrib['form']);
+
+  if (!isset($attrib['id']))
+    $attrib['id'] = 'dsn';
+
+  $attrib['name'] = '_dsn';
+  $attrib['value'] = '1';
+  $checkbox = new html_checkbox($attrib);
+
+  $out = $form_start ? "$form_start\n" : '';
+  $out .= $checkbox->show($RCMAIL->config->get('dsn_default'));
+  $out .= $form_end ? "\n$form_end" : '';
+
+  return $out;
+}
+
+
 function rcmail_editor_selector($attrib)
 {
   global $CONFIG, $MESSAGE, $compose_mode;
@@ -1251,6 +1273,7 @@
   'priorityselector' => 'rcmail_priority_selector',
   'editorselector' => 'rcmail_editor_selector',
   'receiptcheckbox' => 'rcmail_receipt_checkbox',
+  'dsncheckbox' => 'rcmail_dsn_checkbox',
   'storetarget' => 'rcmail_store_target_selection',
 ));
 
diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc
index aad127c..0fa2275 100644
--- a/program/steps/mail/func.inc
+++ b/program/steps/mail/func.inc
@@ -1478,10 +1478,11 @@
  * @param array  $mailto     Array of recipient address strings
  * @param array  $smtp_error SMTP error array (reference)
  * @param string $body_file  Location of file with saved message body (reference)
+ * @param array  $smtp_opts  SMTP options (e.g. DSN request)
  *
  * @return boolean Send status.
  */
-function rcmail_deliver_message(&$message, $from, $mailto, &$smtp_error, &$body_file)
+function rcmail_deliver_message(&$message, $from, $mailto, &$smtp_error, &$body_file, $smtp_opts=null)
 {
   global $CONFIG, $RCMAIL;
 
@@ -1525,7 +1526,7 @@
     if (!is_object($RCMAIL->smtp))
       $RCMAIL->smtp_init(true);
 
-    $sent = $RCMAIL->smtp->send_mail($from, $a_recipients, $smtp_headers, $msg_body);
+    $sent = $RCMAIL->smtp->send_mail($from, $a_recipients, $smtp_headers, $msg_body, $smtp_opts);
     $smtp_response = $RCMAIL->smtp->get_response();
     $smtp_error = $RCMAIL->smtp->get_error();
 
diff --git a/program/steps/mail/sendmail.inc b/program/steps/mail/sendmail.inc
index a9ecf2e..da79c2f 100644
--- a/program/steps/mail/sendmail.inc
+++ b/program/steps/mail/sendmail.inc
@@ -546,7 +546,13 @@
     $OUTPUT->send('iframe'); 
   }
 
-  $sent = rcmail_deliver_message($MAIL_MIME, $from, $mailto, $smtp_error, $mailbody_file);
+  // Handle Delivery Status Notification request
+  if (!empty($_POST['_dsn'])) {
+    $smtp_opts['dsn'] = true;
+  }
+
+  $sent = rcmail_deliver_message($MAIL_MIME, $from, $mailto,
+    $smtp_error, $mailbody_file, $smtp_opts);
 
   // return to compose page if sending failed
   if (!$sent)
diff --git a/program/steps/settings/func.inc b/program/steps/settings/func.inc
index a7d4c11..e0aa562 100644
--- a/program/steps/settings/func.inc
+++ b/program/steps/settings/func.inc
@@ -524,6 +524,16 @@
       );
     }
 
+    if (!isset($no_override['dsn_default'])) {
+      $field_id = 'rcmfd_dsn_default';
+      $input_dsn = new html_checkbox(array('name' => '_dsn_default', 'id' => $field_id, 'value' => 1));
+
+      $blocks['main']['options']['dsn_default'] = array(
+        'title' => html::label($field_id, Q(rcube_label('reqdsn'))),
+        'content' => $input_dsn->show($config['dsn_default']?1:0),
+      );
+    }
+
     if (!isset($no_override['top_posting'])) {
       $field_id = 'rcmfd_top_posting';
       $select_replymode = new html_select(array('name' => '_top_posting', 'id' => $field_id, 'onchange' => "\$('#rcmfd_sig_above').attr('disabled',this.selectedIndex==0)"));
diff --git a/program/steps/settings/save_prefs.inc b/program/steps/settings/save_prefs.inc
index ae3d6d7..63654ee 100644
--- a/program/steps/settings/save_prefs.inc
+++ b/program/steps/settings/save_prefs.inc
@@ -66,6 +66,7 @@
       'mime_param_folding' => isset($_POST['_mime_param_folding']) ? intval($_POST['_mime_param_folding']) : 0,
       'force_7bit'         => isset($_POST['_force_7bit']) ? TRUE : FALSE,
       'mdn_default'        => isset($_POST['_mdn_default']) ? TRUE : FALSE,
+      'dsn_default'        => isset($_POST['_dsn_default']) ? TRUE : FALSE,
       'show_sig'           => isset($_POST['_show_sig']) ? intval($_POST['_show_sig']) : 1,
       'top_posting'        => !empty($_POST['_top_posting']),
       'strip_existing_sig' => isset($_POST['_strip_existing_sig']),
diff --git a/skins/default/templates/compose.html b/skins/default/templates/compose.html
index f99afdd..99015ab 100644
--- a/skins/default/templates/compose.html
+++ b/skins/default/templates/compose.html
@@ -137,6 +137,9 @@
         <td><label for="rcmcomposereceipt"><roundcube:label name="returnreceipt" />:</label></td>
         <td><roundcube:object name="receiptCheckBox" form="form" id="rcmcomposereceipt" /></td>
     </tr><tr>
+        <td><label for="rcmcomposedsn"><roundcube:label name="dsn" />:</label></td>
+        <td><roundcube:object name="dsnCheckBox" form="form" id="rcmcomposedsn" /></td>
+    </tr><tr>
         <td><label for="rcmcomposepriority"><roundcube:label name="priority" />:</label></td>
         <td><roundcube:object name="prioritySelector" form="form" id="rcmcomposepriority" /></td>
     </tr><tr>

--
Gitblit v1.9.1