a6e1fd8970a0bc288d288b99ff11bf836495fb14
[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
14 #include "mutt.h"
15 #include "mx.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   mutt_mktemp (tempfile);
49   if (!(f = safe_fopen (tempfile, "w+"))) {
50     mutt_perror (tempfile);
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   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     mutt_mktemp (path);
353     msg->fp = safe_fopen (path, "w+");
354     if (!msg->fp) {
355       mutt_perror (path);
356       mutt_sleep (2);
357       return -1;
358     }
359
360     snprintf (buf, sizeof (buf), "RETR %d\r\n", h->refno);
361
362     ret = pop_fetch_data (pop_data, buf, &bar, fetch_message, msg->fp);
363     if (ret == PQ_OK)
364       break;
365
366     safe_fclose (&msg->fp);
367     unlink (path);
368
369     if (ret == PQ_ERR) {
370       mutt_error ("%s", pop_data->err_msg);
371       mutt_sleep (2);
372       return -1;
373     }
374
375     if (ret == PFD_FUNCT_ERROR) {
376       mutt_error _("Can't write message to temporary file!");
377
378       mutt_sleep (2);
379       return -1;
380     }
381   }
382
383   /* Update the header information.  Previously, we only downloaded a
384    * portion of the headers, those required for the main display.
385    */
386   cache->index = h->index;
387   cache->path = m_strdup(path);
388   rewind (msg->fp);
389   uidl = h->data;
390   envelope_delete(&h->env);
391   h->env = mutt_read_rfc822_header (msg->fp, h, 0, 0);
392   h->data = uidl;
393   h->lines = 0;
394   fgets (buf, sizeof (buf), msg->fp);
395   while (!feof (msg->fp)) {
396     ctx->hdrs[msgno]->lines++;
397     fgets (buf, sizeof (buf), msg->fp);
398   }
399
400   h->content->length = ftello (msg->fp) - h->content->offset;
401
402   /* This needs to be done in case this is a multipart message */
403   h->security = crypt_query (h->content);
404
405   mutt_clear_error ();
406   rewind (msg->fp);
407
408   return 0;
409 }
410
411 /* update POP mailbox - delete messages from server */
412 static pop_query_status
413 pop_sync_mailbox (CONTEXT * ctx, int unused __attribute__ ((unused)),
414                   int *index_hint __attribute__ ((unused)))
415 {
416   int i;
417   pop_query_status ret;
418   char buf[LONG_STRING];
419   POP_DATA *pop_data = (POP_DATA *) ctx->data;
420
421   pop_data->check_time = 0;
422
423   for (;;) {
424     if (pop_reconnect (ctx) != PQ_OK)
425       return PQ_NOT_CONNECTED;
426
427     mutt_message (_("Marking %d messages deleted..."), ctx->deleted);
428
429     for (i = 0, ret = 0; ret == 0 && i < ctx->msgcount; i++) {
430       if (ctx->hdrs[i]->deleted) {
431         snprintf (buf, sizeof (buf), "DELE %d\r\n", ctx->hdrs[i]->refno);
432         ret = pop_query (pop_data, buf, sizeof (buf));
433       }
434     }
435
436     if (ret == PQ_OK) {
437       m_strcpy(buf, sizeof(buf), "QUIT\r\n");
438       ret = pop_query (pop_data, buf, sizeof (buf));
439     }
440
441     if (ret == PQ_OK) {
442       pop_data->clear_cache = 1;
443       pop_clear_cache (pop_data);
444       pop_data->status = POP_DISCONNECTED;
445       return PQ_OK;
446     }
447
448     if (ret == PQ_ERR) {
449       mutt_error ("%s", pop_data->err_msg);
450       mutt_sleep (2);
451       return PQ_NOT_CONNECTED;
452     }
453   }
454 }
455
456 /* Check for new messages and fetch headers */
457 static int pop_check_mailbox (CONTEXT * ctx,
458                               int *index_hint __attribute__ ((unused)),
459                               int unused __attribute__ ((unused)))
460 {
461   int ret;
462   POP_DATA *pop_data = (POP_DATA *) ctx->data;
463
464   if ((pop_data->check_time + PopCheckTimeout) > time (NULL))
465     return 0;
466
467   pop_logout (ctx);
468
469   mutt_socket_close (pop_data->conn);
470
471   if (pop_open_connection (pop_data) < 0)
472     return -1;
473
474   ctx->size = pop_data->size;
475
476   mutt_message _("Checking for new messages...");
477
478   ret = pop_fetch_headers (ctx);
479   pop_clear_cache (pop_data);
480
481   if (ret < 0)
482     return -1;
483
484   if (ret > 0)
485     return M_NEW_MAIL;
486
487   return 0;
488 }
489
490 /* Fetch messages and save them in $spoolfile */
491 void pop_fetch_mail (void)
492 {
493   char buffer[LONG_STRING];
494   char msgbuf[SHORT_STRING];
495   char *url, *p;
496   int i, delanswer, last = 0, msgs, bytes, rset = 0;
497   pop_query_status ret;
498   CONNECTION *conn;
499   CONTEXT ctx;
500   MESSAGE *msg = NULL;
501   ACCOUNT act;
502   POP_DATA *pop_data;
503
504   if (!PopHost) {
505     mutt_error _("POP host is not defined.");
506
507     return;
508   }
509
510   url = p = p_new(char, strlen (PopHost) + 7);
511   if (url_check_scheme (PopHost) == U_UNKNOWN) {
512     strcpy (url, "pop://");     /* __STRCPY_CHECKED__ */
513     p = strchr (url, '\0');
514   }
515   strcpy (p, PopHost);          /* __STRCPY_CHECKED__ */
516
517   ret = pop_parse_path (url, &act);
518   p_delete(&url);
519   if (ret) {
520     mutt_error (_("%s is an invalid POP path"), PopHost);
521     return;
522   }
523
524   conn = mutt_conn_find (NULL, &act);
525   if (!conn)
526     return;
527
528   pop_data = p_new(POP_DATA, 1);
529   pop_data->conn = conn;
530
531   if (pop_open_connection (pop_data) < 0) {
532     mutt_socket_free (pop_data->conn);
533     p_delete(&pop_data);
534     return;
535   }
536
537   conn->data = pop_data;
538
539   mutt_message _("Checking for new messages...");
540
541   /* find out how many messages are in the mailbox. */
542   m_strcpy(buffer, sizeof(buffer), "STAT\r\n");
543   ret = pop_query (pop_data, buffer, sizeof (buffer));
544   if (ret == PQ_NOT_CONNECTED)
545     goto fail;
546   if (ret == PQ_ERR) {
547     mutt_error ("%s", pop_data->err_msg);
548     goto finish;
549   }
550
551   sscanf (buffer, "+OK %d %d", &msgs, &bytes);
552
553   /* only get unread messages */
554   if (msgs > 0 && option (OPTPOPLAST)) {
555     m_strcpy(buffer, sizeof(buffer), "LAST\r\n");
556     ret = pop_query (pop_data, buffer, sizeof (buffer));
557     if (ret == PQ_NOT_CONNECTED)
558       goto fail;
559     if (ret == PQ_OK)
560       sscanf (buffer, "+OK %d", &last);
561   }
562
563   if (msgs <= last) {
564     mutt_message _("No new mail in POP mailbox.");
565
566     goto finish;
567   }
568
569   if (mx_open_mailbox (NONULL (Spoolfile), M_APPEND, &ctx) == NULL)
570     goto finish;
571
572   delanswer =
573     query_quadoption (OPT_POPDELETE, _("Delete messages from server?"));
574
575   snprintf (msgbuf, sizeof (msgbuf), _("Reading new messages (%d bytes)..."),
576             bytes);
577   mutt_message ("%s", msgbuf);
578
579   for (i = last + 1; i <= msgs; i++) {
580     if ((msg = mx_open_new_message (&ctx, NULL, M_ADD_FROM)) == NULL)
581       ret = -3;
582     else {
583       snprintf (buffer, sizeof (buffer), "RETR %d\r\n", i);
584       ret = pop_fetch_data (pop_data, buffer, NULL, fetch_message, msg->fp);
585       if (ret == PFD_FUNCT_ERROR)
586         rset = 1;
587
588       if (ret == PQ_OK && mx_commit_message (msg, &ctx) != 0) {
589         rset = 1;
590         ret = PFD_FUNCT_ERROR;
591       }
592
593       mx_close_message (&msg);
594     }
595
596     if (ret == PQ_OK && delanswer == M_YES) {
597       /* delete the message on the server */
598       snprintf (buffer, sizeof (buffer), "DELE %d\r\n", i);
599       ret = pop_query (pop_data, buffer, sizeof (buffer));
600     }
601
602     if (ret == PQ_NOT_CONNECTED) {
603       mx_close_mailbox (&ctx, NULL);
604       goto fail;
605     }
606     if (ret == PQ_ERR) {
607       mutt_error ("%s", pop_data->err_msg);
608       break;
609     }
610     if (ret == -3) { /* this is -3 when ret != 0, because it will keep the value from before *gna* */
611       mutt_error _("Error while writing mailbox!");
612
613       break;
614     }
615
616     mutt_message (_("%s [%d of %d messages read]"), msgbuf, i - last,
617                   msgs - last);
618   }
619
620   mx_close_mailbox (&ctx, NULL);
621
622   if (rset) {
623     /* make sure no messages get deleted */
624     m_strcpy(buffer, sizeof(buffer), "RSET\r\n");
625     if (pop_query (pop_data, buffer, sizeof (buffer)) == PQ_NOT_CONNECTED)
626       goto fail;
627   }
628
629 finish:
630   /* exit gracefully */
631   m_strcpy(buffer, sizeof(buffer), "QUIT\r\n");
632   if (pop_query (pop_data, buffer, sizeof (buffer)) == PQ_NOT_CONNECTED)
633     goto fail;
634   mutt_socket_close (conn);
635   p_delete(&pop_data);
636   return;
637
638 fail:
639   mutt_error _("Server closed connection!");
640   mutt_socket_close (conn);
641   p_delete(&pop_data);
642 }
643
644 static int pop_is_magic (const char* path, struct stat* st __attribute__ ((unused))) {
645   url_scheme_t s = url_check_scheme (NONULL (path));
646   return ((s == U_POP || s == U_POPS) ? M_POP : -1);
647 }
648
649 static int acl_check_pop (CONTEXT* ctx __attribute__ ((unused)), int bit) {
650   switch (bit) {
651     case ACL_INSERT:    /* editing messages */
652     case ACL_WRITE:     /* change importance */
653       return (0);
654     case ACL_DELETE:    /* (un)deletion */
655     case ACL_SEEN:      /* mark as read */
656       return (1);
657     default:
658       return (0);
659   }
660 }
661
662 mx_t const pop_mx = {
663     M_POP,
664     0,
665     pop_is_magic,
666     NULL,
667     NULL,
668     pop_open_mailbox,
669     NULL,
670     acl_check_pop,
671     pop_check_mailbox,
672     pop_close_mailbox,
673     pop_sync_mailbox,
674     NULL,
675 };