*/
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 */
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 */
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 */
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 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).
*/
/*
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_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 */
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;
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);
}
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 */
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);
}
*/
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;
}
/*
netsec_b64_snoop_decoder(netsec_context *nsc, const char *string, size_t len,
void *context)
{
- const char *decoded;
+ unsigned char *decoded;
size_t decodedlen;
+ int offset;
NMH_UNUSED(nsc);
- int offset = context ? *((int *) context) : 0;
+ offset = context ? *((int *) context) : 0;
if (offset > 0) {
/*
}
if (decodeBase64(string, &decoded, &decodedlen, 1, NULL) == OK) {
- char *hexified;
- hexify((const unsigned char *) decoded, decodedlen, &hexified);
- fprintf(stderr, "b64<%s>\n", hexified);
- free(hexified);
- free((char *) decoded);
+ /*
+ * 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
*/
*
* - 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 *
if (nsc->ns_snoop) {
#ifdef CYRUS_SASL
if (nsc->sasl_seclayer)
- fprintf(stderr, "(sasl-encrypted) ");
+ fprintf(stderr, "(sasl-decrypted) ");
#endif /* CYRUS_SASL */
#ifdef TLS_SUPPORT
if (nsc->tls_active)
- fprintf(stderr, "(tls-encrypted) ");
+ fprintf(stderr, "(tls-decrypted) ");
#endif /* TLS_SUPPORT */
fprintf(stderr, "<= ");
if (nsc->ns_snoop_cb)
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
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",
*/
}
+ /*
+ * 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.
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 */
* 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");
#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;
}
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.
{
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.
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
*/
/*
* 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) {
}
#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;
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
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;
}
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;
}
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
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,
/*
* 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);
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 */
char *
netsec_get_sasl_mechanism(netsec_context *nsc)
{
-#ifdef CYRUS_SASL
return nsc->sasl_chosen_mech;
-#else /* CYRUS_SASL */
- return NULL;
-#endif /* CYRUS_SASL */
}
/*
nsc->oauth_service = getcpy(service);
return OK;
#else /* OAUTH_SUPPORT */
+ NMH_UNUSED(nsc);
+ NMH_UNUSED(service);
return NOTOK;
#endif /* OAUTH_SUPPORT */
}
if (tls) {
#ifdef TLS_SUPPORT
SSL *ssl;
- BIO *sbio, *ssl_bio;;
+ BIO *rbio, *wbio, *ssl_bio;;
if (! tls_initialized) {
SSL_library_init();
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;
}
* 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);
-
- nsc->ssl_io = BIO_new(BIO_f_buffer());
+ wbio = BIO_new_socket(nsc->ns_writefd, BIO_NOCLOSE);
- 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) {
}
BIO_set_ssl(ssl_bio, ssl, BIO_CLOSE);
- BIO_push(nsc->ssl_io, ssl_bio);
+ nsc->ssl_io = ssl_bio;
return OK;
} else {
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));
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");