Improve failure handling in mail threads.
[monky] / src / mail.c
1 /* -*- mode: c; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: t -*-
2  * vim: ts=4 sw=4 noet ai cindent syntax=c
3  *
4  * Conky, a system monitor, based on torsmo
5  *
6  * Any original torsmo code is licensed under the BSD license
7  *
8  * All code written since the fork of torsmo is licensed under the GPL
9  *
10  * Please see COPYING for details
11  *
12  * Copyright (c) 2004, Hannu Saransaari and Lauri Hakkarainen
13  * Copyright (c) 2005-2010 Brenden Matthews, Philip Kovacs, et. al.
14  *      (see AUTHORS)
15  * All rights reserved.
16  *
17  * This program is free software: you can redistribute it and/or modify
18  * it under the terms of the GNU General Public License as published by
19  * the Free Software Foundation, either version 3 of the License, or
20  * (at your option) any later version.
21  *
22  * This program is distributed in the hope that it will be useful,
23  * but WITHOUT ANY WARRANTY; without even the implied warranty of
24  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25  * GNU General Public License for more details.
26  * You should have received a copy of the GNU General Public License
27  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
28  *
29  */
30
31 #include "config.h"
32 #include "conky.h"
33 #include "common.h"
34 #include "logging.h"
35 #include "text_object.h"
36 #include "timed_thread.h"
37
38 #include <errno.h>
39 #include <stdio.h>
40 #include <string.h>
41 #include <unistd.h>
42 #include <limits.h>
43 #include <netinet/in.h>
44 #include <netdb.h>
45 #include <sys/socket.h>
46 #include <sys/stat.h>
47 #include <sys/time.h>
48 #include <sys/param.h>
49
50 #include <dirent.h>
51 #include <errno.h>
52 #include <termios.h>
53
54 /* MAX() is defined by a header included from conky.h
55  * maybe once this is not true anymore, so have an alternative
56  * waiting to drop in.
57  *
58  * #define MAX(a, b)  ((a > b) ? a : b)
59  */
60
61 #define POP3_TYPE 1
62 #define IMAP_TYPE 2
63
64 #define MAXSIZE 1024
65
66 struct mail_s {                 // for imap and pop3
67         unsigned long unseen;
68         unsigned long messages;
69         unsigned long used;
70         unsigned long quota;
71         unsigned long port;
72         unsigned int retries;
73         float interval;
74         double last_update;
75         char host[MAXSIZE];
76         char user[MAXSIZE];
77         char pass[MAXSIZE];
78         char command[MAXSIZE];
79         char folder[MAXSIZE];
80         timed_thread *p_timed_thread;
81         char secure;
82 };
83
84 struct local_mail_s {
85         char *mbox;
86         int mail_count;
87         int new_mail_count;
88         int seen_mail_count;
89         int unseen_mail_count;
90         int flagged_mail_count;
91         int unflagged_mail_count;
92         int forwarded_mail_count;
93         int unforwarded_mail_count;
94         int replied_mail_count;
95         int unreplied_mail_count;
96         int draft_mail_count;
97         int trashed_mail_count;
98         float interval;
99         time_t last_mtime;
100         double last_update;
101 };
102
103 char *current_mail_spool;
104
105 static struct mail_s *global_mail;
106 static int global_mail_use = 0;
107
108 static void update_mail_count(struct local_mail_s *mail)
109 {
110         struct stat st;
111
112         if (mail == NULL) {
113                 return;
114         }
115
116         /* TODO: use that fine file modification notify on Linux 2.4 */
117
118         /* don't check mail so often (9.5s is minimum interval) */
119         if (current_update_time - mail->last_update < 9.5) {
120                 return;
121         } else {
122                 mail->last_update = current_update_time;
123         }
124
125         if (stat(mail->mbox, &st)) {
126                 static int rep = 0;
127
128                 if (!rep) {
129                         NORM_ERR("can't stat %s: %s", mail->mbox, strerror(errno));
130                         rep = 1;
131                 }
132                 return;
133         }
134 #if HAVE_DIRENT_H
135         /* maildir format */
136         if (S_ISDIR(st.st_mode)) {
137                 DIR *dir;
138                 char *dirname;
139                 struct dirent *dirent;
140                 char *mailflags;
141
142                 mail->mail_count = mail->new_mail_count = 0;
143                 mail->seen_mail_count = mail->unseen_mail_count = 0;
144                 mail->flagged_mail_count = mail->unflagged_mail_count = 0;
145                 mail->forwarded_mail_count = mail->unforwarded_mail_count = 0;
146                 mail->replied_mail_count = mail->unreplied_mail_count = 0;
147                 mail->draft_mail_count = mail->trashed_mail_count = 0;
148                 dirname = (char *) malloc(sizeof(char) * (strlen(mail->mbox) + 5));
149                 if (!dirname) {
150                         NORM_ERR("malloc");
151                         return;
152                 }
153                 strcpy(dirname, mail->mbox);
154                 strcat(dirname, "/");
155                 /* checking the cur subdirectory */
156                 strcat(dirname, "cur");
157
158                 dir = opendir(dirname);
159                 if (!dir) {
160                         NORM_ERR("cannot open directory");
161                         free(dirname);
162                         return;
163                 }
164                 dirent = readdir(dir);
165                 while (dirent) {
166                         /* . and .. are skipped */
167                         if (dirent->d_name[0] != '.') {
168                                 mail->mail_count++;
169                                 mailflags = (char *) malloc(sizeof(char) * strlen(strrchr(dirent->d_name, ',')));
170                                 if (!mailflags) {
171                                         NORM_ERR("malloc");
172                                         free(dirname);
173                                         return;
174                                 }
175                                 strcpy(mailflags, strrchr(dirent->d_name, ','));
176                                 if (!strchr(mailflags, 'T')) { /* The message is not in the trash */
177                                         if (strchr(mailflags, 'S')) { /*The message has been seen */
178                                                 mail->seen_mail_count++;
179                                         } else {
180                                                 mail->unseen_mail_count++;
181                                         }
182                                         if (strchr(mailflags, 'F')) { /*The message was flagged */
183                                                 mail->flagged_mail_count++;
184                                         } else {
185                                                 mail->unflagged_mail_count++;
186                                         }
187                                         if (strchr(mailflags, 'P')) { /*The message was forwarded */
188                                                 mail->forwarded_mail_count++;
189                                         } else {
190                                                 mail->unforwarded_mail_count++;
191                                         }
192                                         if (strchr(mailflags, 'R')) { /*The message was replied */
193                                                 mail->replied_mail_count++;
194                                         } else {
195                                                 mail->unreplied_mail_count++;
196                                         }
197                                         if (strchr(mailflags, 'D')) { /*The message is a draft */
198                                                 mail->draft_mail_count++;
199                                         }
200                                 } else {
201                                         mail->trashed_mail_count++;
202                                 }
203                                 free(mailflags);
204                         }
205                         dirent = readdir(dir);
206                 }
207                 closedir(dir);
208
209                 dirname[strlen(dirname) - 3] = '\0';
210                 strcat(dirname, "new");
211
212                 dir = opendir(dirname);
213                 if (!dir) {
214                         NORM_ERR("cannot open directory");
215                         free(dirname);
216                         return;
217                 }
218                 dirent = readdir(dir);
219                 while (dirent) {
220                         /* . and .. are skipped */
221                         if (dirent->d_name[0] != '.') {
222                                 mail->new_mail_count++;
223                                 mail->mail_count++;
224                                 mail->unseen_mail_count++;  /* new messages cannot have been seen */
225                         }
226                         dirent = readdir(dir);
227                 }
228                 closedir(dir);
229
230                 free(dirname);
231                 return;
232         }
233 #endif
234         /* mbox format */
235         if (st.st_mtime != mail->last_mtime) {
236                 /* yippee, modification time has changed, let's read mail count! */
237                 static int rep;
238                 FILE *fp;
239                 int reading_status = 0;
240
241                 /* could lock here but I don't think it's really worth it because
242                  * this isn't going to write mail spool */
243
244                 mail->new_mail_count = mail->mail_count = 0;
245
246                 /* these flags are not supported for mbox */
247                 mail->seen_mail_count = mail->unseen_mail_count = -1;
248                 mail->flagged_mail_count = mail->unflagged_mail_count = -1;
249                 mail->forwarded_mail_count = mail->unforwarded_mail_count = -1;
250                 mail->replied_mail_count = mail->unreplied_mail_count = -1;
251                 mail->draft_mail_count = mail->trashed_mail_count = -1;
252
253                 fp = open_file(mail->mbox, &rep);
254                 if (!fp) {
255                         return;
256                 }
257
258                 /* NOTE: adds mail as new if there isn't Status-field at all */
259
260                 while (!feof(fp)) {
261                         char buf[128];
262                         int was_new = 0;
263
264                         if (fgets(buf, 128, fp) == NULL) {
265                                 break;
266                         }
267
268                         if (strncmp(buf, "From ", 5) == 0) {
269                                 /* ignore MAILER-DAEMON */
270                                 if (strncmp(buf + 5, "MAILER-DAEMON ", 14) != 0) {
271                                         mail->mail_count++;
272                                         was_new = 0;
273
274                                         if (reading_status == 1) {
275                                                 mail->new_mail_count++;
276                                         } else {
277                                                 reading_status = 1;
278                                         }
279                                 }
280                         } else {
281                                 if (reading_status == 1
282                                                 && strncmp(buf, "X-Mozilla-Status:", 17) == 0) {
283                                         int xms = strtol(buf + 17, NULL, 16);
284                                         /* check that mail isn't marked for deletion */
285                                         if (xms & 0x0008) {
286                                                 mail->trashed_mail_count++;
287                                                 reading_status = 0;
288                                                 /* Don't check whether the trashed email is unread */
289                                                 continue;
290                                         }
291                                         /* check that mail isn't already read */
292                                         if (!(xms & 0x0001)) {
293                                                 mail->new_mail_count++;
294                                                 was_new = 1;
295                                         }
296
297                                         /* check for an additional X-Status header */
298                                         reading_status = 2;
299                                         continue;
300                                 }
301                                 if (reading_status == 1 && strncmp(buf, "Status:", 7) == 0) {
302                                         /* check that mail isn't already read */
303                                         if (strchr(buf + 7, 'R') == NULL) {
304                                                 mail->new_mail_count++;
305                                                 was_new = 1;
306                                         }
307
308                                         reading_status = 2;
309                                         continue;
310                                 }
311                                 if (reading_status >= 1 && strncmp(buf, "X-Status:", 9) == 0) {
312                                         /* check that mail isn't marked for deletion */
313                                         if (strchr(buf + 9, 'D') != NULL) {
314                                                 mail->trashed_mail_count++;
315                                                 /* If the mail was previously detected as new,
316                                                    subtract it from the new mail count */
317                                                 if (was_new)
318                                                         mail->new_mail_count--;
319                                         }
320
321                                         reading_status = 0;
322                                         continue;
323                                 }
324                         }
325
326                         /* skip until \n */
327                         while (strchr(buf, '\n') == NULL && !feof(fp)) {
328                                 fgets(buf, 128, fp);
329                         }
330                 }
331
332                 fclose(fp);
333
334                 if (reading_status) {
335                         mail->new_mail_count++;
336                 }
337
338                 mail->last_mtime = st.st_mtime;
339         }
340 }
341
342 void parse_local_mail_args(struct text_object *obj, const char *arg)
343 {
344         float n1;
345         char mbox[256], dst[256];
346         struct local_mail_s *locmail;
347
348         if (!arg) {
349                 n1 = 9.5;
350                 /* Kapil: Changed from MAIL_FILE to
351                    current_mail_spool since the latter
352                    is a copy of the former if undefined
353                    but the latter should take precedence
354                    if defined */
355                 strncpy(mbox, current_mail_spool, sizeof(mbox));
356         } else {
357                 if (sscanf(arg, "%s %f", mbox, &n1) != 2) {
358                         n1 = 9.5;
359                         strncpy(mbox, arg, sizeof(mbox));
360                 }
361         }
362
363         variable_substitute(mbox, dst, sizeof(dst));
364
365         locmail = malloc(sizeof(struct local_mail_s));
366         memset(locmail, 0, sizeof(struct local_mail_s));
367         locmail->mbox = strndup(dst, text_buffer_size);
368         locmail->interval = n1;
369         obj->data.opaque = locmail;
370 }
371
372 #define PRINT_MAILS_GENERATOR(x) \
373 void print_##x##mails(struct text_object *obj, char *p, int p_max_size) \
374 { \
375         struct local_mail_s *locmail = obj->data.opaque; \
376         if (!locmail) \
377                 return; \
378         update_mail_count(locmail); \
379         snprintf(p, p_max_size, "%d", locmail->x##mail_count); \
380 }
381
382 PRINT_MAILS_GENERATOR()
383 PRINT_MAILS_GENERATOR(new_)
384 PRINT_MAILS_GENERATOR(seen_)
385 PRINT_MAILS_GENERATOR(unseen_)
386 PRINT_MAILS_GENERATOR(flagged_)
387 PRINT_MAILS_GENERATOR(unflagged_)
388 PRINT_MAILS_GENERATOR(forwarded_)
389 PRINT_MAILS_GENERATOR(unforwarded_)
390 PRINT_MAILS_GENERATOR(replied_)
391 PRINT_MAILS_GENERATOR(unreplied_)
392 PRINT_MAILS_GENERATOR(draft_)
393 PRINT_MAILS_GENERATOR(trashed_)
394
395 void free_local_mails(struct text_object *obj)
396 {
397         struct local_mail_s *locmail = obj->data.opaque;
398
399         if (!locmail)
400                 return;
401
402         if (locmail->mbox)
403                 free(locmail->mbox);
404         free(obj->data.opaque);
405         obj->data.opaque = 0;
406 }
407
408 #define MAXDATASIZE 1000
409
410 struct mail_s *parse_mail_args(char type, const char *arg)
411 {
412         struct mail_s *mail;
413         char *tmp;
414
415         mail = malloc(sizeof(struct mail_s));
416         memset(mail, 0, sizeof(struct mail_s));
417
418 #define lenstr "%1023s"
419         if (sscanf(arg, lenstr " " lenstr " " lenstr, mail->host, mail->user, mail->pass)
420                         != 3) {
421                 if (type == POP3_TYPE) {
422                         NORM_ERR("Scanning POP3 args failed");
423                 } else if (type == IMAP_TYPE) {
424                         NORM_ERR("Scanning IMAP args failed");
425                 }
426                 return 0;
427         }
428         // see if password needs prompting
429         if (mail->pass[0] == '*' && mail->pass[1] == '\0') {
430                 int fp = fileno(stdin);
431                 struct termios term;
432
433                 tcgetattr(fp, &term);
434                 term.c_lflag &= ~ECHO;
435                 tcsetattr(fp, TCSANOW, &term);
436                 printf("Enter mailbox password (%s@%s): ", mail->user, mail->host);
437                 scanf(lenstr, mail->pass);
438 #undef lenstr
439                 printf("\n");
440                 term.c_lflag |= ECHO;
441                 tcsetattr(fp, TCSANOW, &term);
442         }
443         // now we check for optional args
444         tmp = strstr(arg, "-r ");
445         if (tmp) {
446                 tmp += 3;
447                 sscanf(tmp, "%u", &mail->retries);
448         } else {
449                 mail->retries = 5;      // 5 retries after failure
450         }
451         tmp = strstr(arg, "-i ");
452         if (tmp) {
453                 tmp += 3;
454                 sscanf(tmp, "%f", &mail->interval);
455         } else {
456                 mail->interval = 300;   // 5 minutes
457         }
458         tmp = strstr(arg, "-p ");
459         if (tmp) {
460                 tmp += 3;
461                 sscanf(tmp, "%lu", &mail->port);
462         } else {
463                 if (type == POP3_TYPE) {
464                         mail->port = 110;       // default pop3 port
465                 } else if (type == IMAP_TYPE) {
466                         mail->port = 143;       // default imap port
467                 }
468         }
469         if (type == IMAP_TYPE) {
470                 tmp = strstr(arg, "-f ");
471                 if (tmp) {
472                         int len = MAXSIZE - 1;
473                         tmp += 3;
474                         if (tmp[0] == '\'') {
475                                 len = strstr(tmp + 1, "'") - tmp;
476                                 if (len > MAXSIZE) {
477                                         len = MAXSIZE;
478                                 }
479                         }
480                         strncpy(mail->folder, tmp + 1, len - 1);
481                 } else {
482                         strncpy(mail->folder, "INBOX", MAXSIZE - 1);    // default imap inbox
483                 }
484         }
485         tmp = strstr(arg, "-e ");
486         if (tmp) {
487                 int len = MAXSIZE - 1;
488                 tmp += 3;
489
490                 if (tmp[0] == '\'') {
491                         len = strstr(tmp + 1, "'") - tmp;
492                         if (len > MAXSIZE) {
493                                 len = MAXSIZE;
494                         }
495                 }
496                 strncpy(mail->command, tmp + 1, len - 1);
497         } else {
498                 mail->command[0] = '\0';
499         }
500         DBGP("mail args parsed: folder: '%s' command: '%s' user: '%s' host: '%s'\n",
501                         mail->folder, mail->command, mail->user, mail->host);
502         mail->p_timed_thread = NULL;
503         return mail;
504 }
505
506 void parse_imap_mail_args(struct text_object *obj, const char *arg)
507 {
508         static int rep = 0;
509
510         if (!arg) {
511                 if (!global_mail && !rep) {
512                         // something is wrong, warn once then stop
513                         NORM_ERR("There's a problem with your mail settings.  "
514                                         "Check that the global mail settings are properly defined"
515                                         " (line %li).", obj->line);
516                         rep = 1;
517                         return;
518                 }
519                 obj->data.opaque = global_mail;
520                 global_mail_use++;
521                 return;
522         }
523         // proccss
524         obj->data.opaque = parse_mail_args(IMAP_TYPE, arg);
525 }
526
527 void parse_pop3_mail_args(struct text_object *obj, const char *arg)
528 {
529         static int rep = 0;
530
531         if (!arg) {
532                 if (!global_mail && !rep) {
533                         // something is wrong, warn once then stop
534                         NORM_ERR("There's a problem with your mail settings.  "
535                                         "Check that the global mail settings are properly defined"
536                                         " (line %li).", obj->line);
537                         rep = 1;
538                         return;
539                 }
540                 obj->data.opaque = global_mail;
541                 global_mail_use++;
542                 return;
543         }
544         // proccss
545         obj->data.opaque = parse_mail_args(POP3_TYPE, arg);
546 }
547
548 void parse_global_imap_mail_args(const char *value)
549 {
550         global_mail = parse_mail_args(IMAP_TYPE, value);
551 }
552
553 void parse_global_pop3_mail_args(const char *value)
554 {
555         global_mail = parse_mail_args(POP3_TYPE, value);
556 }
557
558 void free_mail_obj(struct text_object *obj)
559 {
560         if (!obj->data.opaque)
561                 return;
562
563         if (obj->data.opaque == global_mail) {
564                 if (--global_mail_use == 0) {
565                         free(global_mail);
566                         global_mail = 0;
567                 }
568         } else {
569                 free(obj->data.opaque);
570                 obj->data.opaque = 0;
571         }
572 }
573
574 int imap_command(int sockfd, const char *command, char *response, const char *verify)
575 {
576         struct timeval fetchtimeout;
577         fd_set fdset;
578         int res, numbytes = 0;
579         if (send(sockfd, command, strlen(command), 0) == -1) {
580                 perror("send");
581                 return -1;
582         }
583         fetchtimeout.tv_sec = 60;       // 60 second timeout i guess
584         fetchtimeout.tv_usec = 0;
585         FD_ZERO(&fdset);
586         FD_SET(sockfd, &fdset);
587         res = select(sockfd + 1, &fdset, NULL, NULL, &fetchtimeout);
588         if (res > 0) {
589                 if ((numbytes = recv(sockfd, response, MAXDATASIZE - 1, 0)) == -1) {
590                         perror("recv");
591                         return -1;
592                 }
593         }
594         DBGP2("imap_command()  command: %s", command);
595         DBGP2("imap_command() received: %s", response);
596         response[numbytes] = '\0';
597         if (strstr(response, verify) == NULL) {
598                 return -1;
599         }
600         return 0;
601 }
602
603 int imap_check_status(char *recvbuf, struct mail_s *mail)
604 {
605         char *reply;
606         reply = strstr(recvbuf, " (MESSAGES ");
607         if (!reply || strlen(reply) < 2) {
608                 return -1;
609         }
610         reply += 2;
611         *strchr(reply, ')') = '\0';
612         if (reply == NULL) {
613                 NORM_ERR("Error parsing IMAP response: %s", recvbuf);
614                 return -1;
615         } else {
616                 timed_thread_lock(mail->p_timed_thread);
617                 sscanf(reply, "MESSAGES %lu UNSEEN %lu", &mail->messages,
618                                 &mail->unseen);
619                 timed_thread_unlock(mail->p_timed_thread);
620         }
621         return 0;
622 }
623
624 void imap_unseen_command(struct mail_s *mail, unsigned long old_unseen, unsigned long old_messages)
625 {
626         if (strlen(mail->command) > 1 && (mail->unseen > old_unseen
627                                 || (mail->messages > old_messages && mail->unseen > 0))) {
628                 // new mail goodie
629                 if (system(mail->command) == -1) {
630                         perror("system()");
631                 }
632         }
633 }
634
635 static void ensure_mail_thread(struct mail_s *mail,
636                 void *thread(void *), const char *text)
637 {
638         if (mail->p_timed_thread)
639                 return;
640
641         mail->p_timed_thread = timed_thread_create(thread,
642                                 mail, mail->interval * 1000000);
643         if (!mail->p_timed_thread) {
644                 NORM_ERR("Error creating %s timed thread", text);
645         }
646         timed_thread_register(mail->p_timed_thread,
647                         &mail->p_timed_thread);
648         if (timed_thread_run(mail->p_timed_thread)) {
649                 NORM_ERR("Error running %s timed thread", text);
650         }
651 }
652
653 static void *imap_thread(void *arg)
654 {
655         int sockfd, numbytes;
656         char recvbuf[MAXDATASIZE];
657         char sendbuf[MAXDATASIZE];
658         unsigned int fail = 0;
659         unsigned long old_unseen = ULONG_MAX;
660         unsigned long old_messages = ULONG_MAX;
661         struct stat stat_buf;
662         struct mail_s *mail = (struct mail_s *)arg;
663         int has_idle = 0;
664         int threadfd = timed_thread_readfd(mail->p_timed_thread);
665         char resolved_host = 0;
666         struct addrinfo hints;
667         struct addrinfo *ai = 0, *rp;
668         char portbuf[8];
669
670         while (fail < mail->retries) {
671                 struct timeval fetchtimeout;
672                 int res;
673                 fd_set fdset;
674
675                 if (fail > 0) {
676                         NORM_ERR("Trying IMAP connection again for %s@%s (try %u/%u)",
677                                         mail->user, mail->host, fail + 1, mail->retries);
678                         resolved_host = 0; /* force us to resolve the hostname again */
679                 }
680                 if (!resolved_host) {
681                         memset(&hints, 0, sizeof(struct addrinfo));
682                         hints.ai_family = AF_UNSPEC;
683                         hints.ai_socktype = SOCK_STREAM;
684                         hints.ai_flags = 0;
685                         hints.ai_protocol = 0;
686                         snprintf(portbuf, 8, "%lu", mail->port);
687
688                         res = getaddrinfo(mail->host, portbuf, &hints, &ai);
689                         if (res != 0) {
690                                 NORM_ERR("IMAP getaddrinfo: %s", gai_strerror(res));
691                                 fail++;
692                                 break;
693                         }
694                         resolved_host = 1;
695                 }
696                 do {
697                         for (rp = ai; rp != NULL; rp = rp->ai_next) {
698                                 sockfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
699                                 if (sockfd == -1) {
700                                         continue;
701                                 }
702                                 if (connect(sockfd, rp->ai_addr, rp->ai_addrlen) != -1) {
703                                         break;
704                                 }
705                                 close(sockfd);
706                         }
707                         freeaddrinfo(ai);
708                         ai = 0;
709                         if (rp == NULL) {
710                                 perror("connect");
711                                 fail++;
712                                 break;
713                         }
714
715                         fetchtimeout.tv_sec = 60;       // 60 second timeout i guess
716                         fetchtimeout.tv_usec = 0;
717                         FD_ZERO(&fdset);
718                         FD_SET(sockfd, &fdset);
719                         res = select(sockfd + 1, &fdset, NULL, NULL, &fetchtimeout);
720                         if (res > 0) {
721                                 if ((numbytes = recv(sockfd, recvbuf, MAXDATASIZE - 1, 0)) == -1) {
722                                         perror("recv");
723                                         fail++;
724                                         break;
725                                 }
726                         } else {
727                                 NORM_ERR("IMAP connection failed: timeout");
728                                 fail++;
729                                 break;
730                         }
731                         recvbuf[numbytes] = '\0';
732                         DBGP2("imap_thread() received: %s", recvbuf);
733                         if (strstr(recvbuf, "* OK") != recvbuf) {
734                                 NORM_ERR("IMAP connection failed, probably not an IMAP server");
735                                 fail++;
736                                 break;
737                         }
738                         strncpy(sendbuf, "abc CAPABILITY\r\n", MAXDATASIZE);
739                         if (imap_command(sockfd, sendbuf, recvbuf, "abc OK")) {
740                                 fail++;
741                                 break;
742                         }
743                         if (strstr(recvbuf, " IDLE ") != NULL) {
744                                 has_idle = 1;
745                         }
746
747                         strncpy(sendbuf, "a1 login ", MAXDATASIZE);
748                         strncat(sendbuf, mail->user, MAXDATASIZE - strlen(sendbuf) - 1);
749                         strncat(sendbuf, " ", MAXDATASIZE - strlen(sendbuf) - 1);
750                         strncat(sendbuf, mail->pass, MAXDATASIZE - strlen(sendbuf) - 1);
751                         strncat(sendbuf, "\r\n", MAXDATASIZE - strlen(sendbuf) - 1);
752                         if (imap_command(sockfd, sendbuf, recvbuf, "a1 OK")) {
753                                 fail++;
754                                 break;
755                         }
756
757                         strncpy(sendbuf, "a2 STATUS \"", MAXDATASIZE);
758                         strncat(sendbuf, mail->folder, MAXDATASIZE - strlen(sendbuf) - 1);
759                         strncat(sendbuf, "\" (MESSAGES UNSEEN)\r\n",
760                                         MAXDATASIZE - strlen(sendbuf) - 1);
761                         if (imap_command(sockfd, sendbuf, recvbuf, "a2 OK")) {
762                                 fail++;
763                                 break;
764                         }
765
766                         if (imap_check_status(recvbuf, mail)) {
767                                 fail++;
768                                 break;
769                         }
770                         imap_unseen_command(mail, old_unseen, old_messages);
771                         fail = 0;
772                         old_unseen = mail->unseen;
773                         old_messages = mail->messages;
774
775                         if (has_idle) {
776                                 strncpy(sendbuf, "a4 SELECT \"", MAXDATASIZE);
777                                 strncat(sendbuf, mail->folder, MAXDATASIZE - strlen(sendbuf) - 1);
778                                 strncat(sendbuf, "\"\r\n", MAXDATASIZE - strlen(sendbuf) - 1);
779                                 if (imap_command(sockfd, sendbuf, recvbuf, "a4 OK")) {
780                                         fail++;
781                                         break;
782                                 }
783
784                                 strncpy(sendbuf, "a5 IDLE\r\n", MAXDATASIZE);
785                                 if (imap_command(sockfd, sendbuf, recvbuf, "+ idling")) {
786                                         fail++;
787                                         break;
788                                 }
789                                 recvbuf[0] = '\0';
790
791                                 while (1) {
792                                         /*
793                                          * RFC 2177 says we have to re-idle every 29 minutes.
794                                          * We'll do it every 10 minutes to be safe.
795                                          */
796                                         fetchtimeout.tv_sec = 600;
797                                         fetchtimeout.tv_usec = 0;
798                                         DBGP("idling...");
799                                         FD_ZERO(&fdset);
800                                         FD_SET(sockfd, &fdset);
801                                         FD_SET(threadfd, &fdset);
802                                         res = select(MAX(sockfd + 1, threadfd + 1), &fdset, NULL,
803                                                         NULL, &fetchtimeout);
804                                         DBGP("done idling");
805                                         if (timed_thread_test(mail->p_timed_thread, 1) || (res == -1 && errno == EINTR) || FD_ISSET(threadfd, &fdset)) {
806                                                 if ((fstat(sockfd, &stat_buf) == 0) && S_ISSOCK(stat_buf.st_mode)) {
807                                                         /* if a valid socket, close it */
808                                                         close(sockfd);
809                                                 }
810                                                 timed_thread_exit(mail->p_timed_thread);
811                                         } else if (res > 0) {
812                                                 if ((numbytes = recv(sockfd, recvbuf, MAXDATASIZE - 1, 0)) == -1) {
813                                                         perror("recv idling");
814                                                         fail++;
815                                                         break;
816                                                 }
817                                         }
818                                         recvbuf[numbytes] = '\0';
819                                         DBGP2("imap_thread() received: %s", recvbuf);
820                                         if (strlen(recvbuf) > 2) {
821                                                 unsigned long messages, recent = 0;
822                                                 char *buf = recvbuf;
823                                                 char force_check = 0;
824                                                 buf = strstr(buf, "EXISTS");
825                                                 while (buf && strlen(buf) > 1 && strstr(buf + 1, "EXISTS")) {
826                                                         buf = strstr(buf + 1, "EXISTS");
827                                                 }
828                                                 if (buf) {
829                                                         // back up until we reach '*'
830                                                         while (buf >= recvbuf && buf[0] != '*') {
831                                                                 buf--;
832                                                         }
833                                                         if (sscanf(buf, "* %lu EXISTS\r\n", &messages) == 1) {
834                                                                 timed_thread_lock(mail->p_timed_thread);
835                                                                 if (mail->messages != messages) {
836                                                                         force_check = 1;
837                                                                 }
838                                                                 timed_thread_unlock(mail->p_timed_thread);
839                                                         }
840                                                 }
841                                                 buf = recvbuf;
842                                                 buf = strstr(buf, "RECENT");
843                                                 while (buf && strlen(buf) > 1 && strstr(buf + 1, "RECENT")) {
844                                                         buf = strstr(buf + 1, "RECENT");
845                                                 }
846                                                 if (buf) {
847                                                         // back up until we reach '*'
848                                                         while (buf >= recvbuf && buf[0] != '*') {
849                                                                 buf--;
850                                                         }
851                                                         if (sscanf(buf, "* %lu RECENT\r\n", &recent) != 1) {
852                                                                 recent = 0;
853                                                         }
854                                                 }
855                                                 /*
856                                                  * check if we got a FETCH from server, recent was
857                                                  * something other than 0, or we had a timeout
858                                                  */
859                                                 buf = recvbuf;
860                                                 if (recent > 0 || (buf && strstr(buf, " FETCH ")) || fetchtimeout.tv_sec == 0 || force_check) {
861                                                         // re-check messages and unseen
862                                                         if (imap_command(sockfd, "DONE\r\n", recvbuf, "a5 OK")) {
863                                                                 fail++;
864                                                                 break;
865                                                         }
866                                                         strncpy(sendbuf, "a2 STATUS \"", MAXDATASIZE);
867                                                         strncat(sendbuf, mail->folder, MAXDATASIZE - strlen(sendbuf) - 1);
868                                                         strncat(sendbuf, "\" (MESSAGES UNSEEN)\r\n",
869                                                                         MAXDATASIZE - strlen(sendbuf) - 1);
870                                                         if (imap_command(sockfd, sendbuf, recvbuf, "a2 OK")) {
871                                                                 fail++;
872                                                                 break;
873                                                         }
874                                                         if (imap_check_status(recvbuf, mail)) {
875                                                                 fail++;
876                                                                 break;
877                                                         }
878                                                         strncpy(sendbuf, "a5 IDLE\r\n", MAXDATASIZE);
879                                                         if (imap_command(sockfd, sendbuf, recvbuf, "+ idling")) {
880                                                                 fail++;
881                                                                 break;
882                                                         }
883                                                 }
884                                                 /*
885                                                  * check if we got a BYE from server
886                                                  */
887                                                 buf = recvbuf;
888                                                 if (buf && strstr(buf, "* BYE")) {
889                                                         // need to re-connect
890                                                         break;
891                                                 }
892                                         } else {
893                                                 fail++;
894                                                 break;
895                                         }
896                                         imap_unseen_command(mail, old_unseen, old_messages);
897                                         fail = 0;
898                                         old_unseen = mail->unseen;
899                                         old_messages = mail->messages;
900                                 }
901                                 if (fail) break;
902                         } else {
903                                 strncpy(sendbuf, "a3 logout\r\n", MAXDATASIZE);
904                                 if (send(sockfd, sendbuf, strlen(sendbuf), 0) == -1) {
905                                         perror("send a3");
906                                         fail++;
907                                         break;
908                                 }
909                                 fetchtimeout.tv_sec = 60;       // 60 second timeout i guess
910                                 fetchtimeout.tv_usec = 0;
911                                 FD_ZERO(&fdset);
912                                 FD_SET(sockfd, &fdset);
913                                 res = select(sockfd + 1, &fdset, NULL, NULL, &fetchtimeout);
914                                 if (res > 0) {
915                                         if ((numbytes = recv(sockfd, recvbuf, MAXDATASIZE - 1, 0)) == -1) {
916                                                 perror("recv a3");
917                                                 fail++;
918                                                 break;
919                                         }
920                                 }
921                                 recvbuf[numbytes] = '\0';
922                                 DBGP2("imap_thread() received: %s", recvbuf);
923                                 if (strstr(recvbuf, "a3 OK") == NULL) {
924                                         NORM_ERR("IMAP logout failed: %s", recvbuf);
925                                         fail++;
926                                         break;
927                                 }
928                         }
929                 } while (0);
930                 if ((fstat(sockfd, &stat_buf) == 0) && S_ISSOCK(stat_buf.st_mode)) {
931                         /* if a valid socket, close it */
932                         close(sockfd);
933                 }
934                 if (timed_thread_test(mail->p_timed_thread, 0)) {
935                         timed_thread_exit(mail->p_timed_thread);
936                 }
937         }
938         mail->unseen = 0;
939         mail->messages = 0;
940         return 0;
941 }
942
943 void print_imap_unseen(struct text_object *obj, char *p, int p_max_size)
944 {
945         struct mail_s *mail = obj->data.opaque;
946
947         if (!mail)
948                 return;
949
950         ensure_mail_thread(mail, imap_thread, "imap");
951
952         if (mail && mail->p_timed_thread) {
953                 timed_thread_lock(mail->p_timed_thread);
954                 snprintf(p, p_max_size, "%lu", mail->unseen);
955                 timed_thread_unlock(mail->p_timed_thread);
956         }
957 }
958
959 void print_imap_messages(struct text_object *obj, char *p, int p_max_size)
960 {
961         struct mail_s *mail = obj->data.opaque;
962
963         if (!mail)
964                 return;
965
966         ensure_mail_thread(mail, imap_thread, "imap");
967
968         if (mail && mail->p_timed_thread) {
969                 timed_thread_lock(mail->p_timed_thread);
970                 snprintf(p, p_max_size, "%lu", mail->messages);
971                 timed_thread_unlock(mail->p_timed_thread);
972         }
973 }
974
975 int pop3_command(int sockfd, const char *command, char *response, const char *verify)
976 {
977         struct timeval fetchtimeout;
978         fd_set fdset;
979         int res, numbytes = 0;
980         if (send(sockfd, command, strlen(command), 0) == -1) {
981                 perror("send");
982                 return -1;
983         }
984         fetchtimeout.tv_sec = 60;       // 60 second timeout i guess
985         fetchtimeout.tv_usec = 0;
986         FD_ZERO(&fdset);
987         FD_SET(sockfd, &fdset);
988         res = select(sockfd + 1, &fdset, NULL, NULL, &fetchtimeout);
989         if (res > 0) {
990                 if ((numbytes = recv(sockfd, response, MAXDATASIZE - 1, 0)) == -1) {
991                         perror("recv");
992                         return -1;
993                 }
994         }
995         DBGP2("pop3_command() received: %s", response);
996         response[numbytes] = '\0';
997         if (strstr(response, verify) == NULL) {
998                 return -1;
999         }
1000         return 0;
1001 }
1002
1003 static void *pop3_thread(void *arg)
1004 {
1005         int sockfd, numbytes;
1006         char recvbuf[MAXDATASIZE];
1007         char sendbuf[MAXDATASIZE];
1008         char *reply;
1009         unsigned int fail = 0;
1010         unsigned long old_unseen = ULONG_MAX;
1011         struct stat stat_buf;
1012         struct mail_s *mail = (struct mail_s *)arg;
1013         char resolved_host = 0;
1014         struct addrinfo hints;
1015         struct addrinfo *ai = 0, *rp;
1016         char portbuf[8];
1017
1018         while (fail < mail->retries) {
1019                 struct timeval fetchtimeout;
1020                 int res;
1021                 fd_set fdset;
1022
1023                 if (fail > 0) {
1024                         NORM_ERR("Trying POP3 connection again for %s@%s (try %u/%u)",
1025                                         mail->user, mail->host, fail + 1, mail->retries);
1026                         resolved_host = 0; /* force us to resolve the hostname again */
1027                 }
1028                 if (!resolved_host) {
1029                         memset(&hints, 0, sizeof(struct addrinfo));
1030                         hints.ai_family = AF_UNSPEC;
1031                         hints.ai_socktype = SOCK_STREAM;
1032                         hints.ai_flags = 0;
1033                         hints.ai_protocol = 0;
1034                         snprintf(portbuf, 8, "%lu", mail->port);
1035
1036                         res = getaddrinfo(mail->host, portbuf, &hints, &ai);
1037                         if (res != 0) {
1038                                 NORM_ERR("POP3 getaddrinfo: %s", gai_strerror(res));
1039                                 fail++;
1040                                 break;
1041                         }
1042                         resolved_host = 1;
1043                 }
1044                 do {
1045                         for (rp = ai; rp != NULL; rp = rp->ai_next) {
1046                                 sockfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
1047                                 if (sockfd == -1) {
1048                                         continue;
1049                                 }
1050                                 if (connect(sockfd, rp->ai_addr, rp->ai_addrlen) != -1) {
1051                                         break;
1052                                 }
1053                                 close(sockfd);
1054                         }
1055                         freeaddrinfo(ai);
1056                         ai = 0;
1057                         if (rp == NULL) {
1058                                 perror("connect");
1059                                 fail++;
1060                                 break;
1061                         }
1062
1063                         fetchtimeout.tv_sec = 60;       // 60 second timeout i guess
1064                         fetchtimeout.tv_usec = 0;
1065                         FD_ZERO(&fdset);
1066                         FD_SET(sockfd, &fdset);
1067                         res = select(sockfd + 1, &fdset, NULL, NULL, &fetchtimeout);
1068                         if (res > 0) {
1069                                 if ((numbytes = recv(sockfd, recvbuf, MAXDATASIZE - 1, 0)) == -1) {
1070                                         perror("recv");
1071                                         fail++;
1072                                         break;
1073                                 }
1074                         } else {
1075                                 NORM_ERR("POP3 connection failed: timeout\n");
1076                                 fail++;
1077                                 break;
1078                         }
1079                         DBGP2("pop3_thread received: %s", recvbuf);
1080                         recvbuf[numbytes] = '\0';
1081                         if (strstr(recvbuf, "+OK ") != recvbuf) {
1082                                 NORM_ERR("POP3 connection failed, probably not a POP3 server");
1083                                 fail++;
1084                                 break;
1085                         }
1086                         strncpy(sendbuf, "USER ", MAXDATASIZE);
1087                         strncat(sendbuf, mail->user, MAXDATASIZE - strlen(sendbuf) - 1);
1088                         strncat(sendbuf, "\r\n", MAXDATASIZE - strlen(sendbuf) - 1);
1089                         if (pop3_command(sockfd, sendbuf, recvbuf, "+OK ")) {
1090                                 fail++;
1091                                 break;
1092                         }
1093
1094                         strncpy(sendbuf, "PASS ", MAXDATASIZE);
1095                         strncat(sendbuf, mail->pass, MAXDATASIZE - strlen(sendbuf) - 1);
1096                         strncat(sendbuf, "\r\n", MAXDATASIZE - strlen(sendbuf) - 1);
1097                         if (pop3_command(sockfd, sendbuf, recvbuf, "+OK ")) {
1098                                 NORM_ERR("POP3 server login failed: %s", recvbuf);
1099                                 fail++;
1100                                 break;
1101                         }
1102
1103                         strncpy(sendbuf, "STAT\r\n", MAXDATASIZE);
1104                         if (pop3_command(sockfd, sendbuf, recvbuf, "+OK ")) {
1105                                 perror("send STAT");
1106                                 fail++;
1107                                 break;
1108                         }
1109
1110                         // now we get the data
1111                         reply = recvbuf + 4;
1112                         if (reply == NULL) {
1113                                 NORM_ERR("Error parsing POP3 response: %s", recvbuf);
1114                                 fail++;
1115                                 break;
1116                         } else {
1117                                 timed_thread_lock(mail->p_timed_thread);
1118                                 sscanf(reply, "%lu %lu", &mail->unseen, &mail->used);
1119                                 timed_thread_unlock(mail->p_timed_thread);
1120                         }
1121
1122                         strncpy(sendbuf, "QUIT\r\n", MAXDATASIZE);
1123                         if (pop3_command(sockfd, sendbuf, recvbuf, "+OK")) {
1124                                 NORM_ERR("POP3 logout failed: %s", recvbuf);
1125                                 fail++;
1126                                 break;
1127                         }
1128
1129                         if (strlen(mail->command) > 1 && mail->unseen > old_unseen) {
1130                                 // new mail goodie
1131                                 if (system(mail->command) == -1) {
1132                                         perror("system()");
1133                                 }
1134                         }
1135                         fail = 0;
1136                         old_unseen = mail->unseen;
1137                 } while (0);
1138                 if ((fstat(sockfd, &stat_buf) == 0) && S_ISSOCK(stat_buf.st_mode)) {
1139                         /* if a valid socket, close it */
1140                         close(sockfd);
1141                 }
1142                 if (timed_thread_test(mail->p_timed_thread, 0)) {
1143                         timed_thread_exit(mail->p_timed_thread);
1144                 }
1145         }
1146         mail->unseen = 0;
1147         mail->used = 0;
1148         return 0;
1149 }
1150
1151 void print_pop3_unseen(struct text_object *obj, char *p, int p_max_size)
1152 {
1153         struct mail_s *mail = obj->data.opaque;
1154
1155         if (!mail)
1156                 return;
1157
1158         ensure_mail_thread(mail, pop3_thread, "pop3");
1159
1160         if (mail && mail->p_timed_thread) {
1161                 timed_thread_lock(mail->p_timed_thread);
1162                 snprintf(p, p_max_size, "%lu", mail->unseen);
1163                 timed_thread_unlock(mail->p_timed_thread);
1164         }
1165 }
1166
1167 void print_pop3_used(struct text_object *obj, char *p, int p_max_size)
1168 {
1169         struct mail_s *mail = obj->data.opaque;
1170
1171         if (!mail)
1172                 return;
1173
1174         ensure_mail_thread(mail, pop3_thread, "pop3");
1175
1176         if (mail && mail->p_timed_thread) {
1177                 timed_thread_lock(mail->p_timed_thread);
1178                 snprintf(p, p_max_size, "%.1f",
1179                                 mail->used / 1024.0 / 1024.0);
1180                 timed_thread_unlock(mail->p_timed_thread);
1181         }
1182 }