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