X-Git-Url: https://diplodocus.org/git/nmh/blobdiff_plain/68fbd79dd7baa8211c0b1854dc5e0d6acf186ddd..d945b90675db0ebddfa4e517f78b92e1da7896e5:/mts/smtp/smtp.c?ds=inline diff --git a/mts/smtp/smtp.c b/mts/smtp/smtp.c index e6ac1661..1403102b 100644 --- a/mts/smtp/smtp.c +++ b/mts/smtp/smtp.c @@ -10,6 +10,7 @@ #include "smtp.h" #include #include +#include #ifdef CYRUS_SASL #include @@ -75,9 +76,7 @@ #define SM_DOT 600 /* see above */ #define SM_QUIT 30 #define SM_CLOS 10 -#ifdef CYRUS_SASL #define SM_AUTH 45 -#endif /* CYRUS_SASL */ static int sm_addrs = 0; static int sm_alarmed = 0; @@ -152,7 +151,7 @@ static char *EHLOkeys[MAXEHLO + 1]; * static prototypes */ static int smtp_init (char *, char *, char *, int, int, int, int, int, - char *, char *, int); + char *, char *, const char *, int); static int sendmail_init (char *, char *, int, int, int, int, int, char *, char *); @@ -172,6 +171,7 @@ 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 *); #ifdef CYRUS_SASL /* @@ -183,11 +183,13 @@ static int sm_auth_sasl(char *, int, char *, 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 tls) + int debug, int sasl, int saslssf, char *saslmech, char *user, + const char *xoauth_client_res, int tls) { if (sm_mts == MTS_SMTP) return smtp_init (client, server, port, watch, verbose, - debug, sasl, saslssf, saslmech, user, tls); + debug, sasl, saslssf, saslmech, user, + xoauth_client_res, tls); else return sendmail_init (client, server, watch, verbose, debug, sasl, saslssf, saslmech, user); @@ -196,12 +198,11 @@ sm_init (char *client, char *server, char *port, int watch, int verbose, 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 tls) + int sasl, int saslssf, char *saslmech, char *user, + const char *xoauth_client_res, int tls) { int result, sd1, sd2; -#ifdef CYRUS_SASL - char *server_mechs; -#else /* CYRUS_SASL */ +#ifndef CYRUS_SASL NMH_UNUSED (sasl); NMH_UNUSED (saslssf); NMH_UNUSED (saslmech); @@ -361,6 +362,7 @@ smtp_init (char *client, char *server, char *port, int watch, int verbose, */ if (sasl) { + char *server_mechs; if (! (server_mechs = EHLOset("AUTH"))) { sm_end(NOTOK); return sm_ierror("SMTP server does not support SASL"); @@ -373,7 +375,10 @@ smtp_init (char *client, char *server, char *port, int watch, int verbose, saslmech, server_mechs); } - if (sm_auth_sasl(user, saslssf, saslmech ? saslmech : server_mechs, + /* Don't call sm_auth_sasl() for XAUTH2 with -sasl. Instead, call + sm_auth_xoauth2() below. */ + if (xoauth_client_res == NULL && + sm_auth_sasl(user, saslssf, saslmech ? saslmech : server_mechs, server) != RP_OK) { sm_end(NOTOK); return NOTOK; @@ -381,6 +386,19 @@ smtp_init (char *client, char *server, char *port, int watch, int verbose, } #endif /* CYRUS_SASL */ + if (xoauth_client_res != 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(xoauth_client_res) != RP_OK) { + sm_end(NOTOK); + return NOTOK; + } + } + send_options: ; if (watch && EHLOset ("XVRB")) smtalk (SM_HELO, "VERB on"); @@ -798,17 +816,26 @@ sm_end (int type) * 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) { int result, status; unsigned int buflen, outlen; - char *buf, outbuf[BUFSIZ], host[NI_MAXHOST]; + 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 @@ -905,10 +932,13 @@ sm_auth_sasl(char *user, int saslssf, char *mechlist, char *inhost) */ if (buflen) { - status = sasl_encode64(buf, buflen, outbuf, sizeof(outbuf), NULL); + 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; } @@ -935,8 +965,11 @@ sm_auth_sasl(char *user, int saslssf, char *mechlist, char *inhost) if (status == 235) break; - else if (status < 300 || status > 399) + else if (status < 300 || status > 399) { + if (outbuf) + free(outbuf); return RP_BHST; + } /* * Special case; a zero-length response from the SMTP server @@ -947,12 +980,18 @@ sm_auth_sasl(char *user, int saslssf, char *mechlist, char *inhost) 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, sizeof(outbuf), &outlen); + 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; } } @@ -964,21 +1003,29 @@ sm_auth_sasl(char *user, int saslssf, char *mechlist, char *inhost) smtalk(SM_AUTH, "*"); sm_ierror("SASL client negotiation failed: %s", sasl_errstring(result, NULL, NULL)); + if (outbuf) + free(outbuf); return NOTOK; } - status = sasl_encode64(buf, buflen, outbuf, sizeof(outbuf), NULL); + 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 */ @@ -1102,6 +1149,36 @@ sm_get_pass(sasl_conn_t *conn, void *context, int id, } #endif /* CYRUS_SASL */ +/* https://developers.google.com/gmail/xoauth2_protocol */ +static int +sm_auth_xoauth2(const char *client_res) +{ + int status = smtalk(SM_AUTH, "AUTH XOAUTH2 %s", 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, ...) { @@ -1122,12 +1199,22 @@ smtalk (int time, char *fmt, ...) { va_list ap; int result; - char buffer[BUFSIZ]; + char *buffer; + size_t bufsize = BUFSIZ; + + buffer = mh_xmalloc(bufsize); va_start(ap, fmt); - vsnprintf (buffer, sizeof(buffer), fmt, ap); + result = vsnprintf (buffer, bufsize, 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) { if (sasl_ssf) printf("(sasl-encrypted) "); @@ -1143,6 +1230,8 @@ smtalk (int time, char *fmt, ...) result = smhear (); alarm (0); + free(buffer); + return result; } @@ -1181,7 +1270,7 @@ sm_wstream (char *buffer, int len) return (ferror (sm_wfp) ? sm_werror () : OK); } - for (bp = buffer; len > 0; bp++, len--) { + for (bp = buffer; bp && len > 0; bp++, len--) { switch (*bp) { case '\n': sm_nl = TRUE; @@ -1230,7 +1319,9 @@ sm_fwrite(char *buffer, int len) } } else #endif /* TLS_SUPPORT */ - fwrite(buffer, sizeof(*buffer), len, sm_wfp); + if ((int) fwrite(buffer, sizeof(*buffer), len, sm_wfp) < len) { + advise ("sm_fwrite", "fwrite"); + } #ifdef CYRUS_SASL } else { while (len >= maxoutbuf - sasl_outbuflen) { @@ -1246,7 +1337,10 @@ sm_fwrite(char *buffer, int len) return NOTOK; } - fwrite(output, sizeof(*output), outputlen, sm_wfp); + if (fwrite(output, sizeof(*output), outputlen, sm_wfp) < + outputlen) { + advise ("sm_fwrite", "fwrite"); + } } if (len > 0) { @@ -1393,7 +1487,9 @@ sm_fflush(void) return; } - fwrite(output, sizeof(*output), outputlen, sm_wfp); + if (fwrite(output, sizeof(*output), outputlen, sm_wfp) < outputlen) { + advise ("sm_fflush", "fwrite"); + } sasl_outbuflen = 0; } #endif /* CYRUS_SASL */ @@ -1425,7 +1521,7 @@ smhear (void) int i, code, cont, bc = 0, rc, more; unsigned char *bp; char *rp; - char **ehlo = NULL, buffer[BUFSIZ]; + char **ehlo = EHLOkeys, buffer[BUFSIZ]; if (doingEHLO) { static int at_least_once = 0;