rationnalize includes a lot:
[apps/madmutt.git] / pop / pop_lib.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 2000-2003 Vsevolod Volkov <vvv@mutt.org.ua>
4  *
5  * This file is part of mutt-ng, see http://www.muttng.org/.
6  * It's licensed under the GNU General Public License,
7  * please see the file GPL in the top level source directory.
8  */
9
10 #include <lib-lib/lib-lib.h>
11
12 #include "mutt.h"
13 #include "mx.h"
14 #include "pop.h"
15 #if defined (USE_SSL) || defined (USE_GNUTLS)
16 # include <lib-sys/mutt_ssl.h>
17 #endif
18
19 /* given an POP mailbox name, return host, port, username and password */
20 int pop_parse_path (const char *path, ACCOUNT * act)
21 {
22   ciss_url_t url;
23   char *c;
24   int ret = -1;
25
26   /* Defaults */
27   act->flags = 0;
28   act->port = POP_PORT;
29   act->type = M_ACCT_TYPE_POP;
30
31   c = m_strdup(path);
32   url_parse_ciss (&url, c);
33
34   if (url.scheme == U_POP || url.scheme == U_POPS) {
35     if (url.scheme == U_POPS) {
36       act->flags |= M_ACCT_SSL;
37       act->port = POP_SSL_PORT;
38     }
39
40     if ((!url.path || !*url.path) && mutt_account_fromurl (act, &url) == 0)
41       ret = 0;
42   }
43
44   p_delete(&c);
45   return ret;
46 }
47
48 /* Copy error message to err_msg buffer */
49 void pop_error (POP_DATA * pop_data, char *msg)
50 {
51   char *t, *c, *c2;
52
53   t = strchr (pop_data->err_msg, '\0');
54   c = msg;
55
56   if (!m_strncmp(msg, "-ERR ", 5)) {
57     c2 = vskipspaces(msg + 5);
58     if (*c2)
59       c = c2;
60   }
61
62   m_strcpy(t, sizeof(pop_data->err_msg) - strlen(pop_data->err_msg), c);
63   m_strrtrim(pop_data->err_msg);
64 }
65
66 /* Parse CAPA output */
67 static int fetch_capa (char *line, void *data)
68 {
69   POP_DATA *pop_data = (POP_DATA *) data;
70   char *c;
71
72   if (!ascii_strncasecmp (line, "SASL", 4)) {
73     p_delete(&pop_data->auth_list);
74     c = vskipspaces(line + 4);
75     pop_data->auth_list = m_strdup(c);
76   }
77
78   else if (!ascii_strncasecmp (line, "STLS", 4))
79     pop_data->cmd_stls = CMD_AVAILABLE;
80
81   else if (!ascii_strncasecmp (line, "USER", 4))
82     pop_data->cmd_user = CMD_AVAILABLE;
83
84   else if (!ascii_strncasecmp (line, "UIDL", 4))
85     pop_data->cmd_uidl = CMD_AVAILABLE;
86
87   else if (!ascii_strncasecmp (line, "TOP", 3))
88     pop_data->cmd_top = CMD_AVAILABLE;
89
90   return 0;
91 }
92
93 /* Fetch list of the authentication mechanisms */
94 static int fetch_auth (char *line, void *data)
95 {
96   POP_DATA *pop_data = (POP_DATA *) data;
97
98   if (!pop_data->auth_list) {
99     pop_data->auth_list = p_new(char, strlen(line) + 1);
100   } else {
101     p_realloc(&pop_data->auth_list,
102               strlen(pop_data->auth_list) + strlen(line) + 2);
103     strcat (pop_data->auth_list, " ");  /* __STRCAT_CHECKED__ */
104   }
105   strcat (pop_data->auth_list, line);   /* __STRCAT_CHECKED__ */
106
107   return 0;
108 }
109
110 /*
111  * Get capabilities
112  *  0 - successful,
113  * -1 - conection lost,
114  * -2 - execution error.
115 */
116 static pop_query_status pop_capabilities (POP_DATA * pop_data, int mode)
117 {
118   char buf[LONG_STRING];
119
120   /* don't check capabilities on reconnect */
121   if (pop_data->capabilities)
122     return 0;
123
124   /* init capabilities */
125   if (mode == 0) {
126     pop_data->cmd_capa = CMD_NOT_AVAILABLE;
127     pop_data->cmd_stls = CMD_NOT_AVAILABLE;
128     pop_data->cmd_user = CMD_NOT_AVAILABLE;
129     pop_data->cmd_uidl = CMD_NOT_AVAILABLE;
130     pop_data->cmd_top = CMD_NOT_AVAILABLE;
131     pop_data->resp_codes = 0;
132     pop_data->expire = 1;
133     pop_data->login_delay = 0;
134     p_delete(&pop_data->auth_list);
135   }
136
137   /* Execute CAPA command */
138   if (mode == 0 || pop_data->cmd_capa != CMD_NOT_AVAILABLE) {
139     m_strcpy(buf, sizeof(buf), "CAPA\r\n");
140     switch (pop_fetch_data (pop_data, buf, NULL, fetch_capa, pop_data)) {
141     case PQ_OK:
142       {
143         pop_data->cmd_capa = CMD_AVAILABLE;
144         break;
145       }
146     case PFD_FUNCT_ERROR:
147     case PQ_ERR:
148       {
149         pop_data->cmd_capa = CMD_NOT_AVAILABLE;
150         break;
151       }
152     case PQ_NOT_CONNECTED:
153       return PQ_NOT_CONNECTED;
154     }
155   }
156
157   /* CAPA not supported, use defaults */
158   if (mode == 0 && pop_data->cmd_capa == CMD_NOT_AVAILABLE) {
159     pop_data->cmd_user = CMD_UNKNOWN;
160     pop_data->cmd_uidl = CMD_UNKNOWN;
161     pop_data->cmd_top = CMD_UNKNOWN;
162
163     m_strcpy(buf, sizeof(buf), "AUTH\r\n");
164     if (pop_fetch_data (pop_data, buf, NULL, fetch_auth, pop_data) == PQ_NOT_CONNECTED)
165       return PQ_NOT_CONNECTED;
166   }
167
168   /* Check capabilities */
169   if (mode == 2) {
170     char *msg = NULL;
171
172     if (!pop_data->expire)
173       msg = _("Unable to leave messages on server.");
174     if (pop_data->cmd_top == CMD_NOT_AVAILABLE)
175       msg = _("Command TOP is not supported by server.");
176     if (pop_data->cmd_uidl == CMD_NOT_AVAILABLE)
177       msg = _("Command UIDL is not supported by server.");
178     if (msg && pop_data->cmd_capa != CMD_AVAILABLE) {
179       mutt_error (msg);
180       return PQ_ERR;
181     }
182     pop_data->capabilities = 1;
183   }
184
185   return PQ_OK;
186 }
187
188 /*
189  * Open connection
190  *  0 - successful,
191  * -1 - conection lost,
192  * -2 - invalid response.
193 */
194 pop_query_status pop_connect (POP_DATA * pop_data)
195 {
196   char buf[LONG_STRING];
197
198   pop_data->status = POP_NONE;
199   if (mutt_socket_open (pop_data->conn) < 0 ||
200       mutt_socket_readln (buf, sizeof (buf), pop_data->conn) < 0) {
201     mutt_error (_("Error connecting to server: %s"),
202                 pop_data->conn->account.host);
203     return PQ_NOT_CONNECTED;
204   }
205
206   pop_data->status = POP_CONNECTED;
207
208   if (m_strncmp(buf, "+OK", 3)) {
209     *pop_data->err_msg = '\0';
210     pop_error (pop_data, buf);
211     mutt_error ("%s", pop_data->err_msg);
212     return PQ_ERR;
213   }
214
215   pop_apop_timestamp (pop_data, buf);
216
217   return PQ_OK;
218 }
219
220 /*
221  * Open connection and authenticate
222  *  0 - successful,
223  * -1 - conection lost,
224  * -2 - invalid command or execution error,
225  * -3 - authentication canceled.
226 */
227 pop_query_status pop_open_connection (POP_DATA * pop_data)
228 {
229   pop_query_status ret;
230   unsigned int n, size;
231   char buf[LONG_STRING];
232
233   ret = pop_connect (pop_data);
234   if (ret != PQ_OK) {
235     mutt_sleep (2);
236     return ret;
237   }
238
239   ret = pop_capabilities (pop_data, 0);
240   if (ret == PQ_NOT_CONNECTED)
241     goto err_conn;
242   if (ret == PQ_ERR) {
243     mutt_sleep (2);
244     return PQ_ERR;
245   }
246
247 #if (defined(USE_SSL) || defined(USE_GNUTLS))
248   /* Attempt STLS if available and desired. */
249   if (!pop_data->conn->ssf && (pop_data->cmd_stls || option(OPTSSLFORCETLS))) {
250     if (option (OPTSSLFORCETLS))
251       pop_data->use_stls = 2;
252     if (pop_data->use_stls == 0) {
253       ret = query_quadoption (OPT_SSLSTARTTLS,
254                               _("Secure connection with TLS?"));
255       if (ret == -1)
256         return PQ_ERR;
257       pop_data->use_stls = 1;
258       if (ret == M_YES)
259         pop_data->use_stls = 2;
260     }
261     if (pop_data->use_stls == 2) {
262       m_strcpy(buf, sizeof(buf), "STLS\r\n");
263       ret = pop_query (pop_data, buf, sizeof (buf));
264       if (ret == PQ_NOT_CONNECTED)
265         goto err_conn;
266       if (ret != PQ_OK) {
267         mutt_error ("%s", pop_data->err_msg);
268         mutt_sleep (2);
269       }
270 #if defined (USE_SSL) || defined (USE_GNUTLS)
271       else if (mutt_ssl_starttls (pop_data->conn))
272 #endif
273       {
274         mutt_error (_("Could not negotiate TLS connection"));
275         mutt_sleep (2);
276         return PQ_ERR;
277       }
278       else {
279         /* recheck capabilities after STLS completes */
280         ret = pop_capabilities (pop_data, 1);
281         if (ret == PQ_NOT_CONNECTED)
282           goto err_conn;
283         if (ret == PQ_ERR) {
284           mutt_sleep (2);
285           return PQ_ERR;
286         }
287       }
288     }
289   }
290
291   if (option(OPTSSLFORCETLS) && !pop_data->conn->ssf) {
292     mutt_error _("Encrypted connection unavailable");
293     mutt_sleep (1);
294     return -2;
295   }
296 #endif
297
298   ret = pop_authenticate (pop_data);
299   if (ret == PQ_NOT_CONNECTED)
300     goto err_conn;
301   if (ret == PFD_FUNCT_ERROR)
302     mutt_clear_error ();
303   if (ret != PQ_OK)
304     return ret;
305
306   /* recheck capabilities after authentication */
307   ret = pop_capabilities (pop_data, 2);
308   if (ret == PQ_NOT_CONNECTED)
309     goto err_conn;
310   if (ret == PQ_ERR) {
311     mutt_sleep (2);
312     return PQ_ERR;
313   }
314
315   /* get total size of mailbox */
316   m_strcpy(buf, sizeof(buf), "STAT\r\n");
317   ret = pop_query (pop_data, buf, sizeof (buf));
318   if (ret == PQ_NOT_CONNECTED)
319     goto err_conn;
320   if (ret == PQ_ERR) {
321     mutt_error ("%s", pop_data->err_msg);
322     mutt_sleep (2);
323     return ret;
324   }
325
326   sscanf (buf, "+OK %u %u", &n, &size);
327   pop_data->size = size;
328   return PQ_OK;
329
330 err_conn:
331   pop_data->status = POP_DISCONNECTED;
332   mutt_error _("Server closed connection!");
333
334   mutt_sleep (2);
335   return PQ_NOT_CONNECTED;
336 }
337
338 /* logout from POP server */
339 void pop_logout (CONTEXT * ctx)
340 {
341   pop_query_status ret = 0;
342   char buf[LONG_STRING];
343   POP_DATA *pop_data = (POP_DATA *) ctx->data;
344
345   if (pop_data->status == POP_CONNECTED) {
346     mutt_message _("Closing connection to POP server...");
347
348     if (ctx->readonly) {
349       m_strcpy(buf, sizeof(buf), "RSET\r\n");
350       ret = pop_query (pop_data, buf, sizeof (buf));
351     }
352
353     if (ret != PQ_NOT_CONNECTED) {
354       m_strcpy(buf, sizeof(buf), "QUIT\r\n");
355       pop_query (pop_data, buf, sizeof (buf));
356     }
357
358     mutt_clear_error ();
359   }
360
361   pop_data->status = POP_DISCONNECTED;
362   return;
363 }
364
365 /*
366  * Send data from buffer and receive answer to the same buffer
367  *  0 - successful,
368  * -1 - conection lost,
369  * -2 - invalid command or execution error.
370 */
371 pop_query_status pop_query_d (POP_DATA * pop_data, char *buf, size_t buflen, const char *msg)
372 {
373   char *c;
374
375   if (pop_data->status != POP_CONNECTED)
376     return PQ_NOT_CONNECTED;
377
378   mutt_socket_write(pop_data->conn, buf);
379
380   c = strpbrk (buf, " \r\n");
381   *c = '\0';
382   snprintf (pop_data->err_msg, sizeof (pop_data->err_msg), "%s: ", buf);
383
384   if (mutt_socket_readln (buf, buflen, pop_data->conn) < 0) {
385     pop_data->status = POP_DISCONNECTED;
386     return PQ_NOT_CONNECTED;
387   }
388   if (!m_strncmp(buf, "+OK", 3))
389     return PQ_OK;
390
391   pop_error (pop_data, buf);
392   return PQ_ERR;
393 }
394
395 /*
396  * This function calls  funct(*line, *data)  for each received line,
397  * funct(NULL, *data)  if  rewind(*data)  needs, exits when fail or done.
398  * Returned codes:
399  *  0 - successful,
400  * -1 - conection lost,
401  * -2 - invalid command or execution error,
402  * -3 - error in funct(*line, *data)
403  */
404 pop_query_status pop_fetch_data (POP_DATA * pop_data, const char *query, progress_t* bar,
405                     int (*funct) (char *, void *), void *data)
406 {
407   char buf[LONG_STRING];
408   char *inbuf;
409   char *p;
410   pop_query_status ret;
411   int chunk = 0;
412   long pos = 0;
413   size_t lenbuf = 0;
414
415   m_strcpy(buf, sizeof(buf), query);
416   ret = pop_query (pop_data, buf, sizeof (buf));
417   if (ret != PQ_OK)
418     return ret;
419
420   inbuf = p_new(char, sizeof(buf));
421
422   for (;;) {
423     chunk =
424       mutt_socket_readln(buf, sizeof (buf), pop_data->conn);
425     if (chunk < 0) {
426       pop_data->status = POP_DISCONNECTED;
427       ret = PQ_NOT_CONNECTED;
428       break;
429     }
430
431     p = buf;
432     if (!lenbuf && buf[0] == '.') {
433       if (buf[1] != '.')
434         break;
435       p++;
436     }
437
438     m_strcpy(inbuf + lenbuf,sizeof(buf), p);
439     pos += chunk;
440
441     if (chunk >= ssizeof(buf)) {
442       lenbuf += strlen (p);
443     } else {
444       if (bar)
445         mutt_progress_bar (bar, pos);
446       if (ret == 0 && funct (inbuf, data) < 0)
447         ret = PFD_FUNCT_ERROR;
448       lenbuf = 0;
449     }
450
451     p_realloc(&inbuf, lenbuf + sizeof(buf));
452   }
453
454   p_delete(&inbuf);
455   return ret;
456 }
457
458 /* find message with this UIDL and set refno */
459 static int check_uidl (char *line, void *data)
460 {
461   int i, idx;
462   CONTEXT *ctx = (CONTEXT *)data;
463
464   sscanf (line, "%u %s", &idx, line);
465   for (i = 0; i < ctx->msgcount; i++) {
466     if (!m_strcmp(ctx->hdrs[i]->data, line)) {
467       ctx->hdrs[i]->refno = idx;
468       break;
469     }
470   }
471
472   return 0;
473 }
474
475 /* reconnect and verify indexes if connection was lost */
476 pop_query_status pop_reconnect (CONTEXT * ctx)
477 {
478   pop_query_status ret;
479   POP_DATA *pop_data = (POP_DATA *) ctx->data;
480   progress_t bar;
481
482   if (pop_data->status == POP_CONNECTED)
483     return PQ_OK;
484   if (pop_data->status == POP_BYE)
485     return PQ_NOT_CONNECTED;
486
487   for (;;) {
488     mutt_socket_close (pop_data->conn);
489
490     ret = pop_open_connection (pop_data);
491     if (ret == PQ_OK) {
492       int i;
493
494       bar.msg = _("Verifying message indexes...");
495       bar.size = 0;
496       mutt_progress_bar (&bar, 0);
497
498       for (i = 0; i < ctx->msgcount; i++)
499         ctx->hdrs[i]->refno = -1;
500
501       ret = pop_fetch_data(pop_data, "UIDL\r\n", &bar, check_uidl, ctx);
502       if (ret == PQ_ERR) {
503         mutt_error ("%s", pop_data->err_msg);
504         mutt_sleep (2);
505       }
506     }
507     if (ret == PQ_OK)
508       return PQ_OK;
509
510     pop_logout (ctx);
511
512     if (ret == PQ_ERR)
513       return PQ_NOT_CONNECTED;
514
515     if (query_quadoption (OPT_POPRECONNECT,
516                           _("Connection lost. Reconnect to POP server?")) !=
517         M_YES)
518       return PQ_NOT_CONNECTED;
519   }
520 }