*/
#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);
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,
};
/* 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,
};