X-Git-Url: https://diplodocus.org/git/nmh/blobdiff_plain/ceaab2f46ae8fa4598b9682eaf3210cda21517c9..e5afa1df87c24d49f0572ff903d4b0ee2eb803a8:/sbr/netsec.c diff --git a/sbr/netsec.c b/sbr/netsec.c index 5bfbea60..788b23bf 100644 --- a/sbr/netsec.c +++ b/sbr/netsec.c @@ -54,8 +54,12 @@ static SSL_CTX *sslctx = NULL; /* SSL Context */ */ struct _netsec_context { - int ns_fd; /* Descriptor for network connection */ + int ns_readfd; /* Read descriptor for network connection */ + int ns_writefd; /* Write descriptor for network connection */ int ns_snoop; /* If true, display network data */ + int ns_snoop_noend; /* If true, didn't get a CR/LF on last line */ + netsec_snoop_callback *ns_snoop_cb; /* Snoop output callback */ + void *ns_snoop_context; /* Context data for snoop function */ int ns_timeout; /* Network read timeout, in seconds */ char *ns_userid; /* Userid for authentication */ unsigned char *ns_inbuffer; /* Our read input buffer */ @@ -67,6 +71,8 @@ struct _netsec_context { unsigned int ns_outbuflen; /* Output buffer data length */ unsigned int ns_outbufsize; /* Output buffer size */ char *sasl_mech; /* User-requested mechanism */ + char *sasl_chosen_mech; /* Mechanism chosen by SASL */ + netsec_sasl_callback sasl_proto_cb; /* SASL callback we use */ #ifdef OAUTH_SUPPORT char *oauth_service; /* OAuth2 service name */ #endif /* OAUTH_SUPPORT */ @@ -74,11 +80,9 @@ struct _netsec_context { char *sasl_hostname; /* Hostname we've connected to */ sasl_conn_t *sasl_conn; /* SASL connection context */ sasl_ssf_t sasl_ssf; /* SASL Security Strength Factor */ - netsec_sasl_callback sasl_proto_cb; /* SASL callback we use */ sasl_callback_t *sasl_cbs; /* Callbacks used by SASL */ nmh_creds_t sasl_creds; /* Credentials (username/password) */ sasl_secret_t *sasl_secret; /* SASL password structure */ - char *sasl_chosen_mech; /* Mechanism chosen by SASL */ int sasl_seclayer; /* If true, SASL security layer is enabled */ char *sasl_tmpbuf; /* Temporary read buffer for decodes */ size_t sasl_maxbufsize; /* Maximum negotiated SASL buffer size */ @@ -95,20 +99,35 @@ struct _netsec_context { static int netsec_fillread(netsec_context *ns_context, char **errstr); +/* + * Code to check the ASCII content of a byte array. + */ + +static int checkascii(const unsigned char *byte, size_t len); + /* * How this code works, in general. * - * _If_ we are using no encryption or SASL encryption, then we buffer the - * network data through ns_inbuffer and ns_outbuffer. That should be - * relatively self-explanatory. + * _If_ we are using no encryption then we buffer the network data + * through ns_inbuffer and ns_outbuffer. That should be relatively + * self-explanatory. + * + * If we use encryption, then ns_inbuffer and ns_outbuffer contain the + * cleartext data. When it comes time to send the encrypted data on the + * (either from a flush or the buffer is full) we either use BIO_write() + * for TLS or sasl_encode() (followed by a write() for Cyrus-SASL. For + * reads we either use BIO_read() (TLS) or do a network read into a + * temporary buffer and use sasl_decode() (Cyrus-SASL). Note that if + * negotiate TLS then we disable SASL encryption. * - * If we are using SSL for encryption, then use a buffering BIO for output - * (that just easier). Still do buffering for reads; when we need more - * data we call the BIO_read() function to fill our local buffer. + * We used to use a buffering BIO for the reads/writes for TLS, but it + * ended up being complicated to special-case the buffering for everything + * except TLS, so the buffering is now unified, no matter which encryption + * method is being used (even none). * - * For SASL, we make use of (for now) the Cyrus-SASL library. For some - * mechanisms, we implement those mechanisms directly since the Cyrus SASL - * library doesn't support them (like OAuth). + * For SASL authentication, we make use of (for now) the Cyrus-SASL + * library. For some mechanisms, we implement those mechanisms directly + * since the Cyrus SASL library doesn't support them (like OAuth). */ /* @@ -118,10 +137,15 @@ static int netsec_fillread(netsec_context *ns_context, char **errstr); netsec_context * netsec_init(void) { - netsec_context *nsc = mh_xmalloc(sizeof(*nsc)); + netsec_context *nsc; - nsc->ns_fd = -1; + NEW(nsc); + nsc->ns_readfd = -1; + nsc->ns_writefd = -1; nsc->ns_snoop = 0; + nsc->ns_snoop_noend = 0; + nsc->ns_snoop_cb = NULL; + nsc->ns_snoop_context = NULL; nsc->ns_userid = NULL; nsc->ns_timeout = 60; /* Our default */ nsc->ns_inbufsize = NETSEC_BUFSIZE; @@ -133,6 +157,8 @@ netsec_init(void) nsc->ns_outptr = nsc->ns_outbuffer; nsc->ns_outbuflen = 0; nsc->sasl_mech = NULL; + nsc->sasl_chosen_mech = NULL; + nsc->sasl_proto_cb = NULL; #ifdef OAUTH_SUPPORT nsc->oauth_service = NULL; #endif /* OAUTH_SUPPORT */ @@ -142,7 +168,6 @@ netsec_init(void) nsc->sasl_cbs = NULL; nsc->sasl_creds = NULL; nsc->sasl_secret = NULL; - nsc->sasl_chosen_mech = NULL; nsc->sasl_ssf = 0; nsc->sasl_seclayer = 0; nsc->sasl_tmpbuf = NULL; @@ -171,6 +196,8 @@ netsec_shutdown(netsec_context *nsc, int closeflag) free(nsc->ns_outbuffer); if (nsc->sasl_mech) free(nsc->sasl_mech); + if (nsc->sasl_chosen_mech) + free(nsc->sasl_chosen_mech); #ifdef OAUTH_SERVICE if (nsc->oauth_service) free(nsc->oauth_service); @@ -194,8 +221,6 @@ netsec_shutdown(netsec_context *nsc, int closeflag) } free(nsc->sasl_secret); } - if (nsc->sasl_chosen_mech) - free(nsc->sasl_chosen_mech); if (nsc->sasl_tmpbuf) free(nsc->sasl_tmpbuf); #endif /* CYRUS_SASL */ @@ -208,8 +233,12 @@ netsec_shutdown(netsec_context *nsc, int closeflag) BIO_free_all(nsc->ssl_io); #endif /* TLS_SUPPORT */ - if (closeflag && nsc->ns_fd != -1) - close(nsc->ns_fd); + if (closeflag) { + if (nsc->ns_readfd != -1) + close(nsc->ns_readfd); + if (nsc->ns_writefd != -1 && nsc->ns_writefd != nsc->ns_readfd) + close(nsc->ns_writefd); + } free(nsc); } @@ -219,9 +248,10 @@ netsec_shutdown(netsec_context *nsc, int closeflag) */ void -netsec_set_fd(netsec_context *nsc, int fd) +netsec_set_fd(netsec_context *nsc, int readfd, int writefd) { - nsc->ns_fd = fd; + nsc->ns_readfd = readfd; + nsc->ns_writefd = writefd; } /* @@ -254,6 +284,84 @@ netsec_set_snoop(netsec_context *nsc, int snoop) nsc->ns_snoop = snoop; } +/* + * Set the snoop callback for this connection. + */ + +void netsec_set_snoop_callback(netsec_context *nsc, + netsec_snoop_callback callback, void *context) +{ + nsc->ns_snoop_cb = callback; + nsc->ns_snoop_context = context; +} + +/* + * A base64-decoding snoop callback + */ + +void +netsec_b64_snoop_decoder(netsec_context *nsc, const char *string, size_t len, + void *context) +{ + unsigned char *decoded; + size_t decodedlen; + int offset; + NMH_UNUSED(nsc); + + offset = context ? *((int *) context) : 0; + + if (offset > 0) { + /* + * Output non-base64 data first. + */ + fprintf(stderr, "%.*s", offset, string); + string += offset; + len -= offset; + } + + if (decodeBase64(string, &decoded, &decodedlen, 1, NULL) == OK) { + /* + * Some mechanisms produce large binary tokens, which aren't really + * readable. So let's do a simple heuristic. If the token is greater + * than 100 characters _and_ the first 100 bytes are more than 50% + * non-ASCII, then don't print the decoded buffer, just the + * base64 text. + */ + if (decodedlen > 100 && !checkascii(decoded, 100)) { + fprintf(stderr, "%.*s\n", (int) len, string); + } else { + char *hexified; + hexify(decoded, decodedlen, &hexified); + fprintf(stderr, "b64<%s>\n", hexified); + free(hexified); + } + free(decoded); + } else { + fprintf(stderr, "%.*s\n", (int) len, string); + } +} + +/* + * If the ASCII content is > 50%, return 1 + */ + +static int +checkascii(const unsigned char *bytes, size_t len) +{ + size_t count = 0, half = len / 2; + + while (len-- > 0) { + if (isascii(*bytes) && isprint(*bytes) && ++count > half) + return 1; + bytes++; + /* No chance by this point */ + if (count + len < half) + return 0; + } + + return 0; +} + /* * Set the read timeout for this connection */ @@ -313,11 +421,12 @@ netsec_read(netsec_context *nsc, void *buffer, size_t size, char **errstr) * * - Unlike every other function, we return a pointer to the * existing buffer. This pointer is valid until you call another - * read functiona again. - * - We NUL-terminated the buffer right at the end, before the terminator. + * read function again. + * - We NUL-terminate the buffer right at the end, before the CR-LF terminator. * - Technically we look for a LF; if we find a CR right before it, then * we back up one. - * - If your data may contain embedded NULs, this won't work. + * - If your data may contain embedded NULs, this won't work. You should + * be using netsec_read() in that case. */ char * @@ -342,6 +451,22 @@ retry: *len = ptr - nsc->ns_inptr; nsc->ns_inptr += count; nsc->ns_inbuflen -= count; + if (nsc->ns_snoop) { +#ifdef CYRUS_SASL + if (nsc->sasl_seclayer) + fprintf(stderr, "(sasl-decrypted) "); +#endif /* CYRUS_SASL */ +#ifdef TLS_SUPPORT + if (nsc->tls_active) + fprintf(stderr, "(tls-decrypted) "); +#endif /* TLS_SUPPORT */ + fprintf(stderr, "<= "); + if (nsc->ns_snoop_cb) + nsc->ns_snoop_cb(nsc, sptr, strlen(sptr), + nsc->ns_snoop_context); + else + fprintf(stderr, "%s\n", sptr); + } return sptr; } } @@ -395,7 +520,9 @@ netsec_fillread(netsec_context *nsc, char **errstr) nsc->ns_inptr = nsc->ns_inbuffer; } +#if defined(CYRUS_SASL) || defined(TLS_SUPPORT) retry: +#endif /* CYRUS_SASL || TLS_SUPPORT */ /* * If we are using TLS and there's anything pending, then skip the * select call @@ -408,12 +535,12 @@ retry: fd_set rfds; FD_ZERO(&rfds); - FD_SET(nsc->ns_fd, &rfds); + FD_SET(nsc->ns_readfd, &rfds); tv.tv_sec = nsc->ns_timeout; tv.tv_usec = 0; - rc = select(nsc->ns_fd + 1, &rfds, NULL, NULL, &tv); + rc = select(nsc->ns_readfd + 1, &rfds, NULL, NULL, &tv); if (rc == -1) { netsec_err(errstr, "select() while reading failed: %s", @@ -433,10 +560,38 @@ retry: */ } + /* + * Some explanation: + * + * startoffset is the offset from the beginning of the input + * buffer to data that is in our input buffer, but has not yet + * been consumed. This can be non-zero if functions like + * netsec_readline() leave leftover data. + * + * remaining is the remaining amount of unconsumed data in the input + * buffer. + * + * end is a pointer to the end of the valid data + 1; it's where + * the next read should go. + */ + startoffset = nsc->ns_inptr - nsc->ns_inbuffer; remaining = nsc->ns_inbufsize - (startoffset + nsc->ns_inbuflen); end = nsc->ns_inptr + nsc->ns_inbuflen; + /* + * If we're past the halfway point in our read buffers, shuffle everything + * back to the beginning. + */ + + if (startoffset > nsc->ns_inbufsize / 2) { + memmove(nsc->ns_inbuffer, nsc->ns_inptr, nsc->ns_inbuflen); + nsc->ns_inptr = nsc->ns_inbuffer; + startoffset = 0; + remaining = nsc->ns_inbufsize - nsc->ns_inbuflen; + end = nsc->ns_inptr + nsc->ns_inbuflen; + } + /* * If we are using TLS, then just read via the BIO. But we still * use our local buffer. @@ -445,11 +600,36 @@ retry: if (nsc->tls_active) { rc = BIO_read(nsc->ssl_io, end, remaining); if (rc == 0) { + SSL *ssl; + int errcode; + /* - * Either EOF, or possibly an error. Either way, it was probably - * unexpected, so treat as error. + * Check to see if we're supposed to retry; if so, + * then go back and read again. */ - netsec_err(errstr, "TLS peer aborted connection"); + + if (BIO_should_retry(nsc->ssl_io)) + goto retry; + + /* + * Okay, fine. Get the real error out of the SSL context. + */ + + if (BIO_get_ssl(nsc->ssl_io, &ssl) < 1) { + netsec_err(errstr, "SSL_read() returned 0, but cannot " + "retrieve SSL context"); + return NOTOK; + } + + errcode = SSL_get_error(ssl, rc); + if (errcode == SSL_ERROR_ZERO_RETURN) { + netsec_err(errstr, "TLS peer closed remote connection"); + } else { + netsec_err(errstr, "TLS network read failed: %s", + ERR_error_string(ERR_peek_last_error(), NULL)); + } + if (nsc->ns_snoop) + ERR_print_errors_fp(stderr); return NOTOK; } else if (rc < 0) { /* Definitely an error */ @@ -485,7 +665,7 @@ retry: * select() above) so this read SHOULDN'T block. Hopefully. */ - rc = read(nsc->ns_fd, readbuf, readbufsize); + rc = read(nsc->ns_readfd, readbuf, readbufsize); if (rc == 0) { netsec_err(errstr, "Received EOF on network read"); @@ -537,16 +717,6 @@ retry: #endif /* CYRUS_SASL */ nsc->ns_inbuflen += rc; - /* - * If we're past the halfway point in our read buffers, shuffle everything - * back to the beginning. - */ - - if (startoffset > nsc->ns_inbufsize / 2) { - memmove(nsc->ns_inbuffer, nsc->ns_inptr, nsc->ns_inbuflen); - nsc->ns_inptr = nsc->ns_inbuffer; - } - return OK; } @@ -568,25 +738,6 @@ netsec_write(netsec_context *nsc, const void *buffer, size_t size, if (size == 0) return OK; - /* - * If TLS is active, then bypass all of our buffering logic; just - * write it directly to our BIO. We have a buffering BIO first in - * our stack, so buffering will take place there. - */ -#ifdef TLS_SUPPORT - if (nsc->tls_active) { - rc = BIO_write(nsc->ssl_io, buffer, size); - - if (rc <= 0) { - netsec_err(errstr, "Error writing to TLS connection: %s", - ERR_error_string(ERR_get_error(), NULL)); - return NOTOK; - } - - return OK; - } -#endif /* TLS_SUPPORT */ - /* * Run a loop copying in data to our local buffer; when we're done with * any buffer overflows then just copy any remaining data in. @@ -653,23 +804,6 @@ netsec_vprintf(netsec_context *nsc, char **errstr, const char *format, { int rc; - /* - * Again, if we're using TLS, then bypass our local buffering - */ -#ifdef TLS_SUPPORT - if (nsc->tls_active) { - rc = BIO_vprintf(nsc->ssl_io, format, ap); - - if (rc <= 0) { - netsec_err(errstr, "Error writing to TLS connection: %s", - ERR_error_string(ERR_get_error(), NULL)); - return NOTOK; - } - - return OK; - } -#endif /* TLS_SUPPORT */ - /* * Cheat a little. If we can fit the data into our outgoing buffer, * great! If not, generate a flush and retry once. @@ -711,6 +845,35 @@ retry: } } + if (nsc->ns_snoop) { + int outlen = rc; + if (outlen > 0 && nsc->ns_outptr[outlen - 1] == '\n') { + outlen--; + if (outlen > 0 && nsc->ns_outptr[outlen - 1] == '\r') + outlen--; + } else { + nsc->ns_snoop_noend = 1; + } + if (outlen > 0 || nsc->ns_snoop_noend == 0) { +#ifdef CYRUS_SASL + if (nsc->sasl_seclayer) + fprintf(stderr, "(sasl-encrypted) "); +#endif /* CYRUS_SASL */ +#ifdef TLS_SUPPORT + if (nsc->tls_active) + fprintf(stderr, "(tls-encrypted) "); +#endif /* TLS_SUPPORT */ + fprintf(stderr, "=> "); + if (nsc->ns_snoop_cb) + nsc->ns_snoop_cb(nsc, (char *) nsc->ns_outptr, outlen, + nsc->ns_snoop_context); + else + fprintf(stderr, "%.*s\n", outlen, nsc->ns_outptr); + } else { + nsc->ns_snoop_noend = 0; + } + } + nsc->ns_outptr += rc; nsc->ns_outbuflen += rc; @@ -729,24 +892,6 @@ netsec_flush(netsec_context *nsc, char **errstr) unsigned int netoutlen = nsc->ns_outbuflen; int rc; - /* - * For TLS connections, just call BIO_flush(); we'll let TLS handle - * all of our output buffering. - */ -#ifdef TLS_SUPPORT - if (nsc->tls_active) { - rc = BIO_flush(nsc->ssl_io); - - if (rc <= 0) { - netsec_err(errstr, "Error flushing TLS connection: %s", - ERR_error_string(ERR_get_error(), NULL)); - return NOTOK; - } - - return OK; - } -#endif /* TLS_SUPPORT */ - /* * Small optimization */ @@ -756,7 +901,7 @@ netsec_flush(netsec_context *nsc, char **errstr) /* * If SASL security layers are in effect, run the data through - * sasl_encode() first and then write it. + * sasl_encode() first. */ #ifdef CYRUS_SASL if (nsc->sasl_seclayer) { @@ -771,11 +916,27 @@ netsec_flush(netsec_context *nsc, char **errstr) } #endif /* CYRUS_SASL */ - rc = write(nsc->ns_fd, netoutbuf, netoutlen); - if (rc < 0) { - netsec_err(errstr, "write() failed: %s", strerror(errno)); - return NOTOK; + /* + * If TLS is active, then use those functions to write out the + * data. + */ +#ifdef TLS_SUPPORT + if (nsc->tls_active) { + if (BIO_write(nsc->ssl_io, netoutbuf, netoutlen) <= 0) { + netsec_err(errstr, "Error writing to TLS connection: %s", + ERR_error_string(ERR_get_error(), NULL)); + return NOTOK; + } + } else +#endif /* TLS_SUPPORT */ + { + rc = write(nsc->ns_writefd, netoutbuf, netoutlen); + + if (rc < 0) { + netsec_err(errstr, "write() failed: %s", strerror(errno)); + return NOTOK; + } } nsc->ns_outptr = nsc->ns_outbuffer; @@ -841,16 +1002,31 @@ netsec_set_sasl_params(netsec_context *nsc, const char *hostname, return NOTOK; } - nsc->sasl_mech = mechanism ? getcpy(mechanism) : NULL; + nsc->sasl_hostname = mh_xstrdup(hostname); +#else /* CYRUS_SASL */ + NMH_UNUSED(hostname); + NMH_UNUSED(service); + NMH_UNUSED(errstr); +#endif /* CYRUS_SASL */ + + /* + * According to the RFC, mechanisms can only be uppercase letter, numbers, + * and a hypen or underscore. So make sure we uppercase any letters + * in case the user passed in lowercase. + */ + + if (mechanism) { + char *p; + nsc->sasl_mech = mh_xstrdup(mechanism); + + for (p = nsc->sasl_mech; *p; p++) + if (isascii((unsigned char) *p)) /* Just in case */ + *p = toupper((unsigned char) *p); + } + nsc->sasl_proto_cb = callback; - nsc->sasl_hostname = getcpy(hostname); return OK; -#else /* CYRUS_SASL */ - netsec_err(errstr, "SASL is not supported"); - - return NOTOK; -#endif /* CYRUS_SASL */ } #ifdef CYRUS_SASL @@ -870,14 +1046,14 @@ int netsec_get_user(void *context, int id, const char **result, if (nsc->ns_userid == NULL) { /* * Pass the 1 third argument to nmh_get_credentials() so that - * a defauly user if the -user switch wasn't supplied, and so + * a default user if the -user switch wasn't supplied, 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 (nsc->sasl_creds == NULL) { - nsc->sasl_creds = mh_xmalloc(sizeof(*nsc->sasl_creds)); + NEW(nsc->sasl_creds); nsc->sasl_creds->user = NULL; nsc->sasl_creds->password = NULL; } @@ -917,7 +1093,7 @@ netsec_get_password(sasl_conn_t *conn, void *context, int id, return SASL_BADPARAM; if (nsc->sasl_creds == NULL) { - nsc->sasl_creds = mh_xmalloc(sizeof(*nsc->sasl_creds)); + NEW(nsc->sasl_creds); nsc->sasl_creds->user = NULL; nsc->sasl_creds->password = NULL; } @@ -974,9 +1150,12 @@ netsec_negotiate_sasl(netsec_context *nsc, const char *mechlist, char **errstr) int *outbufmax; #endif #ifdef OAUTH_SUPPORT - const char *xoauth_client_res; + unsigned char *xoauth_client_res; + size_t xoauth_client_res_len; #endif /* OAUTH_SUPPORT */ +#if defined CYRUS_SASL || defined OAUTH_SUPPORT int rc; +#endif /* CYRUS_SASL || OAUTH_SUPPORT */ /* * If we've been passed a requested mechanism, check our mechanism @@ -997,7 +1176,6 @@ netsec_negotiate_sasl(netsec_context *nsc, const char *mechlist, char **errstr) i = (str[i] == NULL); - free(str); free(mlist); if (i) { @@ -1008,11 +1186,11 @@ netsec_negotiate_sasl(netsec_context *nsc, const char *mechlist, char **errstr) } #ifdef OAUTH_SUPPORT - if (strcasecmp(nsc->sasl_mech, "XOAUTH2") == 0) { + if (nsc->sasl_mech && strcasecmp(nsc->sasl_mech, "XOAUTH2") == 0) { /* * This should be relatively straightforward, but requires some * help from the plugin. Basically, if XOAUTH2 is a success, - * the plugin has to return success, but no output data. If + * the callback has to return success, but no output data. If * there is output data, it will be assumed that it is the JSON * error message. */ @@ -1022,21 +1200,49 @@ netsec_negotiate_sasl(netsec_context *nsc, const char *mechlist, char **errstr) return NOTOK; } - nsc->sasl_chosen_mech = getcpy(nsc->sasl_mech); + nsc->sasl_chosen_mech = mh_xstrdup(nsc->sasl_mech); - xoauth_client_res = mh_oauth_do_xoauth(nsc->ns_userid, - nsc->oauth_service, - nsc->ns_snoop ? stderr : NULL); - - if (xoauth_client_res == NULL) { - netsec_err(errstr, "Internal error: mh_oauth_do_xoauth() " - "returned NULL"); + if (mh_oauth_do_xoauth(nsc->ns_userid, nsc->oauth_service, + &xoauth_client_res, &xoauth_client_res_len, + nsc->ns_snoop ? stderr : NULL) != OK) { + netsec_err(errstr, "Internal error: Unable to get OAuth2 " + "bearer token"); return NOTOK; } -#if 0 - rc = nsc->sasl_proto_cb(NETSEC_SASL_START, -#endif + rc = nsc->sasl_proto_cb(NETSEC_SASL_START, xoauth_client_res, + xoauth_client_res_len, NULL, 0, errstr); + free(xoauth_client_res); + + if (rc != OK) + return NOTOK; + + /* + * Okay, we need to do a NETSEC_SASL_FINISH now. If we return + * success, we indicate that with no output data. But if we + * fail, then send a blank message and get the resulting + * error. + */ + + rc = nsc->sasl_proto_cb(NETSEC_SASL_FINISH, NULL, 0, NULL, 0, errstr); + + if (rc != OK) { + /* + * We're going to assume the error here is a JSON response; + * we ignore it and send a blank message in response. We should + * then get a failure messages with a useful error. We should + * NOT get a success message at this point. + */ + free(*errstr); + nsc->sasl_proto_cb(NETSEC_SASL_WRITE, NULL, 0, NULL, 0, NULL); + rc = nsc->sasl_proto_cb(NETSEC_SASL_FINISH, NULL, 0, NULL, 0, + errstr); + if (rc == 0) { + netsec_err(errstr, "Unexpected success after OAuth failure!"); + } + return NOTOK; + } + return OK; } #endif /* OAUTH_SUPPORT */ @@ -1087,21 +1293,10 @@ netsec_negotiate_sasl(netsec_context *nsc, const char *mechlist, char **errstr) nsc->sasl_chosen_mech = getcpy(chosen_mech); - if (nsc->sasl_proto_cb(NETSEC_SASL_START, saslbuf, saslbuflen, &outbuf, - &outbuflen, errstr) != OK) + if (nsc->sasl_proto_cb(NETSEC_SASL_START, saslbuf, saslbuflen, NULL, 0, + errstr) != OK) return NOTOK; - if (outbuflen > 0) { - if (netsec_write(nsc, outbuf, outbuflen, errstr) != OK) { - free(outbuf); - return NOTOK; - } - free(outbuf); - if (netsec_flush(nsc, errstr) != OK) - return NOTOK; - } - - /* * We've written out our first message; enter in the step loop */ @@ -1114,14 +1309,7 @@ netsec_negotiate_sasl(netsec_context *nsc, const char *mechlist, char **errstr) if (nsc->sasl_proto_cb(NETSEC_SASL_READ, NULL, 0, &outbuf, &outbuflen, errstr) != OK) { - if (nsc->sasl_proto_cb(NETSEC_SASL_CANCEL, NULL, 0, &outbuf, - &outbuflen, NULL) == OK) { - if (outbuflen > 0) { - netsec_write(nsc, outbuf, outbuflen, NULL); - netsec_flush(nsc, NULL); - free(outbuf); - } - } + nsc->sasl_proto_cb(NETSEC_SASL_CANCEL, NULL, 0, NULL, 0, NULL); return NOTOK; } @@ -1134,39 +1322,15 @@ netsec_negotiate_sasl(netsec_context *nsc, const char *mechlist, char **errstr) if (rc != SASL_OK && rc != SASL_CONTINUE) { netsec_err(errstr, "SASL client negotiation failed: %s", sasl_errdetail(nsc->sasl_conn)); - if (nsc->sasl_proto_cb(NETSEC_SASL_CANCEL, NULL, 0, &outbuf, - &outbuflen, NULL) == OK) { - if (outbuflen > 0) { - netsec_write(nsc, outbuf, outbuflen, NULL); - netsec_flush(nsc, NULL); - free(outbuf); - } - } + nsc->sasl_proto_cb(NETSEC_SASL_CANCEL, NULL, 0, NULL, 0, NULL); return NOTOK; } if (nsc->sasl_proto_cb(NETSEC_SASL_WRITE, saslbuf, saslbuflen, - &outbuf, &outbuflen, errstr) != OK) { - if (nsc->sasl_proto_cb(NETSEC_SASL_CANCEL, NULL, 0, &outbuf, - &outbuflen, NULL) == OK) { - if (outbuflen > 0) { - netsec_write(nsc, outbuf, outbuflen, NULL); - netsec_flush(nsc, NULL); - free(outbuf); - } - } + NULL, 0, errstr) != OK) { + nsc->sasl_proto_cb(NETSEC_SASL_CANCEL, NULL, 0, NULL, 0, NULL); return NOTOK; } - - if (outbuflen > 0) { - if (netsec_write(nsc, outbuf, outbuflen, errstr) != OK) { - free(outbuf); - return NOTOK; - } - free(outbuf); - if (netsec_flush(nsc, errstr) != OK) - return NOTOK; - } } /* @@ -1256,7 +1420,18 @@ netsec_negotiate_sasl(netsec_context *nsc, const char *mechlist, char **errstr) return OK; #else - netsec_err(errstr, "SASL not supported"); + /* + * If we're at this point, then either we have NEITHER OAuth2 or + * Cyrus-SASL compiled in, or have OAuth2 but didn't give the XOAUTH2 + * mechanism on the command line. + */ + + if (! nsc->sasl_mech) + netsec_err(errstr, "SASL library support not available; please " + "specify a SASL mechanism to use"); + else + netsec_err(errstr, "No support for the %s SASL mechanism", + nsc->sasl_mech); return NOTOK; #endif /* CYRUS_SASL */ @@ -1269,11 +1444,7 @@ netsec_negotiate_sasl(netsec_context *nsc, const char *mechlist, char **errstr) char * netsec_get_sasl_mechanism(netsec_context *nsc) { -#ifdef CYRUS_SASL return nsc->sasl_chosen_mech; -#else /* CYRUS_SASL */ - return NULL; -#endif /* CYRUS_SASL */ } /* @@ -1287,6 +1458,8 @@ netsec_set_oauth_service(netsec_context *nsc, const char *service) nsc->oauth_service = getcpy(service); return OK; #else /* OAUTH_SUPPORT */ + NMH_UNUSED(nsc); + NMH_UNUSED(service); return NOTOK; #endif /* OAUTH_SUPPORT */ } @@ -1301,7 +1474,7 @@ netsec_set_tls(netsec_context *nsc, int tls, char **errstr) if (tls) { #ifdef TLS_SUPPORT SSL *ssl; - BIO *sbio, *ssl_bio;; + BIO *rbio, *wbio, *ssl_bio;; if (! tls_initialized) { SSL_library_init(); @@ -1327,7 +1500,7 @@ netsec_set_tls(netsec_context *nsc, int tls, char **errstr) tls_initialized++; } - if (nsc->ns_fd == -1) { + if (nsc->ns_readfd == -1 || nsc->ns_writefd == -1) { netsec_err(errstr, "Invalid file descriptor in netsec context"); return NOTOK; } @@ -1359,35 +1532,35 @@ netsec_set_tls(netsec_context *nsc, int tls, char **errstr) * supplied socket. * * Then we create an SSL BIO, and assign our current SSL connection - * to it. We then create a buffer BIO and push it in front of our - * SSL BIO. So the chain looks like: + * to it. This is done so our code stays simple if we want to use + * any buffering BIOs (right now we do our own buffering). + * So the chain looks like: * - * buffer BIO -> SSL BIO -> socket BIO. - * - * So writes and reads are buffered (we mostly care about writes). + * SSL BIO -> socket BIO. */ - sbio = BIO_new_socket(nsc->ns_fd, BIO_NOCLOSE); + rbio = BIO_new_socket(nsc->ns_readfd, BIO_NOCLOSE); - if (! sbio) { - netsec_err(errstr, "Unable to create a socket BIO: %s", + if (! rbio) { + netsec_err(errstr, "Unable to create a read socket BIO: %s", ERR_error_string(ERR_get_error(), NULL)); SSL_free(ssl); return NOTOK; } - SSL_set_bio(ssl, sbio, sbio); - SSL_set_connect_state(ssl); + wbio = BIO_new_socket(nsc->ns_writefd, BIO_NOCLOSE); - nsc->ssl_io = BIO_new(BIO_f_buffer()); - - if (! nsc->ssl_io) { - netsec_err(errstr, "Unable to create a buffer BIO: %s", + if (! wbio) { + netsec_err(errstr, "Unable to create a write socket BIO: %s", ERR_error_string(ERR_get_error(), NULL)); SSL_free(ssl); + BIO_free(rbio); return NOTOK; } + SSL_set_bio(ssl, rbio, wbio); + SSL_set_connect_state(ssl); + ssl_bio = BIO_new(BIO_f_ssl()); if (! ssl_bio) { @@ -1398,7 +1571,7 @@ netsec_set_tls(netsec_context *nsc, int tls, char **errstr) } BIO_set_ssl(ssl_bio, ssl, BIO_CLOSE); - BIO_push(nsc->ssl_io, ssl_bio); + nsc->ssl_io = ssl_bio; return OK; } else { @@ -1441,7 +1614,7 @@ netsec_negotiate_tls(netsec_context *nsc, char **errstr) fprintf(stderr, "WARNING: cannot determine SSL ciphers\n"); } else { const SSL_CIPHER *cipher = SSL_get_current_cipher(ssl); - fprintf(stderr, "TLS negotation successful: %s(%d) %s\n", + fprintf(stderr, "TLS negotiation successful: %s(%d) %s\n", SSL_CIPHER_get_name(cipher), SSL_CIPHER_get_bits(cipher, NULL), SSL_CIPHER_get_version(cipher)); @@ -1450,16 +1623,6 @@ netsec_negotiate_tls(netsec_context *nsc, char **errstr) nsc->tls_active = 1; - /* - * At this point, TLS has been activated; we're not going to use - * the output buffer, so free it now to save a little bit of memory. - */ - - if (nsc->ns_outbuffer) { - free(nsc->ns_outbuffer); - nsc->ns_outbuffer = NULL; - } - return OK; #else /* TLS_SUPPORT */ netsec_err(errstr, "TLS not supported");