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