X-Git-Url: https://diplodocus.org/git/nmh/blobdiff_plain/edcc7ae1dc9a6e30e9454da7cf0c093f4852b7ea..6547ca9f73c3f02b2356017bbd7ff85292f8e4a1:/uip/imaptest.c diff --git a/uip/imaptest.c b/uip/imaptest.c index 0ae9c2db..2032e671 100644 --- a/uip/imaptest.c +++ b/uip/imaptest.c @@ -9,6 +9,7 @@ #include #include #include +#include #include "h/done.h" #include "sbr/base64.h" @@ -39,14 +40,27 @@ DEFINE_SWITCH_ENUM(IMAPTEST); DEFINE_SWITCH_ARRAY(IMAPTEST, switches); #undef X +struct imap_msg; + +struct imap_msg { + char *command; /* Command to send */ + int queue; /* If true, queue for later delivery */ + struct imap_msg *next; /* Next pointer */ +}; + +struct imap_msg *msgqueue_head = NULL; +struct imap_msg *msgqueue_tail = NULL; + struct imap_cmd; struct imap_cmd { char tag[16]; /* Command tag */ + struct timeval start; /* Time command was sent */ struct imap_cmd *next; /* Next pointer */ }; static struct imap_cmd *cmdqueue = NULL; + static char *saslmechs = NULL; svector_t imap_capabilities = NULL; @@ -56,20 +70,32 @@ static int imap_sasl_callback(enum sasl_message_type, unsigned const char *, static void parse_capability(const char *, unsigned int len); static int capability_set(const char *); -static int send_imap_command(netsec_context *, char **errstr, - const char *, ...); -static int get_imap_response(netsec_context *, const char *, char **, char **); +static void clear_capability(void); +static int have_capability(void); +static int send_imap_command(netsec_context *, int noflush, char **errstr, + const char *fmt, ...) CHECK_PRINTF(4, 5); +static int get_imap_response(netsec_context *, const char *token, + char **tokenresp, char **status, int failerr, + char **errstr); + +static void ts_report(const char *str, struct timeval *tv); + +static void add_msg(int queue, const char *fmt, ...) CHECK_PRINTF(2, 3); + +static bool timestamp = false; int main (int argc, char **argv) { - bool timestamp = false, sasl = false, tls = false, initialtls = false; + bool sasl = false, tls = false, initialtls = false; bool snoop = false; int fd; char *saslmech = NULL, *host = NULL, *port = "143", *user = NULL; char *cp, **argp, buf[BUFSIZ], *oauth_svc = NULL, *errstr, **arguments, *p; netsec_context *nsc = NULL; + struct imap_msg *imsg; size_t len; + struct timeval tv_start, tv_connect, tv_auth; if (nmh_init(argv[0], 1)) { return 1; } @@ -146,6 +172,12 @@ main (int argc, char **argv) timestamp = false; continue; } + } else if (*cp == '+') { + if (*(cp + 1) == '\0') + adios(NULL, "Invalid null folder name"); + add_msg(0, "SELECT \"%s\"", cp + 1); + } else { + add_msg(0, "%s", cp); } } @@ -168,28 +200,36 @@ main (int argc, char **argv) } } + if (timestamp) + gettimeofday(&tv_start, NULL); + if ((fd = client(host, port, buf, sizeof(buf), snoop)) == NOTOK) adios(NULL, "Connect failed: %s", buf); + if (timestamp) { + ts_report("Connect time", &tv_start); + gettimeofday(&tv_connect, NULL); + } + netsec_set_fd(nsc, fd, fd); netsec_set_snoop(nsc, snoop); if (initialtls || tls) { if (netsec_set_tls(nsc, 1, 0, &errstr) != OK) - adios(NULL, errstr); + adios(NULL, "%s", errstr); if (initialtls && netsec_negotiate_tls(nsc, &errstr) != OK) - adios(NULL, errstr); + adios(NULL, "%s", errstr); } if (sasl) { if (netsec_set_sasl_params(nsc, "imap", saslmech, imap_sasl_callback, nsc, &errstr) != OK) - adios(NULL, errstr); + adios(NULL, "%s", errstr); } if ((cp = netsec_readline(nsc, &len, &errstr)) == NULL) { - adios(NULL, errstr); + adios(NULL, "%s", errstr); } if (has_prefix(cp, "* BYE")) { @@ -219,12 +259,13 @@ main (int argc, char **argv) } else { char *capstring; - if (send_imap_command(nsc, &errstr, "CAPABILITY") != OK) { + if (send_imap_command(nsc, 0, &errstr, "CAPABILITY") != OK) { fprintf(stderr, "Unable to send CAPABILITY command: %s\n", errstr); goto finish; } - if (get_imap_response(nsc, "CAPABILITY", &capstring, &errstr) != OK) { + if (get_imap_response(nsc, "CAPABILITY", &capstring, NULL, 1, + &errstr) != OK) { fprintf(stderr, "Cannot get CAPABILITY response: %s\n", errstr); goto finish; } @@ -238,12 +279,112 @@ main (int argc, char **argv) free(capstring); } - send_imap_command(nsc, NULL, "LOGOUT"); - get_imap_response(nsc, NULL, NULL, NULL); + if (tls) { + if (!capability_set("STARTTLS")) { + fprintf(stderr, "Requested STARTTLS with -tls, but IMAP server " + "has no support for STARTTLS\n"); + goto finish; + } + if (send_imap_command(nsc, 0, &errstr, "STARTTLS") != OK) { + fprintf(stderr, "Unable to issue STARTTLS: %s\n", errstr); + goto finish; + } + + if (get_imap_response(nsc, NULL, NULL, NULL, 1, &errstr) != OK) { + fprintf(stderr, "STARTTLS failed: %s\n", errstr); + goto finish; + } + if (netsec_negotiate_tls(nsc, &errstr) != OK) { + adios(NULL, "%s", errstr); + } + } + + if (sasl) { + if (netsec_negotiate_sasl(nsc, saslmechs, &errstr) != OK) { + fprintf(stderr, "SASL negotiation failed: %s\n", errstr); + goto finish; + } + /* + * Sigh. If we negotiated a SSF AND we got a capability response + * as part of the AUTHENTICATE OK message, we can't actually trust + * it because it's not protected at that point. So discard the + * capability list and we will generate a fresh CAPABILITY command + * later. + */ + if (netsec_get_sasl_ssf(nsc) > 0) { + clear_capability(); + } + } else { + if (capability_set("LOGINDISABLED")) { + fprintf(stderr, "User did not request SASL, but LOGIN " + "is disabled\n"); + goto finish; + } + } + + if (!have_capability()) { + char *capstring; + + if (send_imap_command(nsc, 0, &errstr, "CAPABILITY") != OK) { + fprintf(stderr, "Unable to send CAPABILITY command: %s\n", errstr); + goto finish; + } + + if (get_imap_response(nsc, "CAPABILITY", &capstring, NULL, 1, + &errstr) != OK) { + fprintf(stderr, "Cannot get CAPABILITY response: %s\n", errstr); + goto finish; + } + + if (! capstring) { + fprintf(stderr, "No CAPABILITY response seen\n"); + goto finish; + } + + parse_capability(capstring, strlen(capstring)); + free(capstring); + } + + if (timestamp) { + ts_report("Authentication time", &tv_connect); + gettimeofday(&tv_auth, NULL); + } + + while (msgqueue_head != NULL) { + imsg = msgqueue_head; + + if (send_imap_command(nsc, imsg->queue, &errstr, "%s", + imsg->command) != OK) { + fprintf(stderr, "Cannot send command \"%s\": %s\n", imsg->command, + errstr); + free(errstr); + goto finish; + } + + if (! imsg->queue) { + if (get_imap_response(nsc, NULL, NULL, NULL, 0, &errstr) != OK) { + fprintf(stderr, "Unable to get response for command " + "\"%s\": %s\n", imsg->command, errstr); + goto finish; + } + } + + msgqueue_head = imsg->next; + + free(imsg->command); + free(imsg); + } + + ts_report("Total command execution time", &tv_auth); + + send_imap_command(nsc, 0, NULL, "LOGOUT"); + get_imap_response(nsc, NULL, NULL, NULL, 0, NULL); finish: netsec_shutdown(nsc); + ts_report("Total elapsed time", &tv_start); + exit(0); } @@ -276,7 +417,7 @@ parse_capability(const char *cap, unsigned int len) } for (i = 0; caplist[i] != NULL; i++) { - if (has_prefix(caplist[i], "AUTH=") && caplist[i] + 5 != '\0') { + if (has_prefix(caplist[i], "AUTH=") && *(caplist[i] + 5) != '\0') { if (saslmechs) { saslmechs = add(" ", saslmechs); } @@ -286,7 +427,6 @@ parse_capability(const char *cap, unsigned int len) } } - free(caplist); free(str); } @@ -303,6 +443,23 @@ capability_set(const char *capability) return svector_find(imap_capabilities, capability) != NULL; } +/* + * Clear the CAPABILITY list (used after we call STARTTLS, for example) + */ + +static void +clear_capability(void) +{ + svector_free(imap_capabilities); + imap_capabilities = NULL; +} + +static int +have_capability(void) +{ + return imap_capabilities != NULL; +} + /* * Our SASL callback, which handles the SASL authentication dialog */ @@ -314,7 +471,7 @@ imap_sasl_callback(enum sasl_message_type mtype, unsigned const char *indata, { int rc, snoopoffset; char *mech, *line; - size_t len, b64len; + size_t len; netsec_context *nsc = (netsec_context *) context; switch (mtype) { @@ -334,16 +491,140 @@ imap_sasl_callback(enum sasl_message_type mtype, unsigned const char *indata, b64data = mh_xmalloc(BASE64SIZE(indatalen)); writeBase64raw(indata, indatalen, (unsigned char *) b64data); if (capability_set("SASL-IR")) { - rc = send_imap_command(nsc, errstr, "AUTHENTICATE %s %s", + netsec_set_snoop_callback(nsc, netsec_b64_snoop_decoder, + &snoopoffset); + /* + * Sigh. We're assuming a short tag length here. Really, + * I guess we should figure out a way to get the length + * of the next tag. + */ + snoopoffset = 17 + strlen(mech); + rc = send_imap_command(nsc, 0, errstr, "AUTHENTICATE %s %s", mech, b64data); free(b64data); + netsec_set_snoop_callback(nsc, NULL, NULL); if (rc) return NOTOK; } else { - rc = send_imap_command(nsc, errstr, "AUTHENTICATE %s", mech); + rc = send_imap_command(nsc, 0, errstr, "AUTHENTICATE %s", mech); + line = netsec_readline(nsc, &len, errstr); + if (! line) + return NOTOK; + /* + * We should get 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; + netsec_set_snoop_callback(nsc, netsec_b64_snoop_decoder, NULL); + rc = netsec_flush(nsc, errstr); + netsec_set_snoop_callback(nsc, NULL, NULL); + if (rc != OK) + return NOTOK; + } + } else { + if (send_imap_command(nsc, 0, errstr, "AUTHENTICATE %s", + mech) != OK) + return NOTOK; + } + + break; + + /* + * Get a response, decode it and process it. + */ + + case NETSEC_SASL_READ: + netsec_set_snoop_callback(nsc, netsec_b64_snoop_decoder, &snoopoffset); + snoopoffset = 2; + line = netsec_readline(nsc, &len, errstr); + netsec_set_snoop_callback(nsc, NULL, NULL); + + 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, &len, 0, NULL); + *outdatalen = len; + if (rc != OK) { + netsec_err(errstr, "Unable to decode base64 response"); + return NOTOK; } } + break; + + /* + * Simple request encoding + */ + + case NETSEC_SASL_WRITE: + if (indatalen > 0) { + unsigned char *b64data; + b64data = mh_xmalloc(BASE64SIZE(indatalen)); + writeBase64raw(indata, indatalen, b64data); + rc = netsec_printf(nsc, errstr, "%s", b64data); + free(b64data); + if (rc != OK) + return NOTOK; + } + if (netsec_printf(nsc, errstr, "\r\n") != OK) + return NOTOK; + + netsec_set_snoop_callback(nsc, netsec_b64_snoop_decoder, NULL); + rc = netsec_flush(nsc, errstr); + netsec_set_snoop_callback(nsc, NULL, NULL); + if (rc != OK) + return NOTOK; + break; + + /* + * Finish protocol + */ + + case NETSEC_SASL_FINISH: + line = NULL; + if (get_imap_response(nsc, NULL, NULL, &line, 1, errstr) != OK) + return NOTOK; + /* + * We MIGHT get a capability response here. If so, be sure we + * parse it. + */ + + if (line && has_prefix(line, "OK [CAPABILITY ")) { + char *p = line + 15, *q; + q = strchr(p, ']'); + + if (q) + parse_capability(p, q - p); + } + + break; + + /* + * Cancel an authentication dialog + */ + + case NETSEC_SASL_CANCEL: + rc = netsec_printf(nsc, errstr, "*\r\n"); + if (rc == OK) + rc = netsec_flush(nsc, errstr); + if (rc != OK) + return NOTOK; break; } return OK; @@ -354,7 +635,8 @@ imap_sasl_callback(enum sasl_message_type mtype, unsigned const char *indata, */ static int -send_imap_command(netsec_context *nsc, char **errstr, const char *fmt, ...) +send_imap_command(netsec_context *nsc, int noflush, char **errstr, + const char *fmt, ...) { static unsigned int seq = 0; /* Tag sequence number */ va_list ap; @@ -365,6 +647,9 @@ send_imap_command(netsec_context *nsc, char **errstr, const char *fmt, ...) snprintf(cmd->tag, sizeof(cmd->tag), "A%u ", seq++); + if (timestamp) + gettimeofday(&cmd->start, NULL); + if (netsec_write(nsc, cmd->tag, strlen(cmd->tag), errstr) != OK) { free(cmd); return NOTOK; @@ -382,7 +667,7 @@ send_imap_command(netsec_context *nsc, char **errstr, const char *fmt, ...) return NOTOK; } - if (netsec_flush(nsc, errstr) != OK) { + if (!noflush && netsec_flush(nsc, errstr) != OK) { free(cmd); return NOTOK; } @@ -400,10 +685,11 @@ send_imap_command(netsec_context *nsc, char **errstr, const char *fmt, ...) static int get_imap_response(netsec_context *nsc, const char *token, char **tokenresponse, - char **errstr) + char **status, int failerr, char **errstr) { char *line; struct imap_cmd *cmd; + int numerrs = 0; if (tokenresponse) *tokenresponse = NULL; @@ -422,6 +708,8 @@ getline: if (has_prefix(line, cmdqueue->tag)) { cmd = cmdqueue; + if (timestamp) + ts_report("Command execution time", &cmd->start); cmdqueue = cmd->next; free(cmd); } else { @@ -429,7 +717,14 @@ getline: if (has_prefix(line, cmd->next->tag)) { struct imap_cmd *cmd2 = cmd->next; cmd->next = cmd->next->next; + if (failerr && strncmp(line + strlen(cmd2->tag), + "OK ", 3) != 0) { + numerrs++; + netsec_err(errstr, "%s", line + strlen(cmd2->tag)); + } free(cmd2); + if (status) + *status = getcpy(line); goto getline; } netsec_err(errstr, "Non-matching response line: %s\n", @@ -440,5 +735,59 @@ getline: } } - return OK; + return numerrs == 0 ? OK : NOTOK; +} + +/* + * Add an IMAP command to the msg queue + */ + +static void +add_msg(int queue, const char *fmt, ...) +{ + struct imap_msg *imsg; + va_list ap; + size_t msgbufsize; + char *msg = NULL; + int rc = 63; + + do { + msgbufsize = rc + 1; + msg = mh_xrealloc(msg, msgbufsize); + va_start(ap, fmt); + rc = vsnprintf(msg, msgbufsize, fmt, ap); + va_end(ap); + } while (rc >= (int) msgbufsize); + + imsg = mh_xmalloc(sizeof(*imsg)); + + imsg->command = msg; + imsg->queue = queue; + imsg->next = NULL; + + if (msgqueue_head == NULL) { + msgqueue_head = imsg; + msgqueue_tail = imsg; + } else { + msgqueue_tail->next = imsg; + msgqueue_tail = imsg; + } +} + +/* + * Give a timestamp report. + */ + +static void +ts_report(const char *str, struct timeval *tv) +{ + struct timeval now; + double delta; + + gettimeofday(&now, NULL); + + delta = ((double) now.tv_sec) - ((double) tv->tv_sec) + + (now.tv_usec / 1E6 - tv->tv_usec / 1E6); + + printf("%s: %f sec\n", str, delta); }