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 \
- 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.
- inc(1) and msgchk(1) now support TLS encryption natively.
+- 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 and use
+ of a UTF-8 locale.
-----------------
OBSOLETE FEATURES
void mnfree(struct mailname *);
int ismymbox(struct mailname *);
+/*
+ * Enable Email Address Internationalization, which requires
+ * support of 8-bit addresses.
+ */
+void enable_eai();
+
/*
* Parse an address header, and return a sequence of email addresses.
* This function is the main entry point into the nmh address parser.
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 base\-64 encoding,
.I quoted
-for quoted\-printable encoding.
+for quoted\-printable encoding, and
+.I utf\-8
+which requires that all 8\-bit header field bodies be encoded as UTF\-8
+(RFC 6530) and that the message be sent to a SMTP server that supports
+SMTPUTF8 (RFC 6531).
The
.B \-autoheaderencoding
-switch will instruct
+switch instructs
.B mhbuild
-to automatically pick the algorithm that results in a shorter encoded string.
+to automatically pick the encoding, either base64 or quoted\-printable,
+that results in a shorter encoded string.
.PP
Putting this all together,
here is an example of a more complicated message draft.
.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, const char *,
- const char *, const char *, int);
-int sm_winit (char *);
+ const char *, const char *, int);
+int sm_winit (char *, int);
int sm_wadr (char *, char *, char *);
int sm_waend (void);
int sm_wtxt (char *, int);
static char *note = NULL;
static char err[BUFSIZ];
static char adr[BUFSIZ];
+static int eai = 0;
+
+void
+enable_eai() {
+ eai = 1;
+}
char *
getname (const char *addrs)
{
struct adrx *ap;
-
pers = mbox = host = route = grp = note = NULL;
err[0] = '\0';
- if ((ap = getadrx (addrs ? addrs : "")) == NULL)
+ if ((ap = getadrx (addrs ? addrs : "", eai)) == 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");
# check with incompatible locale
# This is here as a place holder. We should either try to run the (8-bit) text
# through iconv, or refuse to display it if the locale is incompatible. In the
-# cAse of EAI, that means not UTF-8.
+# case of EAI, that means not UTF-8.
# start_test 'incompatible locale'
# LC_ALL=C
# run_test "$mhl -form mhl.headers `mhpath last`" \
--- /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 utf-8%" "$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"
+
+
+finish_test
+exit $failed
Connecting to 127.0.0.1:${localport}...
<= 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
+/* utf-8 is for Email Address Internationalization, using SMTPUTF8. */
#define MIMEENCODING_SWITCHES \
X("base64", 0, BASE64SW) \
X("quoted-printable", 0, QUOTEDPRINTSW) \
+ X("utf-8", 0, UTF8SW) \
#define X(sw, minchars, id) id,
DEFINE_SWITCH_ENUM(MIMEENCODING);
case QUOTEDPRINTSW:
header_encoding = CE_QUOTED;
break;
+ case UTF8SW:
+ 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);
}
/*
* static prototypes
*/
-static void putfmt (char *, char *, FILE *);
+static void putfmt (char *, char *, int *, FILE *);
static void start_headers (void);
static void finish_headers (FILE *);
static int get_header (char *, struct headers *);
static int annoaux (struct mailname *);
static void insert_fcc (struct headers *, char *);
static void make_bcc_file (int);
-static void verify_all_addresses (int, char *, int, char *);
+static void verify_all_addresses (int, int, char *, int, char *);
static void chkadr (void);
static void sigon (void);
static void sigoff (void);
static void p_refile (char *);
static void fcc (char *, char *);
static void die (char *, char *, ...);
-static void post (char *, int, int, char *, int, char *);
+static void post (char *, int, int, int, char *, int, char *);
static void do_text (char *file, int fd);
static void do_an_address (struct mailname *, int);
static void do_addresses (int, int);
main (int argc, char **argv)
{
int state, compnum, dashstuff = 0, swnum, oauth_flag = 0;
+ int eai = 0; /* use Email Address Internationalization (EAI) (SMTPUTF8) */
char *cp, *msg = NULL, **argp, **arguments, *envelope;
char buf[BUFSIZ], name[NAMESZ], *auth_svc = NULL;
FILE *in, *out;
switch (state = m_getfld (&gstate, name, buf, &bufsz, in)) {
case FLD:
case FLDPLUS:
- compnum++;
+ compnum++;
cp = add (buf, NULL);
while (state == FLDPLUS) {
bufsz = sizeof buf;
state = m_getfld (&gstate, name, buf, &bufsz, in);
cp = add (buf, cp);
}
- putfmt (name, cp, out);
+ putfmt (name, cp, &eai, out);
free (cp);
continue;
/* If we are doing a "whom" check */
if (whomsw) {
/* This won't work with MTS_SENDMAIL_PIPE. */
- verify_all_addresses (1, envelope, oauth_flag, auth_svc);
+ verify_all_addresses (1, eai, envelope, oauth_flag, auth_svc);
done (0);
}
verify_all_addresses with MTS_SENDMAIL_PIPE, but
that might require running sendmail as root. Note
that spost didn't verify addresses. */
- verify_all_addresses (verbose, envelope, oauth_flag, auth_svc);
+ verify_all_addresses (verbose, eai, envelope, oauth_flag,
+ auth_svc);
}
- post (tmpfil, 0, verbose, envelope, oauth_flag, auth_svc);
+ post (tmpfil, 0, verbose, eai, envelope, oauth_flag, auth_svc);
}
- post (bccfil, 1, verbose, envelope, oauth_flag, auth_svc);
+ post (bccfil, 1, verbose, eai, envelope, oauth_flag, auth_svc);
(void) m_unlink (bccfil);
} else {
- post (tmpfil, 0, isatty (1), envelope, oauth_flag, auth_svc);
+ post (tmpfil, 0, isatty (1), eai, envelope, oauth_flag, auth_svc);
}
p_refile (tmpfil);
*/
static void
-putfmt (char *name, char *str, FILE *out)
+putfmt (char *name, char *str, int *eai, FILE *out)
{
int count, grp, i, keep;
char *cp, *pp, *qp;
return;
}
+ if (! *eai) {
+ /* Check each header field value to see if it has any 8-bit characters.
+ If it does, enable EAI support. */
+ if (contains8bit(str, NULL)) {
+ if (verbose) {
+ printf ("EAI/SMTPUTF8 enabled\n");
+ }
+
+ /* Enable SMTPUTF8. */
+ *eai = 1;
+
+ /* Enable passing of utf-8 setting to getname()/getadrx(). */
+ enable_eai();
+ }
+ }
+
if ((i = get_header (name, hdrtab)) == NOTOK) {
- if (strncasecmp (name, "nmh-", 4)) {
+ if (strncasecmp (name, "nmh-", 4)) {
fprintf (out, "%s: %s", name, str);
} else {
/* Filter out all Nmh-* headers, because Norm asked. They
}
tmpaddrs.m_next = NULL;
- for (count = 0; (cp = getname (str)); count++)
+
+ for (count = 0; (cp = getname (str)); count++) {
if ((mp = getm (cp, NULL, 0, error, sizeof(error)))) {
if (tmpaddrs.m_next)
np->m_next = mp;
else
badmsg++;
}
+ }
if (count < 1) {
if (hdr->flags & HNIL)
*/
static void
-post (char *file, int bccque, int talk, 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;
break;
}
} else {
- if (rp_isbad (retval = sm_init (clientsw, serversw, port, watch,
- verbose, snoop, sasl, saslmech, user,
+ 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)))
+ || 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)
/* Address Verification */
static void
-verify_all_addresses (int talk, char *envelope, int oauth_flag, char *auth_svc)
+verify_all_addresses (int talk, int eai, char *envelope, int oauth_flag,
+ char *auth_svc)
{
int retval;
struct mailname *lp;
sigon ();
- if (!whomsw || checksw)
+ if (!whomsw || checksw) {
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)))
+ || rp_isbad (retval = sm_winit (envelope, eai))) {
die (NULL, "problem initializing server; %s", rp_string (retval));
+ }
+ }
if (talk && !whomsw)
printf (" -- Address Verification --\n");
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:
break;
case OK:
- buildvec = argsplit(buildmimeproc, &buildprogram, &i);
+ buildvec = argsplit(buildmimeproc, &buildprogram, &i);
buildvec[i++] = "-auto";
if (distfile)
buildvec[i++] = "-dist";