- 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 (). */
- }
-
- sasl_complete = 1;
-
- return RP_OK;
-}
-
-/*
- * Our callback functions to feed data to the SASL library
- */
-
-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)
-{
- 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 *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, ...)
-{
- 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);
- sm_reply.code = NOTOK;
-
- return RP_BHST;
-}
-
-static int
-smtalk (int time, char *fmt, ...)
-{
- va_list ap;
- int result;
- char *buffer;
- size_t bufsize = BUFSIZ;
-
- buffer = mh_xmalloc(bufsize);
-
- va_start(ap, fmt);
- 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) ");
- if (tls_active)
- printf("(tls-encrypted) ");
- printf ("=> %s\n", buffer);
- fflush (stdout);
- }
-
- sm_alarmed = 0;
- alarm ((unsigned) time);
- if ((result = sm_wrecord (buffer, strlen (buffer))) != NOTOK)
- result = smhear ();
- alarm (0);
-
- free(buffer);
-
- return result;
-}
-
-
-/*
- * write the buffer to the open SMTP channel
- */
-
-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);
-}
-
-
-static int
-sm_wstream (char *buffer, int len)
-{
- char *bp;
- static char lc = '\0';
-
- if (sm_wfp == NULL)
- return sm_werror ();
-
- if (buffer == NULL && len == 0) {
- if (lc != '\n')
- sm_fputs ("\r\n");
- lc = '\0';
- return (ferror (sm_wfp) ? sm_werror () : OK);
- }
-
- for (bp = buffer; bp && len > 0; bp++, len--) {
- switch (*bp) {
- case '\n':
- sm_nl = TRUE;
- sm_fputc ('\r');
- break;
-
- case '.':
- if (sm_nl)
- sm_fputc ('.');/* FALL THROUGH */
- default:
- sm_nl = FALSE;
- }
- sm_fputc (*bp);
- if (ferror (sm_wfp))
- return sm_werror ();
- }
-
- 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 occurences 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);
-}
-
-
-static int
-smhear (void)
-{
- int i, code, cont, bc = 0, rc, more;
- unsigned char *bp;
- char *rp;
- char **ehlo = EHLOkeys, buffer[BUFSIZ];
-
- if (doingEHLO) {
- static int at_least_once = 0;
-
- if (at_least_once) {
- char *ep;
-
- for (ehlo = EHLOkeys; *ehlo; ehlo++) {
- ep = *ehlo;
- free (ep);
- }
- } else {
- at_least_once = 1;
- }
-
- ehlo = EHLOkeys;
- *ehlo = NULL;
- }
-
-again: ;
-
- sm_reply.length = 0;
- sm_reply.text[0] = 0;
- 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) {
- if (sasl_ssf > 0)
- printf("(sasl-decrypted) ");
- if (tls_active)
- printf("(tls-decrypted) ");
- printf ("<= %s\n", buffer);
- fflush (stdout);
- }
-
- if (doingEHLO
- && strncmp (buffer, "250", sizeof("250") - 1) == 0
- && (buffer[3] == '-' || doingEHLO == 2)
- && buffer[4]) {
- if (doingEHLO == 2) {
- if ((*ehlo = malloc ((size_t) (strlen (buffer + 4) + 1)))) {
- strcpy (*ehlo++, buffer + 4);
- *ehlo = NULL;
- if (ehlo >= EHLOkeys + MAXEHLO)
- doingEHLO = 0;
- }
- else
- doingEHLO = 0;
- }
- else
- doingEHLO = 2;
- }
-
- for (; bc > 0 && (!isascii (*bp) || !isdigit (*bp)); bp++, bc--)
- continue;
-
- cont = FALSE;
- code = atoi ((char *) bp);
- bp += 3, bc -= 3;
- for (; bc > 0 && isspace (*bp); bp++, bc--)
- continue;
- if (bc > 0 && *bp == '-') {
- cont = TRUE;
- bp++, bc--;
- for (; bc > 0 && isspace (*bp); bp++, bc--)
- continue;