#include <h/utils.h>
#include <h/netsec.h>
#include <stdarg.h>
+#include <sys/time.h>
#include "h/done.h"
#include "sbr/base64.h"
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;
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; }
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);
}
}
}
}
+ 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")) {
} 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;
}
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);
}
}
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);
}
}
}
- free(caplist);
free(str);
}
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
*/
{
int rc, snoopoffset;
char *mech, *line;
- size_t len, b64len;
+ size_t len;
netsec_context *nsc = (netsec_context *) context;
switch (mtype) {
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;
*/
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;
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;
return NOTOK;
}
- if (netsec_flush(nsc, errstr) != OK) {
+ if (!noflush && netsec_flush(nsc, errstr) != OK) {
free(cmd);
return NOTOK;
}
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;
if (has_prefix(line, cmdqueue->tag)) {
cmd = cmdqueue;
+ if (timestamp)
+ ts_report("Command execution time", &cmd->start);
cmdqueue = cmd->next;
free(cmd);
} else {
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",
}
}
- 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);
}