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