X-Git-Url: https://diplodocus.org/git/nmh/blobdiff_plain/15d18ddcd4ec201c4c1b9dfbf8b05c48fb4f2e43..9a6d835cfe7761f6a85f84233d9d93722efe6ecc:/sbr/netsec.c diff --git a/sbr/netsec.c b/sbr/netsec.c index cb297cc1..3fd31854 100644 --- a/sbr/netsec.c +++ b/sbr/netsec.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -55,6 +56,8 @@ static SSL_CTX *sslctx = NULL; /* SSL Context */ struct _netsec_context { int ns_fd; /* 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 */ int ns_timeout; /* Network read timeout, in seconds */ char *ns_userid; /* Userid for authentication */ unsigned char *ns_inbuffer; /* Our read input buffer */ @@ -65,9 +68,12 @@ struct _netsec_context { unsigned char *ns_outptr; /* Output buffer pointer */ unsigned int ns_outbuflen; /* Output buffer data length */ unsigned int ns_outbufsize; /* Output buffer size */ + char *sasl_mech; /* User-requested mechanism */ +#ifdef OAUTH_SUPPORT + char *oauth_service; /* OAuth2 service name */ +#endif /* OAUTH_SUPPORT */ #ifdef CYRUS_SASL char *sasl_hostname; /* Hostname we've connected to */ - char *sasl_mech; /* User-requested mechanism */ 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 */ @@ -85,12 +91,6 @@ struct _netsec_context { #endif /* TLS_SUPPORT */ }; -/* - * Function to allocate error message strings - */ - -static void netsec_err(char **errstr, const char *format, ...); - /* * Function to read data from the actual network socket */ @@ -124,6 +124,8 @@ netsec_init(void) nsc->ns_fd = -1; nsc->ns_snoop = 0; + nsc->ns_snoop_noend = 0; + nsc->ns_snoop_cb = NULL; nsc->ns_userid = NULL; nsc->ns_timeout = 60; /* Our default */ nsc->ns_inbufsize = NETSEC_BUFSIZE; @@ -134,10 +136,13 @@ netsec_init(void) nsc->ns_outbuffer = mh_xmalloc(nsc->ns_outbufsize); nsc->ns_outptr = nsc->ns_outbuffer; nsc->ns_outbuflen = 0; + nsc->sasl_mech = NULL; +#ifdef OAUTH_SUPPORT + nsc->oauth_service = NULL; +#endif /* OAUTH_SUPPORT */ #ifdef CYRUS_SASL nsc->sasl_conn = NULL; nsc->sasl_hostname = NULL; - nsc->sasl_mech = NULL; nsc->sasl_cbs = NULL; nsc->sasl_creds = NULL; nsc->sasl_secret = NULL; @@ -156,11 +161,11 @@ netsec_init(void) /* * Shutdown the connection completely and free all resources. - * The connection is not closed, however. + * The connection is only closed if the flag is given. */ void -netsec_shutdown(netsec_context *nsc) +netsec_shutdown(netsec_context *nsc, int closeflag) { if (nsc->ns_userid) free(nsc->ns_userid); @@ -168,13 +173,17 @@ netsec_shutdown(netsec_context *nsc) free(nsc->ns_inbuffer); if (nsc->ns_outbuffer) free(nsc->ns_outbuffer); + if (nsc->sasl_mech) + free(nsc->sasl_mech); +#ifdef OAUTH_SERVICE + if (nsc->oauth_service) + free(nsc->oauth_service); +#endif /* OAUTH_SERVICE */ #ifdef CYRUS_SASL if (nsc->sasl_conn) sasl_dispose(&nsc->sasl_conn); if (nsc->sasl_hostname) free(nsc->sasl_hostname); - if (nsc->sasl_mech) - free(nsc->sasl_mech); if (nsc->sasl_cbs) free(nsc->sasl_cbs); if (nsc->sasl_creds) { @@ -203,6 +212,9 @@ netsec_shutdown(netsec_context *nsc) BIO_free_all(nsc->ssl_io); #endif /* TLS_SUPPORT */ + if (closeflag && nsc->ns_fd != -1) + close(nsc->ns_fd); + free(nsc); } @@ -313,7 +325,7 @@ netsec_read(netsec_context *nsc, void *buffer, size_t size, char **errstr) */ char * -netsec_readline(netsec_context *nsc, char **errstr) +netsec_readline(netsec_context *nsc, size_t *len, char **errstr) { unsigned char *ptr = nsc->ns_inptr; size_t count = 0, offset; @@ -326,12 +338,29 @@ retry: while (count < nsc->ns_inbuflen) { count++; if (*ptr++ == '\n') { - char *sptr = nsc->ns_inptr; + char *sptr = (char *) nsc->ns_inptr; if (count > 1 && *(ptr - 2) == '\r') ptr--; *--ptr = '\0'; + if (len) + *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-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, sptr, strlen(sptr)); + else + fprintf(stderr, "%s\n", sptr); + } return sptr; } } @@ -355,7 +384,7 @@ retry: offset = ptr - nsc->ns_inptr; if (netsec_fillread(nsc, errstr) != OK) - return NOTOK; + return NULL; ptr = nsc->ns_inptr + offset; @@ -553,6 +582,11 @@ netsec_write(netsec_context *nsc, const void *buffer, size_t size, const unsigned char *bufptr = buffer; int rc, remaining; + /* Just in case */ + + 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 @@ -608,6 +642,23 @@ netsec_write(netsec_context *nsc, const void *buffer, size_t size, return OK; } +/* + * Our network printf() routine, which really just calls netsec_vprintf(). + */ + +int +netsec_printf(netsec_context *nsc, char **errstr, const char *format, ...) +{ + va_list ap; + int rc; + + va_start(ap, format); + rc = netsec_vprintf(nsc, errstr, format, ap); + va_end(ap); + + return rc; +} + /* * Write bytes to the network using printf()-style formatting. * @@ -616,9 +667,9 @@ netsec_write(netsec_context *nsc, const void *buffer, size_t size, */ int -netsec_printf(netsec_context *nsc, char **errstr, const char *format, ...) +netsec_vprintf(netsec_context *nsc, char **errstr, const char *format, + va_list ap) { - va_list ap; int rc; /* @@ -626,9 +677,7 @@ netsec_printf(netsec_context *nsc, char **errstr, const char *format, ...) */ #ifdef TLS_SUPPORT if (nsc->tls_active) { - va_start(ap, format); rc = BIO_vprintf(nsc->ssl_io, format, ap); - va_end(ap); if (rc <= 0) { netsec_err(errstr, "Error writing to TLS connection: %s", @@ -646,10 +695,8 @@ netsec_printf(netsec_context *nsc, char **errstr, const char *format, ...) */ retry: - va_start(ap, format); rc = vsnprintf((char *) nsc->ns_outptr, nsc->ns_outbufsize - nsc->ns_outbuflen, format, ap); - va_end(ap); if (rc >= (int) (nsc->ns_outbufsize - nsc->ns_outbuflen)) { /* @@ -683,6 +730,34 @@ 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, nsc->ns_outptr, outlen); + else + fprintf(stderr, "%.*s\n", outlen, nsc->ns_outptr); + } else { + nsc->ns_snoop_noend = 0; + } + } + nsc->ns_outptr += rc; nsc->ns_outbuflen += rc; @@ -719,6 +794,13 @@ netsec_flush(netsec_context *nsc, char **errstr) } #endif /* TLS_SUPPORT */ + /* + * Small optimization + */ + + if (netoutlen == 0) + return OK; + /* * If SASL security layers are in effect, run the data through * sasl_encode() first and then write it. @@ -936,8 +1018,104 @@ netsec_negotiate_sasl(netsec_context *nsc, const char *mechlist, char **errstr) unsigned char *outbuf; unsigned int saslbuflen, outbuflen; sasl_ssf_t *ssf; - int rc, *outbufmax; + int *outbufmax; +#endif +#ifdef OAUTH_SUPPORT + unsigned char *xoauth_client_res; + size_t xoauth_client_res_len; +#endif /* OAUTH_SUPPORT */ + int rc; + /* + * If we've been passed a requested mechanism, check our mechanism + * list from the protocol. If it's not supported, return an error. + */ + + if (nsc->sasl_mech) { + char **str, *mlist = getcpy(mechlist); + int i; + + str = brkstring(mlist, " ", NULL); + + for (i = 0; str[i] != NULL; i++) { + if (strcasecmp(nsc->sasl_mech, str[i]) == 0) { + break; + } + } + + i = (str[i] == NULL); + + free(str); + free(mlist); + + if (i) { + netsec_err(errstr, "Chosen mechanism %s not supported by server", + nsc->sasl_mech); + return NOTOK; + } + } + +#ifdef OAUTH_SUPPORT + 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 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. + */ + + if (! nsc->oauth_service) { + netsec_err(errstr, "Internal error: OAuth2 service name not given"); + return NOTOK; + } + + nsc->sasl_chosen_mech = getcpy(nsc->sasl_mech); + + 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; + } + + 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 either an +OK or -ERR + */ + 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 */ + +#ifdef CYRUS_SASL /* * In netsec_set_sasl_params, we've already done all of our setup with * sasl_client_init() and sasl_client_new(). So time to set security @@ -971,7 +1149,8 @@ netsec_negotiate_sasl(netsec_context *nsc, const char *mechlist, char **errstr) * sasl_client_step() loop (after sasl_client_start, of course). */ - rc = sasl_client_start(nsc->sasl_conn, mechlist, NULL, + rc = sasl_client_start(nsc->sasl_conn, + nsc->sasl_mech ? nsc->sasl_mech : mechlist, NULL, (const char **) &saslbuf, &saslbuflen, &chosen_mech); @@ -983,18 +1162,8 @@ 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) - return NOTOK; - - if (netsec_write(nsc, outbuf, outbuflen, errstr) != OK) { - free(outbuf); - return NOTOK; - } - - free(outbuf); - - if (netsec_flush(nsc, errstr) != OK) + if (nsc->sasl_proto_cb(NETSEC_SASL_START, saslbuf, saslbuflen, NULL, 0, + errstr) != OK) return NOTOK; /* @@ -1009,52 +1178,28 @@ 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) { - 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; } rc = sasl_client_step(nsc->sasl_conn, (char *) outbuf, outbuflen, NULL, (const char **) &saslbuf, &saslbuflen); - free(outbuf); + if (outbuf) + free(outbuf); 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) { - 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) { - 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 (netsec_write(nsc, outbuf, outbuflen, errstr) != OK) { - free(outbuf); - return NOTOK; - } - - free(outbuf); - - if (netsec_flush(nsc, errstr) != OK) - return NOTOK; } /* @@ -1164,6 +1309,21 @@ netsec_get_sasl_mechanism(netsec_context *nsc) #endif /* CYRUS_SASL */ } +/* + * Set an OAuth2 service name, if we support it. + */ + +int +netsec_set_oauth_service(netsec_context *nsc, const char *service) +{ +#ifdef OAUTH_SUPPORT + nsc->oauth_service = getcpy(service); + return OK; +#else /* OAUTH_SUPPORT */ + return NOTOK; +#endif /* OAUTH_SUPPORT */ +} + /* * Initialize (and enable) TLS for this connection */ @@ -1345,7 +1505,7 @@ netsec_negotiate_tls(netsec_context *nsc, char **errstr) * Generate an (allocated) error string */ -static void +void netsec_err(char **errstr, const char *fmt, ...) { va_list ap;