further pop_mx_ng work
[apps/madmutt.git] / lib-mx / pop-new.c
index 4978f8f..a46cc82 100644 (file)
  */
 
 #include <lib-sys/evtloop.h>
+#include <lib-sys/mutt_socket.h>
 #include "pop.h"
 #include "account.h"
 
 #define POP3_PORT         110
 #define POP3S_PORT        995
+/* maximal length of the server response (RFC1939) */
+#define POP_CMD_MAXLEN    512
 
 enum pop_state {
-    POP_NEW,
+    POP_CONNECTING,
+    POP_CHECK_CAPA0,
+    POP_STLS,
+    POP_CHECK_CAPA1,
+    POP_AUTHENTICATE,
+    POP_CHECK_CAPA2,
+    POP_STAT,
     POP_READY,
 };
 
+typedef enum {
+    CMD_UNKNOWN,  /* unknown whether it is available or not */
+    CMD_NOT_AVAILABLE,
+    CMD_AVAILABLE,
+} cmd_status;
+
 typedef struct pop_data_t {
     int refcnt;
     enum pop_state state;
 
-    volatile job_t *w;
-
+    unsigned multiline    : 1;
+    unsigned known_capa   : 1;
+    unsigned use_stls     : 2;
+    cmd_status cmd_capa   : 2;      /* optional command CAPA */
+    cmd_status cmd_stls   : 2;      /* optional command STLS */
+    cmd_status cmd_uidl   : 2;      /* optional command UIDL */
+    cmd_status cmd_top    : 2;      /* optional command TOP  */
+    cmd_status cmd_user   : 2;      /* optional command USER */
     ACCOUNT act;
+
+    char *apop_token;
+    char *auth_mechs;
+
+    volatile job_t *w;
+    buffer_t ibuf, obuf;
 } pop_data_t;
 static void pop_data_delete(pop_data_t **tp);
 DO_ARRAY_TYPE(pop_data_t, pop_data);
@@ -45,7 +72,7 @@ DO_ARRAY_FUNCS(pop_data_t, pop_data, pop_data_delete);
 
 /****************************************************************************/
 /* module                                                                   */
-/****************************************************************************/
+/************************************************************************{{{*/
 
 static pop_data_array conns;
 
@@ -61,8 +88,15 @@ static inline pop_data_t *pop_data_dup(pop_data_t *t)
     t->refcnt++;
     return t;
 }
+static void pop_data_reset(pop_data_t *pd)
+{
+    p_delete(&pd->apop_token);
+    buffer_reset(&pd->ibuf);
+    buffer_reset(&pd->obuf);
+}
 static void pop_data_wipe(pop_data_t *pd)
 {
+    p_delete(&pd->apop_token);
     if (pd->w)
         IGNORE(el_job_release((job_t *)pd->w, EL_KILLED));
 }
@@ -93,25 +127,163 @@ static __fini void pop_shutdown(void)
     pop_data_array_wipe(&conns);
 }
 
-/****************************************************************************/
+/************************************************************************}}}*/
 /* pop3 machine                                                             */
 /****************************************************************************/
 
+#define POP_CHECK_EOL(w, pd)                                  \
+    ({                                                        \
+        const char *__eol = strchr((pd)->ibuf.data, '\n');    \
+        if (!__eol) {                                         \
+            if ((pd)->ibuf.len > POP_CMD_MAXLEN)              \
+                return el_job_release((w), EL_ERROR);         \
+            return 0;                                         \
+        }                                                     \
+        __eol + 1;                                            \
+     })
+
+static int pop_read_banner(job_t *w, pop_data_t *pd)
+{
+    const char *eol = POP_CHECK_EOL(w, pd);
+    const char *p, *q;
+
+    if (m_strncmp(pd->ibuf.data, "+OK", 3))
+        return el_job_release(w, EL_ERROR);
+
+    p = memchr(pd->ibuf.data, '<', eol - pd->ibuf.data);
+    if (p && (q = memchr(p + 1, '>', eol - p - 1))) {
+        pd->apop_token = p_dupstr(p, q + 1 - p);
+    }
+    buffer_consume_upto(&pd->ibuf, eol);
+
+    /* TODO do not check capa again */
+    pd->state = POP_CHECK_CAPA0;
+    buffer_addstr(&pd->obuf, "CAPA\r\n");
+    return el_job_setmode(w, EL_WRITING);
+}
+
+static int pop_check_capa(job_t *w, pop_data_t *pd)
+{
+    const char *p = POP_CHECK_EOL(w, pd);
+
+    if (!pd->multiline) {
+        if (m_strncmp(pd->ibuf.data, "+OK", 3)) {
+            pd->cmd_capa = CMD_NOT_AVAILABLE;
+            pd->cmd_stls = CMD_NOT_AVAILABLE;
+            goto end;
+        }
+        pd->multiline = true;
+    } else {
+        p = pd->ibuf.data;
+    }
+
+    for (;;) {
+        const char *q = strchr(p, '\n');
+        if (!q) {
+            buffer_consume_upto(&pd->ibuf, p);
+            return 0;
+        }
+        if (p[0] == '.' && p[1] != '.') {
+            buffer_consume_upto(&pd->ibuf, q);
+            break;
+        }
+        if (m_strcasestart(p, "SASL", &p)) {
+            p = skipspaces(p);
+            pd->auth_mechs = p_dupstr(p, q - p);
+        } else if (m_strcasestart(p, "STLS", NULL)) {
+            pd->cmd_stls = CMD_AVAILABLE;
+        } else if (m_strcasestart(p, "UIDL", NULL)) {
+            pd->cmd_uidl = CMD_AVAILABLE;
+        } else if (m_strcasestart(p, "TOP", NULL)) {
+            pd->cmd_top = CMD_AVAILABLE;
+        }
+        p = q;
+    }
+
+  end:
+    pd->multiline = false;
+    switch (pd->state) {
+      case POP_CHECK_CAPA0:
+        if (!pd->act.has_ssl && (pd->cmd_stls || mod_ssl.force_tls)) {
+            buffer_addstr(&pd->obuf, "STLS\r\n");
+            pd->state = POP_STLS;
+            return el_job_setmode(w, EL_WRITING);
+        }
+        /* FALLTHROUGH */
+      case POP_CHECK_CAPA1:
+        pd->state = POP_AUTHENTICATE;
+        /* TODO */
+        return -1;
+
+      case POP_CHECK_CAPA2:
+        pd->state = POP_STAT;
+        return -1;
+
+      default:
+        return el_job_release(w, EL_ERROR);
+    }
+}
+
+static int (*pop_actions[])(job_t *, pop_data_t *pd) = {
+    [POP_CONNECTING]  = pop_read_banner,
+    [POP_CHECK_CAPA0] = pop_check_capa,
+    NULL,
+};
+
 static int pop_setup(job_t *w, void *cfg)
 {
-    w->ptr = pop_data_dup(cfg);
-    /* FIXME */ return el_job_release(w, EL_ERROR);
-    return 0;
+    pop_data_t *pd = w->ptr = pop_data_dup(cfg);
+    return el_job_connect2(w, &pd->act);
 }
+static int pop_on_event(job_t *w, el_event evt)
+{
+    pop_data_t *pd = w->ptr;
+    switch (evt) {
+      case EL_EVT_RUNNING:
+        return el_job_setmode(w, EL_READING);
 
+      case EL_EVT_IN:
+        EL_JOB_CHECK(el_job_read(w, &pd->ibuf));
+        EL_JOB_CHECK(pop_actions[pd->state](w, pd));
+        /* FALLTHROUGH */
+      case EL_EVT_OUT:
+        EL_JOB_CHECK(el_job_write(w, &pd->obuf));
+        if (pd->obuf.len == 0)
+            return el_job_setmode(w, EL_READING);
+        return 0;
+
+      case EL_EVT_WAKEUP:
+        return 0;
+
+      default:
+        abort();
+    }
+}
 static void pop_finalize(job_t *w, el_status reason)
 {
+    pop_data_t *pd = w->ptr;
+    switch (pd->state) {
+      case POP_CONNECTING:
+      case POP_CHECK_CAPA0:
+      case POP_STLS:
+      case POP_CHECK_CAPA1:
+      case POP_AUTHENTICATE:
+      case POP_CHECK_CAPA2:
+      case POP_STAT:
+        mutt_error(_("Error connecting to server: %s"), pd->act.host);
+        break;
+
+      case POP_READY:
+        break;
+    }
+    pop_data_reset(pd);
     pop_data_delete((pop_data_t **)(void *)&w->ptr);
 }
 
 static machine_t const pop_machine = {
     .name     = "POP3",
     .setup    = &pop_setup,
+    .on_event = &pop_on_event,
     .finalize = &pop_finalize,
 };