From: David Levine Date: Thu, 6 Oct 2016 13:11:45 +0000 (-0400) Subject: Enable SMTP 8BITMIME for messages with 8-bit content: X-Git-Url: https://diplodocus.org/git/nmh/commitdiff_plain/641e461b10b6fd320ed6a13ec4075754d29bbb0b?ds=inline;hp=9fc84ef71655096d563cf9190ce3e127f638c6d9 Enable SMTP 8BITMIME for messages with 8-bit content: 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. --- diff --git a/docs/pending-release-notes b/docs/pending-release-notes index ffcfcc9f..bec2a5ae 100644 --- a/docs/pending-release-notes +++ b/docs/pending-release-notes @@ -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. +- 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 diff --git a/mts/smtp/smtp.c b/mts/smtp/smtp.c index b5e68fa1..c94ac313 100644 --- a/mts/smtp/smtp.c +++ b/mts/smtp/smtp.c @@ -441,19 +441,33 @@ rclient (char *server, char *service) } 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)) { diff --git a/mts/smtp/smtp.h b/mts/smtp/smtp.h index fb842fe6..df02c1a4 100644 --- a/mts/smtp/smtp.h +++ b/mts/smtp/smtp.h @@ -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 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); diff --git a/test/mhmail/test-mhmail b/test/mhmail/test-mhmail index ac7bc86d..2e7375f0 100755 --- a/test/mhmail/test-mhmail +++ b/test/mhmail/test-mhmail @@ -772,7 +772,7 @@ To: recipient@example.com 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 @@ -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 \ --headerfield Content-Transfer-Encoding:8bit" \ +-headerfield Content-Transfer-Encoding:7bit" \ -b 'with added header fields' [ ${failed:-0} -eq 0 ] || exit ${failed:-0} diff --git a/test/post/test-post-basic b/test/post/test-post-basic index c198007b..2cd512a6 100755 --- a/test/post/test-post-basic +++ b/test/post/test-post-basic @@ -16,7 +16,7 @@ fi # 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" < To: Somebody Else @@ -49,7 +49,7 @@ test_post "${testname}.actual" "${testname}.expected" # # Make sure a draft without a From: is rejected # - +start_test 'reject draft without a From:' cat > "${MH_TEST_DIR}/Mail/draft" < 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. # +start_test 'ignore Nmh-* header lines' cat > "${MH_TEST_DIR}/Mail/draft" < To: Somebody Else @@ -105,5 +106,103 @@ test_post "${testname}.actual" "${testname}.expected" \ 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" < +To: Somebody Else +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" < "${testname}.err.expected" <"${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" < BODY=8BITMIME +RCPT TO: +DATA +From: Mr Nobody +To: Somebody Else +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" < +To: Somebody Else +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" < BODY=8BITMIME +RCPT TO: +DATA +From: Mr Nobody +To: Somebody Else +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} diff --git a/uip/post.c b/uip/post.c index db9c0298..d1e58ebf 100644 --- a/uip/post.c +++ b/uip/post.c @@ -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 HMIM 0x2000 /* MIME-Version: header */ +#define HCTE 0x4000 /* Content-Transfer-Encoding: header */ /* * 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 MMIM 0x0100 /* We've seen Mime-Version: */ 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 }, + { "MIME-Version", HMIM, MMIM }, + { "Content-Transfer-Encoding", HCTE, 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 }, + { "MIME-Version", HMIM, MMIM }, + { "Content-Transfer-Encoding", HCTE, 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 */ +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 */ @@ -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 int scan_input (int, int *); 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; } - + 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; @@ -1600,7 +1617,6 @@ static void 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; @@ -1646,16 +1662,33 @@ post (char *file, int bccque, int talk, int eai, char *envelope, 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)) - || 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); - if ((fd = open (file, O_RDONLY)) == NOTOK) - die (file, "unable to re-open"); 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) { + /* 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)) - || rp_isbad (retval = sm_winit (envelope, eai))) { + || rp_isbad (retval = sm_winit (envelope, eai, eightbit))) { 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 */