Replace deprecated luaL_openlib() by luaL_register()
[apps/madmutt.git] / lib-mx / pop-new.c
index 3936cbb..fcc17df 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_CONNECTING,
+    POP_CHECK_CAPA0,
+    POP_STLS,
+    POP_AUTHENTICATE,
+    POP_CHECK_CAPA1,
+    POP_STAT,
+    POP_READY,
+};
+
+typedef enum {
+    CMD_NOT_AVAILABLE,
+    CMD_AVAILABLE,
+    CMD_UNKNOWN,  /* unknown whether it is available or not */
+} cmd_status;
 
 typedef struct pop_data_t {
     int refcnt;
-    job_t *w;
+    enum pop_state state;
+
+    unsigned multiline    : 1;
+    unsigned capa_done    : 1;
+    unsigned use_stls     : 2;
+    cmd_status cmd_capa   : 2;      /* optional command CAPA */
+    cmd_status cmd_stls   : 2;      /* optional command STLS */
+    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;
-DO_INIT(pop_data_t, pop_data);
-DO_WIPE(pop_data_t, pop_data);
-DO_REFCNT(pop_data_t, pop_data);
+static void pop_data_delete(pop_data_t **tp);
 DO_ARRAY_TYPE(pop_data_t, pop_data);
 DO_ARRAY_FUNCS(pop_data_t, pop_data, pop_data_delete);
 
 /****************************************************************************/
 /* module                                                                   */
-/****************************************************************************/
+/************************************************************************{{{*/
 
 static pop_data_array conns;
 
+static inline pop_data_t *pop_data_new(void)
+{
+    pop_data_t *res = p_new(pop_data_t, 1);
+    res->refcnt = 1;
+    pop_data_array_append(&conns, res);
+    return res;
+}
+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));
+}
+static void pop_data_delete(pop_data_t **tp)
+{
+    if (!*tp)
+        return;
+    if (--(*tp)->refcnt > 0) {
+        *tp = NULL;
+    } else {
+        for (int i = 0; i < conns.len; i++) {
+            if (conns.arr[i] == *tp) {
+                pop_data_array_take(&conns, i);
+                break;
+            }
+        }
+        pop_data_wipe(*tp);
+        p_delete(tp);
+    }
+}
+
 static __init void pop_initialize(void)
 {
     pop_data_array_init(&conns);
@@ -47,22 +124,175 @@ 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_parse_capa(job_t *w, pop_data_t *pd, const char *eol)
+{
+    const char *p = pd->ibuf.data;
+
+    if (!pd->multiline) {
+        if (m_strncmp(pd->ibuf.data, "+OK", 3)) {
+            buffer_consume_upto(&pd->ibuf, eol);
+            return 1;
+        }
+        pd->multiline = true;
+        p = eol;
+    }
+
+    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);
+            return 1;
+        }
+        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;
+        }
+        p = q;
+    }
+}
+
+static int pop_do_connect(job_t *w, pop_data_t *pd)
+{
+    const char *eol = POP_CHECK_EOL(w, pd);
+    const char *p, *q;
+
+    switch (pd->state) {
+      case POP_CONNECTING:
+        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);
+        if (pd->capa_done)
+            goto capa0_choice;
+        pd->state = POP_CHECK_CAPA0;
+        buffer_addstr(&pd->obuf, "CAPA\r\n");
+        return el_job_setmode(w, EL_WRITING);
+
+      case POP_CHECK_CAPA0:
+        if (!pop_parse_capa(w, pd, eol))
+            return 0;
+
+      capa0_choice:
+        pd->multiline = false;
+        if (pd->act.has_ssl ||  (!pd->cmd_stls && !mod_ssl.force_tls))
+            goto do_authenticate;
+        pd->state = POP_STLS;
+        buffer_addstr(&pd->obuf, "STLS\r\n");
+        return el_job_setmode(w, EL_WRITING);
+
+      case POP_STLS:
+        if (m_strncmp(pd->ibuf.data, "+OK", 3)) {
+            pd->cmd_stls = CMD_NOT_AVAILABLE;
+            buffer_consume_upto(&pd->ibuf, eol);
+            goto do_authenticate;
+        }
+      do_authenticate:
+        pd->state = POP_AUTHENTICATE;
+        return el_job_starttls(w);
+
+      case POP_AUTHENTICATE:
+        abort();
+        pd->state = POP_CHECK_CAPA1;
+        buffer_addstr(&pd->obuf, "CAPA\r\n");
+        return el_job_setmode(w, EL_WRITING);
+
+      case POP_CHECK_CAPA1:
+        if (!pop_parse_capa(w, pd, eol))
+            return 0;
+        pd->capa_done = true;
+        pd->state = POP_STAT;
+        buffer_addstr(&pd->obuf, "STAT\r\n");
+        return el_job_setmode(w, EL_WRITING);
+
+      case POP_STAT:
+        pd->state = POP_READY;
+        return el_job_setmode(w, EL_IDLE);
+
+      default:
+        abort();
+    }
+}
+
 static int pop_setup(job_t *w, void *cfg)
 {
-    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));
+        if (pd->state <= POP_STAT)
+            return pop_do_connect(w, pd);
+        abort();
+
+      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_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,
 };
 
@@ -70,18 +300,85 @@ static machine_t const pop_machine = {
 /* pop3 mx driver                                                           */
 /****************************************************************************/
 
+static int pop_parse_path(const char *path, ACCOUNT *act)
+{
+    ciss_url_t url;
+    char s[BUFSIZ];
+
+    /* Defaults */
+    act->flags = 0;
+    act->port  = POP3_PORT;
+    act->type  = M_ACCT_TYPE_POP;
+    m_strcpy(s, sizeof(s), path);
+    url_parse_ciss(&url, s);
+
+    if (url.scheme == U_POP || url.scheme == U_POPS) {
+        if (url.scheme == U_POPS) {
+            act->has_ssl = 1;
+            act->port    = POP3S_PORT;
+        }
+
+        if (m_strisempty(url.path) && !mutt_account_fromurl(act, &url))
+            return 0;
+    }
+
+    return -1;
+}
+
+static pop_data_t *pop_find_conn(ACCOUNT *act)
+{
+    pop_data_t *pd = NULL;
+
+    el_lock();
+    for (int i = 0; i < conns.len; i++) {
+        if (mutt_account_match(act, &conns.arr[i]->act)) {
+            pd = pop_data_dup(conns.arr[i]);
+            break;
+        }
+    }
+    if (!pd) {
+        pd = pop_data_new();
+        pd->act = *act;
+    }
+    if (!pd->w) {
+        pd->w = el_job_start(&pop_machine, pd);
+    }
+    while (pd->w && pd->state != POP_READY) {
+        el_wait(pd->w);
+    }
+    if (!pd->w)
+        pop_data_delete(&pd);
+
+    el_unlock();
+    return pd;
+}
+
+static int pop_open_mailbox(CONTEXT *ctx)
+{
+    char buf[BUFSIZ];
+    ACCOUNT act;
+    ciss_url_t url;
+    pop_data_t *pd = NULL;
+
+    if (pop_parse_path(ctx->path, &act)) {
+        mutt_error(_("%s is an invalid POP path"), ctx->path);
+        mutt_sleep(2);
+        return -1;
+    }
+
+    p_clear(&url, 1);
+    mutt_account_tourl(&act, &url);
+
+    pd = pop_find_conn(&act);
+    if (pd) {
+        ctx->data = pd;
+        url_ciss_tostring(&url, buf, sizeof (buf), 0);
+        m_strreplace(&ctx->path, buf);
+    }
+    return 0;
+}
+
 mx_t const pop_mx_ng = {
-    M_POP,
-    0,
-    NULL,
-    NULL,
-    NULL,
-    NULL,
-    NULL,
-    NULL,
-    NULL,
-    NULL,
-    NULL,
-    NULL,
-    NULL,
+    .type            = M_POP,
+    .mx_open_mailbox = pop_open_mailbox,
 };