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_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       else if (mutt_ssl_starttls (pop_data->conn))
299       {
300         mutt_error (_("Could not negotiate TLS connection"));
301         mutt_sleep (2);
302         return -2;
303       }
304       else
305       {
306         /* recheck capabilities after STLS completes */
307         ret = pop_capabilities (pop_data, 1);
308         if (ret == -1)
309           goto err_conn;
310         if (ret == -2)
311         {
312           mutt_sleep (2);
313           return -2;
314         }
315       }
316     }
317   }
318 #endif
319
320   ret = pop_authenticate (pop_data);
321   if (ret == -1)
322     goto err_conn;
323   if (ret == -3)
324     mutt_clear_error ();
325   if (ret != 0)
326     return ret;
327
328   /* recheck capabilities after authentication */
329   ret = pop_capabilities (pop_data, 2);
330   if (ret == -1)
331     goto err_conn;
332   if (ret == -2)
333   {
334     mutt_sleep (2);
335     return -2;
336   }
337
338   /* get total size of mailbox */
339   strfcpy (buf, "STAT\r\n", sizeof (buf));
340   ret = pop_query (pop_data, buf, sizeof (buf));
341   if (ret == -1)
342     goto err_conn;
343   if (ret == -2)
344   {
345     mutt_error ("%s", pop_data->err_msg);
346     mutt_sleep (2);
347     return ret;
348   }
349
350   sscanf (buf, "+OK %u %u", &n, &size);
351   pop_data->size = size;
352   return 0;
353
354 err_conn:
355   pop_data->status = POP_DISCONNECTED;
356   mutt_error _("Server closed connection!");
357   mutt_sleep (2);
358   return -1;
359 }
360
361 /* logout from POP server */
362 void pop_logout (CONTEXT *ctx)
363 {
364   int ret = 0;
365   char buf[LONG_STRING];
366   POP_DATA *pop_data = (POP_DATA *)ctx->data;
367
368   if (pop_data->status == POP_CONNECTED)
369   {
370     mutt_message _("Closing connection to POP server...");
371
372     if (ctx->readonly)
373     {
374       strfcpy (buf, "RSET\r\n", sizeof (buf));
375       ret = pop_query (pop_data, buf, sizeof (buf));
376     }
377
378     if (ret != -1)
379     {
380       strfcpy (buf, "QUIT\r\n", sizeof (buf));
381       pop_query (pop_data, buf, sizeof (buf));
382     }
383
384     mutt_clear_error ();
385   }
386
387   pop_data->status = POP_DISCONNECTED;
388   return;
389 }
390
391 /*
392  * Send data from buffer and receive answer to the same buffer
393  *  0 - successful,
394  * -1 - conection lost,
395  * -2 - invalid command or execution error.
396 */
397 int pop_query_d (POP_DATA *pop_data, char *buf, size_t buflen, char *msg)
398 {
399   int dbg = M_SOCK_LOG_CMD;
400   char *c;
401
402   if (pop_data->status != POP_CONNECTED)
403     return -1;
404
405 #ifdef DEBUG
406     /* print msg instaed of real command */
407     if (msg)
408     {
409       dbg = M_SOCK_LOG_FULL;
410       dprint (M_SOCK_LOG_CMD, (debugfile, "> %s", msg));
411     }
412 #endif
413
414   mutt_socket_write_d (pop_data->conn, buf, dbg);
415
416   c = strpbrk (buf, " \r\n");
417   *c = '\0';
418   snprintf (pop_data->err_msg, sizeof (pop_data->err_msg), "%s: ", buf);
419
420   if (mutt_socket_readln (buf, buflen, pop_data->conn) < 0)
421   {
422     pop_data->status = POP_DISCONNECTED;
423     return -1;
424   }
425   if (!mutt_strncmp (buf, "+OK", 3))
426     return 0;
427
428   pop_error (pop_data, buf);
429   return -2;
430 }
431
432 /*
433  * This function calls  funct(*line, *data)  for each received line,
434  * funct(NULL, *data)  if  rewind(*data)  needs, exits when fail or done.
435  * Returned codes:
436  *  0 - successful,
437  * -1 - conection lost,
438  * -2 - invalid command or execution error,
439  * -3 - error in funct(*line, *data)
440  */
441 int pop_fetch_data (POP_DATA *pop_data, char *query, char *msg,
442                     int (*funct) (char *, void *), void *data)
443 {
444   char buf[LONG_STRING];
445   char *inbuf;
446   char *p;
447   int ret, chunk, line = 0;
448   size_t lenbuf = 0;
449
450   strfcpy (buf, query, sizeof (buf));
451   ret = pop_query (pop_data, buf, sizeof (buf));
452   if (ret < 0)
453     return ret;
454
455   inbuf = safe_malloc (sizeof (buf));
456
457   FOREVER
458   {
459     chunk = mutt_socket_readln_d (buf, sizeof (buf), pop_data->conn, M_SOCK_LOG_HDR);
460     if (chunk < 0)
461     {
462       pop_data->status = POP_DISCONNECTED;
463       ret = -1;
464       break;
465     }
466
467     p = buf;
468     if (!lenbuf && buf[0] == '.')
469     {
470       if (buf[1] != '.')
471         break;
472       p++;
473     }
474
475     strfcpy (inbuf + lenbuf, p, sizeof (buf));
476
477     if (chunk >= sizeof (buf))
478     {
479       lenbuf += strlen (p);
480     }
481     else
482     {
483       line++;
484       if (msg && ReadInc && (line % ReadInc == 0))
485         mutt_message ("%s %d", msg, line);
486       if (ret == 0 && funct (inbuf, data) < 0)
487         ret = -3;
488       lenbuf = 0;
489     }
490
491     safe_realloc (&inbuf, lenbuf + sizeof (buf));
492   }
493
494   FREE (&inbuf);
495   return ret;
496 }
497
498 /* find message with this UIDL and set refno */
499 static int check_uidl (char *line, void *data)
500 {
501   int i;
502   unsigned int index;
503   CONTEXT *ctx = (CONTEXT *)data;
504
505   sscanf (line, "%u %s", &index, line);
506   for (i = 0; i < ctx->msgcount; i++)
507   {
508     if (!mutt_strcmp (ctx->hdrs[i]->data, line))
509     {
510       ctx->hdrs[i]->refno = index;
511       break;
512     }
513   }
514
515   return 0;
516 }
517
518 /* reconnect and verify idnexes if connection was lost */
519 int pop_reconnect (CONTEXT *ctx)
520 {
521   int ret;
522   POP_DATA *pop_data = (POP_DATA *)ctx->data;
523
524   if (pop_data->status == POP_CONNECTED)
525     return 0;
526   if (pop_data->status == POP_BYE)
527     return -1;
528
529   FOREVER
530   {
531     mutt_socket_close (pop_data->conn);
532
533     ret = pop_open_connection (pop_data);
534     if (ret == 0)
535     {
536       char *msg = _("Verifying message indexes...");
537       int i;
538
539       for (i = 0; i < ctx->msgcount; i++)
540         ctx->hdrs[i]->refno = -1;
541
542       mutt_message (msg);
543
544       ret = pop_fetch_data (pop_data, "UIDL\r\n", msg, check_uidl, ctx);
545       if (ret == -2)
546       {
547         mutt_error ("%s", pop_data->err_msg);
548         mutt_sleep (2);
549       }
550     }
551     if (ret == 0)
552       return 0;
553
554     pop_logout (ctx);
555
556     if (ret < -1)
557       return -1;
558
559     if (query_quadoption (OPT_POPRECONNECT,
560                 _("Connection lost. Reconnect to POP server?")) != M_YES)
561       return -1;
562   }
563 }