]> diplodocus.org Git - nmh/commitdiff
Enable SMTP 8BITMIME for messages with 8-bit content:
authorDavid Levine <levinedl@acm.org>
Thu, 6 Oct 2016 13:11:45 +0000 (09:11 -0400)
committerDavid Levine <levinedl@acm.org>
Thu, 6 Oct 2016 13:17:08 +0000 (09:17 -0400)
1) In post, look for a Content-Transfer-Encoding header.  It has to
   be the header for the message, not any MIME parts.  If found,
   post trusts that it's correct.  If there isn't one, post scans
   the entire message body for any 8-bit bytes.

2) If the message body is 8-bit:
   If the server supports 8BITMIME, enable it.

   If the server doesn't support 8BITMIME, fail with a message to
   user that they need to encode the message for 7-bit transport.

docs/pending-release-notes
mts/smtp/smtp.c
mts/smtp/smtp.h
test/mhmail/test-mhmail
test/post/test-post-basic
uip/post.c

index ffcfcc9f357752d5510cab8ca87aca078decf7f6..bec2a5aed103460845eea2022d85897e492691a9 100644 (file)
@@ -55,6 +55,9 @@ NEW FEATURES
 - mhfixmsg now replaces RFC 2047 encoding with RFC 2231 encoding of name and
   filename parameters in Content-Type and Content-Disposition headers,
   respectively.
 - mhfixmsg now replaces RFC 2047 encoding with RFC 2231 encoding of name and
   filename parameters in Content-Type and Content-Disposition headers,
   respectively.
+- If a message body contains 8-bit bytes, post(8) uses SMTP 8BITMIME if the
+  server supports it.  If not, post fails with a message to the user to
+  encode the message for 7-bit transport.
 
 -----------------
 OBSOLETE FEATURES
 
 -----------------
 OBSOLETE FEATURES
index b5e68fa1fa6b03cc90d8ea8e3f2ab4046febed45..c94ac31308063d2de637c531b857779b5051bae3 100644 (file)
@@ -441,19 +441,33 @@ rclient (char *server, char *service)
 }
 
 int
 }
 
 int
-sm_winit (char *from, int smtputf8)
+sm_winit (char *from, int smtputf8, int eightbit)
 {
 {
-    const char *const mail_parameters = smtputf8
-        ? " BODY=8BITMIME SMTPUTF8"
-        : "";
-
-    /* Just for information, if an attempt is made to send to an 8-bit
-       address without specifying SMTPUTF8, Gmail responds with
-       555 5.5.2 Syntax error.
-       Gmail doesn't require the 8BITMIME, but RFC 6531 Sec. 1.2 does. */
-    if (smtputf8  &&  (! EHLOset("8BITMIME") || ! EHLOset("SMTPUTF8"))) {
-       sm_end (NOTOK);
-       return RP_UCMD;
+    const char *mail_parameters = "";
+
+    if (smtputf8) {
+        /* Just for information, if an attempt is made to send to an 8-bit
+           address without specifying SMTPUTF8, Gmail responds with
+           555 5.5.2 Syntax error.
+           Gmail doesn't require the 8BITMIME, but RFC 6531 Sec. 1.2 does. */
+        if (EHLOset ("8BITMIME")  &&  EHLOset ("SMTPUTF8")) {
+            mail_parameters = " BODY=8BITMIME SMTPUTF8";
+        } else {
+            sm_end (NOTOK);
+            return RP_UCMD;
+        }
+    } else if (eightbit) {
+        /* Comply with RFC 6152, for messages that have any 8-bit characters
+           in their body. */
+        if (EHLOset ("8BITMIME")) {
+            mail_parameters = " BODY=8BITMIME";
+        } else {
+            advise (NULL, "SMTP server does not support 8BITMIME, not sending.\n"
+                    "Suggest encoding message for 7-bit transport by setting your\n"
+                    "locale to C, and/or specifying *b64 in mhbuild directives.\n");
+            sm_end (NOTOK);
+            return RP_UCMD;
+        }
     }
 
     switch (smtalk (SM_MAIL, "MAIL FROM:<%s>%s", from, mail_parameters)) {
     }
 
     switch (smtalk (SM_MAIL, "MAIL FROM:<%s>%s", from, mail_parameters)) {
index fb842fe6f241dd1b8b52036d3808aae0f2eea1ef..df02c1a4a47cd2a756d5679b9a3d3f00b21f5860 100644 (file)
@@ -18,7 +18,7 @@ struct smtp {
 /* int client (); */
 int sm_init (char *, char *, char *, int, int, int, int, const char *,
              const char *, const char *, int);
 /* int client (); */
 int sm_init (char *, char *, char *, int, int, int, int, const char *,
              const char *, const char *, int);
-int sm_winit (char *, int);
+int sm_winit (char *, int, int);
 int sm_wadr (char *, char *, char *);
 int sm_waend (void);
 int sm_wtxt (char *, int);
 int sm_wadr (char *, char *, char *);
 int sm_waend (void);
 int sm_wtxt (char *, int);
index ac7bc86d9031007191f4635377e7c9e287e8ca4b..2e7375f0b6e087f9e9dae3542bdcd99bdca86c00 100755 (executable)
@@ -772,7 +772,7 @@ To: recipient@example.com
 From: sender27@example.com
 MIME-Version: 1.0
 Content-Type: text/plain;charset=utf-8
 From: sender27@example.com
 MIME-Version: 1.0
 Content-Type: text/plain;charset=utf-8
-Content-Transfer-Encoding: 8bit
+Content-Transfer-Encoding: 7bit
 Date:
 
 with added header fields
 Date:
 
 with added header fields
@@ -783,7 +783,7 @@ EOF
 test_mhmail "$expected" \
   "-from sender27@example.com -headerfield MIME-Version:1.0 \
 -headerfield Content-Type:text/plain;charset=utf-8 \
 test_mhmail "$expected" \
   "-from sender27@example.com -headerfield MIME-Version:1.0 \
 -headerfield Content-Type:text/plain;charset=utf-8 \
--headerfield Content-Transfer-Encoding:8bit" \
+-headerfield Content-Transfer-Encoding:7bit" \
   -b 'with added header fields'
 
 [ ${failed:-0} -eq 0 ] || exit ${failed:-0}
   -b 'with added header fields'
 
 [ ${failed:-0} -eq 0 ] || exit ${failed:-0}
index c198007b780c754bfb6fc864a05f9c36ca12da91..2cd512a62751b042cb3ac3fea84e3f90c4575f2b 100755 (executable)
@@ -16,7 +16,7 @@ fi
 # Basic test - Simple message, single user, single recipient.  Note that
 # we test dot-stuffing here as well.
 #
 # Basic test - Simple message, single user, single recipient.  Note that
 # we test dot-stuffing here as well.
 #
-
+start_test 'simple message'
 cat > "${MH_TEST_DIR}/Mail/draft" <<EOF
 From: Mr Nobody <nobody@example.com>
 To: Somebody Else <somebody@example.com>
 cat > "${MH_TEST_DIR}/Mail/draft" <<EOF
 From: Mr Nobody <nobody@example.com>
 To: Somebody Else <somebody@example.com>
@@ -49,7 +49,7 @@ test_post "${testname}.actual" "${testname}.expected"
 #
 # Make sure a draft without a From: is rejected
 #
 #
 # Make sure a draft without a From: is rejected
 #
-
+start_test 'reject draft without a From:'
 cat > "${MH_TEST_DIR}/Mail/draft" <<EOF
 To: Somebody Else <somebody@example.com>
 Subject: Blank Test
 cat > "${MH_TEST_DIR}/Mail/draft" <<EOF
 To: Somebody Else <somebody@example.com>
 Subject: Blank Test
@@ -67,6 +67,7 @@ send: message not delivered to anyone"
 # Make sure that empty Nmh-* header lines are ignored, and that post
 # warns about non-empty ones.
 #
 # Make sure that empty Nmh-* header lines are ignored, and that post
 # warns about non-empty ones.
 #
+start_test 'ignore Nmh-* header lines'
 cat > "${MH_TEST_DIR}/Mail/draft" <<EOF
 From: Mr Nobody <nobody@example.com>
 To: Somebody Else <somebody@example.com>
 cat > "${MH_TEST_DIR}/Mail/draft" <<EOF
 From: Mr Nobody <nobody@example.com>
 To: Somebody Else <somebody@example.com>
@@ -105,5 +106,103 @@ test_post "${testname}.actual" "${testname}.expected" \
 
 check "${testname}.send_output" "${testname}.expected_send_output"
 
 
 check "${testname}.send_output" "${testname}.expected_send_output"
 
+#
+# 8-bit without 8BITMIME support
+#
+start_test '8-bit without 8BITMIME support'
+cat > "${MH_TEST_DIR}/Mail/draft" <<EOF
+From: Mr Nobody <nobody@example.com>
+To: Somebody Else <somebody@example.com>
+Subject: Test
+MIME-Version: 1.0
+Content-Type: text/plain; charset="utf-8"
+Content-Transfer-Encoding: 8bit
+
+This is a test
+.
+EOF
+
+cat > "${testname}.expected" <<EOF
+EHLO nosuchhost.example.com
+RSET
+QUIT
+EOF
+
+cat > "${testname}.err.expected" <<EOF
+post: SMTP server does not support 8BITMIME, not sending.
+Suggest encoding message for 7-bit transport by setting your
+locale to C, and/or specifying *b64 in mhbuild directives.
+
+post: problem initializing server; [BHST] ready; I'll buy that for a dollar!
+send: message not delivered to anyone
+EOF
+
+set +e
+test_post "${testname}.actual" "${testname}.expected" 2>"${testname}.err"
+set +e
+check "${testname}.err" "${testname}.err.expected"
+
+#
+# 8-bit with 8BITMIME support
+#
+start_test '8-bit with 8BITMIME support'
+# Cheat: SMTPUTF8 enables 8BITMIME in fakestmp
+SMTPUTF8=1; export SMTPUTF8
+cat > "${testname}.expected" <<EOF
+EHLO nosuchhost.example.com
+MAIL FROM:<nobody@example.com> BODY=8BITMIME
+RCPT TO:<somebody@example.com>
+DATA
+From: Mr Nobody <nobody@example.com>
+To: Somebody Else <somebody@example.com>
+Subject: Test
+MIME-Version: 1.0
+Content-Type: text/plain; charset="utf-8"
+Content-Transfer-Encoding: 8bit
+Date:
+
+This is a test
+..
+.
+QUIT
+EOF
+test_post "${testname}.actual" "${testname}.expected"
+
+
+#
+# 8-bit with 8BITMIME support, inferred from content
+#
+start_test '8-bit, inferred, with 8BITMIME support'
+cat > "${MH_TEST_DIR}/Mail/draft" <<EOF
+From: Mr Nobody <nobody@example.com>
+To: Somebody Else <somebody@example.com>
+Subject: Test
+MIME-Version: 1.0
+Content-Type: text/plain; charset="utf-8"
+
+This is a test, with a non-ascii character:  §
+.
+EOF
+
+cat > "${testname}.expected" <<EOF
+EHLO nosuchhost.example.com
+MAIL FROM:<nobody@example.com> BODY=8BITMIME
+RCPT TO:<somebody@example.com>
+DATA
+From: Mr Nobody <nobody@example.com>
+To: Somebody Else <somebody@example.com>
+Subject: Test
+MIME-Version: 1.0
+Content-Type: text/plain; charset="utf-8"
+Date:
+
+This is a test, with a non-ascii character:  §
+..
+.
+QUIT
+EOF
+test_post "${testname}.actual" "${testname}.expected"
+
 
 
+finish_test
 exit ${failed:-0}
 exit ${failed:-0}
index db9c02986cb078f223045c404af0588762e57fdf..d1e58ebf8e6037053d5e743d0ea0eceeb66a347f 100644 (file)
@@ -156,6 +156,8 @@ struct headers {
 #define        HDCC  0x0400    /* another undocumented feature                       */
 #define HONE  0x0800   /* Only (zero or) one address allowed                 */
 #define HEFM  0x1000   /* Envelope-From: header                              */
 #define        HDCC  0x0400    /* another undocumented feature                       */
 #define HONE  0x0800   /* Only (zero or) one address allowed                 */
 #define HEFM  0x1000   /* Envelope-From: header                              */
+#define HMIM  0x2000    /* MIME-Version: header                               */
+#define HCTE  0x4000    /* Content-Transfer-Encoding: header                  */
 
 /*
  * flags for headers->set
 
 /*
  * flags for headers->set
@@ -168,7 +170,7 @@ struct headers {
 #define MSND  0x0020   /* we've seen a Sender:      */
 #define MRSN  0x0040   /* We've seen a Resent-Sendr:*/
 #define MEFM  0x0080   /* We've seen Envelope-From: */
 #define MSND  0x0020   /* we've seen a Sender:      */
 #define MRSN  0x0040   /* We've seen a Resent-Sendr:*/
 #define MEFM  0x0080   /* We've seen Envelope-From: */
-
+#define MMIM  0x0100    /* We've seen Mime-Version:  */
 
 static struct headers NHeaders[] = {
     { "Return-Path",   HBAD,                0 },
 
 static struct headers NHeaders[] = {
     { "Return-Path",   HBAD,                0 },
@@ -185,6 +187,8 @@ static struct headers NHeaders[] = {
     { "Message-ID",    HBAD,                0 },
     { "Fcc",           HFCC,                0 },
     { "Envelope-From", HADR|HONE|HEFM,      MEFM },
     { "Message-ID",    HBAD,                0 },
     { "Fcc",           HFCC,                0 },
     { "Envelope-From", HADR|HONE|HEFM,      MEFM },
+    { "MIME-Version",  HMIM,                MMIM },
+    { "Content-Transfer-Encoding",  HCTE,   0 },
     { NULL,            0,                   0 }
 };
 
     { NULL,            0,                   0 }
 };
 
@@ -208,6 +212,8 @@ static struct headers RHeaders[] = {
     { "Bcc",               HADR|HTRY|HBCC|HNIL, 0 },
     { "Fcc",               HIGN,                0 },
     { "Envelope-From",     HADR|HONE|HEFM,      MEFM },
     { "Bcc",               HADR|HTRY|HBCC|HNIL, 0 },
     { "Fcc",               HIGN,                0 },
     { "Envelope-From",     HADR|HONE|HEFM,      MEFM },
+    { "MIME-Version",      HMIM,                MMIM },
+    { "Content-Transfer-Encoding",  HCTE,       0 },
     { NULL,                0,                   0 }
 };
 
     { NULL,                0,                   0 }
 };
 
@@ -257,6 +263,8 @@ static char fullfrom[BUFSIZ];       /* full contents of From header  */
 static char *filter = NULL;    /* the filter for BCC'ing        */
 static char *subject = NULL;   /* the subject field for BCC'ing */
 static char *fccfold[FCCS];    /* foldernames for FCC'ing       */
 static char *filter = NULL;    /* the filter for BCC'ing        */
 static char *subject = NULL;   /* the subject field for BCC'ing */
 static char *fccfold[FCCS];    /* foldernames for FCC'ing       */
+enum encoding { UNKNOWN = 0, BINARY = 1, SEVENBIT = 7, EIGHTBIT = 8 };
+static enum encoding cte = UNKNOWN;
 
 static struct headers  *hdrtab;        /* table for the message we're doing */
 
 
 static struct headers  *hdrtab;        /* table for the message we're doing */
 
@@ -297,6 +305,7 @@ static void fcc (char *, char *);
 static void die (char *, char *, ...);
 static void post (char *, int, int, int, char *, int, char *);
 static void do_text (char *file, int fd);
 static void die (char *, char *, ...);
 static void post (char *, int, int, int, char *, int, char *);
 static void do_text (char *file, int fd);
+static int scan_input (int, int *);
 static void do_an_address (struct mailname *, int);
 static void do_addresses (int, int);
 static int find_prefix (void);
 static void do_an_address (struct mailname *, int);
 static void do_addresses (int, int);
 static int find_prefix (void);
@@ -830,7 +839,15 @@ putfmt (char *name, char *str, int *eai, FILE *out)
        insert_fcc (hdr, pp);
        return;
     }
        insert_fcc (hdr, pp);
        return;
     }
-
+    if (hdr->flags & HCTE) {
+        if (strncasecmp (str, "7bit", 4) == 0) {
+            cte = SEVENBIT;
+        } else if (strncasecmp (str, "8bit", 4) == 0) {
+            cte = EIGHTBIT;
+        } else if (strncasecmp (str, "binary", 6) == 0) {
+            cte = BINARY;
+        }
+    }
     if (!(hdr->flags & HADR)) {
        fprintf (out, "%s: %s", name, str);
        return;
     if (!(hdr->flags & HADR)) {
        fprintf (out, "%s: %s", name, str);
        return;
@@ -1600,7 +1617,6 @@ static void
 post (char *file, int bccque, int talk, int eai, char *envelope,
       int oauth_flag, char *auth_svc)
 {
 post (char *file, int bccque, int talk, int eai, char *envelope,
       int oauth_flag, char *auth_svc)
 {
-    int fd;
     int        retval, i;
     pid_t child_id;
 
     int        retval, i;
     pid_t child_id;
 
@@ -1646,16 +1662,33 @@ post (char *file, int bccque, int talk, int eai, char *envelope,
                break;
        }
     } else {
                break;
        }
     } else {
+        const int fd = open (file, O_RDONLY);
+        int eightbit = 0;
+
+        if (fd == NOTOK) {
+          die (file, "unable to re-open");
+        }
+
+        if (msgflags & MMIM  &&  cte != UNKNOWN) {
+            /* MIME message with C-T-E header.  (BINARYMIME isn't
+               supported, use 8BITMIME instead for binary.) */
+            eightbit = cte != SEVENBIT;
+        } else {
+            if (scan_input (fd, &eightbit) == NOTOK) {
+                close (fd);
+                die (file, "problem reading from");
+            }
+        }
+
        if (rp_isbad (retval = sm_init (clientsw, serversw, port, watch,
                                        verbose, snoop, sasl, saslmech, user,
                                        oauth_flag ? auth_svc : NULL, tls))
        if (rp_isbad (retval = sm_init (clientsw, serversw, port, watch,
                                        verbose, snoop, sasl, saslmech, user,
                                        oauth_flag ? auth_svc : NULL, tls))
-            || rp_isbad (retval = sm_winit (envelope, eai))) {
+               || rp_isbad (retval = sm_winit (envelope, eai, eightbit))) {
+           close (fd);
            die (NULL, "problem initializing server; %s", rp_string (retval));
        }
 
         do_addresses (bccque, talk && verbose);
            die (NULL, "problem initializing server; %s", rp_string (retval));
        }
 
         do_addresses (bccque, talk && verbose);
-        if ((fd = open (file, O_RDONLY)) == NOTOK)
-          die (file, "unable to re-open");
         do_text (file, fd);
         close (fd);
         fflush (stdout);
         do_text (file, fd);
         close (fd);
         fflush (stdout);
@@ -1688,10 +1721,13 @@ verify_all_addresses (int talk, int eai, char *envelope, int oauth_flag,
     sigon ();
 
     if (!whomsw || checksw) {
     sigon ();
 
     if (!whomsw || checksw) {
+        /* Not sending message body, so don't need to use 8BITMIME. */
+        const int eightbit = 0;
+
        if (rp_isbad (retval = sm_init (clientsw, serversw, port, watch,
                                        verbose, snoop, sasl, saslmech, user,
                                        oauth_flag ? auth_svc : NULL, tls))
        if (rp_isbad (retval = sm_init (clientsw, serversw, port, watch,
                                        verbose, snoop, sasl, saslmech, user,
                                        oauth_flag ? auth_svc : NULL, tls))
-               || rp_isbad (retval = sm_winit (envelope, eai))) {
+               || rp_isbad (retval = sm_winit (envelope, eai, eightbit))) {
            die (NULL, "problem initializing server; %s", rp_string (retval));
        }
     }
            die (NULL, "problem initializing server; %s", rp_string (retval));
        }
     }
@@ -1819,6 +1855,27 @@ do_text (char *file, int fd)
 }
 
 
 }
 
 
+/*
+ * See if input has any 8-bit bytes.
+ */
+static int
+scan_input (int fd, int *eightbit) {
+    int state;
+    char buf[BUFSIZ];
+
+    lseek (fd, (off_t) 0, SEEK_SET);
+
+    while ((state = read (fd, buf, sizeof buf)) > 0) {
+        if (contains8bit (buf, buf + state)) {
+            *eightbit = 1;
+            return OK;
+        }
+    }
+
+    return state == NOTOK  ?  NOTOK  :  OK;
+}
+
+
 /*
  * SIGNAL HANDLING
  */
 /*
  * SIGNAL HANDLING
  */