]> diplodocus.org Git - nmh/blobdiff - uip/imaptest.c
folder_realloc.c: Move interface to own file.
[nmh] / uip / imaptest.c
index befedabbfb0b82f8fcc921820cbd58b6cc07a15e..6c5d0046d90407045cbf046f4e3828e112b3a28c 100644 (file)
@@ -5,9 +5,14 @@
  * 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"
     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) \
 
@@ -44,7 +54,10 @@ 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 */
 };
 
@@ -75,15 +88,22 @@ static void clear_capability(void);
 static int have_capability(void);
 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, const char *fmt, ...) CHECK_PRINTF(2, 3);
+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;
 
@@ -91,10 +111,11 @@ int
 main (int argc, char **argv)
 {
     bool sasl = false, tls = false, initialtls = false;
-    bool snoop = false;
-    int fd;
+    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;
@@ -136,6 +157,31 @@ main (int argc, char **argv)
                    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:
                snoop = true;
                continue;
@@ -168,6 +214,12 @@ main (int argc, char **argv)
                tls = false;
                initialtls = false;
                continue;
+           case QUEUESW:
+               queue = true;
+               continue;
+           case NOQUEUESW:
+               queue = false;
+               continue;
            case TIMESTAMPSW:
                timestamp = true;
                continue;
@@ -178,9 +230,9 @@ main (int argc, char **argv)
        } else if (*cp == '+') {
            if (*(cp + 1) == '\0')
                die("Invalid null folder name");
-           add_msg(0, "SELECT \"%s\"", cp + 1);
+           add_msg(false, NULL, "SELECT \"%s\"", cp + 1);
        } else {
-           add_msg(0, "%s", cp);
+           add_msg(queue, NULL, "%s", cp);
        }
     }
 
@@ -194,6 +246,9 @@ main (int argc, char **argv)
 
     netsec_set_hostname(nsc, host);
 
+    if (timeout)
+       netsec_set_timeout(nsc, timeout);
+
     if (snoop)
        netsec_set_snoop(nsc, 1);
 
@@ -267,7 +322,7 @@ main (int argc, char **argv)
            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;
@@ -278,7 +333,9 @@ main (int argc, char **argv)
            goto finish;
        }
 
-       parse_capability(capstring, strlen(capstring));
+       p = capstring + 11;     /* "CAPABILITY " */
+
+       parse_capability(p, strlen(p));
        free(capstring);
     }
 
@@ -293,7 +350,8 @@ main (int argc, char **argv)
            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;
        }
@@ -332,8 +390,8 @@ main (int argc, char **argv)
            goto finish;
        }
 
-       if (get_imap_response(nsc, "CAPABILITY", &capstring, NULL, 1,
-                             &errstr) != OK) {
+       if (get_imap_response(nsc, "CAPABILITY ", &capstring, NULL, false,
+                             true, &errstr) != OK) {
            fprintf(stderr, "Cannot get CAPABILITY response: %s\n", errstr);
            goto finish;
        }
@@ -343,7 +401,9 @@ main (int argc, char **argv)
            goto finish;
        }
 
-       parse_capability(capstring, strlen(capstring));
+       p = capstring + 11;     /* "CAPABILITY " */
+
+       parse_capability(p, strlen(p));
        free(capstring);
     }
 
@@ -355,16 +415,24 @@ main (int argc, char **argv)
     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->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, 0, &errstr) != OK) {
+           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;
@@ -374,14 +442,29 @@ main (int argc, char **argv)
        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, 0, NULL, "LOGOUT");
-    get_imap_response(nsc, NULL, NULL, NULL, 0, NULL);
+    send_imap_command(nsc, false, NULL, "LOGOUT");
+    get_imap_response(nsc, NULL, NULL, NULL, false, false, NULL);
 
 finish:
     netsec_shutdown(nsc);
@@ -474,7 +557,7 @@ imap_sasl_callback(enum sasl_message_type mtype, unsigned const char *indata,
                   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;
 
@@ -602,15 +685,24 @@ imap_sasl_callback(enum sasl_message_type mtype, unsigned const char *indata,
 
     case NETSEC_SASL_FINISH:
         line = NULL;
-       if (get_imap_response(nsc, NULL, NULL, &line, 1, errstr) != OK)
+       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 (line && 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)
@@ -693,6 +785,116 @@ send_imap_command(netsec_context *nsc, bool noflush, char **errstr,
     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.
@@ -700,7 +902,7 @@ send_imap_command(netsec_context *nsc, bool noflush, char **errstr,
 
 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;
@@ -719,6 +921,15 @@ getline:
                    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)) {
@@ -738,14 +949,14 @@ getline:
                            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;
                }
            }
        }
@@ -759,7 +970,7 @@ getline:
  */
 
 static void
-add_msg(bool queue, const char *fmt, ...)
+add_msg(bool queue, struct imap_msg **ret_imsg, const char *fmt, ...)
 {
     struct imap_msg *imsg;
     va_list ap;
@@ -778,6 +989,8 @@ add_msg(bool queue, const char *fmt, ...)
     imsg = mh_xmalloc(sizeof(*imsg));
 
     imsg->command = msg;
+    imsg->folder = NULL;
+    imsg->append = false;
     imsg->queue = queue;
     imsg->next = NULL;
 
@@ -788,6 +1001,107 @@ add_msg(bool queue, const char *fmt, ...)
        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);
 }
 
 /*
@@ -832,3 +1146,36 @@ ts_report(struct timeval *tv, const char *fmt, ...)
 
     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;
+}