- 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
}
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)) {
/* 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);
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
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}
# 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>
#
# 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
# 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>
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}
#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
#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 },
{ "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 }
};
{ "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 }
};
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 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);
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;
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;
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);
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));
}
}
}
+/*
+ * 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
*/