X-Git-Url: https://diplodocus.org/git/nmh/blobdiff_plain/337b4e616e8f53ba06285b1645e1df9918ed5c16..9dbb49a38ef76acdaa1217d2f66af1c58449d6ff:/uip/popsbr.c diff --git a/uip/popsbr.c b/uip/popsbr.c index 2d194a67..b8e5a448 100644 --- a/uip/popsbr.c +++ b/uip/popsbr.c @@ -8,6 +8,7 @@ #include #include +#include #ifdef CYRUS_SASL # include @@ -21,6 +22,11 @@ # endif /* SASL_VERSION_FULL < 0x020125 */ #endif /* CYRUS_SASL */ +#ifdef TLS_SUPPORT +#include +#include +#endif /* TLS_SUPPORT */ + #include #include @@ -58,8 +64,6 @@ static sasl_callback_t callbacks[] = { #define SASL_BUFFER_SIZE 262144 }; -#else /* CYRUS_SASL */ -# define sasl_fgetc fgetc #endif /* CYRUS_SASL */ /* @@ -71,43 +75,31 @@ static int multiline(void); #ifdef CYRUS_SASL static int pop_auth_sasl(char *, char *, char *); -static int sasl_fgetc(FILE *); #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 */ + +static int tls_active = 0; + static int traverse (int (*)(char *), const char *, ...); static int vcommand(const char *, va_list); static int sasl_getline (char *, int, FILE *); +static int sasl_fgetc(FILE *); static int putline (char *, FILE *); -#ifdef CYRUS_SASL -/* - * This function implements the AUTH command for various SASL mechanisms - * - * We do the whole SASL dialog here. If this completes, then we've - * authenticated successfully and have (possibly) 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); \ - } \ - } - int -pop_auth_sasl(char *user, char *host, char *mech) +check_mech(char *server_mechs, size_t server_mechs_size, char *mech) { - int result, status, sasl_capability = 0; - unsigned int buflen, outlen; - char server_mechs[256], *buf, *outbuf = NULL; - size_t outbufsize = 0; - const char *chosen_mech; - sasl_security_properties_t secprops; - struct pass_context p_context; - sasl_ssf_t *ssf; - int *moutbuf; + int status, sasl_capability = 0; /* * First off, we're going to send the CAPA command to see if we can @@ -137,7 +129,7 @@ pop_auth_sasl(char *user, char *host, char *mech) * We've seen the SASL capability. Grab the mech list */ sasl_capability++; - strncpy(server_mechs, response + 5, sizeof(server_mechs)); + strncpy(server_mechs, response + 5, server_mechs_size); } break; } @@ -159,6 +151,42 @@ pop_auth_sasl(char *user, char *host, char *mech) return NOTOK; } + return OK; +} + +#ifdef CYRUS_SASL +/* + * This function implements the AUTH command for various SASL mechanisms + * + * We do the whole SASL dialog here. If this completes, then we've + * authenticated successfully and have (possibly) 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); \ + } \ + } + +int +pop_auth_sasl(char *user, char *host, char *mech) +{ + int result, status; + unsigned int buflen, outlen; + char server_mechs[256], *buf, *outbuf = NULL; + size_t outbufsize = 0; + const char *chosen_mech; + sasl_security_properties_t secprops; + struct pass_context p_context; + sasl_ssf_t *ssf; + int *moutbuf; + + if ((status = check_mech(server_mechs, sizeof(server_mechs), mech)) != OK) { + return status; + } + /* * Start the SASL process. First off, initialize the SASL library. */ @@ -190,7 +218,7 @@ pop_auth_sasl(char *user, char *host, char *mech) memset(&secprops, 0, sizeof(secprops)); secprops.maxbufsize = SASL_BUFFER_SIZE; - secprops.max_ssf = UINT_MAX; + secprops.max_ssf = tls_active ? 0 : UINT_MAX; result = sasl_setprop(conn, SASL_SEC_PROPS, &secprops); @@ -422,6 +450,26 @@ sasl_get_pass(sasl_conn_t *conn, void *context, int id, sasl_secret_t **psecret) } #endif /* CYRUS_SASL */ +int +pop_auth_xoauth(const char *client_res) +{ + char server_mechs[256]; + int status = check_mech(server_mechs, sizeof(server_mechs), "XOAUTH"); + + if (status != OK) return status; + + if ((status = command("AUTH XOAUTH2 %s", client_res)) != OK) { + return status; + } + if (strncmp(response, "+OK", 3) == 0) { + return OK; + } + + /* response contains base64-encoded JSON, which is always the same. + * See mts/smtp/smtp.c for more notes on that. */ + /* Then we're supposed to send an empty response ("\r\n"). */ + return command(""); +} /* * Split string containing proxy command into an array of arguments @@ -467,21 +515,33 @@ parse_proxy(char *proxy, char *host) } else if (!isspace(*cur)) *c++ = *cur; } + *c = '\0'; *++p = NULL; return pargv; } int pop_init (char *host, char *port, char *user, char *pass, char *proxy, - int snoop, int sasl, char *mech) + int snoop, int sasl, char *mech, int tls, const char *oauth_svc) { int fd1, fd2; char buffer[BUFSIZ]; + const char *xoauth_client_res = NULL; #ifndef CYRUS_SASL NMH_UNUSED (sasl); NMH_UNUSED (mech); #endif /* ! CYRUS_SASL */ +#ifdef OAUTH_SUPPORT + if (oauth_svc != NULL) { + xoauth_client_res = mh_oauth_do_xoauth(user, oauth_svc, + snoop ? stderr : NULL); + } +#else + NMH_UNUSED (oauth_svc); + NMH_UNUSED (xoauth_client_res); +#endif /* OAUTH_SUPPORT */ + if (proxy && *proxy) { int pid; int inpipe[2]; /* for reading from the server */ @@ -554,6 +614,9 @@ pop_init (char *host, char *port, char *user, char *pass, char *proxy, SIGNAL (SIGPIPE, SIG_IGN); + if (tls && tls_negotiate()) + return NOTOK; + switch (sasl_getline (response, sizeof response, input)) { case OK: if (poprint) @@ -565,6 +628,12 @@ pop_init (char *host, char *port, char *user, char *pass, char *proxy, return OK; } else # endif /* CYRUS_SASL */ +# if OAUTH_SUPPORT + if (xoauth_client_res != NULL) { + if (pop_auth_xoauth(xoauth_client_res) != NOTOK) + return OK; + } else +# endif /* OAUTH_SUPPORT */ if (command ("USER %s", user) != NOTOK && command ("%s %s", (pophack++, "PASS"), pass) != NOTOK) @@ -804,8 +873,12 @@ vcommand (const char *fmt, va_list ap) if (poprint) { #ifdef CYRUS_SASL if (sasl_ssf) - fprintf(stderr, "(encrypted) "); + fprintf(stderr, "(sasl-encrypted) "); #endif /* CYRUS_SASL */ +#ifdef TLS_SUPPORT + if (tls_active) + fprintf(stderr, "(tls-encrypted) "); +#endif /* TLS_SUPPORT */ if (pophack) { if ((cp = strchr (buffer, ' '))) *cp = 0; @@ -914,8 +987,23 @@ putline (char *s, FILE *iop) int result; unsigned int buflen; - if (!sasl_complete) { + if (sasl_complete == 0 || sasl_ssf == 0) { #endif /* CYRUS_SASL */ +#ifdef TLS_SUPPORT + if (tls_active) { + int ret; + + BIO_printf(io, "%s\r\n"); + ret = BIO_flush(io); + + if (ret != 1) { + strncpy(response, "lost connection", sizeof(response)); + return NOTOK; + else { + return OK; + } + } else +#endif /* TLS_SUPPORT */ fprintf (iop, "%s\r\n", s); #ifdef CYRUS_SASL } else { @@ -936,7 +1024,9 @@ putline (char *s, FILE *iop) return NOTOK; } - fwrite(buf, buflen, 1, iop); + if (fwrite(buf, buflen, 1, iop) < 1) { + advise ("putline", "fwrite"); + } } #endif /* CYRUS_SASL */ @@ -979,6 +1069,9 @@ sasl_fgetc(FILE *f) while (retbufsize == 0) { +#ifdef TLS_SUPPORT + if (tls_active) { + cc = read(fileno(f), tmpbuf, sizeof(tmpbuf)); if (cc == 0) @@ -1025,3 +1118,102 @@ sasl_fgetc(FILE *f) return (int) buffer[0]; } #endif /* CYRUS_SASL */ + +#ifdef TLS_SUPPORT +/* + * Negotiate TLS at this point. Will call pop_done() on failure. + */ + +static int +tls_negotiate(void) +{ + BIO *ssl_bio; + + if (! sslctx) { + SSL_library_init(); + SSL_load_error_strings(); + + sslctx = SSL_CTX_new(SSLv23_client_method()); + + if (! sslctx) { + pop_done(); + advise(NULL, "Unable to initialize OpenSSL context: %s", + ERR_error_string(ERR_get_error(), NULL)); + return NOTOK; + } + + SSL_CTX_set_options(sslctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | + SSL_OP_NO_TLSv1); + } + + ssl = SSL_new(sslctx); + + if (! ssl) { + pop_done(); + advise(NULL, "Unable to create SSL connection: %s", + ERR_error_string(ERR_get_error(), NULL)); + return NOTOK; + } + + sbior = BIO_new_socket(fileno(input), BIO_NOCLOSE); + sbiow = BIO_new_socket(fileno(output), BIO_NOCLOSE); + + if (sbior == NULL || sbiow == NULL) { + pop_done(); + advise(NULL, "Unable to create BIO endpoints: %s", + ERR_error_string(ERR_get_error(), NULL)); + return NOTOK; + } + + SSL_set_bio(ssl, sbior, sbiow); + SSL_set_connect_state(ssl); + + /* + * Set up a BIO to do buffering for us + */ + + io = BIO_new(BIO_f_buffer()); + + if (! io) { + pop_done(); + advise(NULL, "Unable to create a buffer BIO: %s", + ERR_error_string(ERR_get_error(), NULL)); + return NOTOK; + } + + ssl_bio = BIO_new(BIO_f_ssl()); + + if (! ssl_bio) { + pop_done(); + advise(NULL, "Unable to create a SSL BIO: %s", + ERR_error_string(ERR_get_error(), NULL)); + return NOTOK; + } + + BIO_set_ssl(ssl_bio, ssl, BIO_CLOSE); + BIO_push(io, ssl_bio); + + /* + * Try doing the handshake now + */ + + if (BIO_do_handshake(io) < 1) { + pop_done(); + advise(NULL, "Unable to negotiate SSL connection: %s", + ERR_error_string(ERR_get_error(), NULL)); + return NOTOK; + } + + if (poprint) { + 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 OK; +} +#endif /* TLS_SUPPORT */