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