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