X-Git-Url: https://diplodocus.org/git/nmh/blobdiff_plain/461f5a831dc4b72e92defaa8ae2d0357727e2556..e5afa1df87c24d49f0572ff903d4b0ee2eb803a8:/sbr/netsec.c diff --git a/sbr/netsec.c b/sbr/netsec.c index a3be37ee..788b23bf 100644 --- a/sbr/netsec.c +++ b/sbr/netsec.c @@ -108,17 +108,26 @@ 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 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. + * 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. * - * 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). + * 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 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). */ /* @@ -128,8 +137,9 @@ static int checkascii(const unsigned char *byte, size_t len); netsec_context * netsec_init(void) { - netsec_context *nsc = mh_xmalloc(sizeof(*nsc)); + netsec_context *nsc; + NEW(nsc); nsc->ns_readfd = -1; nsc->ns_writefd = -1; nsc->ns_snoop = 0; @@ -311,7 +321,7 @@ netsec_b64_snoop_decoder(netsec_context *nsc, const char *string, size_t len, if (decodeBase64(string, &decoded, &decodedlen, 1, NULL) == OK) { /* - * Some mechanisms preoduce large binary tokens, which aren't really + * 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 @@ -411,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 * @@ -509,9 +520,9 @@ netsec_fillread(netsec_context *nsc, char **errstr) nsc->ns_inptr = nsc->ns_inbuffer; } -#ifdef CYRUS_SASL +#if defined(CYRUS_SASL) || defined(TLS_SUPPORT) retry: -#endif /* CYRUS_SASL */ +#endif /* CYRUS_SASL || TLS_SUPPORT */ /* * If we are using TLS and there's anything pending, then skip the * select call @@ -549,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. @@ -561,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 */ @@ -653,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; } @@ -948,6 +1002,13 @@ netsec_set_sasl_params(netsec_context *nsc, const char *hostname, return NOTOK; } + 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 @@ -956,7 +1017,7 @@ netsec_set_sasl_params(netsec_context *nsc, const char *hostname, if (mechanism) { char *p; - nsc->sasl_mech = getcpy(mechanism); + nsc->sasl_mech = mh_xstrdup(mechanism); for (p = nsc->sasl_mech; *p; p++) if (isascii((unsigned char) *p)) /* Just in case */ @@ -964,20 +1025,8 @@ netsec_set_sasl_params(netsec_context *nsc, const char *hostname, } nsc->sasl_proto_cb = callback; - nsc->sasl_hostname = getcpy(hostname); return OK; -#else /* CYRUS_SASL */ - NMH_UNUSED(nsc); - NMH_UNUSED(hostname); - NMH_UNUSED(service); - NMH_UNUSED(mechanism); - NMH_UNUSED(callback); - - netsec_err(errstr, "SASL is not supported"); - - return NOTOK; -#endif /* CYRUS_SASL */ } #ifdef CYRUS_SASL @@ -997,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; } @@ -1044,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; } @@ -1104,7 +1153,9 @@ netsec_negotiate_sasl(netsec_context *nsc, const char *mechlist, char **errstr) 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 @@ -1149,7 +1200,7 @@ 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); if (mh_oauth_do_xoauth(nsc->ns_userid, nsc->oauth_service, &xoauth_client_res, &xoauth_client_res_len, @@ -1179,7 +1230,8 @@ netsec_negotiate_sasl(netsec_context *nsc, const char *mechlist, char **errstr) /* * 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 either an +OK or -ERR + * 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); @@ -1368,9 +1420,18 @@ netsec_negotiate_sasl(netsec_context *nsc, const char *mechlist, char **errstr) return OK; #else - NMH_UNUSED(nsc); + /* + * 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. + */ - netsec_err(errstr, "SASL not supported"); + 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 */ @@ -1383,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 */ } /* @@ -1401,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 */ } @@ -1473,12 +1532,11 @@ 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: - * - * buffer BIO -> SSL BIO -> socket BIO. + * 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: * - * So writes and reads are buffered (we mostly care about writes). + * SSL BIO -> socket BIO. */ rbio = BIO_new_socket(nsc->ns_readfd, BIO_NOCLOSE);