From: David Levine Date: Sun, 25 Sep 2016 13:28:05 +0000 (-0400) Subject: Merge remote-tracking branch 'origin' into smtputf8 X-Git-Url: https://diplodocus.org/git/nmh/commitdiff_plain/aaf014c77a4fb19bdc33370f5b6af5b8497decf8?hp=-c Merge remote-tracking branch 'origin' into smtputf8 --- aaf014c77a4fb19bdc33370f5b6af5b8497decf8 diff --combined Makefile.am index ca46a4cd,4197dc75..cb1c39fb --- a/Makefile.am +++ b/Makefile.am @@@ -94,8 -94,7 +94,8 @@@ TESTS = test/ali/test-ali test/anno/tes 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 \ @@@ -118,8 -117,8 +118,8 @@@ check_PROGRAMS = test/getfullname test/ ## distcheck, $nmhetcdir. For distcheck, prepend $MH_INST_DIR (from ## test/common.sh.in), which is based on $MH_TEST_DIR (from ## $TESTS_ENVIRONMENT). - DISTCHECK_CONFIGURE_FLAGS = DISABLE_SETGID_MAIL=1 \ - NMHETCDIRINST='$${abs_builddir}/test/testdir/inst' + AM_DISTCHECK_CONFIGURE_FLAGS = DISABLE_SETGID_MAIL=1 \ + NMHETCDIRINST='$${abs_builddir}/test/testdir/inst' ## ## Stuff that should be cleaned via "make clean" @@@ -198,9 -197,9 +198,9 @@@ noinst_LIBRARIES = sbr/libmh.a mts/libm noinst_HEADERS = h/addrsbr.h h/aliasbr.h h/crawl_folders.h h/dropsbr.h \ h/fmt_compile.h h/fmt_scan.h h/icalendar.h h/md5.h h/mf.h \ h/mh.h h/mhcachesbr.h h/mhparse.h h/mime.h \ - h/mts.h h/nmh.h h/picksbr.h h/popsbr.h h/prototypes.h \ - h/rcvmail.h h/scansbr.h h/signals.h h/tws.h h/utils.h \ - mts/smtp/smtp.h sbr/ctype-checked.h h/oauth.h \ + h/mts.h h/nmh.h h/netsec.h h/picksbr.h h/popsbr.h \ + h/prototypes.h h/rcvmail.h h/scansbr.h h/signals.h h/tws.h \ + h/utils.h mts/smtp/smtp.h sbr/ctype-checked.h h/oauth.h \ thirdparty/jsmn/jsmn.h ## @@@ -241,7 -240,8 +241,8 @@@ dist_doc_DATA = COPYRIGHT INSTALL NEWS ## docs_contribdir = $(docdir)/contrib dist_docs_contrib_SCRIPTS = docs/contrib/replyfilter docs/contrib/build_nmh \ - docs/contrib/ml docs/contrib/vpick + docs/contrib/localpostproc docs/contrib/ml \ + docs/contrib/vpick docs/contrib/replaliases dist_docs_contrib_DATA = docs/contrib/replaliases ## @@@ -343,7 -343,8 +344,8 @@@ uip_forw_SOURCES = uip/forw.c uip/whatn uip_forw_LDADD = $(LDADD) $(READLINELIB) $(TERMLIB) $(ICONVLIB) $(POSTLINK) uip_inc_SOURCES = uip/inc.c uip/scansbr.c uip/dropsbr.c uip/popsbr.c - uip_inc_LDADD = $(LDADD) $(TERMLIB) $(ICONVLIB) $(SASLLIB) $(CURLLIB) $(POSTLINK) + uip_inc_LDADD = $(LDADD) $(TERMLIB) $(ICONVLIB) $(SASLLIB) $(CURLLIB) \ + $(TLSLIB) $(POSTLINK) uip_install_mh_SOURCES = uip/install-mh.c uip_install_mh_LDADD = $(LDADD) $(POSTLINK) @@@ -390,7 -391,7 +392,7 @@@ uip_mhstore_SOURCES = uip/mhstore.c uip uip_mhstore_LDADD = $(LDADD) $(TERMLIB) $(ICONVLIB) $(POSTLINK) uip_msgchk_SOURCES = uip/msgchk.c uip/popsbr.c - uip_msgchk_LDADD = $(LDADD) $(SASLLIB) $(CURLLIB) $(POSTLINK) + uip_msgchk_LDADD = $(LDADD) $(SASLLIB) $(CURLLIB) $(TLSLIB) $(POSTLINK) uip_new_SOURCES = uip/new.c uip_new_LDADD = $(LDADD) $(POSTLINK) @@@ -610,7 -611,8 +612,8 @@@ sbr_libmh_a_SOURCES = sbr/addrsbr.c sbr sbr/m_draft.c sbr/m_getfld.c sbr/m_gmprot.c \ sbr/m_maildir.c sbr/m_name.c sbr/m_popen.c sbr/m_rand.c \ sbr/makedir.c sbr/md5.c sbr/message_id.c \ - sbr/mime_type.c sbr/mts.c sbr/norm_charmap.c sbr/path.c \ + sbr/mime_type.c sbr/mts.c sbr/netsec.c \ + sbr/norm_charmap.c sbr/path.c \ sbr/peekc.c sbr/pidwait.c sbr/pidstatus.c \ sbr/print_help.c sbr/print_sw.c sbr/print_version.c \ sbr/push.c sbr/putenv.c sbr/refile.c sbr/remdir.c \ diff --combined docs/pending-release-notes index 70ed4ff6,4dfc9ec5..f44eb066 --- a/docs/pending-release-notes +++ b/docs/pending-release-notes @@@ -48,8 -48,7 +48,10 @@@ NEW FEATURE - 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. + - 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. ++ RFC 6532, assuming all 8-bit message header field bodies are UTF-8 and use ++ of a UTF-8 locale. ----------------- OBSOLETE FEATURES diff --combined mts/smtp/smtp.c index 47215a8b,4fb6b1f4..b5e68fa1 --- a/mts/smtp/smtp.c +++ b/mts/smtp/smtp.c @@@ -11,28 -11,9 +11,9 @@@ #include #include #include - #include - - #ifdef CYRUS_SASL - #include - #include - # if SASL_VERSION_FULL < 0x020125 - /* Cyrus SASL 2.1.25 introduced the sasl_callback_ft prototype, - which has an explicit void parameter list, according to best - practice. So we need to cast to avoid compile warnings. - Provide this prototype for earlier versions. */ - typedef int (*sasl_callback_ft)(); - # endif /* SASL_VERSION_FULL < 0x020125 */ - #include - #include - #include - #include - #endif /* CYRUS_SASL */ + #include - #ifdef TLS_SUPPORT - #include - #include - #endif /* TLS_SUPPORT */ + #include /* * This module implements an interface to SendMail very similar @@@ -80,66 -61,11 +61,11 @@@ #define SM_AUTH 45 static int sm_addrs = 0; - static int sm_alarmed = 0; static int sm_child = NOTOK; static int sm_debug = 0; static int sm_nl = TRUE; static int sm_verbose = 0; - - static FILE *sm_rfp = NULL; - static FILE *sm_wfp = NULL; - - static int next_line_encoded = 0; - - #ifdef CYRUS_SASL - /* - * Some globals needed by SASL - */ - - static sasl_conn_t *conn = NULL; /* SASL connection state */ - static int sasl_complete = 0; /* Has authentication succeeded? */ - static sasl_ssf_t sasl_ssf; /* Our security strength factor */ - static int maxoutbuf; /* Maximum crypto output buffer */ - static char *sasl_outbuffer; /* SASL output buffer for encryption */ - static int sasl_outbuflen; /* Current length of data in outbuf */ - static int sm_get_user(void *, int, const char **, unsigned *); - static int sm_get_pass(sasl_conn_t *, void *, int, sasl_secret_t **); - - static sasl_callback_t callbacks[] = { - { SASL_CB_USER, (sasl_callback_ft) sm_get_user, NULL }, - #define SM_SASL_N_CB_USER 0 - { SASL_CB_AUTHNAME, (sasl_callback_ft) sm_get_user, NULL }, - #define SM_SASL_N_CB_AUTHNAME 1 - { SASL_CB_PASS, (sasl_callback_ft) sm_get_pass, NULL }, - #define SM_SASL_N_CB_PASS 2 - { SASL_CB_LIST_END, NULL, NULL }, - }; - - #else /* CYRUS_SASL */ - int sasl_ssf = 0; - #endif /* CYRUS_SASL */ - - #ifdef TLS_SUPPORT - static SSL_CTX *sslctx = NULL; - static SSL *ssl = NULL; - static BIO *sbior = NULL; - static BIO *sbiow = NULL; - static BIO *io = NULL; - - static int tls_negotiate(void); - #endif /* TLS_SUPPORT */ - - #if defined(CYRUS_SASL) || defined(TLS_SUPPORT) - #define SASL_MAXRECVBUF 65536 - static int sm_fgetc(FILE *); - static char *sasl_inbuffer; /* SASL input buffer for encryption */ - static char *sasl_inptr; /* Pointer to current inbuf position */ - static int sasl_inbuflen; /* Current length of data in inbuf */ - #else - #define sm_fgetc fgetc - #endif - - static int tls_active = 0; + static netsec_context *nsc = NULL; static char *sm_noreply = "No reply text given"; static char *sm_moreply = "; "; @@@ -153,65 -79,42 +79,42 @@@ static char *EHLOkeys[MAXEHLO + 1] /* * static prototypes */ - static int smtp_init (char *, char *, char *, int, int, int, int, int, - char *, char *, const char *, int); - static int sendmail_init (char *, char *, int, int, int, int, int, - char *, char *); + static int smtp_init (char *, char *, char *, int, int, int, int, const char *, + const char *, const char *, int); + static int sendmail_init (char *, int, int, int, int, const char *, + const char *); static int rclient (char *, char *); - static int sm_ierror (char *fmt, ...); + static int sm_ierror (const char *fmt, ...); + static int sm_nerror (char *); static int smtalk (int time, char *fmt, ...); - static int sm_wrecord (char *, int); static int sm_wstream (char *, int); - static int sm_werror (void); static int smhear (void); - static int sm_rrecord (char *, int *); - static int sm_rerror (int); - static void alrmser (int); static char *EHLOset (char *); - static char *prepare_for_display (const char *, int *); - static int sm_fwrite(char *, int); - static int sm_fputs(char *); - static int sm_fputc(int); - static void sm_fflush(void); - static int sm_fgets(char *, int, FILE *); - static int sm_auth_xoauth2(const char *, const char *, int); - - #ifdef CYRUS_SASL - /* - * Function prototypes needed for SASL - */ - - static int sm_auth_sasl(char *, int, char *, char *); - #endif /* CYRUS_SASL */ + static int sm_sasl_callback(enum sasl_message_type, unsigned const char *, + unsigned int, unsigned char **, unsigned int *, + char **); int sm_init (char *client, char *server, char *port, int watch, int verbose, - int debug, int sasl, int saslssf, char *saslmech, char *user, + int debug, int sasl, const char *saslmech, const char *user, const char *oauth_svc, int tls) { if (sm_mts == MTS_SMTP) return smtp_init (client, server, port, watch, verbose, - debug, sasl, saslssf, saslmech, user, - oauth_svc, tls); + debug, sasl, saslmech, user, oauth_svc, tls); else - return sendmail_init (client, server, watch, verbose, - debug, sasl, saslssf, saslmech, user); + return sendmail_init (client, watch, verbose, debug, sasl, + saslmech, user); } static int smtp_init (char *client, char *server, char *port, int watch, int verbose, - int debug, - int sasl, int saslssf, char *saslmech, char *user, + int debug, int sasl, const char *saslmech, const char *user, const char *oauth_svc, int tls) { - int result, sd1, sd2; - #ifndef CYRUS_SASL - NMH_UNUSED (sasl); - NMH_UNUSED (saslssf); - NMH_UNUSED (saslmech); - NMH_UNUSED (user); - #endif /* CYRUS_SASL */ + int result, sd1; + char *errstr; if (watch) verbose = TRUE; @@@ -219,7 -122,7 +122,7 @@@ sm_verbose = verbose; sm_debug = debug; - if (sm_rfp != NULL && sm_wfp != NULL) + if (nsc != NULL) goto send_options; if (client == NULL || *client == '\0') { @@@ -237,35 -140,37 +140,37 @@@ if (client == NULL || *client == '\0') client = "localhost"; - #if defined(CYRUS_SASL) || defined(TLS_SUPPORT) - sasl_inbuffer = malloc(SASL_MAXRECVBUF); - if (!sasl_inbuffer) - return sm_ierror("Unable to allocate %d bytes for read buffer", - SASL_MAXRECVBUF); - #endif /* CYRUS_SASL || TLS_SUPPORT */ + nsc = netsec_init(); - if ((sd1 = rclient (server, port)) == NOTOK) - return RP_BHST; + if (user) + netsec_set_userid(nsc, user); + + if (sm_debug) + netsec_set_snoop(nsc, 1); - if ((sd2 = dup (sd1)) == NOTOK) { - close (sd1); - return sm_ierror ("unable to dup"); + if (sasl) { + if (netsec_set_sasl_params(nsc, server, "smtp", saslmech, + sm_sasl_callback, &errstr) != OK) + return sm_nerror(errstr); + } + + if (oauth_svc) { + if (netsec_set_oauth_service(nsc, oauth_svc) != OK) + return sm_ierror("OAuth2 not supported"); } - SIGNAL (SIGALRM, alrmser); + if ((sd1 = rclient (server, port)) == NOTOK) + return RP_BHST; + SIGNAL (SIGPIPE, SIG_IGN); - if ((sm_rfp = fdopen (sd1, "r")) == NULL - || (sm_wfp = fdopen (sd2, "w")) == NULL) { - close (sd1); - close (sd2); - sm_rfp = sm_wfp = NULL; - return sm_ierror ("unable to fdopen"); - } + netsec_set_fd(nsc, sd1, sd1); - tls_active = 0; + if (tls) { + if (netsec_set_tls(nsc, 1, &errstr) != OK) + return sm_nerror(errstr); + } - #ifdef TLS_SUPPORT /* * If tls == 2, that means that the user requested "initial" TLS, * which happens right after the connection has opened. Do that @@@ -273,21 -178,14 +178,14 @@@ */ if (tls == 2) { - result = tls_negotiate(); - - /* - * Note: if tls_negotiate() fails it will call sm_end() for us, - * which closes the connection. - */ - if (result != RP_OK) - return result; + if (netsec_negotiate_tls(nsc, &errstr) != OK) { + sm_end(NOTOK); + return sm_nerror(errstr); + } } - #endif /* TLS_SUPPORT */ - sm_alarmed = 0; - alarm (SM_OPEN); + netsec_set_timeout(nsc, SM_OPEN); result = smhear (); - alarm (0); switch (result) { case 220: @@@ -314,7 -212,6 +212,6 @@@ return RP_RPLY; } - #ifdef TLS_SUPPORT /* * If the user requested TLS support, then try to do the STARTTLS command * as part of the initial dialog. Assuming this works, we then need to @@@ -339,10 -236,10 +236,10 @@@ * negotiation. Oblige them. */ - result = tls_negotiate(); - - if (result != RP_OK) - return result; + if (netsec_negotiate_tls(nsc, &errstr) != OK) { + sm_end(NOTOK); + return sm_nerror(errstr); + } doingEHLO = 1; result = smtalk (SM_HELO, "EHLO %s", client); @@@ -353,11 -250,7 +250,7 @@@ return RP_RPLY; } } - #else /* TLS_SUPPORT */ - NMH_UNUSED (tls); - #endif /* TLS_SUPPORT */ - #ifdef CYRUS_SASL /* * If the user asked for SASL, then check to see if the SMTP server * supports it. Otherwise, error out (because the SMTP server @@@ -372,34 -265,9 +265,9 @@@ return sm_ierror("SMTP server does not support SASL"); } - if (saslmech && stringdex(saslmech, server_mechs) == -1) { - sm_end(NOTOK); - return sm_ierror("Requested SASL mech \"%s\" is not in the " - "list of supported mechanisms:\n%s", - saslmech, server_mechs); - } - - /* Don't call sm_auth_sasl() for XAUTH2 with -sasl. Instead, call - sm_auth_xoauth2() below. */ - if (oauth_svc == NULL && - sm_auth_sasl(user, saslssf, saslmech ? saslmech : server_mechs, - server) != RP_OK) { - sm_end(NOTOK); - return NOTOK; - } - } - #endif /* CYRUS_SASL */ - - if (oauth_svc != NULL) { - char *server_mechs; - if ((server_mechs = EHLOset("AUTH")) == NULL - || stringdex("XOAUTH2", server_mechs) == -1) { - sm_end(NOTOK); - return sm_ierror("SMTP server does not support SASL XOAUTH2"); - } - if (sm_auth_xoauth2(user, oauth_svc, debug) != RP_OK) { + if (netsec_negotiate_sasl(nsc, server_mechs, &errstr) != OK) { sm_end(NOTOK); - return NOTOK; + return sm_nerror(errstr); } } @@@ -411,28 -279,19 +279,19 @@@ send_options: } int - sendmail_init (char *client, char *server, int watch, int verbose, - int debug, int sasl, int saslssf, char *saslmech, char *user) + sendmail_init (char *client, int watch, int verbose, int debug, int sasl, + const char *saslmech, const char *user) { unsigned int i, result, vecp; int pdi[2], pdo[2]; - char *vec[15]; - #ifdef CYRUS_SASL - char *server_mechs; - #else /* CYRUS_SASL */ - NMH_UNUSED (server); - NMH_UNUSED (sasl); - NMH_UNUSED (saslssf); - NMH_UNUSED (saslmech); - NMH_UNUSED (user); - #endif /* CYRUS_SASL */ + char *vec[15], *errstr; if (watch) verbose = TRUE; sm_verbose = verbose; sm_debug = debug; - if (sm_rfp != NULL && sm_wfp != NULL) + if (nsc) return RP_OK; if (client == NULL || *client == '\0') { @@@ -449,12 -308,19 +308,19 @@@ if (client == NULL || *client == '\0') client = "localhost"; - #if defined(CYRUS_SASL) || defined(TLS_SUPPORT) - sasl_inbuffer = malloc(SASL_MAXRECVBUF); - if (!sasl_inbuffer) - return sm_ierror("Unable to allocate %d bytes for read buffer", - SASL_MAXRECVBUF); - #endif /* CYRUS_SASL || TLS_SUPPORT */ + nsc = netsec_init(); + + if (user) + netsec_set_userid(nsc, user); + + if (sm_debug) + netsec_set_snoop(nsc, 1); + + if (sasl) { + if (netsec_set_sasl_params(nsc, client, "smtp", saslmech, + sm_sasl_callback, &errstr) != OK) + return sm_nerror(errstr); + } if (pipe (pdi) == NOTOK) return sm_ierror ("no pipes"); @@@ -501,22 -367,14 +367,14 @@@ _exit (-1); /* NOTREACHED */ default: - SIGNAL (SIGALRM, alrmser); SIGNAL (SIGPIPE, SIG_IGN); close (pdi[1]); close (pdo[0]); - if ((sm_rfp = fdopen (pdi[0], "r")) == NULL - || (sm_wfp = fdopen (pdo[1], "w")) == NULL) { - close (pdi[0]); - close (pdo[1]); - sm_rfp = sm_wfp = NULL; - return sm_ierror ("unable to fdopen"); - } - sm_alarmed = 0; - alarm (SM_OPEN); + + netsec_set_fd(nsc, pdi[i], pdo[1]); + netsec_set_timeout(nsc, SM_OPEN); result = smhear (); - alarm (0); switch (result) { case 220: break; @@@ -542,34 -400,24 +400,24 @@@ return RP_RPLY; } - #ifdef CYRUS_SASL - /* - * If the user asked for SASL, then check to see if the SMTP server - * supports it. Otherwise, error out (because the SMTP server - * might have been spoofed; we don't want to just silently not - * do authentication - */ - - if (sasl) { - if (! (server_mechs = EHLOset("AUTH"))) { - sm_end(NOTOK); - return sm_ierror("SMTP server does not support SASL"); - } - - if (saslmech && stringdex(saslmech, server_mechs) == -1) { - sm_end(NOTOK); - return sm_ierror("Requested SASL mech \"%s\" is not in the " - "list of supported mechanisms:\n%s", - saslmech, server_mechs); - } - - if (sm_auth_sasl(user, saslssf, saslmech ? saslmech : server_mechs, - server) != RP_OK) { - sm_end(NOTOK); - return NOTOK; - } - } - #endif /* CYRUS_SASL */ + /* + * If the user asked for SASL, then check to see if the SMTP server + * supports it. Otherwise, error out (because the SMTP server + * might have been spoofed; we don't want to just silently not + * do authentication + */ + + if (sasl) { + char *server_mechs; + if (! (server_mechs = EHLOset("AUTH"))) { + sm_end(NOTOK); + return sm_ierror("SMTP server does not support SASL"); + } + if (netsec_negotiate_sasl(nsc, server_mechs, &errstr) != OK) { + sm_end(NOTOK); + return sm_nerror(errstr); + } + } if (watch) smtalk (SM_HELO, "VERB on"); @@@ -593,22 -441,9 +441,22 @@@ rclient (char *server, char *service } 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; @@@ -694,10 -529,7 +542,7 @@@ sm_wtxt (char *buffer, int len { int result; - sm_alarmed = 0; - alarm (SM_TEXT); result = sm_wstream (buffer, len); - alarm (0); return (result == NOTOK ? RP_BHST : RP_OK); } @@@ -746,7 -578,7 +591,7 @@@ sm_end (int type } } - if (sm_rfp == NULL && sm_wfp == NULL) + if (nsc == NULL) return RP_OK; switch (type) { @@@ -786,36 -618,13 +631,13 @@@ break; } - #ifdef TLS_SUPPORT - if (tls_active) { - BIO_ssl_shutdown(io); - BIO_free_all(io); - } - #endif /* TLS_SUPPORT */ - - if (sm_rfp != NULL) { - alarm (SM_CLOS); - fclose (sm_rfp); - alarm (0); - } - if (sm_wfp != NULL) { - alarm (SM_CLOS); - fclose (sm_wfp); - alarm (0); + if (nsc != NULL) { + netsec_shutdown(nsc, 1); + nsc = NULL; } if (sm_mts == MTS_SMTP) { status = 0; - #ifdef CYRUS_SASL - if (conn) { - sasl_dispose(&conn); - if (sasl_outbuffer) { - free(sasl_outbuffer); - } - } - if (sasl_inbuffer) - free(sasl_inbuffer); - #endif /* CYRUS_SASL */ } else if (sm_child != NOTOK) { status = pidwait (sm_child, OK); sm_child = NOTOK; @@@ -823,407 -632,37 +645,37 @@@ status = OK; } - sm_rfp = sm_wfp = NULL; return (status ? RP_BHST : RP_OK); } - #ifdef CYRUS_SASL - /* - * This function implements SASL authentication for SMTP. If this function - * completes successfully, then authentication is successful and we've - * (optionally) negotiated a security layer. - */ - - #define CHECKB64SIZE(insize, outbuf, outsize) \ - { size_t wantout = (((insize + 2) / 3) * 4) + 32; \ - if (wantout > outsize) { \ - outbuf = mh_xrealloc(outbuf, outsize = wantout); \ - } \ - } static int - sm_auth_sasl(char *user, int saslssf, char *mechlist, char *inhost) + sm_ierror (const char *fmt, ...) { - int result, status; - unsigned int buflen, outlen; - char *buf, *outbuf = NULL, host[NI_MAXHOST]; - const char *chosen_mech; - sasl_security_properties_t secprops; - sasl_ssf_t *ssf; - int *outbufmax; - struct nmh_creds creds = { 0, 0, 0 }; - size_t outbufsize = 0; - - /* - * Initialize the callback contexts - */ - - /* - * This is a _bit_ of a hack ... but if the hostname wasn't supplied - * to us on the command line, then call getpeername and do a - * reverse-address lookup on the IP address to get the name. - */ - - memset(host, 0, sizeof(host)); - - if (!inhost) { - struct sockaddr_storage sin; - socklen_t len = sizeof(sin); - int result; - - if (getpeername(fileno(sm_wfp), (struct sockaddr *) &sin, &len) < 0) { - sm_ierror("getpeername on SMTP socket failed: %s", - strerror(errno)); - return NOTOK; - } - - result = getnameinfo((struct sockaddr *) &sin, len, host, sizeof(host), - NULL, 0, NI_NAMEREQD); - if (result != 0) { - sm_ierror("Unable to look up name of connected host: %s", - gai_strerror(result)); - return NOTOK; - } - } else { - strncpy(host, inhost, sizeof(host) - 1); - } - - /* It's OK to copy the addresses here. The callbacks that use - them will only be called before this function returns. */ - creds.host = host; - creds.user = user; - callbacks[SM_SASL_N_CB_USER].context = &creds; - callbacks[SM_SASL_N_CB_AUTHNAME].context = &creds; - callbacks[SM_SASL_N_CB_PASS].context = &creds; - - result = sasl_client_init(callbacks); - - if (result != SASL_OK) { - sm_ierror("SASL library initialization failed: %s", - sasl_errstring(result, NULL, NULL)); - return NOTOK; - } - - result = sasl_client_new("smtp", host, NULL, NULL, NULL, 0, &conn); - - if (result != SASL_OK) { - sm_ierror("SASL client initialization failed: %s", - sasl_errstring(result, NULL, NULL)); - return NOTOK; - } - - /* - * Initialize the security properties. But if TLS is active, then - * don't negotiate encryption here. - */ - - memset(&secprops, 0, sizeof(secprops)); - secprops.maxbufsize = SASL_MAXRECVBUF; - secprops.max_ssf = - tls_active ? 0 : (saslssf != -1 ? (unsigned int) saslssf : UINT_MAX); - - result = sasl_setprop(conn, SASL_SEC_PROPS, &secprops); - - if (result != SASL_OK) { - sm_ierror("SASL security property initialization failed: %s", - sasl_errstring(result, NULL, NULL)); - return NOTOK; - } - - /* - * Start the actual protocol. Feed the mech list into the library - * and get out a possible initial challenge - */ - - result = sasl_client_start(conn, mechlist, NULL, (const char **) &buf, - &buflen, (const char **) &chosen_mech); - - if (result != SASL_OK && result != SASL_CONTINUE) { - sm_ierror("SASL client start failed: %s", sasl_errdetail(conn)); - return NOTOK; - } - - /* - * If we got an initial challenge, send it as part of the AUTH - * command; otherwise, just send a plain AUTH command. - */ - - if (buflen) { - CHECKB64SIZE(buflen, outbuf, outbufsize); - status = sasl_encode64(buf, buflen, outbuf, outbufsize, NULL); - if (status != SASL_OK) { - sm_ierror("SASL base64 encode failed: %s", - sasl_errstring(status, NULL, NULL)); - if (outbuf) - free(outbuf); - return NOTOK; - } - - status = smtalk(SM_AUTH, "AUTH %s %s", chosen_mech, outbuf); - } else - status = smtalk(SM_AUTH, "AUTH %s", chosen_mech); - - /* - * Now we loop until we either fail, get a SASL_OK, or a 235 - * response code. Receive the challenges and process them until - * we're all done. - */ - - while (result == SASL_CONTINUE) { - - /* - * If we get a 235 response, that means authentication has - * succeeded and we need to break out of the loop (yes, even if - * we still get SASL_CONTINUE from sasl_client_step()). - * - * Otherwise, if we get a message that doesn't seem to be a - * valid response, then abort - */ - - if (status == 235) - break; - else if (status < 300 || status > 399) { - if (outbuf) - free(outbuf); - return RP_BHST; - } - - /* - * Special case; a zero-length response from the SMTP server - * is returned as a single =. If we get that, then set buflen - * to be zero. Otherwise, just decode the response. - */ - - if (strcmp("=", sm_reply.text) == 0) { - outlen = 0; - } else { - if (sm_reply.length > (int) outbufsize) { - outbuf = mh_xrealloc(outbuf, outbufsize = sm_reply.length); - } - - result = sasl_decode64(sm_reply.text, sm_reply.length, - outbuf, outbufsize, &outlen); - if (result != SASL_OK) { - smtalk(SM_AUTH, "*"); - sm_ierror("SASL base64 decode failed: %s", - sasl_errstring(result, NULL, NULL)); - if (outbuf) - free(outbuf); - return NOTOK; - } - } - - result = sasl_client_step(conn, outbuf, outlen, NULL, - (const char **) &buf, &buflen); - - if (result != SASL_OK && result != SASL_CONTINUE) { - smtalk(SM_AUTH, "*"); - sm_ierror("SASL client negotiation failed: %s", - sasl_errstring(result, NULL, NULL)); - if (outbuf) - free(outbuf); - return NOTOK; - } - - CHECKB64SIZE(buflen, outbuf, outbufsize); - status = sasl_encode64(buf, buflen, outbuf, outbufsize, NULL); - - if (status != SASL_OK) { - smtalk(SM_AUTH, "*"); - sm_ierror("SASL base64 encode failed: %s", - sasl_errstring(status, NULL, NULL)); - if (outbuf) - free(outbuf); - return NOTOK; - } - - status = smtalk(SM_AUTH, outbuf); - } - - if (outbuf) - free(outbuf); - - /* - * Make sure that we got the correct response - */ - - if (status < 200 || status > 299) - return RP_BHST; - - /* - * We _should_ have completed the authentication successfully. - * Get a few properties from the authentication exchange. - */ - - result = sasl_getprop(conn, SASL_MAXOUTBUF, (const void **) &outbufmax); - - if (result != SASL_OK) { - sm_ierror("Cannot retrieve SASL negotiated output buffer size: %s", - sasl_errstring(result, NULL, NULL)); - return NOTOK; - } - - maxoutbuf = *outbufmax; - - result = sasl_getprop(conn, SASL_SSF, (const void **) &ssf); - - sasl_ssf = *ssf; - - if (result != SASL_OK) { - sm_ierror("Cannot retrieve SASL negotiated security strength " - "factor: %s", sasl_errstring(result, NULL, NULL)); - return NOTOK; - } - - if (sasl_ssf > 0) { - sasl_outbuffer = malloc(maxoutbuf); + va_list ap; - if (sasl_outbuffer == NULL) { - sm_ierror("Unable to allocate %d bytes for SASL output " - "buffer", maxoutbuf); - return NOTOK; - } - sasl_outbuflen = 0; - sasl_inbuflen = 0; - sasl_inptr = sasl_inbuffer; - } else { - sasl_outbuffer = NULL; - /* Don't NULL out sasl_inbuffer because it could be used in - sm_fgetc (). */ - } + va_start(ap, fmt); + vsnprintf (sm_reply.text, sizeof(sm_reply.text), fmt, ap); + va_end(ap); - sasl_complete = 1; + sm_reply.length = strlen (sm_reply.text); + sm_reply.code = NOTOK; - return RP_OK; + return RP_BHST; } /* - * Our callback functions to feed data to the SASL library + * Like sm_ierror, but assume it's an allocated error string we need to free. */ static int - sm_get_user(void *context, int id, const char **result, unsigned *len) - { - nmh_creds_t creds = (nmh_creds_t) context; - - if (! result || ((id != SASL_CB_USER) && (id != SASL_CB_AUTHNAME))) - return SASL_BADPARAM; - - if (creds->user == NULL) { - /* - * Pass the 1 third argument to nmh_get_credentials() so - * that a default user if the -user switch to send(1)/post(8) - * wasn't used, and so that a default password will be supplied. - * That's used when those values really don't matter, and only - * with legacy/.netrc, i.e., with a credentials profile entry. - */ - if (nmh_get_credentials (creds->host, creds->user, 1, creds) != OK) { - return SASL_BADPARAM; - } - } - - *result = creds->user; - if (len) - *len = strlen(creds->user); - - return SASL_OK; - } - - static int - sm_get_pass(sasl_conn_t *conn, void *context, int id, - sasl_secret_t **psecret) + sm_nerror (char *str) { - nmh_creds_t creds = (nmh_creds_t) context; - int len; - - NMH_UNUSED (conn); - - if (! psecret || id != SASL_CB_PASS) - return SASL_BADPARAM; - - if (creds->password == NULL) { - /* - * Pass the 0 third argument to nmh_get_credentials() so - * that the default password isn't used. With legacy/.netrc - * credentials support, we'll only get here if the -user - * switch to send(1)/post(8) wasn't used. - */ - if (nmh_get_credentials (creds->host, creds->user, 0, creds) != OK) { - return SASL_BADPARAM; - } - } - - len = strlen (creds->password); - - if (! (*psecret = (sasl_secret_t *) malloc(sizeof(sasl_secret_t) + len))) { - return SASL_NOMEM; - } - - (*psecret)->len = len; - strcpy((char *) (*psecret)->data, creds->password); - - return SASL_OK; - } - #endif /* CYRUS_SASL */ - - /* https://developers.google.com/gmail/xoauth2_protocol */ - static int - sm_auth_xoauth2(const char *user, const char *oauth_svc, int snoop) - { - const char *xoauth_client_res; - int status; - - #ifdef OAUTH_SUPPORT - xoauth_client_res = mh_oauth_do_xoauth(user, oauth_svc, - snoop ? stderr : NULL); - - if (xoauth_client_res == NULL) { - return sm_ierror("Internal error: mh_oauth_do_xoauth() returned NULL"); - } - #else - NMH_UNUSED(user); - NMH_UNUSED(snoop); - adios(NULL, "sendfrom built without OAUTH_SUPPORT, " - "so oauth_svc %s is not supported", oauth_svc); - #endif /* OAUTH_SUPPORT */ - - status = smtalk(SM_AUTH, "AUTH XOAUTH2 %s", xoauth_client_res); - if (status == 235) { - /* It worked! */ - return RP_OK; - } - - /* - * Status is 334 and sm_reply.text contains base64-encoded JSON. As far as - * epg can tell, no matter the error, the JSON is always the same: - * {"status":"400","schemes":"Bearer","scope":"https://mail.google.com/"} - * I tried these errors: - * - garbage token - * - expired token - * - wrong scope - * - wrong username - */ - /* Then we're supposed to send an empty response ("\r\n"). */ - smtalk(SM_AUTH, ""); - /* - * And now we always get this, again, no matter the error: - * 535-5.7.8 Username and Password not accepted. Learn more at - * 535 5.7.8 http://support.google.com/mail/bin/answer.py?answer=14257 - */ - return RP_BHST; - } - - static int - sm_ierror (char *fmt, ...) - { - va_list ap; - - va_start(ap, fmt); - vsnprintf (sm_reply.text, sizeof(sm_reply.text), fmt, ap); - va_end(ap); - - sm_reply.length = strlen (sm_reply.text); + strncpy(sm_reply.text, str, sizeof(sm_reply.text)); + sm_reply.text[sizeof(sm_reply.text) - 1] = '\0'; + sm_reply.length = strlen(sm_reply.text); sm_reply.code = NOTOK; + free(str); return RP_BHST; } @@@ -1232,334 -671,91 +684,91 @@@ static in smtalk (int time, char *fmt, ...) { va_list ap; + char *errstr; int result; - char *buffer; - size_t bufsize = BUFSIZ; - - buffer = mh_xmalloc(bufsize); va_start(ap, fmt); - result = vsnprintf (buffer, bufsize, fmt, ap); + result = netsec_vprintf (nsc, &errstr, fmt, ap); va_end(ap); - if (result > (int) bufsize) { - buffer = mh_xrealloc(buffer, bufsize = result + 1); - va_start(ap, fmt); - vsnprintf (buffer, bufsize, fmt, ap); - va_end(ap); - } - - if (sm_debug) { - char *decoded_buffer = - prepare_for_display (buffer, &next_line_encoded); - - if (sasl_ssf) - printf("(sasl-encrypted) "); - if (tls_active) - printf("(tls-encrypted) "); - printf ("=> %s\n", decoded_buffer); - free (decoded_buffer); - fflush (stdout); - } - - sm_alarmed = 0; - alarm ((unsigned) time); - if ((result = sm_wrecord (buffer, strlen (buffer))) != NOTOK) - result = smhear (); - alarm (0); - - free(buffer); + if (result != OK) + return sm_nerror(errstr); - return result; - } + if (netsec_printf (nsc, &errstr, "\r\n") != OK) + return sm_nerror(errstr); + if (netsec_flush (nsc, &errstr) != OK) + return sm_nerror(errstr); - /* - * write the buffer to the open SMTP channel - */ + netsec_set_timeout(nsc, time); - static int - sm_wrecord (char *buffer, int len) - { - if (sm_wfp == NULL) - return sm_werror (); - - sm_fwrite (buffer, len); - sm_fputs ("\r\n"); - sm_fflush (); - - return (ferror (sm_wfp) ? sm_werror () : OK); + return smhear (); } static int sm_wstream (char *buffer, int len) { - char *bp; + char *bp, *errstr; static char lc = '\0'; + int rc; - if (sm_wfp == NULL) - return sm_werror (); + if (nsc == NULL) { + sm_ierror("No socket opened"); + return NOTOK; + } if (buffer == NULL && len == 0) { + rc = OK; if (lc != '\n') - sm_fputs ("\r\n"); + rc = netsec_write(nsc, "\r\n", 2, &errstr); lc = '\0'; - return (ferror (sm_wfp) ? sm_werror () : OK); + if (rc != OK) + sm_nerror(errstr); + return rc; } for (bp = buffer; bp && len > 0; bp++, len--) { switch (*bp) { case '\n': sm_nl = TRUE; - sm_fputc ('\r'); + if (netsec_write(nsc, "\r", 1, &errstr) != OK) { + sm_nerror(errstr); + return NOTOK; + } break; case '.': if (sm_nl) - sm_fputc ('.');/* FALL THROUGH */ + if (netsec_write(nsc, ".", 1, &errstr) != OK) { + sm_nerror(errstr); + return NOTOK; + } /* FALL THROUGH */ + default: sm_nl = FALSE; } - sm_fputc (*bp); - if (ferror (sm_wfp)) - return sm_werror (); + if (netsec_write(nsc, bp, 1, &errstr) != OK) { + sm_nerror(errstr); + return NOTOK; + } } if (bp > buffer) lc = *--bp; - return (ferror (sm_wfp) ? sm_werror () : OK); - } - - /* - * Write out to the network, but do buffering for SASL (if enabled) - */ - - static int - sm_fwrite(char *buffer, int len) - { - #ifdef CYRUS_SASL - const char *output; - unsigned int outputlen; - - if (sasl_complete == 0 || sasl_ssf == 0) { - #endif /* CYRUS_SASL */ - #ifdef TLS_SUPPORT - if (tls_active) { - int ret; - - ret = BIO_write(io, buffer, len); - - if (ret <= 0) { - sm_ierror("TLS error during write: %s", - ERR_error_string(ERR_get_error(), NULL)); - return NOTOK; - } - } else - #endif /* TLS_SUPPORT */ - if ((int) fwrite(buffer, sizeof(*buffer), len, sm_wfp) < len) { - advise ("sm_fwrite", "fwrite"); - } - #ifdef CYRUS_SASL - } else { - while (len >= maxoutbuf - sasl_outbuflen) { - memcpy(sasl_outbuffer + sasl_outbuflen, buffer, - maxoutbuf - sasl_outbuflen); - len -= maxoutbuf - sasl_outbuflen; - sasl_outbuflen = 0; - - if (sasl_encode(conn, sasl_outbuffer, maxoutbuf, - &output, &outputlen) != SASL_OK) { - sm_ierror("Unable to SASL encode connection data: %s", - sasl_errdetail(conn)); - return NOTOK; - } - - if (fwrite(output, sizeof(*output), outputlen, sm_wfp) < - outputlen) { - advise ("sm_fwrite", "fwrite"); - } - } - - if (len > 0) { - memcpy(sasl_outbuffer + sasl_outbuflen, buffer, len); - sasl_outbuflen += len; - } - } - #endif /* CYRUS_SASL */ - return ferror(sm_wfp) ? NOTOK : RP_OK; - } - - #ifdef TLS_SUPPORT - /* - * Negotiate Transport Layer Security - */ - - static int - tls_negotiate(void) - { - BIO *ssl_bio; - - if (! sslctx) { - const SSL_METHOD *method; - - SSL_library_init(); - SSL_load_error_strings(); - - method = TLSv1_client_method(); /* Not sure about this */ - - /* Older ssl takes a non-const arg. */ - sslctx = SSL_CTX_new((SSL_METHOD *) method); - - if (! sslctx) { - sm_end(NOTOK); - return sm_ierror("Unable to initialize OpenSSL context: %s", - ERR_error_string(ERR_get_error(), NULL)); - } - } - - ssl = SSL_new(sslctx); - - if (! ssl) { - sm_end(NOTOK); - return sm_ierror("Unable to create SSL connection: %s", - ERR_error_string(ERR_get_error(), NULL)); - } - - sbior = BIO_new_socket(fileno(sm_rfp), BIO_NOCLOSE); - sbiow = BIO_new_socket(fileno(sm_wfp), BIO_NOCLOSE); - - if (sbior == NULL || sbiow == NULL) { - sm_end(NOTOK); - return sm_ierror("Unable to create BIO endpoints: %s", - ERR_error_string(ERR_get_error(), NULL)); - } - - SSL_set_bio(ssl, sbior, sbiow); - SSL_set_connect_state(ssl); - - /* - * Set up a BIO to handle buffering for us - */ - - io = BIO_new(BIO_f_buffer()); - - if (! io) { - sm_end(NOTOK); - return sm_ierror("Unable to create a buffer BIO: %s", - ERR_error_string(ERR_get_error(), NULL)); - } - - ssl_bio = BIO_new(BIO_f_ssl()); - - if (! ssl_bio) { - sm_end(NOTOK); - return sm_ierror("Unable to create a SSL BIO: %s", - ERR_error_string(ERR_get_error(), NULL)); - } - - BIO_set_ssl(ssl_bio, ssl, BIO_CLOSE); - BIO_push(io, ssl_bio); - - /* - * Try doing the handshake now - */ - - if (BIO_do_handshake(io) < 1) { - sm_end(NOTOK); - return sm_ierror("Unable to negotiate SSL connection: %s", - ERR_error_string(ERR_get_error(), NULL)); - } - - if (sm_debug) { - const SSL_CIPHER *cipher = SSL_get_current_cipher(ssl); - printf("SSL negotiation successful: %s(%d) %s\n", - SSL_CIPHER_get_name(cipher), - SSL_CIPHER_get_bits(cipher, NULL), - SSL_CIPHER_get_version(cipher)); - - } - - tls_active = 1; - - return RP_OK; - } - #endif /* TLS_SUPPORT */ - - /* - * Convenience functions to replace occurrences of fputs() and fputc() - */ - - static int - sm_fputs(char *buffer) - { - return sm_fwrite(buffer, strlen(buffer)); - } - - static int - sm_fputc(int c) - { - char h = c; - - return sm_fwrite(&h, 1); - } - - /* - * Flush out any pending data on the connection - */ - - static void - sm_fflush(void) - { - #ifdef CYRUS_SASL - const char *output; - unsigned int outputlen; - int result; - - if (sasl_complete == 1 && sasl_ssf > 0 && sasl_outbuflen > 0) { - result = sasl_encode(conn, sasl_outbuffer, sasl_outbuflen, - &output, &outputlen); - if (result != SASL_OK) { - sm_ierror("Unable to SASL encode connection data: %s", - sasl_errdetail(conn)); - return; - } - - if (fwrite(output, sizeof(*output), outputlen, sm_wfp) < outputlen) { - advise ("sm_fflush", "fwrite"); - } - sasl_outbuflen = 0; - } - #endif /* CYRUS_SASL */ - - #ifdef TLS_SUPPORT - if (tls_active) { - (void) BIO_flush(io); - } - #endif /* TLS_SUPPORT */ - - fflush(sm_wfp); - } - - static int - sm_werror (void) - { - sm_reply.length = - strlen (strcpy (sm_reply.text, sm_wfp == NULL ? "no socket opened" - : sm_alarmed ? "write to socket timed out" - : "error writing to socket")); - - return (sm_reply.code = NOTOK); + return OK; } static int smhear (void) { - int i, code, cont, bc = 0, rc, more; + int i, code, cont, more; + size_t buflen, rc; unsigned char *bp; char *rp; - char **ehlo = EHLOkeys, buffer[BUFSIZ]; + char *errstr; + char **ehlo = EHLOkeys, *buffer; if (doingEHLO) { static int at_least_once = 0; @@@ -1586,20 -782,8 +795,8 @@@ again: rp = sm_reply.text; rc = sizeof(sm_reply.text) - 1; - for (more = FALSE; sm_rrecord ((char *) (bp = (unsigned char *) buffer), - &bc) != NOTOK ; ) { - if (sm_debug) { - char *decoded_buffer = - prepare_for_display (buffer, &next_line_encoded); - - if (sasl_ssf > 0) - printf("(sasl-decrypted) "); - if (tls_active) - printf("(tls-decrypted) "); - printf ("<= %s\n", decoded_buffer); - free (decoded_buffer); - fflush (stdout); - } + for (more = FALSE; (buffer = netsec_readline(nsc, &buflen, + &errstr)) != NULL ; ) { if (doingEHLO && strncmp (buffer, "250", sizeof("250") - 1) == 0 @@@ -1619,18 -803,20 +816,20 @@@ doingEHLO = 2; } - for (; bc > 0 && (!isascii (*bp) || !isdigit (*bp)); bp++, bc--) + bp = (unsigned char *) buffer; + + for (; buflen > 0 && (!isascii (*bp) || !isdigit (*bp)); bp++, buflen--) continue; cont = FALSE; code = atoi ((char *) bp); - bp += 3, bc -= 3; - for (; bc > 0 && isspace (*bp); bp++, bc--) + bp += 3, buflen -= 3; + for (; buflen > 0 && isspace (*bp); bp++, buflen--) continue; - if (bc > 0 && *bp == '-') { + if (buflen > 0 && *bp == '-') { cont = TRUE; - bp++, bc--; - for (; bc > 0 && isspace (*bp); bp++, bc--) + bp++, buflen--; + for (; buflen > 0 && isspace (*bp); bp++, buflen--) continue; } @@@ -1641,20 -827,18 +840,18 @@@ } else { sm_reply.code = code; more = cont; - if (bc <= 0) { - /* can never fail to 0-terminate because of size of buffer vs fixed string */ - strncpy (buffer, sm_noreply, sizeof(buffer)); - bp = (unsigned char *) buffer; - bc = strlen (sm_noreply); + if (buflen <= 0) { + bp = (unsigned char *) sm_noreply; + buflen = strlen (sm_noreply); } } - if ((i = min (bc, rc)) > 0) { + if ((i = min (buflen, rc)) > 0) { memcpy (rp, bp, i); rp += i; rc -= i; i = strlen(sm_moreply); - if (more && rc > i + 1) { + if (more && (int) rc > i + 1) { memcpy (rp, sm_moreply, i); /* safe because of check in if() */ rp += i; rc -= i; @@@ -1674,198 -858,9 +871,9 @@@ sm_reply.text[sm_reply.length] = 0; return sm_reply.code; } - return NOTOK; - } - - - static int - sm_rrecord (char *buffer, int *len) - { - int retval; - - if (sm_rfp == NULL) - return sm_rerror(0); - - buffer[*len = 0] = 0; - - if ((retval = sm_fgets (buffer, BUFSIZ, sm_rfp)) != RP_OK) - return sm_rerror (retval); - *len = strlen (buffer); - /* *len should be >0 except on EOF, but check for safety's sake */ - if (*len == 0) - return sm_rerror (RP_EOF); - if (buffer[*len - 1] != '\n') - while ((retval = sm_fgetc (sm_rfp)) != '\n' && retval != EOF && - retval != -2) - continue; - else - if ((*len > 1) && (buffer[*len - 2] == '\r')) - *len -= 1; - *len -= 1; - buffer[*len] = 0; - - return OK; - } - - /* - * Our version of fgets, which calls our private fgetc function - */ - - static int - sm_fgets(char *buffer, int size, FILE *f) - { - int c; - - do { - c = sm_fgetc(f); - - if (c == EOF) - return RP_EOF; - - if (c == -2) - return NOTOK; - *buffer++ = c; - } while (size > 1 && c != '\n'); - - *buffer = '\0'; - - return RP_OK; - } - - - #if defined(CYRUS_SASL) || defined(TLS_SUPPORT) - /* - * Read from the network, but do SASL or TLS encryption - */ - - static int - sm_fgetc(FILE *f) - { - char tmpbuf[BUFSIZ], *retbuf; - unsigned int retbufsize = 0; - int cc, result; - - /* - * If we have leftover data, return it - */ - - if (sasl_inbuflen) { - sasl_inbuflen--; - return (int) *sasl_inptr++; - } - - /* - * If not, read from the network until we have some data to return - */ - - while (retbufsize == 0) { - - #ifdef TLS_SUPPORT - if (tls_active) { - cc = SSL_read(ssl, tmpbuf, sizeof(tmpbuf)); - - if (cc == 0) { - result = SSL_get_error(ssl, cc); - - if (result != SSL_ERROR_ZERO_RETURN) { - sm_ierror("TLS peer aborted connection"); - } - - return EOF; - } - - if (cc < 0) { - sm_ierror("SSL_read failed: %s", - ERR_error_string(ERR_get_error(), NULL)); - return -2; - } - } else - #endif /* TLS_SUPPORT */ - - cc = read(fileno(f), tmpbuf, sizeof(tmpbuf)); - - if (cc == 0) - return EOF; - - if (cc < 0) { - sm_ierror("Unable to read from network: %s", strerror(errno)); - return -2; - } - - /* - * Don't call sasl_decode unless sasl is complete and we have - * encryption working - */ - - #ifdef CYRUS_SASL - if (sasl_complete == 0 || sasl_ssf == 0) { - retbuf = tmpbuf; - retbufsize = cc; - } else { - result = sasl_decode(conn, tmpbuf, cc, (const char **) &retbuf, - &retbufsize); - - if (result != SASL_OK) { - sm_ierror("Unable to decode SASL network data: %s", - sasl_errdetail(conn)); - return -2; - } - } - #else /* ! CYRUS_SASL */ - retbuf = tmpbuf; - retbufsize = cc; - #endif /* CYRUS_SASL */ - } - - if (retbufsize > SASL_MAXRECVBUF) { - sm_ierror("Received data (%d bytes) is larger than the buffer " - "size (%d bytes)", retbufsize, SASL_MAXRECVBUF); - return -2; - } - - memcpy(sasl_inbuffer, retbuf, retbufsize); - sasl_inptr = sasl_inbuffer + 1; - sasl_inbuflen = retbufsize - 1; - - return (int) sasl_inbuffer[0]; - } - #endif /* CYRUS_SASL || TLS_SUPPORT */ - - static int - sm_rerror (int rc) - { - if (sm_mts == MTS_SMTP) - sm_reply.length = - strlen (strcpy (sm_reply.text, sm_rfp == NULL ? "no socket opened" - : sm_alarmed ? "read from socket timed out" - : rc == RP_EOF ? "premature end-of-file on socket" - : "error reading from socket")); - else - sm_reply.length = - strlen (strcpy (sm_reply.text, sm_rfp == NULL ? "no pipe opened" - : sm_alarmed ? "read from pipe timed out" - : rc == RP_EOF ? "premature end-of-file on pipe" - : "error reading from pipe")); - - return (sm_reply.code = NOTOK); - } - - - static void - alrmser (int i) - { - NMH_UNUSED (i); - - #ifndef RELIABLE_SIGNALS - SIGNAL (SIGALRM, alrmser); - #endif - - sm_alarmed++; - if (sm_debug) { - printf ("timed out...\n"); - fflush (stdout); - } + sm_nerror(errstr); + return NOTOK; } @@@ -1940,68 -935,143 +948,143 @@@ EHLOset (char *s return 0; } - /* - * Detects, using heuristics, if an SMTP server or client response string - * contains a base64-encoded portion. If it does, decodes it and replaces - * any non-printable characters with a hex representation. Caller is - * responsible for free'ing return value. If the decode fails, a copy of - * the input string is returned. + * Our SASL callback; we are either given SASL tokens to generate network + * protocols messages for, or we decode incoming protocol messages and + * convert them to binary SASL tokens to pass up into the SASL library. */ - static - char * - prepare_for_display (const char *string, int *next_line_encoded) { - const char *start = NULL; - const char *decoded; - size_t decoded_len; - int prefix_len = -1; - - if (strncmp (string, "AUTH ", 5) == 0) { - /* AUTH line: the mechanism isn't encoded. If there's an initial - response, it must be base64 encoded.. */ - char *mechanism = strchr (string + 5, ' '); - - if (mechanism != NULL) { - prefix_len = (int) (mechanism - string + 1); - } /* else no space following the mechanism, so no initial response */ - *next_line_encoded = 0; - } else if (strncmp (string, "334 ", 4) == 0) { - /* 334 is the server's request for user or password. */ - prefix_len = 4; - /* The next (client response) line must be base64 encoded. */ - *next_line_encoded = 1; - } else if (*next_line_encoded) { - /* "next" line now refers to this line, which is a base64-encoded - client response. */ - prefix_len = 0; - *next_line_encoded = 0; - } else { - *next_line_encoded = 0; - } - /* Don't attempt to decoded unencoded initial response ('=') or cancel - response ('*'). */ - if (prefix_len > -1 && - string[prefix_len] != '=' && string[prefix_len] != '*') { - start = string + prefix_len; - } + static int + sm_sasl_callback(enum sasl_message_type mtype, unsigned const char *indata, + unsigned int indatalen, unsigned char **outdata, + unsigned int *outdatalen, char **errstr) + { + int rc, snoopoffset; + char *mech, *line; + size_t len; - if (start && decodeBase64 (start, &decoded, &decoded_len, 1, NULL) == OK) { - char *hexified; - char *prefix = mh_xmalloc(prefix_len + 1); - char *display_string; - - /* prefix is the beginning portion, which isn't base64 encoded. */ - snprintf (prefix, prefix_len + 1, "%*s", prefix_len, string); - hexify ((const unsigned char *) decoded, decoded_len, &hexified); - /* Wrap the decoded portion in "b64<>". */ - display_string = concat (prefix, "b64<", hexified, ">", NULL); - free (hexified); - free (prefix); - free ((char *) decoded); - - return display_string; - } else { - return getcpy (string); + switch (mtype) { + case NETSEC_SASL_START: + /* + * Generate an AUTH message; if we were given an input token + * then generate a an AUTH message that includes the initial + * response. + */ + + mech = netsec_get_sasl_mechanism(nsc); + + if (indatalen) { + char *b64data; + + b64data = mh_xmalloc(BASE64SIZE(indatalen)); + writeBase64raw(indata, indatalen, (unsigned char *) b64data); + + netsec_set_snoop_callback(nsc, netsec_b64_snoop_decoder, + &snoopoffset); + snoopoffset = 6 + strlen(mech); + rc = netsec_printf(nsc, errstr, "AUTH %s %s\r\n", mech, b64data); + free(b64data); + netsec_set_snoop_callback(nsc, NULL, NULL); + } else { + rc = netsec_printf(nsc, errstr, "AUTH %s\r\n", mech); + } + + if (rc != OK) + return NOTOK; + + if (netsec_flush(nsc, errstr) != OK) + return NOTOK; + + break; + + case NETSEC_SASL_READ: + /* + * Read in a line that should contain a 334 response code, followed + * by base64 response data. + */ + + netsec_set_snoop_callback(nsc, netsec_b64_snoop_decoder, &snoopoffset); + snoopoffset = 4; + line = netsec_readline(nsc, &len, errstr); + netsec_set_snoop_callback(nsc, NULL, NULL); + + if (line == NULL) + return NOTOK; + + if (len < 4) { + netsec_err(errstr, "Invalid format for SASL response"); + return NOTOK; + } + + if (strncmp(line, "334 ", 4) != 0) { + netsec_err(errstr, "Improper SASL protocol response: %s", line); + return NOTOK; + } + + if (len == 4) { + *outdata = NULL; + *outdatalen = 0; + } else { + rc = decodeBase64(line + 4, outdata, &len, 0, NULL); + if (rc != OK) { + netsec_err(errstr, "Unable to decode base64 response"); + return NOTOK; + } + *outdatalen = len; + } + break; + + case NETSEC_SASL_WRITE: + /* + * The output encoding is pretty simple, so this is easy. + */ + if (indatalen == 0) { + rc = netsec_printf(nsc, errstr, "\r\n"); + } else { + unsigned char *b64data; + b64data = mh_xmalloc(BASE64SIZE(indatalen)); + writeBase64raw(indata, indatalen, b64data); + netsec_set_snoop_callback(nsc, netsec_b64_snoop_decoder, NULL); + rc = netsec_printf(nsc, errstr, "%s\r\n", b64data); + netsec_set_snoop_callback(nsc, NULL, NULL); + free(b64data); + } + + if (rc != OK) + return NOTOK; + + if (netsec_flush(nsc, errstr) != OK) + return NOTOK; + break; + + case NETSEC_SASL_FINISH: + /* + * Finish the protocol; we're looking for a 235 message. + */ + line = netsec_readline(nsc, &len, errstr); + if (line == NULL) + return NOTOK; + + if (strncmp(line, "235 ", 4) != 0) { + if (len > 4) + netsec_err(errstr, "Authentication failed: %s", line + 4); + else + netsec_err(errstr, "Authentication failed: %s", line); + return NOTOK; + } + break; + + case NETSEC_SASL_CANCEL: + /* + * Cancel the SASL exchange; this is done by sending a single "*". + */ + rc = netsec_printf(nsc, errstr, "*\r\n"); + if (rc == OK) + rc = netsec_flush(nsc, errstr); + if (rc != OK) + return NOTOK; + break; } + + return OK; } diff --combined mts/smtp/smtp.h index 3063866e,45e8c4c3..fb842fe6 --- a/mts/smtp/smtp.h +++ b/mts/smtp/smtp.h @@@ -16,9 -16,9 +16,9 @@@ struct smtp * prototypes */ /* int client (); */ - int sm_init (char *, char *, char *, int, int, int, int, int, char *, char *, - const char *, int); + 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); diff --combined test/send/test-sendfrom index 2d704f16,8e55f7ab..0f2dda84 --- a/test/send/test-sendfrom +++ b/test/send/test-sendfrom @@@ -51,21 -51,14 +51,23 @@@ QUI EOF cat > "${testname}.post-expected" < EHLO nosuchhost.example.com +<= 250-ready +<= 250-8BITMIME +<= 250-SMTPUTF8 <= 250 I'll buy that for a dollar! => MAIL FROM: +<= 250-ready +<= 250-8BITMIME +<= 250-SMTPUTF8 <= 250 I'll buy that for a dollar! => RCPT TO: +<= 250-ready +<= 250-8BITMIME +<= 250-SMTPUTF8 <= 250 I'll buy that for a dollar! => DATA <= 354 Go ahead @@@ -76,7 -69,7 +78,7 @@@ EOF test_post "${testname}.actual" "${testname}.expected" \ - > ${testname}.post 2>/dev/null + 2> ${testname}.post check "${testname}.post-expected" "${testname}.post" diff --combined uip/post.c index 47941afc,0a42ad2f..db9c0298 --- a/uip/post.c +++ b/uip/post.c @@@ -83,7 -83,6 +83,6 @@@ X("partno", -6, PARTSW) \ X("sasl", SASLminc(4), SASLSW) \ X("nosasl", SASLminc(6), NOSASLSW) \ - X("saslmaxssf", SASLminc(10), SASLMXSSFSW) \ X("saslmech", SASLminc(5), SASLMECHSW) \ X("user", SASLminc(-4), USERSW) \ X("port server submission port name/number", 4, PORTSW) \ @@@ -231,7 -230,6 +230,6 @@@ static int checksw = 0; /* whom -chec static int linepos=0; /* putadr()'s position on the line */ static int nameoutput=0; /* putadr() has output header name */ static int sasl=0; /* Use SASL auth for SMTP */ - static int saslssf=-1; /* Our maximum SSF for SASL */ static char *saslmech=NULL; /* Force use of particular SASL mech */ static char *user=NULL; /* Authenticate as this user */ static char *port="submission"; /* Name of server port for SMTP submission */ @@@ -278,7 -276,7 +276,7 @@@ static char *partno = NULL /* * 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 *); @@@ -290,14 -288,14 +288,14 @@@ static void anno (void) 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); @@@ -308,7 -306,6 +306,7 @@@ in 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; @@@ -463,12 -460,6 +461,6 @@@ sasl = 0; continue; - case SASLMXSSFSW: - if (!(cp = *argp++) || *cp == '-') - adios (NULL, "missing argument to %s", argp[-2]); - saslssf = atoi(cp); - continue; - case SASLMECHSW: if (!(saslmech = *argp++) || *saslmech == '-') adios (NULL, "missing argument to %s", argp[-2]); @@@ -611,14 -602,14 +603,14 @@@ 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; @@@ -720,7 -711,7 +712,7 @@@ /* 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); } @@@ -732,15 -723,14 +724,15 @@@ 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); @@@ -763,7 -753,7 +755,7 @@@ */ 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; @@@ -780,24 -770,8 +772,24 @@@ 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 @@@ -845,8 -819,7 +837,8 @@@ } 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; @@@ -861,7 -834,6 +853,7 @@@ else badmsg++; } + } if (count < 1) { if (hdr->flags & HNIL) @@@ -1605,8 -1577,8 +1597,8 @@@ do_addresses (int bccque, int talk */ 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; @@@ -1654,13 -1626,11 +1646,12 @@@ 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, saslssf, - saslmech, user, ++ 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) @@@ -1688,23 -1658,19 +1679,22 @@@ /* 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, saslssf, - saslmech, user, + 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"); diff --combined uip/send.c index a4911989,4a6e0399..1e48ea3a --- a/uip/send.c +++ b/uip/send.c @@@ -63,7 -63,6 +63,6 @@@ X("snoop", 5, SNOOPSW) \ X("sasl", SASLminc(4), SASLSW) \ X("nosasl", SASLminc(6), NOSASLSW) \ - X("saslmaxssf", SASLminc(6), SASLMXSSFSW) \ X("saslmech mechanism", SASLminc(6), SASLMECHSW) \ X("authservice", SASLminc(0), AUTHSERVICESW) \ X("user username", SASLminc(-4), USERSW) \ @@@ -287,7 -286,6 +286,6 @@@ main (int argc, char **argv case WIDTHSW: case CLIESW: case SERVSW: - case SASLMXSSFSW: case PORTSW: case MTSSM: case MTSSW: @@@ -478,7 -476,7 +476,7 @@@ go_to_it 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: