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