* complete copyright information.
*/
-#include <h/mh.h>
-#include <h/utils.h>
-#include <h/netsec.h>
+#include "h/mh.h"
+#include "sbr/brkstring.h"
+#include "sbr/ambigsw.h"
+#include "sbr/print_version.h"
+#include "sbr/print_help.h"
+#include "sbr/error.h"
+#include "h/utils.h"
+#include "h/netsec.h"
#include <stdarg.h>
+#include <sys/time.h>
#include "h/done.h"
#include "sbr/base64.h"
X("tls", 0, TLSSW) \
X("notls", 0, NOTLSSW) \
X("initialtls", 0, INITIALTLSSW) \
+ X("queue", 0, QUEUESW) \
+ X("noqueue", 0, NOQUEUESW) \
X("append filename", 0, APPENDSW) \
+ X("afolder foldername", 0, AFOLDERSW) \
+ X("batch filename", 0, BATCHSW) \
X("timestamp", 0, TIMESTAMPSW) \
X("notimestamp", 0, NOTIMESTAMPSW) \
+ X("timeout", 0, TIMEOUTSW) \
X("version", 0, VERSIONSW) \
X("help", 0, HELPSW) \
DEFINE_SWITCH_ARRAY(IMAPTEST, switches);
#undef X
+struct imap_msg;
+
+struct imap_msg {
+ char *command; /* Command to send */
+ char *folder; /* Folder (for append) */
+ bool queue; /* If true, queue for later delivery */
+ bool append; /* If true, append "command" to mbox */
+ size_t msgsize; /* RFC822 size of message */
+ 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 */
+ char prefix[64]; /* Command prefix */
struct imap_cmd *next; /* Next pointer */
};
static struct imap_cmd *cmdqueue = NULL;
+
static char *saslmechs = NULL;
svector_t imap_capabilities = NULL;
static int capability_set(const 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, ...);
+static int send_imap_command(netsec_context *, bool noflush, char **errstr,
+ const char *fmt, ...) CHECK_PRINTF(4, 5);
+static int send_append(netsec_context *, struct imap_msg *, char **errstr);
static int get_imap_response(netsec_context *, const char *token,
- char **tokenresp, char **status, int failerr,
- char **errstr);
+ char **tokenresp, char **status, bool contok,
+ bool failerr, char **errstr);
+
+static void ts_report(struct timeval *tv, const char *fmt, ...)
+ CHECK_PRINTF(2, 3);
+static void imap_negotiate_tls(netsec_context *);
+
+static void add_msg(bool queue, struct imap_msg **, const char *fmt, ...)
+ CHECK_PRINTF(3, 4);
+static void add_append(const char *filename, const char *folder, bool queue);
+
+static void batchfile(const char *filename, char *afolder, bool queue);
+
+static size_t rfc822size(const char *filename);
+
+static bool timestamp = false;
int
main (int argc, char **argv)
{
- bool timestamp = false, sasl = false, tls = false, initialtls = false;
- bool snoop = false;
- int fd;
+ bool sasl = false, tls = false, initialtls = false;
+ bool snoop = false, queue = false;
+ int fd, timeout = 0;
char *saslmech = NULL, *host = NULL, *port = "143", *user = NULL;
char *cp, **argp, buf[BUFSIZ], *oauth_svc = NULL, *errstr, **arguments, *p;
+ char *afolder = NULL;
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; }
+ if (nmh_init(argv[0], true, true)) { return 1; }
arguments = getarguments (invo_name, argc, argv, 1);
argp = arguments;
ambigsw (cp, switches);
done (1);
case UNKWNSW:
- adios (NULL, "-%s unknown", cp);
+ die("-%s unknown", cp);
case HELPSW:
snprintf (buf, sizeof(buf), "%s [switches] +folder command "
case HOSTSW:
if (!(host = *argp++) || *host == '-')
- adios(NULL, "missing argument to %s", argp[-2]);
+ die("missing argument to %s", argp[-2]);
continue;
case PORTSW:
if (!(port = *argp++) || *port == '-')
- adios(NULL, "missing argument to %s", argp[-2]);
+ die("missing argument to %s", argp[-2]);
continue;
case USERSW:
if (!(user = *argp++) || *user == '-')
- adios(NULL, "missing argument to %s", argp[-2]);
+ die("missing argument to %s", argp[-2]);
+ continue;
+
+ case AFOLDERSW:
+ if (!(afolder = *argp++) || *afolder == '-')
+ die("missing argument to %s", argp[-2]);
+ continue;
+ case APPENDSW:
+ if (!*argp || (**argp == '-'))
+ die("missing argument to %s", argp[-1]);
+ if (! afolder)
+ die("Append folder must be set with -afolder first");
+ add_append(*argp++, afolder, queue);
+ continue;
+
+ case BATCHSW:
+ if (! *argp || (**argp == '-'))
+ die("missing argument to %s", argp[-1]);
+ batchfile(*argp++, afolder, queue);
+ continue;
+
+ case TIMEOUTSW:
+ if (! *argp || (**argp == '-'))
+ die("missing argument to %s", argp[-1]);
+ if (! (timeout = atoi(*argp++)))
+ die("Invalid timeout: %s", argp[-1]);
continue;
case SNOOPSW:
continue;
case SASLMECHSW:
if (!(saslmech = *argp++) || *saslmech == '-')
- adios(NULL, "missing argument to %s", argp[-2]);
+ die("missing argument to %s", argp[-2]);
continue;
case AUTHSERVICESW:
if (!(oauth_svc = *argp++) || *oauth_svc == '-')
- adios(NULL, "missing argument to %s", argp[-2]);
+ die("missing argument to %s", argp[-2]);
continue;
case TLSSW:
tls = true;
tls = false;
initialtls = false;
continue;
+ case QUEUESW:
+ queue = true;
+ continue;
+ case NOQUEUESW:
+ queue = false;
+ continue;
case TIMESTAMPSW:
timestamp = true;
continue;
timestamp = false;
continue;
}
+ } else if (*cp == '+') {
+ if (*(cp + 1) == '\0')
+ die("Invalid null folder name");
+ add_msg(false, NULL, "SELECT \"%s\"", cp + 1);
+ } else {
+ add_msg(queue, NULL, "%s", cp);
}
}
if (! host)
- adios(NULL, "A hostname must be given with -host");
+ die("A hostname must be given with -host");
nsc = netsec_init();
netsec_set_hostname(nsc, host);
+ if (timeout)
+ netsec_set_timeout(nsc, timeout);
+
if (snoop)
netsec_set_snoop(nsc, 1);
if (oauth_svc) {
if (netsec_set_oauth_service(nsc, oauth_svc) != OK) {
- adios(NULL, "OAuth2 not supported");
+ die("OAuth2 not supported");
}
}
+ if (timestamp)
+ gettimeofday(&tv_start, NULL);
+
if ((fd = client(host, port, buf, sizeof(buf), snoop)) == NOTOK)
- adios(NULL, "Connect failed: %s", buf);
+ die("Connect failed: %s", buf);
+
+ if (timestamp) {
+ ts_report(&tv_start, "Connect time");
+ 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);
+ die("%s", errstr);
- if (initialtls && netsec_negotiate_tls(nsc, &errstr) != OK)
- adios(NULL, errstr);
+ if (initialtls)
+ imap_negotiate_tls(nsc);
}
if (sasl) {
if (netsec_set_sasl_params(nsc, "imap", saslmech, imap_sasl_callback,
nsc, &errstr) != OK)
- adios(NULL, errstr);
+ die("%s", errstr);
}
if ((cp = netsec_readline(nsc, &len, &errstr)) == NULL) {
- adios(NULL, errstr);
+ die("%s", errstr);
}
if (has_prefix(cp, "* BYE")) {
} else {
char *capstring;
- if (send_imap_command(nsc, 0, &errstr, "CAPABILITY") != OK) {
+ if (send_imap_command(nsc, false, &errstr, "CAPABILITY") != OK) {
fprintf(stderr, "Unable to send CAPABILITY command: %s\n", errstr);
goto finish;
}
- if (get_imap_response(nsc, "CAPABILITY", &capstring, NULL, 1,
+ if (get_imap_response(nsc, "CAPABILITY ", &capstring, NULL, false, true,
&errstr) != OK) {
fprintf(stderr, "Cannot get CAPABILITY response: %s\n", errstr);
goto finish;
goto finish;
}
- parse_capability(capstring, strlen(capstring));
+ p = capstring + 11; /* "CAPABILITY " */
+
+ parse_capability(p, strlen(p));
free(capstring);
}
"has no support for STARTTLS\n");
goto finish;
}
- if (send_imap_command(nsc, 0, &errstr, "STARTTLS") != OK) {
+ if (send_imap_command(nsc, false, &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) {
+ if (get_imap_response(nsc, NULL, NULL, NULL, false, true,
+ &errstr) != OK) {
fprintf(stderr, "STARTTLS failed: %s\n", errstr);
goto finish;
}
- if (netsec_negotiate_tls(nsc, &errstr) != OK) {
- adios(NULL, errstr);
- }
+
+ imap_negotiate_tls(nsc);
}
if (sasl) {
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 "
}
}
- send_imap_command(nsc, 0, NULL, "LOGOUT");
- get_imap_response(nsc, NULL, NULL, NULL, 0, NULL);
+ if (!have_capability()) {
+ char *capstring;
+
+ if (send_imap_command(nsc, false, &errstr, "CAPABILITY") != OK) {
+ fprintf(stderr, "Unable to send CAPABILITY command: %s\n", errstr);
+ goto finish;
+ }
+
+ if (get_imap_response(nsc, "CAPABILITY ", &capstring, NULL, false,
+ true, &errstr) != OK) {
+ fprintf(stderr, "Cannot get CAPABILITY response: %s\n", errstr);
+ goto finish;
+ }
+
+ if (! capstring) {
+ fprintf(stderr, "No CAPABILITY response seen\n");
+ goto finish;
+ }
+
+ p = capstring + 11; /* "CAPABILITY " */
+
+ parse_capability(p, strlen(p));
+ free(capstring);
+ }
+
+ if (timestamp) {
+ ts_report(&tv_connect, "Authentication time");
+ gettimeofday(&tv_auth, NULL);
+ }
+
+ while (msgqueue_head != NULL) {
+ imsg = msgqueue_head;
+
+ if (imsg->append) {
+ if (send_append(nsc, imsg, &errstr) != OK) {
+ fprintf(stderr, "Cannot send APPEND for %s to mbox %s: %s\n",
+ imsg->command, imsg->folder, errstr);
+ free(errstr);
+ goto finish;
+ }
+ } else 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, false, false,
+ &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->folder);
+ free(imsg);
+ }
+
+ /*
+ * Flush out any pending network data and get any responses
+ */
+
+ if (netsec_flush(nsc, &errstr) != OK) {
+ fprintf(stderr, "Error performing final network flush: %s\n", errstr);
+ free(errstr);
+ }
+
+ if (get_imap_response(nsc, NULL, NULL, NULL, false, false, &errstr) != OK) {
+ fprintf(stderr, "Error fetching final command responses: %s\n", errstr);
+ free(errstr);
+ }
+
+ if (timestamp)
+ ts_report(&tv_auth, "Total command execution time");
+
+ send_imap_command(nsc, false, NULL, "LOGOUT");
+ get_imap_response(nsc, NULL, NULL, NULL, false, false, NULL);
finish:
netsec_shutdown(nsc);
+ if (timestamp)
+ ts_report(&tv_start, "Total elapsed time");
+
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);
}
*/
static void
-capability_clear(void)
+clear_capability(void)
{
svector_free(imap_capabilities);
imap_capabilities = NULL;
unsigned int *outdatalen, void *context, char **errstr)
{
int rc, snoopoffset;
- char *mech, *line;
+ char *mech, *line, *capstring, *p;
size_t len;
netsec_context *nsc = (netsec_context *) context;
*/
case NETSEC_SASL_FINISH:
- if (get_imap_response(nsc, NULL, NULL, &line, 1, errstr) != OK)
+ line = NULL;
+ if (get_imap_response(nsc, "CAPABILITY ", &capstring, &line,
+ false, true, errstr) != OK)
return NOTOK;
/*
* We MIGHT get a capability response here. If so, be sure we
- * parse it.
+ * parse it. We ALSO might get a untagged CAPABILITY response.
+ * Which one should we prefer? I guess we'll go with the untagged
+ * one.
*/
- if (has_prefix(line, "OK [CAPABILITY ")) {
- char *p = line + 15, *q;
+ if (capstring) {
+ p = capstring + 11; /* "CAPABILITY " */
+ parse_capability(p, strlen(p));
+ free(capstring);
+ } else if (line && has_prefix(line, "OK [CAPABILITY ")) {
+ char *q;
+
+ p = line + 15;
q = strchr(p, ']');
if (q)
*/
static int
-send_imap_command(netsec_context *nsc, int noflush, char **errstr,
+send_imap_command(netsec_context *nsc, bool noflush, char **errstr,
const char *fmt, ...)
{
static unsigned int seq = 0; /* Tag sequence number */
snprintf(cmd->tag, sizeof(cmd->tag), "A%u ", seq++);
+ if (timestamp) {
+ char *p;
+ va_start(ap, fmt);
+ vsnprintf(cmd->prefix, sizeof(cmd->prefix), fmt, ap);
+ va_end(ap);
+
+ p = strchr(cmd->prefix, ' ');
+
+ if (p)
+ *p = '\0';
+
+ gettimeofday(&cmd->start, NULL);
+ }
+
if (netsec_write(nsc, cmd->tag, strlen(cmd->tag), errstr) != OK) {
free(cmd);
return NOTOK;
return OK;
}
+/*
+ * Send an APPEND to the server, which requires some extra semantics
+ */
+
+static int
+send_append(netsec_context *nsc, struct imap_msg *imsg, char **errstr)
+{
+ FILE *f;
+ bool nonsynlit = false;
+ char *status = NULL, *line = NULL;
+ size_t linesize = 0;
+ ssize_t rc;
+
+ /*
+ * If we have the LITERAL+ extension, or we have LITERAL- and our
+ * message is 4096 bytes or less, we can do it all in one message.
+ * Otherwise we have to wait for a contination marker (+).
+ */
+
+ if (capability_set("LITERAL+") ||
+ (capability_set("LITERAL-") && imsg->msgsize <= 4096)) {
+ nonsynlit = true;
+ }
+
+ /*
+ * Send our APPEND command
+ */
+
+ if (send_imap_command(nsc, nonsynlit, errstr, "APPEND \"%s\" {%u%s}",
+ imsg->folder, (unsigned int) imsg->msgsize,
+ nonsynlit ? "+" : "") != OK)
+ return NOTOK;
+
+ /*
+ * If we need to wait for a syncing literal, do that now
+ */
+
+ if (! nonsynlit) {
+ if (get_imap_response(nsc, NULL, NULL, &status, true,
+ true, errstr) != OK) {
+ imsg->queue = true; /* XXX Sigh */
+ fprintf(stderr, "APPEND command failed: %s\n", *errstr);
+ free(*errstr);
+ return OK;
+ }
+ if (!(status && has_prefix(status, "+"))) {
+ netsec_err(errstr, "Expected contination (+), but got: %s", status);
+ free(status);
+ return NOTOK;
+ }
+ free(status);
+ }
+
+ /*
+ * Now write the message out, but make sure we end each line with \r\n
+ */
+
+ if ((f = fopen(imsg->command, "r")) == NULL) {
+ netsec_err(errstr, "Unable to open %s: %s", imsg->command,
+ strerror(errno));
+ return NOTOK;
+ }
+
+ while ((rc = getline(&line, &linesize, f)) > 0) {
+ if (rc > 1 && line[rc - 1] == '\n' && line[rc - 2] == '\r') {
+ if (netsec_write(nsc, line, rc, errstr) != OK) {
+ free(line);
+ fclose(f);
+ return NOTOK;
+ }
+ } else {
+ if (line[rc - 1] == '\n')
+ rc--;
+ if (netsec_write(nsc, line, rc, errstr) != OK) {
+ free(line);
+ fclose(f);
+ return NOTOK;
+ }
+ if (netsec_write(nsc, "\r\n", 2, errstr) != OK) {
+ free(line);
+ fclose(f);
+ return NOTOK;
+ }
+ }
+ }
+
+ free(line);
+
+ if (! feof(f)) {
+ netsec_err(errstr, "Error reading %s: %s", imsg->command,
+ strerror(errno));
+ fclose(f);
+ return NOTOK;
+ }
+
+ fclose(f);
+
+ /*
+ * Send a final \r\n for the end of the command
+ */
+
+ if (netsec_write(nsc, "\r\n", 2, errstr) != OK)
+ return NOTOK;
+
+ if (! imsg->queue)
+ return netsec_flush(nsc, errstr);
+
+ return OK;
+}
+
/*
* Get all outstanding responses. If we were passed in a token string
* to look for, return it.
static int
get_imap_response(netsec_context *nsc, const char *token, char **tokenresponse,
- char **status, int failerr, char **errstr)
+ char **status, bool condok, bool failerr, char **errstr)
{
char *line;
struct imap_cmd *cmd;
- int numerrs = 0;
+ bool numerrs = false;
if (tokenresponse)
*tokenresponse = NULL;
free(*tokenresponse);
*tokenresponse = getcpy(line + 2);
}
+ } if (condok && has_prefix(line, "+")) {
+ if (status) {
+ *status = getcpy(line);
+ }
+ /*
+ * Special case; return now but don't dequeue the tag,
+ * since we will want to get final result later.
+ */
+ return OK;
} else {
if (has_prefix(line, cmdqueue->tag)) {
cmd = cmdqueue;
+ if (timestamp)
+ ts_report(&cmd->start, "Command (%s) execution time",
+ cmd->prefix);
cmdqueue = cmd->next;
free(cmd);
} else {
cmd->next = cmd->next->next;
if (failerr && strncmp(line + strlen(cmd2->tag),
"OK ", 3) != 0) {
- numerrs++;
+ numerrs = true;
netsec_err(errstr, "%s", line + strlen(cmd2->tag));
}
+ if (timestamp)
+ ts_report(&cmd2->start, "Command (%s) execution "
+ "time", cmd2->prefix);
free(cmd2);
if (status)
*status = getcpy(line);
goto getline;
}
- netsec_err(errstr, "Non-matching response line: %s\n",
- line);
- return NOTOK;
}
}
}
}
- return numerrs == 0 ? OK : NOTOK;
+ return numerrs ? NOTOK : OK;
+}
+
+/*
+ * Add an IMAP command to the msg queue
+ */
+
+static void
+add_msg(bool queue, struct imap_msg **ret_imsg, 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->folder = NULL;
+ imsg->append = false;
+ imsg->queue = queue;
+ imsg->next = NULL;
+
+ if (msgqueue_head == NULL) {
+ msgqueue_head = imsg;
+ msgqueue_tail = imsg;
+ } else {
+ msgqueue_tail->next = imsg;
+ msgqueue_tail = imsg;
+ }
+
+ if (ret_imsg)
+ *ret_imsg = imsg;
+}
+
+/*
+ * Add an APPEND command to the queue
+ */
+
+static void
+add_append(const char *filename, const char *folder, bool queue)
+{
+ size_t filesize = rfc822size(filename);
+ struct imap_msg *imsg;
+
+ add_msg(queue, &imsg, "%s", filename);
+
+ imsg->folder = getcpy(folder);
+ imsg->append = true;
+ imsg->msgsize = filesize;
+}
+
+/*
+ * Process a batch file, which can contain commands (and some arguments)
+ */
+
+static void
+batchfile(const char *filename, char *afolder, bool queue)
+{
+ FILE *f;
+ char *line = NULL;
+ size_t linesize = 0;
+ ssize_t rc;
+ bool afolder_alloc = false;
+
+ if (!(f = fopen(filename, "r"))) {
+ die("Unable to open batch file %s: %s", filename, strerror(errno));
+ }
+
+ while ((rc = getline(&line, &linesize, f)) > 0) {
+ line[rc - 1] = '\0';
+ if (*line == '-') {
+ switch (smatch (line + 1, switches)) {
+ case QUEUESW:
+ queue = true;
+ continue;
+ case NOQUEUESW:
+ queue = false;
+ continue;
+ case AFOLDERSW:
+ if (afolder_alloc)
+ free(afolder);
+ rc = getline(&line, &linesize, f);
+ if (rc <= 0)
+ die("Unable to read next line for -afolder");
+ if (rc == 1)
+ die("Folder name cannot be blank");
+ line[rc - 1] = '\0';
+ afolder = getcpy(line);
+ afolder_alloc = true;
+ continue;
+
+ case APPENDSW:
+ rc = getline(&line, &linesize, f);
+ if (rc <= 0)
+ die("Unable to read filename for -append");
+ if (rc == 1)
+ die("Filename for -append cannot be blank");
+ line[rc - 1] = '\0';
+ add_append(line, afolder, queue);
+ continue;
+
+ case AMBIGSW:
+ ambigsw (line, switches);
+ done (1);
+ case UNKWNSW:
+ die("%s unknown", line);
+ default:
+ die("Switch %s not supported in batch mode", line);
+ }
+ } else if (*line == '+') {
+ if (*(line + 1) == '\0')
+ die("Invalid null folder name");
+ add_msg(false, NULL, "SELECT \"%s\"", line + 1);
+ } else {
+ if (*line == '\0')
+ continue; /* Ignore blank line */
+ add_msg(queue, NULL, "%s", line);
+ }
+ }
+
+ if (!feof(f)) {
+ die("Read of \"%s\" failed: %s", filename, strerror(errno));
+ }
+
+ fclose(f);
+
+ if (afolder_alloc)
+ free(afolder);
+
+ free(line);
+}
+
+/*
+ * Negotiate TLS connection, with optional timestamp
+ */
+
+static void
+imap_negotiate_tls(netsec_context *nsc)
+{
+ char *errstr;
+ struct timeval tv;
+
+ if (timestamp)
+ gettimeofday(&tv, NULL);
+
+ if (netsec_negotiate_tls(nsc, &errstr) != OK)
+ die("%s", errstr);
+
+ if (timestamp)
+ ts_report(&tv, "TLS negotation time");
+}
+
+/*
+ * Give a timestamp report.
+ */
+
+static void
+ts_report(struct timeval *tv, const char *fmt, ...)
+{
+ struct timeval now;
+ double delta;
+ va_list ap;
+
+ gettimeofday(&now, NULL);
+
+ delta = ((double) now.tv_sec) - ((double) tv->tv_sec) +
+ (now.tv_usec / 1E6 - tv->tv_usec / 1E6);
+
+ va_start(ap, fmt);
+ vfprintf(stdout, fmt, ap);
+ va_end(ap);
+
+ printf(": %f sec\n", delta);
+}
+
+/*
+ * Calculate the RFC 822 size of file.
+ */
+
+static size_t
+rfc822size(const char *filename)
+{
+ FILE *f;
+ size_t total = 0, linecap = 0;
+ ssize_t rc;
+ char *line = NULL;
+
+ if (! (f = fopen(filename, "r")))
+ die("Unable to open %s: %s", filename, strerror(errno));
+
+ while ((rc = getline(&line, &linecap, f)) > 0) {
+ total += rc;
+ if (line[rc - 1] == '\n' && (rc == 1 || line[rc - 2] != '\r'))
+ total++;
+ if (line[rc - 1] != '\n')
+ total += 2;
+ }
+
+ free(line);
+
+ if (! feof(f))
+ die("Error while reading %s: %s", filename, strerror(errno));
+
+ fclose(f);
+
+ return total;
}