#include <h/mh.h>
#include <h/utils.h>
#include <h/netsec.h>
+#include <h/oauth.h>
#include <stdarg.h>
#include <sys/select.h>
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 */
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 */
#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
*/
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;
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;
/*
* 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);
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) {
BIO_free_all(nsc->ssl_io);
#endif /* TLS_SUPPORT */
+ if (closeflag && nsc->ns_fd != -1)
+ close(nsc->ns_fd);
+
free(nsc);
}
*/
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;
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;
}
}
offset = ptr - nsc->ns_inptr;
if (netsec_fillread(nsc, errstr) != OK)
- return NOTOK;
+ return NULL;
ptr = nsc->ns_inptr + offset;
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
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.
*
*/
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;
/*
*/
#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",
*/
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)) {
/*
}
}
+ 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;
}
#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.
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
* 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);
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;
/*
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;
}
/*
#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
*/
* Generate an (allocated) error string
*/
-static void
+void
netsec_err(char **errstr, const char *fmt, ...)
{
va_list ap;