further pop_mx_ng work
[apps/madmutt.git] / lib-mx / pop-new.c
1 /*
2  *  This program is free software; you can redistribute it and/or modify
3  *  it under the terms of the GNU General Public License as published by
4  *  the Free Software Foundation; either version 2 of the License, or (at
5  *  your option) any later version.
6  *
7  *  This program is distributed in the hope that it will be useful, but
8  *  WITHOUT ANY WARRANTY; without even the implied warranty of
9  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
10  *  General Public License for more details.
11  *
12  *  You should have received a copy of the GNU General Public License
13  *  along with this program; if not, write to the Free Software
14  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
15  *  MA 02110-1301, USA.
16  */
17 /*
18  *  Copyright © 2007 Pierre Habouzit
19  *  Copyright © 2000-2002 Vsevolod Volkov <vvv@mutt.org.ua>
20  */
21
22 #include <lib-sys/evtloop.h>
23 #include <lib-sys/mutt_socket.h>
24 #include "pop.h"
25 #include "account.h"
26
27 #define POP3_PORT         110
28 #define POP3S_PORT        995
29 /* maximal length of the server response (RFC1939) */
30 #define POP_CMD_MAXLEN    512
31
32 enum pop_state {
33     POP_CONNECTING,
34     POP_CHECK_CAPA0,
35     POP_STLS,
36     POP_CHECK_CAPA1,
37     POP_AUTHENTICATE,
38     POP_CHECK_CAPA2,
39     POP_STAT,
40     POP_READY,
41 };
42
43 typedef enum {
44     CMD_UNKNOWN,  /* unknown whether it is available or not */
45     CMD_NOT_AVAILABLE,
46     CMD_AVAILABLE,
47 } cmd_status;
48
49 typedef struct pop_data_t {
50     int refcnt;
51     enum pop_state state;
52
53     unsigned multiline    : 1;
54     unsigned known_capa   : 1;
55     unsigned use_stls     : 2;
56     cmd_status cmd_capa   : 2;      /* optional command CAPA */
57     cmd_status cmd_stls   : 2;      /* optional command STLS */
58     cmd_status cmd_uidl   : 2;      /* optional command UIDL */
59     cmd_status cmd_top    : 2;      /* optional command TOP  */
60     cmd_status cmd_user   : 2;      /* optional command USER */
61     ACCOUNT act;
62
63     char *apop_token;
64     char *auth_mechs;
65
66     volatile job_t *w;
67     buffer_t ibuf, obuf;
68 } pop_data_t;
69 static void pop_data_delete(pop_data_t **tp);
70 DO_ARRAY_TYPE(pop_data_t, pop_data);
71 DO_ARRAY_FUNCS(pop_data_t, pop_data, pop_data_delete);
72
73 /****************************************************************************/
74 /* module                                                                   */
75 /************************************************************************{{{*/
76
77 static pop_data_array conns;
78
79 static inline pop_data_t *pop_data_new(void)
80 {
81     pop_data_t *res = p_new(pop_data_t, 1);
82     res->refcnt = 1;
83     pop_data_array_append(&conns, res);
84     return res;
85 }
86 static inline pop_data_t *pop_data_dup(pop_data_t *t)
87 {
88     t->refcnt++;
89     return t;
90 }
91 static void pop_data_reset(pop_data_t *pd)
92 {
93     p_delete(&pd->apop_token);
94     buffer_reset(&pd->ibuf);
95     buffer_reset(&pd->obuf);
96 }
97 static void pop_data_wipe(pop_data_t *pd)
98 {
99     p_delete(&pd->apop_token);
100     if (pd->w)
101         IGNORE(el_job_release((job_t *)pd->w, EL_KILLED));
102 }
103 static void pop_data_delete(pop_data_t **tp)
104 {
105     if (!*tp)
106         return;
107     if (--(*tp)->refcnt > 0) {
108         *tp = NULL;
109     } else {
110         for (int i = 0; i < conns.len; i++) {
111             if (conns.arr[i] == *tp) {
112                 pop_data_array_take(&conns, i);
113                 break;
114             }
115         }
116         pop_data_wipe(*tp);
117         p_delete(tp);
118     }
119 }
120
121 static __init void pop_initialize(void)
122 {
123     pop_data_array_init(&conns);
124 }
125 static __fini void pop_shutdown(void)
126 {
127     pop_data_array_wipe(&conns);
128 }
129
130 /************************************************************************}}}*/
131 /* pop3 machine                                                             */
132 /****************************************************************************/
133
134 #define POP_CHECK_EOL(w, pd)                                  \
135     ({                                                        \
136         const char *__eol = strchr((pd)->ibuf.data, '\n');    \
137         if (!__eol) {                                         \
138             if ((pd)->ibuf.len > POP_CMD_MAXLEN)              \
139                 return el_job_release((w), EL_ERROR);         \
140             return 0;                                         \
141         }                                                     \
142         __eol + 1;                                            \
143      })
144
145 static int pop_read_banner(job_t *w, pop_data_t *pd)
146 {
147     const char *eol = POP_CHECK_EOL(w, pd);
148     const char *p, *q;
149
150     if (m_strncmp(pd->ibuf.data, "+OK", 3))
151         return el_job_release(w, EL_ERROR);
152
153     p = memchr(pd->ibuf.data, '<', eol - pd->ibuf.data);
154     if (p && (q = memchr(p + 1, '>', eol - p - 1))) {
155         pd->apop_token = p_dupstr(p, q + 1 - p);
156     }
157     buffer_consume_upto(&pd->ibuf, eol);
158
159     /* TODO do not check capa again */
160     pd->state = POP_CHECK_CAPA0;
161     buffer_addstr(&pd->obuf, "CAPA\r\n");
162     return el_job_setmode(w, EL_WRITING);
163 }
164
165 static int pop_check_capa(job_t *w, pop_data_t *pd)
166 {
167     const char *p = POP_CHECK_EOL(w, pd);
168
169     if (!pd->multiline) {
170         if (m_strncmp(pd->ibuf.data, "+OK", 3)) {
171             pd->cmd_capa = CMD_NOT_AVAILABLE;
172             pd->cmd_stls = CMD_NOT_AVAILABLE;
173             goto end;
174         }
175         pd->multiline = true;
176     } else {
177         p = pd->ibuf.data;
178     }
179
180     for (;;) {
181         const char *q = strchr(p, '\n');
182         if (!q) {
183             buffer_consume_upto(&pd->ibuf, p);
184             return 0;
185         }
186         if (p[0] == '.' && p[1] != '.') {
187             buffer_consume_upto(&pd->ibuf, q);
188             break;
189         }
190         if (m_strcasestart(p, "SASL", &p)) {
191             p = skipspaces(p);
192             pd->auth_mechs = p_dupstr(p, q - p);
193         } else if (m_strcasestart(p, "STLS", NULL)) {
194             pd->cmd_stls = CMD_AVAILABLE;
195         } else if (m_strcasestart(p, "UIDL", NULL)) {
196             pd->cmd_uidl = CMD_AVAILABLE;
197         } else if (m_strcasestart(p, "TOP", NULL)) {
198             pd->cmd_top = CMD_AVAILABLE;
199         }
200         p = q;
201     }
202
203   end:
204     pd->multiline = false;
205     switch (pd->state) {
206       case POP_CHECK_CAPA0:
207         if (!pd->act.has_ssl && (pd->cmd_stls || mod_ssl.force_tls)) {
208             buffer_addstr(&pd->obuf, "STLS\r\n");
209             pd->state = POP_STLS;
210             return el_job_setmode(w, EL_WRITING);
211         }
212         /* FALLTHROUGH */
213       case POP_CHECK_CAPA1:
214         pd->state = POP_AUTHENTICATE;
215         /* TODO */
216         return -1;
217
218       case POP_CHECK_CAPA2:
219         pd->state = POP_STAT;
220         return -1;
221
222       default:
223         return el_job_release(w, EL_ERROR);
224     }
225 }
226
227 static int (*pop_actions[])(job_t *, pop_data_t *pd) = {
228     [POP_CONNECTING]  = pop_read_banner,
229     [POP_CHECK_CAPA0] = pop_check_capa,
230     NULL,
231 };
232
233 static int pop_setup(job_t *w, void *cfg)
234 {
235     pop_data_t *pd = w->ptr = pop_data_dup(cfg);
236     return el_job_connect2(w, &pd->act);
237 }
238 static int pop_on_event(job_t *w, el_event evt)
239 {
240     pop_data_t *pd = w->ptr;
241     switch (evt) {
242       case EL_EVT_RUNNING:
243         return el_job_setmode(w, EL_READING);
244
245       case EL_EVT_IN:
246         EL_JOB_CHECK(el_job_read(w, &pd->ibuf));
247         EL_JOB_CHECK(pop_actions[pd->state](w, pd));
248         /* FALLTHROUGH */
249       case EL_EVT_OUT:
250         EL_JOB_CHECK(el_job_write(w, &pd->obuf));
251         if (pd->obuf.len == 0)
252             return el_job_setmode(w, EL_READING);
253         return 0;
254
255       case EL_EVT_WAKEUP:
256         return 0;
257
258       default:
259         abort();
260     }
261 }
262 static void pop_finalize(job_t *w, el_status reason)
263 {
264     pop_data_t *pd = w->ptr;
265     switch (pd->state) {
266       case POP_CONNECTING:
267       case POP_CHECK_CAPA0:
268       case POP_STLS:
269       case POP_CHECK_CAPA1:
270       case POP_AUTHENTICATE:
271       case POP_CHECK_CAPA2:
272       case POP_STAT:
273         mutt_error(_("Error connecting to server: %s"), pd->act.host);
274         break;
275
276       case POP_READY:
277         break;
278     }
279     pop_data_reset(pd);
280     pop_data_delete((pop_data_t **)(void *)&w->ptr);
281 }
282
283 static machine_t const pop_machine = {
284     .name     = "POP3",
285     .setup    = &pop_setup,
286     .on_event = &pop_on_event,
287     .finalize = &pop_finalize,
288 };
289
290 /****************************************************************************/
291 /* pop3 mx driver                                                           */
292 /****************************************************************************/
293
294 static int pop_parse_path(const char *path, ACCOUNT *act)
295 {
296     ciss_url_t url;
297     char s[BUFSIZ];
298
299     /* Defaults */
300     act->flags = 0;
301     act->port  = POP3_PORT;
302     act->type  = M_ACCT_TYPE_POP;
303     m_strcpy(s, sizeof(s), path);
304     url_parse_ciss(&url, s);
305
306     if (url.scheme == U_POP || url.scheme == U_POPS) {
307         if (url.scheme == U_POPS) {
308             act->has_ssl = 1;
309             act->port    = POP3S_PORT;
310         }
311
312         if (m_strisempty(url.path) && !mutt_account_fromurl(act, &url))
313             return 0;
314     }
315
316     return -1;
317 }
318
319 static pop_data_t *pop_find_conn(ACCOUNT *act)
320 {
321     pop_data_t *pd = NULL;
322
323     el_lock();
324     for (int i = 0; i < conns.len; i++) {
325         if (mutt_account_match(act, &conns.arr[i]->act)) {
326             pd = pop_data_dup(conns.arr[i]);
327             break;
328         }
329     }
330     if (!pd) {
331         pd = pop_data_new();
332         pd->act = *act;
333     }
334     if (!pd->w) {
335         pd->w = el_job_start(&pop_machine, pd);
336     }
337     while (pd->w && pd->state != POP_READY) {
338         el_wait(pd->w);
339     }
340     if (!pd->w)
341         pop_data_delete(&pd);
342
343     el_unlock();
344     return pd;
345 }
346
347 static int pop_open_mailbox(CONTEXT *ctx)
348 {
349     char buf[BUFSIZ];
350     ACCOUNT act;
351     ciss_url_t url;
352     pop_data_t *pd = NULL;
353
354     if (pop_parse_path(ctx->path, &act)) {
355         mutt_error(_("%s is an invalid POP path"), ctx->path);
356         mutt_sleep(2);
357         return -1;
358     }
359
360     p_clear(&url, 1);
361     mutt_account_tourl(&act, &url);
362
363     pd = pop_find_conn(&act);
364     if (pd) {
365         ctx->data = pd;
366         url_ciss_tostring(&url, buf, sizeof (buf), 0);
367         m_strreplace(&ctx->path, buf);
368     }
369     return 0;
370 }
371
372 mx_t const pop_mx_ng = {
373     .type            = M_POP,
374     .mx_open_mailbox = pop_open_mailbox,
375 };