1) Allow 8-bit characters in addresses.
2) Disable RFC 2047 encoding of (all) header field bodies, via
mhbuild -headerencoding 8bit.
3) Have post enable it in the SMTP transaction.
4) If enabled, add the commands to the SMTP transaction
(MAIL FROM: <address> BODY=8BITMIME SMTPUTF8).
Some things may changed, such as the 8bit encoding algorithm name.
test/post/test-post-dcc test/post/test-post-fcc \
test/post/test-post-multifrom test/post/test-post-envelope \
test/post/test-post-group test/post/test-mts test/post/test-messageid \
- test/post/test-sendfiles test/prompter/test-prompter \
+ test/post/test-rfc6531 test/post/test-sendfiles \
+ test/prompter/test-prompter \
test/rcv/test-rcvdist test/rcv/test-rcvpack test/rcv/test-rcvstore \
test/rcv/test-rcvtty test/refile/test-refile test/repl/test-convert \
test/repl/test-if-str test/repl/test-trailing-newline \
- post(8) -snoop now attempts to decode base64-encoded SMTP traffic.
- folder(1) -nocreate now prints a warning message for a non-existant folder.
- mhfixmsg(1) now allows -decodetext binary, though 8bit is still the default.
+- Support for SMTPUTF8 (RFC 6531) has been added. mhshow(1) already supported
+ RFC 6532, assuming all 8-bit message header field bodies are UTF-8.
-----------------
OBSOLETE FEATURES
int lequal (const char *, const char *);
int mfgets (FILE *, char **);
char *legal_person (const char *);
-struct adrx *getadrx (const char *);
+struct adrx *getadrx (const char *, int);
-.TH MHBUILD %manext1% "December 14, 2014" "%nmhversion%"
+.TH MHBUILD %manext1% "September 23, 2016" "%nmhversion%"
.\"
.\" %nmhwarning%
.\"
that contain 8\-bit characters.
The valid arguments are
.I base64
-for based\-64 encoding and
+for based\-64 encoding,
.I quoted
-for quoted\-printable encoding.
+for quoted\-printable encoding, and
+.I 8bit
+for raw 8-bit encoding, which requires that all header field bodies be
+encoded as UTF-8 (RFC 6530) and that the SMTP server support SMTPUTF8
+(RFC 6531).
The
.B \-autoheaderencoding
switch will instruct
.IR mhstore (1)
.PP
.I "Multipurpose Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies"
-(RFC 2045),
+(RFC 2045)
.PP
.I "Multipurpose Internet Mail Extensions (MIME) Part Two: Media Types"
-(RFC 2046),
+(RFC 2046)
.PP
.I "Multipurpose Internet Mail Extensions (MIME) Part Three: Message Header Extensions for Non-ASCII Text"
-(RFC 2047),
+(RFC 2047)
.PP
.I "MIME Parameter Value and Encoded Word Extensions: Character Sets, Languages, and Continuations"
(RFC 2231)
.PP
.I "Proposed Standard for Message Encapsulation"
-(RFC 934),
+(RFC 934)
.PP
.I "The Content-MD5 Header Field"
-(RFC 1864),
+(RFC 1864)
.PP
.I "Definition of the URL MIME External-Body Access-Type"
-(RFC 2017),
+(RFC 2017)
+.PP
+.I "Overview and Framework for Internationalized Email"
+(RFC 6530)
+.PP
+.I "SMTP Extension for Internationalized Email"
+(RFC 6531)
.SH DEFAULTS
.nf
\-autoheaderencoding
}
int
-sm_winit (char *from)
+sm_winit (char *from, int smtputf8)
{
- switch (smtalk (SM_MAIL, "MAIL FROM:<%s>", from)) {
+ 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;
+ }
+
+ switch (smtalk (SM_MAIL, "MAIL FROM:<%s>%s", from, mail_parameters)) {
case 250:
sm_addrs = 0;
return RP_OK;
/* int client (); */
int sm_init (char *, char *, char *, int, int, int, int, int, char *, char *,
const char *, int);
-int sm_winit (char *);
+int sm_winit (char *, int);
int sm_wadr (char *, char *, char *);
int sm_waend (void);
int sm_wtxt (char *, int);
getname (const char *addrs)
{
struct adrx *ap;
-
+ const char *const eai = context_find ("eai");
pers = mbox = host = route = grp = note = NULL;
err[0] = '\0';
- if ((ap = getadrx (addrs ? addrs : "")) == NULL)
+ if ((ap = getadrx (addrs ? addrs : "", eai && strncasecmp (eai, "8bit", 4) == 0)) == NULL) {
return NULL;
+ }
strncpy (adr, ap->text, sizeof(adr));
pers = ap->pers;
static struct adrx adrxs2;
+/* eai = Email Address Internationalization */
struct adrx *
-getadrx (const char *addrs)
+getadrx (const char *addrs, int eai)
{
register char *bp;
register struct adrx *adrxp = &adrxs2;
break;
}
- /*
- * Reject the address if key fields contain 8bit characters
- */
+ if (! eai) {
+ /*
+ * Reject the address if key fields contain 8bit characters
+ */
- if (contains8bit(mbox, NULL) || contains8bit(host, NULL) ||
- contains8bit(path, NULL) || contains8bit(grp, NULL)) {
- strcpy(err, "Address contains 8-bit characters");
+ if (contains8bit(mbox, NULL) || contains8bit(host, NULL) ||
+ contains8bit(path, NULL) || contains8bit(grp, NULL)) {
+ strcpy(err, "Address contains 8-bit characters");
+ }
}
if (err[0])
smtp_state = SMTP_DATA;
continue;
}
- if (xoauth != NULL) {
+ if (xoauth == NULL) {
+ putcrlf(conn, "250-ready");
+ putcrlf(conn, "250-8BITMIME");
+ putcrlf(conn, "250-SMTPUTF8");
+ } else {
/* XOAUTH2 support enabled; handle EHLO and AUTH. */
if (strncmp(line, "EHLO", 4) == 0) {
putcrlf(conn, "250-ready");
--- /dev/null
+#!/bin/sh
+######################################################
+#
+# Test SMTPUTF8 (RFC 6531) support.
+#
+######################################################
+
+set -e
+
+if test -z "${MH_OBJ_DIR}"; then
+ srcdir=`dirname $0`/../..
+ MH_OBJ_DIR=`cd $srcdir && pwd`; export MH_OBJ_DIR
+fi
+
+. "${srcdir}/test/post/test-post-common.sh"
+
+setup_test
+mhl="${MH_LIBEXEC_DIR}/mhl"
+
+#### Make sure that html-to-text conversion is what we expect.
+require_locale en_US.utf-8 en_US.utf8
+LC_ALL=en_US.UTF-8; export LC_ALL
+
+#### Enable EAI in mhbuild, via the profile.
+sed "s%^\(mhbuild: .*\)%\1 -headerencoding 8bit%" "$MH" >"$MH.new"
+mv -f "$MH.new" "$MH"
+
+# check SMTPUTF8 basic
+start_test 'SMTPUTF8 basic'
+
+cat > "${testname}.expected" <<EOF
+EHLO nosuchhost.example.com
+MAIL FROM:<senderø@example.com> BODY=8BITMIME SMTPUTF8
+RCPT TO:<recipientæ@example.com>
+DATA
+From: senderø@example.com
+To: recipientæ@example.com
+Subject: Blåbærsyltetøy
+MIME-Version: 1.0
+Content-Type: text/plain; charset="UTF-8"
+Content-Transfer-Encoding: 8bit
+Date:
+
+Blåbærsyltetøy er veldig godt. Jeg liker blåbærsyltetøy.
+Jøran.
+
+Text source:
+Arnt Gulbrandsen and Jiankang Yao, "Email Address Internationalization",
+Computing Edge 1:6, June 2015,
+http://www.computer.org/cms/computing-edge/ce-jun15-final.pdf, p. 50.
+.
+QUIT
+EOF
+
+cat > "${MH_TEST_DIR}/Mail/draft" <<EOF
+From: senderø@example.com
+To: recipientæ@example.com
+Subject: Blåbærsyltetøy
+--------
+Blåbærsyltetøy er veldig godt. Jeg liker blåbærsyltetøy.
+Jøran.
+
+Text source:
+Arnt Gulbrandsen and Jiankang Yao, "Email Address Internationalization",
+Computing Edge 1:6, June 2015,
+http://www.computer.org/cms/computing-edge/ce-jun15-final.pdf, p. 50.
+EOF
+
+test_post "${testname}.actual" "${testname}.expected" -eai
+
+
+finish_test
+exit $failed
cat > "${testname}.post-expected" <<EOF
<= 220 Not really an ESMTP server
=> EHLO nosuchhost.example.com
+<= 250-ready
+<= 250-8BITMIME
+<= 250-SMTPUTF8
<= 250 I'll buy that for a dollar!
=> MAIL FROM:<nobody@example.com>
+<= 250-ready
+<= 250-8BITMIME
+<= 250-SMTPUTF8
<= 250 I'll buy that for a dollar!
=> RCPT TO:<somebody@example.com>
+<= 250-ready
+<= 250-8BITMIME
+<= 250-SMTPUTF8
<= 250 I'll buy that for a dollar!
=> DATA
<= 354 Go ahead
DEFINE_SWITCH_ARRAY(MHBUILD, switches);
#undef X
+/* 8bit is Email Address Internationalization. */
#define MIMEENCODING_SWITCHES \
X("base64", 0, BASE64SW) \
X("quoted-printable", 0, QUOTEDPRINTSW) \
+ X("8bit", 0, EIGHTBITSW) \
#define X(sw, minchars, id) id,
DEFINE_SWITCH_ENUM(MIMEENCODING);
case QUOTEDPRINTSW:
header_encoding = CE_QUOTED;
break;
+ case EIGHTBITSW:
+ header_encoding = CE_8BIT;
+ break;
default:
adios (NULL, "Internal error: algorithm %s", cp);
}
}
m_getfld_state_destroy (&gstate);
- /*
- * Iterate through the list of headers and call the function to MIME-ify
- * them if required.
- */
-
- for (hp = ct->c_first_hf; hp != NULL; hp = hp->next) {
- if (encode_rfc2047(hp->name, &hp->value, header_encoding, NULL)) {
- adios(NULL, "Unable to encode header \"%s\"", hp->name);
- }
+ if (header_encoding != CE_8BIT) {
+ /*
+ * Iterate through the list of headers and call the function to MIME-ify
+ * them if required.
+ */
+
+ for (hp = ct->c_first_hf; hp != NULL; hp = hp->next) {
+ if (encode_rfc2047(hp->name, &hp->value, header_encoding, NULL)) {
+ adios(NULL, "Unable to encode header \"%s\"", hp->name);
+ }
+ }
}
/*
if (ct->c_descr) {
np = add (DESCR_FIELD, NULL);
vp = concat (" ", ct->c_descr, NULL);
- if (encode_rfc2047(DESCR_FIELD, &vp, header_encoding, NULL))
- adios(NULL, "Unable to encode %s header", DESCR_FIELD);
+ if (header_encoding != CE_8BIT) {
+ if (encode_rfc2047(DESCR_FIELD, &vp, header_encoding, NULL)) {
+ adios(NULL, "Unable to encode %s header", DESCR_FIELD);
+ }
+ }
add_header (ct, np, vp);
}
X("library directory", -7, LIBSW) /* interface from send, whom */ \
X("mime", 0, MIMESW) \
X("nomime", 0, NMIMESW) \
+ X("eai", 0, EAISW) \
+ X("noeai", 0, NEAISW) \
X("msgid", 0, MSGDSW) \
X("nomsgid", 0, NMSGDSW) \
X("verbose", 0, VERBSW) \
static int verbose = 0; /* spell it out */
static int format = 1; /* format addresses */
static int mime = 0; /* use MIME-style encapsulations for Bcc */
+static int eai = 0; /* use EAI (SMTPUTF8) */
static int msgid = 0; /* add msgid */
static int debug = 0; /* debugging post */
static int watch = 0; /* watch the delivery process */
mime = 0;
continue;
+ case EAISW:
+ eai = 1;
+ continue;
+ case NEAISW:
+ eai = 0;
+ continue;
+
case MSGDSW:
msgid++;
continue;
if ((in = fopen (msg, "r")) == NULL)
adios (msg, "unable to open");
+ /* Support EAI. */
+ if (eai) {
+ /* Add eai profile entry, to pass 8bit setting to getname()/getadrx(). */
+ add_profile_entry ("eai", "8bit");
+ }
+
start_headers ();
if (debug) {
verbose++;
break;
}
} else {
- if (rp_isbad (retval = sm_init (clientsw, serversw, port, watch,
- verbose, snoop, sasl, saslssf,
+ if (rp_isbad (retval = sm_init (clientsw, serversw, port, watch,
+ verbose, snoop, sasl, saslssf,
saslmech, user,
oauth_flag ? auth_svc : NULL, tls))
- || rp_isbad (retval = sm_winit (envelope)))
+ || rp_isbad (retval = sm_winit (envelope, eai))) {
die (NULL, "problem initializing server; %s", rp_string (retval));
+ }
do_addresses (bccque, talk && verbose);
if ((fd = open (file, O_RDONLY)) == NOTOK)
sigon ();
- if (!whomsw || checksw)
+ if (!whomsw || checksw) {
if (rp_isbad (retval = sm_init (clientsw, serversw, port, watch,
verbose, snoop, sasl, saslssf,
saslmech, user,
oauth_flag ? auth_svc : NULL, tls))
- || rp_isbad (retval = sm_winit (envelope)))
+ || rp_isbad (retval = sm_winit (envelope, eai))) {
die (NULL, "problem initializing server; %s", rp_string (retval));
+ }
+ }
if (talk && !whomsw)
printf (" -- Address Verification --\n");
X("noforward", 0, NFORWSW) \
X("mime", 0, MIMESW) \
X("nomime", 0, NMIMESW) \
+ X("eai", 0, EAISW) \
+ X("noeai", 0, NEAISW) \
X("msgid", 0, MSGDSW) \
X("nomsgid", 0, NMSGDSW) \
X("push", 0, PUSHSW) \
vec[vecp++] = --cp;
continue;
+ case EAISW:
+ vec[vecp++] = --cp;
+ continue;
+ case NEAISW:
+ vec[vecp++] = --cp;
+ continue;
+
case SNOOPSW:
snoop++;
vec[vecp++] = --cp;
closefds (3);
for (msgnum = 0; msgnum < msgp; msgnum++) {
- switch (sendsbr (vec, vecp, program, msgs[msgnum], &st, 1, auth_svc)) {
+ switch (sendsbr (vec, vecp, program, msgs[msgnum], &st, 1, auth_svc)) {
case DONE:
done (++status);
case NOTOK:
by longjmp. */
volatile int nvecs = vecp;
int *nvecsp = (int *) &nvecs;
+ int eai = 0;
/*
* Run the mimebuildproc (which is by default mhbuild) on the message
* with the addition of the "-auto" flag
*/
+ /* Make sure that EAI support is set up before calling buildmimeproc. */
+ for (i = 0; i < vecp; ++i) {
+ if (strcasecmp (vec[i], "-eai") == 0) {
+ eai = 1;
+ } else if (strcasecmp (vec[i], "-noeai") == 0) {
+ eai = 0;
+ }
+ }
+
switch (child = fork()) {
case NOTOK:
adios("fork", "unable to");
break;
case OK:
- buildvec = argsplit(buildmimeproc, &buildprogram, &i);
+ buildvec = argsplit(buildmimeproc, &buildprogram, &i);
buildvec[i++] = "-auto";
if (distfile)
buildvec[i++] = "-dist";
buildvec[i++] = (char *) drft;
+ if (eai) {
+ /* Add eai profile entry, to pass 8bit setting to
+ getname()/getadrx(). This doesn't seem to be necessary
+ now, but it's here just in case the code changes
+ later. */
+ add_profile_entry("eai", "8bit");
+
+ /* Add mhbuild switch to enable 8bit headers. */
+ buildvec[i++] = "-headerencoding";
+ buildvec[i++] = "8bit";
+ }
buildvec[i] = NULL;
execvp(buildprogram, buildvec);
fprintf(stderr, "unable to exec ");
X("noforward", 0, NFORWSW) \
X("mime", 0, MIMESW) \
X("nomime", 0, NMIMESW) \
+ X("eai", 0, EAISW) \
+ X("noeai", 0, NEAISW) \
X("msgid", 0, MSGDSW) \
X("nomsgid", 0, NMSGDSW) \
X("push", 0, SPSHSW) \
case NBITSTUFFSW:
case MIMESW:
case NMIMESW:
+ case EAISW:
+ case NEAISW:
case MSGDSW:
case NMSGDSW:
case WATCSW:
X("tls", TLSminc(-3), TLSSW) \
X("initialtls", TLSminc(-10), INITTLSSW) \
X("notls", TLSminc(-5), NTLSSW) \
+ X("eai", 0, EAISW) \
+ X("noeai", 0, NEAISW) \
X("mts smtp|sendmail/smtp|sendmail/pipe", 0, MTSSW) \
#define X(sw, minchars, id) id,
case TLSSW:
case INITTLSSW:
case NTLSSW:
+ case EAISW:
+ case NEAISW:
vec[vecp++] = --cp;
continue;