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