well this makes things fail for people ...
[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_AUTHENTICATE,
37     POP_CHECK_CAPA1,
38     POP_STAT,
39     POP_READY,
40 };
41
42 typedef enum {
43     CMD_NOT_AVAILABLE,
44     CMD_AVAILABLE,
45     CMD_UNKNOWN,  /* unknown whether it is available or not */
46 } cmd_status;
47
48 typedef struct pop_data_t {
49     int refcnt;
50     enum pop_state state;
51
52     unsigned multiline    : 1;
53     unsigned capa_done    : 1;
54     unsigned use_stls     : 2;
55     cmd_status cmd_capa   : 2;      /* optional command CAPA */
56     cmd_status cmd_stls   : 2;      /* optional command STLS */
57     cmd_status cmd_user   : 2;      /* optional command USER */
58     ACCOUNT act;
59
60     char *apop_token;
61     char *auth_mechs;
62
63     volatile job_t *w;
64     buffer_t ibuf, obuf;
65 } pop_data_t;
66 static void pop_data_delete(pop_data_t **tp);
67 DO_ARRAY_TYPE(pop_data_t, pop_data);
68 DO_ARRAY_FUNCS(pop_data_t, pop_data, pop_data_delete);
69
70 /****************************************************************************/
71 /* module                                                                   */
72 /************************************************************************{{{*/
73
74 static pop_data_array conns;
75
76 static inline pop_data_t *pop_data_new(void)
77 {
78     pop_data_t *res = p_new(pop_data_t, 1);
79     res->refcnt = 1;
80     pop_data_array_append(&conns, res);
81     return res;
82 }
83 static inline pop_data_t *pop_data_dup(pop_data_t *t)
84 {
85     t->refcnt++;
86     return t;
87 }
88 static void pop_data_reset(pop_data_t *pd)
89 {
90     p_delete(&pd->apop_token);
91     buffer_reset(&pd->ibuf);
92     buffer_reset(&pd->obuf);
93 }
94 static void pop_data_wipe(pop_data_t *pd)
95 {
96     p_delete(&pd->apop_token);
97     if (pd->w)
98         IGNORE(el_job_release((job_t *)pd->w, EL_KILLED));
99 }
100 static void pop_data_delete(pop_data_t **tp)
101 {
102     if (!*tp)
103         return;
104     if (--(*tp)->refcnt > 0) {
105         *tp = NULL;
106     } else {
107         for (int i = 0; i < conns.len; i++) {
108             if (conns.arr[i] == *tp) {
109                 pop_data_array_take(&conns, i);
110                 break;
111             }
112         }
113         pop_data_wipe(*tp);
114         p_delete(tp);
115     }
116 }
117
118 static __init void pop_initialize(void)
119 {
120     pop_data_array_init(&conns);
121 }
122 static __fini void pop_shutdown(void)
123 {
124     pop_data_array_wipe(&conns);
125 }
126
127 /************************************************************************}}}*/
128 /* pop3 machine                                                             */
129 /****************************************************************************/
130
131 #define POP_CHECK_EOL(w, pd)                                  \
132     ({                                                        \
133         const char *__eol = strchr((pd)->ibuf.data, '\n');    \
134         if (!__eol) {                                         \
135             if ((pd)->ibuf.len > POP_CMD_MAXLEN)              \
136                 return el_job_release((w), EL_ERROR);         \
137             return 0;                                         \
138         }                                                     \
139         __eol + 1;                                            \
140      })
141
142 static int pop_parse_capa(job_t *w, pop_data_t *pd, const char *eol)
143 {
144     const char *p = pd->ibuf.data;
145
146     if (!pd->multiline) {
147         if (m_strncmp(pd->ibuf.data, "+OK", 3)) {
148             buffer_consume_upto(&pd->ibuf, eol);
149             return 1;
150         }
151         pd->multiline = true;
152         p = eol;
153     }
154
155     for (;;) {
156         const char *q = strchr(p, '\n');
157         if (!q) {
158             buffer_consume_upto(&pd->ibuf, p);
159             return 0;
160         }
161         if (p[0] == '.' && p[1] != '.') {
162             buffer_consume_upto(&pd->ibuf, q);
163             return 1;
164         }
165         if (m_strcasestart(p, "SASL", &p)) {
166             p = skipspaces(p);
167             pd->auth_mechs = p_dupstr(p, q - p);
168         } else if (m_strcasestart(p, "STLS", NULL)) {
169             pd->cmd_stls = CMD_AVAILABLE;
170         }
171         p = q;
172     }
173 }
174
175 static int pop_do_connect(job_t *w, pop_data_t *pd)
176 {
177     const char *eol = POP_CHECK_EOL(w, pd);
178     const char *p, *q;
179
180     switch (pd->state) {
181       case POP_CONNECTING:
182         if (m_strncmp(pd->ibuf.data, "+OK", 3))
183             return el_job_release(w, EL_ERROR);
184
185         p = memchr(pd->ibuf.data, '<', eol - pd->ibuf.data);
186         if (p && (q = memchr(p + 1, '>', eol - p - 1))) {
187             pd->apop_token = p_dupstr(p, q + 1 - p);
188         }
189         buffer_consume_upto(&pd->ibuf, eol);
190         if (pd->capa_done)
191             goto capa0_choice;
192         pd->state = POP_CHECK_CAPA0;
193         buffer_addstr(&pd->obuf, "CAPA\r\n");
194         return el_job_setmode(w, EL_WRITING);
195
196       case POP_CHECK_CAPA0:
197         if (!pop_parse_capa(w, pd, eol))
198             return 0;
199
200       capa0_choice:
201         pd->multiline = false;
202         if (pd->act.has_ssl ||  (!pd->cmd_stls && !mod_ssl.force_tls))
203             goto do_authenticate;
204         pd->state = POP_STLS;
205         buffer_addstr(&pd->obuf, "STLS\r\n");
206         return el_job_setmode(w, EL_WRITING);
207
208       case POP_STLS:
209         if (m_strncmp(pd->ibuf.data, "+OK", 3)) {
210             pd->cmd_stls = CMD_NOT_AVAILABLE;
211             buffer_consume_upto(&pd->ibuf, eol);
212             goto do_authenticate;
213         }
214       do_authenticate:
215         pd->state = POP_AUTHENTICATE;
216         return el_job_starttls(w);
217
218       case POP_AUTHENTICATE:
219         abort();
220         pd->state = POP_CHECK_CAPA1;
221         buffer_addstr(&pd->obuf, "CAPA\r\n");
222         return el_job_setmode(w, EL_WRITING);
223
224       case POP_CHECK_CAPA1:
225         if (!pop_parse_capa(w, pd, eol))
226             return 0;
227         pd->capa_done = true;
228         pd->state = POP_STAT;
229         buffer_addstr(&pd->obuf, "STAT\r\n");
230         return el_job_setmode(w, EL_WRITING);
231
232       case POP_STAT:
233         pd->state = POP_READY;
234         return el_job_setmode(w, EL_IDLE);
235
236       default:
237         abort();
238     }
239 }
240
241 static int pop_setup(job_t *w, void *cfg)
242 {
243     pop_data_t *pd = w->ptr = pop_data_dup(cfg);
244     return el_job_connect2(w, &pd->act);
245 }
246 static int pop_on_event(job_t *w, el_event evt)
247 {
248     pop_data_t *pd = w->ptr;
249     switch (evt) {
250       case EL_EVT_RUNNING:
251         return el_job_setmode(w, EL_READING);
252
253       case EL_EVT_IN:
254         EL_JOB_CHECK(el_job_read(w, &pd->ibuf));
255         if (pd->state <= POP_STAT)
256             return pop_do_connect(w, pd);
257         abort();
258
259       case EL_EVT_OUT:
260         EL_JOB_CHECK(el_job_write(w, &pd->obuf));
261         if (pd->obuf.len == 0)
262             return el_job_setmode(w, EL_READING);
263         return 0;
264
265       case EL_EVT_WAKEUP:
266         return 0;
267
268       default:
269         abort();
270     }
271 }
272 static void pop_finalize(job_t *w, el_status reason)
273 {
274     pop_data_t *pd = w->ptr;
275     switch (pd->state) {
276       case POP_CONNECTING:
277       case POP_CHECK_CAPA0:
278       case POP_STLS:
279       case POP_CHECK_CAPA1:
280       case POP_AUTHENTICATE:
281       case POP_STAT:
282         mutt_error(_("Error connecting to server: %s"), pd->act.host);
283         break;
284
285       case POP_READY:
286         break;
287     }
288     pop_data_reset(pd);
289     pop_data_delete((pop_data_t **)(void *)&w->ptr);
290 }
291
292 static machine_t const pop_machine = {
293     .name     = "POP3",
294     .setup    = &pop_setup,
295     .on_event = &pop_on_event,
296     .finalize = &pop_finalize,
297 };
298
299 /****************************************************************************/
300 /* pop3 mx driver                                                           */
301 /****************************************************************************/
302
303 static int pop_parse_path(const char *path, ACCOUNT *act)
304 {
305     ciss_url_t url;
306     char s[BUFSIZ];
307
308     /* Defaults */
309     act->flags = 0;
310     act->port  = POP3_PORT;
311     act->type  = M_ACCT_TYPE_POP;
312     m_strcpy(s, sizeof(s), path);
313     url_parse_ciss(&url, s);
314
315     if (url.scheme == U_POP || url.scheme == U_POPS) {
316         if (url.scheme == U_POPS) {
317             act->has_ssl = 1;
318             act->port    = POP3S_PORT;
319         }
320
321         if (m_strisempty(url.path) && !mutt_account_fromurl(act, &url))
322             return 0;
323     }
324
325     return -1;
326 }
327
328 static pop_data_t *pop_find_conn(ACCOUNT *act)
329 {
330     pop_data_t *pd = NULL;
331
332     el_lock();
333     for (int i = 0; i < conns.len; i++) {
334         if (mutt_account_match(act, &conns.arr[i]->act)) {
335             pd = pop_data_dup(conns.arr[i]);
336             break;
337         }
338     }
339     if (!pd) {
340         pd = pop_data_new();
341         pd->act = *act;
342     }
343     if (!pd->w) {
344         pd->w = el_job_start(&pop_machine, pd);
345     }
346     while (pd->w && pd->state != POP_READY) {
347         el_wait(pd->w);
348     }
349     if (!pd->w)
350         pop_data_delete(&pd);
351
352     el_unlock();
353     return pd;
354 }
355
356 static int pop_open_mailbox(CONTEXT *ctx)
357 {
358     char buf[BUFSIZ];
359     ACCOUNT act;
360     ciss_url_t url;
361     pop_data_t *pd = NULL;
362
363     if (pop_parse_path(ctx->path, &act)) {
364         mutt_error(_("%s is an invalid POP path"), ctx->path);
365         mutt_sleep(2);
366         return -1;
367     }
368
369     p_clear(&url, 1);
370     mutt_account_tourl(&act, &url);
371
372     pd = pop_find_conn(&act);
373     if (pd) {
374         ctx->data = pd;
375         url_ciss_tostring(&url, buf, sizeof (buf), 0);
376         m_strreplace(&ctx->path, buf);
377     }
378     return 0;
379 }
380
381 mx_t const pop_mx_ng = {
382     .type            = M_POP,
383     .mx_open_mailbox = pop_open_mailbox,
384 };