a40e2f84888695363de8d5e7f951597d66e107ab
[apps/madmutt.git] / pop / pop.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 2000-2002 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
12 #include <lib-ui/curses.h>
13 #include <lib-mx/mx.h>
14
15 #include "mutt.h"
16 #include "pop.h"
17 #include <lib-crypt/crypt.h>
18
19 /* write line to file */
20 static int fetch_message (char *line, void *file)
21 {
22   FILE *f = (FILE *) file;
23
24   fputs (line, f);
25   if (fputc ('\n', f) == EOF)
26     return -1;
27
28   return 0;
29 }
30
31 /*
32  * Read header
33  * returns:
34  *  0 on success
35  * -1 - conection lost,
36  * -2 - invalid command or execution error,
37  * -3 - error writing to tempfile
38  */
39 static pop_query_status pop_read_header (POP_DATA * pop_data, HEADER * h)
40 {
41   FILE *f;
42   int idx;
43   pop_query_status ret;
44   long length;
45   char buf[LONG_STRING];
46   char tempfile[_POSIX_PATH_MAX];
47
48   f = m_tempfile(tempfile, sizeof(tempfile), NONULL(Tempdir), NULL);
49   if (!f) {
50     mutt_error(_("Could not create temporary file"));
51     return PFD_FUNCT_ERROR;
52   }
53
54   snprintf (buf, sizeof (buf), "string_list_t %d\r\n", h->refno);
55   ret = pop_query (pop_data, buf, sizeof (buf));
56   if (ret == PQ_OK) {
57     sscanf (buf, "+OK %d %ld", &idx, &length);
58
59     snprintf (buf, sizeof (buf), "TOP %d 0\r\n", h->refno);
60     ret = pop_fetch_data (pop_data, buf, NULL, fetch_message, f);
61
62     if (pop_data->cmd_top == CMD_UNKNOWN) {
63       if (ret == PQ_OK) {
64         pop_data->cmd_top = CMD_AVAILABLE;
65       }
66
67       if (ret == PQ_ERR) {
68         pop_data->cmd_top = CMD_NOT_AVAILABLE;
69         snprintf (pop_data->err_msg, sizeof (pop_data->err_msg),
70                   _("Command TOP is not supported by server."));
71       }
72     }
73   }
74
75   switch (ret) {
76   case PQ_OK:
77     {
78       rewind (f);
79       h->env = mutt_read_rfc822_header (f, h, 0, 0);
80       h->content->length = length - h->content->offset + 1;
81       rewind (f);
82       while (!feof (f)) {
83         h->content->length--;
84         fgets (buf, sizeof (buf), f);
85       }
86       break;
87     }
88   case PQ_ERR:
89     {
90       mutt_error ("%s", pop_data->err_msg);
91       break;
92     }
93   case PFD_FUNCT_ERROR:
94     {
95       mutt_error _("Can't write header to temporary file!");
96
97       break;
98     }
99   case PQ_NOT_CONNECTED:
100     {
101       mutt_error _("Can't fetch header: Not connected!");
102       break;
103     }
104   }
105
106   m_fclose(&f);
107   unlink (tempfile);
108   return ret;
109 }
110
111 /* parse UIDL */
112 static int fetch_uidl (char *line, void *data)
113 {
114   int i, idx;
115   CONTEXT *ctx = (CONTEXT *) data;
116   POP_DATA *pop_data = (POP_DATA *) ctx->data;
117
118   sscanf (line, "%d %s", &idx, line);
119   for (i = 0; i < ctx->msgcount; i++)
120     if (!m_strcmp(line, ctx->hdrs[i]->data))
121       break;
122
123   if (i == ctx->msgcount) {
124     if (i >= ctx->hdrmax)
125       mx_alloc_memory (ctx);
126
127     ctx->msgcount++;
128     ctx->hdrs[i] = header_new();
129     ctx->hdrs[i]->data = m_strdup(line);
130   }
131   else if (ctx->hdrs[i]->index != idx - 1)
132     pop_data->clear_cache = 1;
133
134   ctx->hdrs[i]->refno = idx;
135   ctx->hdrs[i]->index = idx - 1;
136
137   return 0;
138 }
139
140 /*
141  * Read headers
142  * returns:
143  *  0 on success
144  * -1 - conection lost,
145  * -2 - invalid command or execution error,
146  * -3 - error writing to tempfile
147  */
148 static int pop_fetch_headers (CONTEXT * ctx)
149 {
150   int i, old_count, new_count;
151   pop_query_status ret;
152   POP_DATA *pop_data = (POP_DATA *) ctx->data;
153
154   time (&pop_data->check_time);
155   pop_data->clear_cache = 0;
156
157   for (i = 0; i < ctx->msgcount; i++)
158     ctx->hdrs[i]->refno = -1;
159
160   old_count = ctx->msgcount;
161   ret = pop_fetch_data (pop_data, "UIDL\r\n", NULL, fetch_uidl, ctx);
162   new_count = ctx->msgcount;
163   ctx->msgcount = old_count;
164
165   if (pop_data->cmd_uidl == CMD_UNKNOWN) {
166     if (ret == PQ_OK) {
167       pop_data->cmd_uidl = CMD_AVAILABLE;
168     }
169
170     if (ret == PQ_ERR && pop_data->cmd_uidl == CMD_UNKNOWN) {
171       pop_data->cmd_uidl = CMD_NOT_AVAILABLE;
172
173       snprintf (pop_data->err_msg, sizeof (pop_data->err_msg),
174                 _("Command UIDL is not supported by server."));
175     }
176   }
177
178   if (ret == PQ_OK) {
179     for (i = 0; i < old_count; i++)
180       if (ctx->hdrs[i]->refno == -1)
181         ctx->hdrs[i]->deleted = 1;
182
183     for (i = old_count; i < new_count; i++) {
184       mutt_message (_("Fetching message headers... [%d/%d]"),
185                     i + 1 - old_count, new_count - old_count);
186
187       ret = pop_read_header (pop_data, ctx->hdrs[i]);
188       if (ret != PQ_OK)
189         break;
190
191       ctx->msgcount++;
192     }
193
194     if (i > old_count)
195       mx_update_context (ctx, i - old_count);
196   }
197
198   if (ret != PQ_OK) {
199     for (i = ctx->msgcount; i < new_count; i++)
200       header_delete(&ctx->hdrs[i]);
201     return ret;
202   }
203
204   mutt_clear_error ();
205   return (new_count - old_count);
206 }
207
208 /* open POP mailbox - fetch only headers */
209 static int pop_open_mailbox (CONTEXT * ctx)
210 {
211   int ret;
212   char buf[LONG_STRING];
213   CONNECTION *conn;
214   ACCOUNT act;
215   POP_DATA *pop_data;
216   ciss_url_t url;
217
218   if (pop_parse_path (ctx->path, &act)) {
219     mutt_error (_("%s is an invalid POP path"), ctx->path);
220     mutt_sleep (2);
221     return -1;
222   }
223
224   mutt_account_tourl (&act, &url);
225   url.path = NULL;
226   url_ciss_tostring (&url, buf, sizeof (buf), 0);
227   conn = mutt_conn_find (NULL, &act);
228   if (!conn)
229     return -1;
230
231   p_delete(&ctx->path);
232   ctx->path = m_strdup(buf);
233
234   pop_data = p_new(POP_DATA, 1);
235   pop_data->conn = conn;
236   ctx->data = pop_data;
237
238   if (pop_open_connection (pop_data) < 0)
239     return -1;
240
241   conn->data = pop_data;
242
243   for (;;) {
244     if (pop_reconnect (ctx) != PQ_OK)
245       return -1;
246
247     ctx->size = pop_data->size;
248
249     mutt_message _("Fetching list of messages...");
250
251     ret = pop_fetch_headers (ctx);
252
253     if (ret >= 0)
254       return 0;
255
256     if (ret < -1) {
257       mutt_sleep (2);
258       return -1;
259     }
260   }
261 }
262
263 /* delete all cached messages */
264 static void pop_clear_cache (POP_DATA * pop_data)
265 {
266   int i;
267
268   if (!pop_data->clear_cache)
269     return;
270
271   for (i = 0; i < POP_CACHE_LEN; i++) {
272     if (pop_data->cache[i].path) {
273       unlink (pop_data->cache[i].path);
274       p_delete(&pop_data->cache[i].path);
275     }
276   }
277 }
278
279 /* close POP mailbox */
280 static void pop_close_mailbox (CONTEXT * ctx)
281 {
282   POP_DATA *pop_data = (POP_DATA *) ctx->data;
283
284   if (!pop_data)
285     return;
286
287   pop_logout (ctx);
288
289   if (pop_data->status != POP_NONE)
290     mutt_socket_close (pop_data->conn);
291
292   pop_data->status = POP_NONE;
293
294   pop_data->clear_cache = 1;
295   pop_clear_cache (pop_data);
296
297   if (!pop_data->conn->data)
298     mutt_socket_free (pop_data->conn);
299
300   return;
301 }
302
303 /* fetch message from POP server */
304 int pop_fetch_message (MESSAGE * msg, CONTEXT * ctx, int msgno)
305 {
306   int ret;
307   void *uidl;
308   char buf[LONG_STRING];
309   char path[_POSIX_PATH_MAX];
310   progress_t bar;
311   POP_DATA *pop_data = (POP_DATA *) ctx->data;
312   POP_CACHE *cache;
313   HEADER *h = ctx->hdrs[msgno];
314
315   /* see if we already have the message in our cache */
316   cache = &pop_data->cache[h->index % POP_CACHE_LEN];
317
318   if (cache->path) {
319     if (cache->index == h->index) {
320       /* yes, so just return a pointer to the message */
321       msg->fp = fopen (cache->path, "r");
322       if (msg->fp)
323         return 0;
324
325       mutt_perror (cache->path);
326       mutt_sleep (2);
327       return -1;
328     }
329     else {
330       /* clear the previous entry */
331       unlink (cache->path);
332       p_delete(&cache->path);
333     }
334   }
335
336   for (;;) {
337     if (pop_reconnect (ctx) != PQ_OK)
338       return -1;
339
340     /* verify that massage index is correct */
341     if (h->refno < 0) {
342       mutt_error
343         _("The message index is incorrect. Try reopening the mailbox.");
344       mutt_sleep (2);
345       return -1;
346     }
347
348     bar.size = h->content->length + h->content->offset - 1;
349     bar.msg = _("Fetching message...");
350     mutt_progress_bar (&bar, 0);
351
352     msg->fp = m_tempfile(path, sizeof(path), NONULL(Tempdir), NULL);
353     if (!msg->fp) {
354       mutt_error(_("Could not create temporary file"));
355       mutt_sleep(2);
356       return -1;
357     }
358
359     snprintf (buf, sizeof (buf), "RETR %d\r\n", h->refno);
360
361     ret = pop_fetch_data (pop_data, buf, &bar, fetch_message, msg->fp);
362     if (ret == PQ_OK)
363       break;
364
365     m_fclose(&msg->fp);
366     unlink (path);
367
368     if (ret == PQ_ERR) {
369       mutt_error ("%s", pop_data->err_msg);
370       mutt_sleep (2);
371       return -1;
372     }
373
374     if (ret == PFD_FUNCT_ERROR) {
375       mutt_error _("Can't write message to temporary file!");
376
377       mutt_sleep (2);
378       return -1;
379     }
380   }
381
382   /* Update the header information.  Previously, we only downloaded a
383    * portion of the headers, those required for the main display.
384    */
385   cache->index = h->index;
386   cache->path = m_strdup(path);
387   rewind (msg->fp);
388   uidl = h->data;
389   envelope_delete(&h->env);
390   h->env = mutt_read_rfc822_header (msg->fp, h, 0, 0);
391   h->data = uidl;
392   h->lines = 0;
393   fgets (buf, sizeof (buf), msg->fp);
394   while (!feof (msg->fp)) {
395     ctx->hdrs[msgno]->lines++;
396     fgets (buf, sizeof (buf), msg->fp);
397   }
398
399   h->content->length = ftello (msg->fp) - h->content->offset;
400
401   /* This needs to be done in case this is a multipart message */
402   h->security = crypt_query (h->content);
403
404   mutt_clear_error ();
405   rewind (msg->fp);
406
407   return 0;
408 }
409
410 /* update POP mailbox - delete messages from server */
411 static pop_query_status
412 pop_sync_mailbox (CONTEXT * ctx, int unused __attribute__ ((unused)),
413                   int *index_hint __attribute__ ((unused)))
414 {
415   int i;
416   pop_query_status ret;
417   char buf[LONG_STRING];
418   POP_DATA *pop_data = (POP_DATA *) ctx->data;
419
420   pop_data->check_time = 0;
421
422   for (;;) {
423     if (pop_reconnect (ctx) != PQ_OK)
424       return PQ_NOT_CONNECTED;
425
426     mutt_message (_("Marking %d messages deleted..."), ctx->deleted);
427
428     for (i = 0, ret = 0; ret == 0 && i < ctx->msgcount; i++) {
429       if (ctx->hdrs[i]->deleted) {
430         snprintf (buf, sizeof (buf), "DELE %d\r\n", ctx->hdrs[i]->refno);
431         ret = pop_query (pop_data, buf, sizeof (buf));
432       }
433     }
434
435     if (ret == PQ_OK) {
436       m_strcpy(buf, sizeof(buf), "QUIT\r\n");
437       ret = pop_query (pop_data, buf, sizeof (buf));
438     }
439
440     if (ret == PQ_OK) {
441       pop_data->clear_cache = 1;
442       pop_clear_cache (pop_data);
443       pop_data->status = POP_DISCONNECTED;
444       return PQ_OK;
445     }
446
447     if (ret == PQ_ERR) {
448       mutt_error ("%s", pop_data->err_msg);
449       mutt_sleep (2);
450       return PQ_NOT_CONNECTED;
451     }
452   }
453 }
454
455 /* Check for new messages and fetch headers */
456 static int pop_check_mailbox (CONTEXT * ctx,
457                               int *index_hint __attribute__ ((unused)),
458                               int unused __attribute__ ((unused)))
459 {
460   int ret;
461   POP_DATA *pop_data = (POP_DATA *) ctx->data;
462
463   if ((pop_data->check_time + PopCheckTimeout) > time (NULL))
464     return 0;
465
466   pop_logout (ctx);
467
468   mutt_socket_close (pop_data->conn);
469
470   if (pop_open_connection (pop_data) < 0)
471     return -1;
472
473   ctx->size = pop_data->size;
474
475   mutt_message _("Checking for new messages...");
476
477   ret = pop_fetch_headers (ctx);
478   pop_clear_cache (pop_data);
479
480   if (ret < 0)
481     return -1;
482
483   if (ret > 0)
484     return M_NEW_MAIL;
485
486   return 0;
487 }
488
489 /* Fetch messages and save them in $spoolfile */
490 void pop_fetch_mail (void)
491 {
492   char buffer[LONG_STRING];
493   char msgbuf[SHORT_STRING];
494   char *url, *p;
495   int i, delanswer, last = 0, msgs, bytes, rset = 0;
496   pop_query_status ret;
497   CONNECTION *conn;
498   CONTEXT ctx;
499   MESSAGE *msg = NULL;
500   ACCOUNT act;
501   POP_DATA *pop_data;
502   ssize_t plen;
503
504   if (!PopHost) {
505     mutt_error _("POP host is not defined.");
506
507     return;
508   }
509
510   plen = m_strlen(PopHost) + 7;
511   url = p = p_new(char, plen);
512   if (url_check_scheme (PopHost) == U_UNKNOWN) {
513     plen -= m_strcpy(url, plen, "pop://");
514     p += plen;
515   }
516   m_strcpy(p, plen, PopHost);
517
518   ret = pop_parse_path (url, &act);
519   p_delete(&url);
520   if (ret) {
521     mutt_error (_("%s is an invalid POP path"), PopHost);
522     return;
523   }
524
525   conn = mutt_conn_find (NULL, &act);
526   if (!conn)
527     return;
528
529   pop_data = p_new(POP_DATA, 1);
530   pop_data->conn = conn;
531
532   if (pop_open_connection (pop_data) < 0) {
533     mutt_socket_free (pop_data->conn);
534     p_delete(&pop_data);
535     return;
536   }
537
538   conn->data = pop_data;
539
540   mutt_message _("Checking for new messages...");
541
542   /* find out how many messages are in the mailbox. */
543   m_strcpy(buffer, sizeof(buffer), "STAT\r\n");
544   ret = pop_query (pop_data, buffer, sizeof (buffer));
545   if (ret == PQ_NOT_CONNECTED)
546     goto fail;
547   if (ret == PQ_ERR) {
548     mutt_error ("%s", pop_data->err_msg);
549     goto finish;
550   }
551
552   sscanf (buffer, "+OK %d %d", &msgs, &bytes);
553
554   /* only get unread messages */
555   if (msgs > 0 && option (OPTPOPLAST)) {
556     m_strcpy(buffer, sizeof(buffer), "LAST\r\n");
557     ret = pop_query (pop_data, buffer, sizeof (buffer));
558     if (ret == PQ_NOT_CONNECTED)
559       goto fail;
560     if (ret == PQ_OK)
561       sscanf (buffer, "+OK %d", &last);
562   }
563
564   if (msgs <= last) {
565     mutt_message _("No new mail in POP mailbox.");
566
567     goto finish;
568   }
569
570   if (mx_open_mailbox (NONULL (Spoolfile), M_APPEND, &ctx) == NULL)
571     goto finish;
572
573   delanswer =
574     query_quadoption (OPT_POPDELETE, _("Delete messages from server?"));
575
576   snprintf (msgbuf, sizeof (msgbuf), _("Reading new messages (%d bytes)..."),
577             bytes);
578   mutt_message ("%s", msgbuf);
579
580   for (i = last + 1; i <= msgs; i++) {
581     if ((msg = mx_open_new_message (&ctx, NULL, M_ADD_FROM)) == NULL)
582       ret = -3;
583     else {
584       snprintf (buffer, sizeof (buffer), "RETR %d\r\n", i);
585       ret = pop_fetch_data (pop_data, buffer, NULL, fetch_message, msg->fp);
586       if (ret == PFD_FUNCT_ERROR)
587         rset = 1;
588
589       if (ret == PQ_OK && mx_commit_message (msg, &ctx) != 0) {
590         rset = 1;
591         ret = PFD_FUNCT_ERROR;
592       }
593
594       mx_close_message (&msg);
595     }
596
597     if (ret == PQ_OK && delanswer == M_YES) {
598       /* delete the message on the server */
599       snprintf (buffer, sizeof (buffer), "DELE %d\r\n", i);
600       ret = pop_query (pop_data, buffer, sizeof (buffer));
601     }
602
603     if (ret == PQ_NOT_CONNECTED) {
604       mx_close_mailbox (&ctx, NULL);
605       goto fail;
606     }
607     if (ret == PQ_ERR) {
608       mutt_error ("%s", pop_data->err_msg);
609       break;
610     }
611     if (ret == -3) { /* this is -3 when ret != 0, because it will keep the value from before *gna* */
612       mutt_error _("Error while writing mailbox!");
613
614       break;
615     }
616
617     mutt_message (_("%s [%d of %d messages read]"), msgbuf, i - last,
618                   msgs - last);
619   }
620
621   mx_close_mailbox (&ctx, NULL);
622
623   if (rset) {
624     /* make sure no messages get deleted */
625     m_strcpy(buffer, sizeof(buffer), "RSET\r\n");
626     if (pop_query (pop_data, buffer, sizeof (buffer)) == PQ_NOT_CONNECTED)
627       goto fail;
628   }
629
630 finish:
631   /* exit gracefully */
632   m_strcpy(buffer, sizeof(buffer), "QUIT\r\n");
633   if (pop_query (pop_data, buffer, sizeof (buffer)) == PQ_NOT_CONNECTED)
634     goto fail;
635   mutt_socket_close (conn);
636   p_delete(&pop_data);
637   return;
638
639 fail:
640   mutt_error _("Server closed connection!");
641   mutt_socket_close (conn);
642   p_delete(&pop_data);
643 }
644
645 static int pop_is_magic (const char* path, struct stat* st __attribute__ ((unused))) {
646   url_scheme_t s = url_check_scheme (NONULL (path));
647   return ((s == U_POP || s == U_POPS) ? M_POP : -1);
648 }
649
650 static int acl_check_pop (CONTEXT* ctx __attribute__ ((unused)), int bit) {
651   switch (bit) {
652     case ACL_INSERT:    /* editing messages */
653     case ACL_WRITE:     /* change importance */
654       return (0);
655     case ACL_DELETE:    /* (un)deletion */
656     case ACL_SEEN:      /* mark as read */
657       return (1);
658     default:
659       return (0);
660   }
661 }
662
663 mx_t const pop_mx = {
664     M_POP,
665     0,
666     pop_is_magic,
667     NULL,
668     NULL,
669     pop_open_mailbox,
670     NULL,
671     acl_check_pop,
672     pop_check_mailbox,
673     pop_close_mailbox,
674     pop_sync_mailbox,
675     NULL,
676 };