From: Ken Hornstein Date: Mon, 19 Sep 2016 23:59:40 +0000 (-0400) Subject: More work, but not there just yet. X-Git-Url: https://diplodocus.org/git/nmh/commitdiff_plain/ceaab2f46ae8fa4598b9682eaf3210cda21517c9?ds=inline;hp=-c More work, but not there just yet. --- ceaab2f46ae8fa4598b9682eaf3210cda21517c9 diff --git a/h/netsec.h b/h/netsec.h index 65b867bd..ef0d521d 100644 --- a/h/netsec.h +++ b/h/netsec.h @@ -37,7 +37,7 @@ void netsec_shutdown(netsec_context *ns_context, int closeflag); * fd - File descriptor of network connection. */ -void netset_set_fd(netsec_context *ns_context, int fd); +void netsec_set_fd(netsec_context *ns_context, int fd); /* * Set the userid used to authenticate to this connection. @@ -89,18 +89,19 @@ void netsec_set_timeout(netsec_context *ns_context, int timeout); * Read a "line" from the network. This reads one CR/LF terminated line. * Returns a pointer to a NUL-terminated string. This memory is valid * until the next call to any read function. Will return an error if - * the line does not terminated with CR/LF. Note that this will not work - * if the data might have embedded NULs. + * the line does not terminate with a CR/LF. * * Arguments: * * ns_context - Network security context + * length - Returned length of string * errstr - Error string * * Returns pointer to string, or NULL on error. */ -char *netsec_readline(netsec_context *ns_context, char **errstr); +char *netsec_readline(netsec_context *ns_context, size_t *lenght, + char **errstr); /* * Read bytes from the network. @@ -217,7 +218,8 @@ enum sasl_message_type { * Otherwise they will be set to NULL and 0. * The complete protocol message should be * stored in outdata/outdatasize, to be free()d - * by the caller. + * by the caller. Alternatively, the plugin + * can choose to send the data on their own. * NETSEC_SASL_READ - Parse and decode a protocol message and extract * out the SASL payload data. indata will be set * to NULL; the callback must read in the necessary @@ -227,7 +229,9 @@ enum sasl_message_type { * NETSEC_SASL_WRITE - Generate a protocol message to send over the * network. indata/indatasize will contain the * SASL payload data. outdata/outdatasize should - * contain the complete protocol message. + * contain the complete protocol message. Alternatively + * the plugin can write the data to the network + * directly. * NETSEC_SASL_FINISH - Process the final SASL message exchange; at * this point SASL exchange should have completed * and we should get a message back from the server @@ -240,6 +244,11 @@ enum sasl_message_type { * The callback should return OK on success, NOTOK on failure. Depending * at the point of the authentication exchange, the callback may be asked * to generate a cancel message. + * + * Some higher-level notes in terms of protocol management: + * + * Any data returned in outdata should consist of allocated data that + * the sasl routines is expected to free. */ typedef int (*netsec_sasl_callback)(enum sasl_message_type mtype, @@ -348,3 +357,17 @@ int netsec_set_tls(netsec_context *context, int tls, char **errstr); */ int netsec_negotiate_tls(netsec_context *ns_context, char **errstr); + +/* + * Allocate and format an error string; should be used by plugins + * to report errors. + * + * Arguments: + * + * errstr - Error string to be returned + * format - printf(3) format string + * ... - Arguments to printf(3) + * + */ + +void netsec_err(char **errstr, const char *format, ...); diff --git a/h/prototypes.h b/h/prototypes.h index 5c1b5cf1..cb47ca6a 100644 --- a/h/prototypes.h +++ b/h/prototypes.h @@ -457,6 +457,10 @@ int what_now (char *, int, int, char *, char *, int, struct msgs *, char *, int, char *, int); int WhatNow(int, char **); +/* Includes trailing NUL */ + +#define BASE64SIZE(x) ((((x + 2) / 3) * 4) + 1) + /* * Copy data from one file to another, converting to base64-encoding. * diff --git a/sbr/netsec.c b/sbr/netsec.c index 372732c3..5bfbea60 100644 --- a/sbr/netsec.c +++ b/sbr/netsec.c @@ -89,12 +89,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 */ @@ -327,7 +321,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; @@ -344,6 +338,8 @@ retry: if (count > 1 && *(ptr - 2) == '\r') ptr--; *--ptr = '\0'; + if (len) + *len = ptr - nsc->ns_inptr; nsc->ns_inptr += count; nsc->ns_inbuflen -= count; return sptr; @@ -567,6 +563,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 @@ -624,6 +625,7 @@ netsec_write(netsec_context *nsc, const void *buffer, size_t size, /* * Our network printf() routine, which really just calls netsec_vprintf(). + */ int netsec_printf(netsec_context *nsc, char **errstr, const char *format, ...) @@ -631,7 +633,7 @@ netsec_printf(netsec_context *nsc, char **errstr, const char *format, ...) va_list ap; int rc; - va_start(format, ap); + va_start(ap, format); rc = netsec_vprintf(nsc, errstr, format, ap); va_end(ap); @@ -745,6 +747,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. @@ -1082,15 +1091,16 @@ netsec_negotiate_sasl(netsec_context *nsc, const char *mechlist, char **errstr) &outbuflen, errstr) != OK) return NOTOK; - if (netsec_write(nsc, outbuf, outbuflen, errstr) != OK) { - free(outbuf); - 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; } - free(outbuf); - - if (netsec_flush(nsc, errstr) != OK) - return NOTOK; /* * We've written out our first message; enter in the step loop @@ -1106,9 +1116,11 @@ netsec_negotiate_sasl(netsec_context *nsc, const char *mechlist, char **errstr) 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); + if (outbuflen > 0) { + netsec_write(nsc, outbuf, outbuflen, NULL); + netsec_flush(nsc, NULL); + free(outbuf); + } } return NOTOK; } @@ -1116,16 +1128,19 @@ netsec_negotiate_sasl(netsec_context *nsc, const char *mechlist, char **errstr) 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); + if (outbuflen > 0) { + netsec_write(nsc, outbuf, outbuflen, NULL); + netsec_flush(nsc, NULL); + free(outbuf); + } } return NOTOK; } @@ -1134,22 +1149,24 @@ netsec_negotiate_sasl(netsec_context *nsc, const char *mechlist, char **errstr) &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); + if (outbuflen > 0) { + netsec_write(nsc, outbuf, outbuflen, NULL); + netsec_flush(nsc, NULL); + free(outbuf); + } } return NOTOK; } - if (netsec_write(nsc, outbuf, outbuflen, errstr) != OK) { + if (outbuflen > 0) { + if (netsec_write(nsc, outbuf, outbuflen, errstr) != OK) { + free(outbuf); + return NOTOK; + } free(outbuf); - return NOTOK; + if (netsec_flush(nsc, errstr) != OK) + return NOTOK; } - - free(outbuf); - - if (netsec_flush(nsc, errstr) != OK) - return NOTOK; } /* @@ -1455,7 +1472,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; diff --git a/uip/popsbr.c b/uip/popsbr.c index 0153dcb1..7126b950 100644 --- a/uip/popsbr.c +++ b/uip/popsbr.c @@ -32,13 +32,16 @@ static int multiline(void); static int traverse (int (*)(char *), const char *, ...); static int vcommand(const char *, va_list); -static int getline (char *, int, FILE *); +static int pop_getline (char *, int, netsec_context *); static int sasl_fgetc(FILE *); static int putline (char *, FILE *); +static int pop_sasl_callback(enum sasl_message_type, unsigned const char *, + unsigned int, unsigned char **, unsigned int *, + char **); -int -check_mech(char *server_mechs, size_t server_mechs_size, char *mech) +static int +check_mech(char *server_mechs, size_t server_mechs_size) { int status, sasl_capability = 0; @@ -81,17 +84,6 @@ check_mech(char *server_mechs, size_t server_mechs_size, char *mech) return NOTOK; } - /* - * If we received a preferred mechanism, see if the server supports it. - */ - - if (mech && stringdex(mech, server_mechs) == -1) { - snprintf(response, sizeof(response), "Requested SASL mech \"%s\" is " - "not in list of supported mechanisms:\n%s", - mech, server_mechs); - return NOTOK; - } - return OK; } @@ -111,6 +103,7 @@ check_mech(char *server_mechs, size_t server_mechs_size, char *mech) } \ } +#if 0 int pop_auth_sasl(char *user, char *host, char *mech) { @@ -389,7 +382,6 @@ sasl_get_pass(sasl_conn_t *conn, void *context, int id, sasl_secret_t **psecret) return SASL_OK; } -#endif /* CYRUS_SASL */ int pop_auth_xoauth(const char *client_res) @@ -411,7 +403,7 @@ pop_auth_xoauth(const char *client_res) /* Then we're supposed to send an empty response ("\r\n"). */ return command(""); } - +#endif /* CYRUS_SASL */ /* * Split string containing proxy command into an array of arguments * suitable for passing to exec. Returned array must be freed. Shouldn't @@ -465,23 +457,9 @@ int pop_init (char *host, char *port, char *user, char *pass, char *proxy, int snoop, int sasl, char *mech, int tls, const char *oauth_svc) { - int fd1; + int fd1, fd2; char buffer[BUFSIZ]; char *errstr; -#ifndef CYRUS_SASL - NMH_UNUSED (sasl); - NMH_UNUSED (mech); -#endif /* ! CYRUS_SASL */ - -#ifdef OAUTH_SUPPORT - if (oauth_svc != NULL) { - xoauth_client_res = mh_oauth_do_xoauth(user, oauth_svc, - snoop ? stderr : NULL); - } -#else - NMH_UNUSED (oauth_svc); - NMH_UNUSED (xoauth_client_res); -#endif /* OAUTH_SUPPORT */ nsc = netsec_init(); @@ -579,23 +557,24 @@ pop_init (char *host, char *port, char *user, char *pass, char *proxy, } } - switch (getline (response, sizeof response, input)) { + switch (pop_getline (response, sizeof response, nsc)) { case OK: if (poprint) fprintf (stderr, "<--- %s\n", response); if (*response == '+') { -# ifdef CYRUS_SASL if (sasl) { - if (pop_auth_sasl(user, host, mech) != NOTOK) - return OK; - } else -# endif /* CYRUS_SASL */ -# if OAUTH_SUPPORT - if (xoauth_client_res != NULL) { - if (pop_auth_xoauth(xoauth_client_res) != NOTOK) - return OK; + char server_mechs[256]; + if (check_mech(server_mechs, sizeof(server_mechs) != OK)) + return NOTOK; + if (netsec_negotiate_sasl(nsc, server_mechs, + &errstr) != OK) { + strncpy(response, errstr, sizeof(response)); + response[sizeof(response) - 1] = '\0'; + free(errstr); + return NOTOK; + } + return OK; } else -# endif /* OAUTH_SUPPORT */ if (command ("USER %s", user) != NOTOK && command ("%s %s", (pophack++, "PASS"), pass) != NOTOK) @@ -610,8 +589,8 @@ pop_init (char *host, char *port, char *user, char *pass, char *proxy, case DONE: if (poprint) fprintf (stderr, "%s\n", response); - fclose (input); - fclose (output); + netsec_shutdown(nsc, 1); + nsc = NULL; return NOTOK; } @@ -619,6 +598,112 @@ pop_init (char *host, char *port, char *user, char *pass, char *proxy, } +/* + * Our SASL callback; we are given SASL tokens and then have to format + * them according to the protocol requirements, and then process incoming + * messages and feed them back into the SASL library. + */ + +static int +pop_sasl_callback(enum sasl_message_type mtype, unsigned const char *indata, + unsigned int indatasize, unsigned char **outdata, + unsigned int *outdatasize, char **errstr) +{ + int rc; + char *mech, *line; + size_t len, b64len; + + switch (mtype) { + case NETSEC_SASL_START: + /* + * Generate our AUTH message, but there is a wrinkle. + * + * Technically, according to RFC 5034, if your command INCLUDING + * an initial response exceeds 255 octets (including CRLF), you + * can't issue this all in one go, but have to just issue the + * AUTH command, wait for a blank initial response, and then + * send your data. + */ + + mech = netsec_get_sasl_mechanism(nsc); + + if (indatasize) { + char *b64data; + b64data = mh_xmalloc(BASE64SIZE(indatasize)); + writeBase64raw(indata, indatasize, line); + b64len = strlen(b64data); + + /* Formula here is AUTH + SP + mech + SP + out + CR + LF */ + len = b64len + 8 + strlen(mech); + if (len > 255) { + rc = netsec_printf(nsc, errstr, "AUTH %s\r\n", mech); + if (rc) + return NOTOK; + if (netsec_flush(nsc, errstr) != OK) + return NOTOK; + line = netsec_readline(nsc, &len, errstr); + if (! line) + return NOTOK; + /* + * If the protocol is being followed correctly, should just + * be a "+ ", nothing else. + */ + if (len != 2 || strcmp(line, "+ ") != 0) { + netsec_err(errstr, "Did not get expected blank response " + "for initial challenge response"); + return NOTOK; + } + rc = netsec_printf(nsc, errstr, "%s\r\n", b64data); + free(b64data); + if (rc != OK) + return NOTOK; + if (netsec_flush(nsc, errstr) != OK) + return NOTOK; + } else { + rc = netsec_printf(nsc, errstr, "AUTH %s %s\r\n", mech, + b64data); + free(b64data); + if (rc != OK) + return NOTOK; + if (netsec_flush(nsc, errstr) != OK) + return NOTOK; + } + } else { + if (netsec_printf(nsc, errstr, "AUTH %s\r\n", mech) != OK) + return NOTOK; + if (netsec_flush(nsc, errstr) != OK) + return NOTOK; + } + + break; + + /* + * We should get one line back, with our base64 data. Decode that + * and feed it back in. + */ + case NETSEC_SASL_READ: + line = netsec_readline(nsc, &len, errstr); + + if (line == NULL) + return NOTOK; + if (len < 2 || (len == 2 && strcmp(line, "+ ") != 0) { + netsec_err(errstr, "Invalid format for SASL response"); + return NOTOK; + } + + if (len == 2) { + *outdata = NULL; + *outdatalen = 0; + } else { + rc = decodeBase64(line + 2, &outdata, &outdatasize, 0, NULL); + if (rc != OK) + return NOTOK; + } + break; + + return OK; +} + /* * Find out number of messages available * and their total size. @@ -820,7 +905,7 @@ vcommand (const char *fmt, va_list ap) fprintf(stderr, "(decrypted) "); #endif /* CYRUS_SASL */ - switch (sasl_getline (response, sizeof response, input)) { + switch (pop_getline (response, sizeof response, nsc)) { case OK: if (poprint) fprintf (stderr, "<--- %s\n", response); @@ -842,7 +927,7 @@ multiline (void) { char buffer[BUFSIZ + TRMLEN]; - if (sasl_getline (buffer, sizeof buffer, input) != OK) + if (pop_getline (buffer, sizeof buffer, nsc) != OK) return NOTOK; #ifdef DEBUG if (poprint) { @@ -866,36 +951,43 @@ multiline (void) } /* - * Note that these functions have been modified to deal with layer encryption - * in the SASL case + * This is now just a thin wrapper around netsec_readline(). */ static int -sasl_getline (char *s, int n, FILE *iop) +pop_getline (char *s, int n, netsec_context *ns) { int c = -2; char *p; + size_t len, destlen; + int rc; + char *errstr; - p = s; - while (--n > 0 && (c = sasl_fgetc (iop)) != EOF && c != -2) - if ((*p++ = c) == '\n') - break; - if (c == -2) - return NOTOK; - if (ferror (iop) && c != EOF) { - strncpy (response, "error on connection", sizeof(response)); + p = netsec_readline(ns, &len, &errstr); + + if (p == NULL) { + strncpy(response, errstr, sizeof(response)); + response[sizeof(response) - 1] = '\0'; + free(errstr); return NOTOK; } - if (c == EOF && p == s) { - strncpy (response, "connection closed by foreign host", sizeof(response)); - return DONE; - } - *p = 0; - if (*--p == '\n') - *p = 0; - if (p > s && *--p == '\r') - *p = 0; + /* + * If we had an error, it should have been returned already. Since + * netsec_readline() strips off the CR-LF ending, just copy the existing + * buffer into response now. + * + * We get a length back from netsec_readline, but the rest of the POP + * code doesn't handle it; the assumptions are that everything from + * the network can be respresented as C strings. That should get fixed + * someday. + */ + + destlen = len > n - 1 ? len : n - 1; + + memcpy(s, p, destlen); + s[destlen] = '\0'; + return OK; }