Fix bug where IMAP doesn't refresh counts after IDLE timeout.
[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 hostent he, *he_res = 0;
663         int he_errno;
664         char hostbuff[2048];
665         struct sockaddr_in their_addr;  // connector's address information
666         struct mail_s *mail = (struct mail_s *)arg;
667         int has_idle = 0;
668         int threadfd = timed_thread_readfd(mail->p_timed_thread);
669         char resolved_host = 0;
670
671         while (fail < mail->retries) {
672                 struct timeval fetchtimeout;
673                 int res;
674                 fd_set fdset;
675
676                 if (!resolved_host) {
677 #ifdef HAVE_GETHOSTBYNAME_R
678                         if (gethostbyname_r(mail->host, &he, hostbuff, sizeof(hostbuff), &he_res, &he_errno)) { // get the host info
679                                 NORM_ERR("IMAP gethostbyname_r: %s", hstrerror(h_errno));
680                                 fail++;
681                                 break;
682                         }
683 #else /* HAVE_GETHOSTBYNAME_R */
684                         if ((he_res = gethostbyname(mail->host)) == NULL) {     // get the host info
685                                 herror("gethostbyname");
686                                 fail++;
687                                 break;
688                         }
689 #endif /* HAVE_GETHOSTBYNAME_R */
690                         resolved_host = 1;
691                 }
692                 if (fail > 0) {
693                         NORM_ERR("Trying IMAP connection again for %s@%s (try %u/%u)",
694                                         mail->user, mail->host, fail + 1, mail->retries);
695                 }
696                 do {
697                         if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
698                                 perror("socket");
699                                 fail++;
700                                 break;
701                         }
702
703                         // host byte order
704                         their_addr.sin_family = AF_INET;
705                         // short, network byte order
706                         their_addr.sin_port = htons(mail->port);
707                         their_addr.sin_addr = *((struct in_addr *) he_res->h_addr);
708                         // zero the rest of the struct
709                         memset(&(their_addr.sin_zero), '\0', 8);
710
711                         if (connect(sockfd, (struct sockaddr *) &their_addr,
712                                                 sizeof(struct sockaddr)) == -1) {
713                                 perror("connect");
714                                 fail++;
715                                 break;
716                         }
717
718                         fetchtimeout.tv_sec = 60;       // 60 second timeout i guess
719                         fetchtimeout.tv_usec = 0;
720                         FD_ZERO(&fdset);
721                         FD_SET(sockfd, &fdset);
722                         res = select(sockfd + 1, &fdset, NULL, NULL, &fetchtimeout);
723                         if (res > 0) {
724                                 if ((numbytes = recv(sockfd, recvbuf, MAXDATASIZE - 1, 0)) == -1) {
725                                         perror("recv");
726                                         fail++;
727                                         break;
728                                 }
729                         } else {
730                                 NORM_ERR("IMAP connection failed: timeout");
731                                 fail++;
732                                 break;
733                         }
734                         recvbuf[numbytes] = '\0';
735                         DBGP2("imap_thread() received: %s", recvbuf);
736                         if (strstr(recvbuf, "* OK") != recvbuf) {
737                                 NORM_ERR("IMAP connection failed, probably not an IMAP server");
738                                 fail++;
739                                 break;
740                         }
741                         strncpy(sendbuf, "abc CAPABILITY\r\n", MAXDATASIZE);
742                         if (imap_command(sockfd, sendbuf, recvbuf, "abc OK")) {
743                                 fail++;
744                                 break;
745                         }
746                         if (strstr(recvbuf, " IDLE ") != NULL) {
747                                 has_idle = 1;
748                         }
749
750                         strncpy(sendbuf, "a1 login ", MAXDATASIZE);
751                         strncat(sendbuf, mail->user, MAXDATASIZE - strlen(sendbuf) - 1);
752                         strncat(sendbuf, " ", MAXDATASIZE - strlen(sendbuf) - 1);
753                         strncat(sendbuf, mail->pass, MAXDATASIZE - strlen(sendbuf) - 1);
754                         strncat(sendbuf, "\r\n", MAXDATASIZE - strlen(sendbuf) - 1);
755                         if (imap_command(sockfd, sendbuf, recvbuf, "a1 OK")) {
756                                 fail++;
757                                 break;
758                         }
759
760                         strncpy(sendbuf, "a2 STATUS \"", MAXDATASIZE);
761                         strncat(sendbuf, mail->folder, MAXDATASIZE - strlen(sendbuf) - 1);
762                         strncat(sendbuf, "\" (MESSAGES UNSEEN)\r\n",
763                                         MAXDATASIZE - strlen(sendbuf) - 1);
764                         if (imap_command(sockfd, sendbuf, recvbuf, "a2 OK")) {
765                                 fail++;
766                                 break;
767                         }
768
769                         if (imap_check_status(recvbuf, mail)) {
770                                 fail++;
771                                 break;
772                         }
773                         imap_unseen_command(mail, old_unseen, old_messages);
774                         fail = 0;
775                         old_unseen = mail->unseen;
776                         old_messages = mail->messages;
777
778                         if (has_idle) {
779                                 strncpy(sendbuf, "a4 SELECT \"", MAXDATASIZE);
780                                 strncat(sendbuf, mail->folder, MAXDATASIZE - strlen(sendbuf) - 1);
781                                 strncat(sendbuf, "\"\r\n", MAXDATASIZE - strlen(sendbuf) - 1);
782                                 if (imap_command(sockfd, sendbuf, recvbuf, "a4 OK")) {
783                                         fail++;
784                                         break;
785                                 }
786
787                                 strncpy(sendbuf, "a5 IDLE\r\n", MAXDATASIZE);
788                                 if (imap_command(sockfd, sendbuf, recvbuf, "+ idling")) {
789                                         fail++;
790                                         break;
791                                 }
792                                 recvbuf[0] = '\0';
793
794                                 while (1) {
795                                         /*
796                                          * RFC 2177 says we have to re-idle every 29 minutes.
797                                          * We'll do it every 10 minutes to be safe.
798                                          */
799                                         fetchtimeout.tv_sec = 600;
800                                         fetchtimeout.tv_usec = 0;
801                                         DBGP("idling...");
802                                         FD_ZERO(&fdset);
803                                         FD_SET(sockfd, &fdset);
804                                         FD_SET(threadfd, &fdset);
805                                         res = select(MAX(sockfd + 1, threadfd + 1), &fdset, NULL,
806                                                         NULL, &fetchtimeout);
807                                         DBGP("done idling");
808                                         if (timed_thread_test(mail->p_timed_thread, 1) || (res == -1 && errno == EINTR) || FD_ISSET(threadfd, &fdset)) {
809                                                 if ((fstat(sockfd, &stat_buf) == 0) && S_ISSOCK(stat_buf.st_mode)) {
810                                                         /* if a valid socket, close it */
811                                                         close(sockfd);
812                                                 }
813                                                 timed_thread_exit(mail->p_timed_thread);
814                                         } else if (res > 0) {
815                                                 if ((numbytes = recv(sockfd, recvbuf, MAXDATASIZE - 1, 0)) == -1) {
816                                                         perror("recv idling");
817                                                         fail++;
818                                                         break;
819                                                 }
820                                         }
821                                         recvbuf[numbytes] = '\0';
822                                         DBGP2("imap_thread() received: %s", recvbuf);
823                                         if (strlen(recvbuf) > 2) {
824                                                 unsigned long messages, recent = 0;
825                                                 char *buf = recvbuf;
826                                                 char force_check = 0;
827                                                 buf = strstr(buf, "EXISTS");
828                                                 while (buf && strlen(buf) > 1 && strstr(buf + 1, "EXISTS")) {
829                                                         buf = strstr(buf + 1, "EXISTS");
830                                                 }
831                                                 if (buf) {
832                                                         // back up until we reach '*'
833                                                         while (buf >= recvbuf && buf[0] != '*') {
834                                                                 buf--;
835                                                         }
836                                                         if (sscanf(buf, "* %lu EXISTS\r\n", &messages) == 1) {
837                                                                 timed_thread_lock(mail->p_timed_thread);
838                                                                 if (mail->messages != messages) {
839                                                                         force_check = 1;
840                                                                 }
841                                                                 timed_thread_unlock(mail->p_timed_thread);
842                                                         }
843                                                 }
844                                                 buf = recvbuf;
845                                                 buf = strstr(buf, "RECENT");
846                                                 while (buf && strlen(buf) > 1 && strstr(buf + 1, "RECENT")) {
847                                                         buf = strstr(buf + 1, "RECENT");
848                                                 }
849                                                 if (buf) {
850                                                         // back up until we reach '*'
851                                                         while (buf >= recvbuf && buf[0] != '*') {
852                                                                 buf--;
853                                                         }
854                                                         if (sscanf(buf, "* %lu RECENT\r\n", &recent) != 1) {
855                                                                 recent = 0;
856                                                         }
857                                                 }
858                                                 /*
859                                                  * check if we got a FETCH from server, recent was
860                                                  * something other than 0, or we had a timeout
861                                                  */
862                                                 buf = recvbuf;
863                                                 if (recent > 0 || (buf && strstr(buf, " FETCH ")) || fetchtimeout.tv_sec == 0 || force_check) {
864                                                         // re-check messages and unseen
865                                                         if (imap_command(sockfd, "DONE\r\n", recvbuf, "a5 OK")) {
866                                                                 fail++;
867                                                                 break;
868                                                         }
869                                                         strncpy(sendbuf, "a2 STATUS \"", MAXDATASIZE);
870                                                         strncat(sendbuf, mail->folder, MAXDATASIZE - strlen(sendbuf) - 1);
871                                                         strncat(sendbuf, "\" (MESSAGES UNSEEN)\r\n",
872                                                                         MAXDATASIZE - strlen(sendbuf) - 1);
873                                                         if (imap_command(sockfd, sendbuf, recvbuf, "a2 OK")) {
874                                                                 fail++;
875                                                                 break;
876                                                         }
877                                                         if (imap_check_status(recvbuf, mail)) {
878                                                                 fail++;
879                                                                 break;
880                                                         }
881                                                         strncpy(sendbuf, "a5 IDLE\r\n", MAXDATASIZE);
882                                                         if (imap_command(sockfd, sendbuf, recvbuf, "+ idling")) {
883                                                                 fail++;
884                                                                 break;
885                                                         }
886                                                 }
887                                                 /*
888                                                  * check if we got a BYE from server
889                                                  */
890                                                 buf = recvbuf;
891                                                 if (buf && strstr(buf, "* BYE")) {
892                                                         // need to re-connect
893                                                         break;
894                                                 }
895                                         } else {
896                                                 fail++;
897                                                 break;
898                                         }
899                                         imap_unseen_command(mail, old_unseen, old_messages);
900                                         fail = 0;
901                                         old_unseen = mail->unseen;
902                                         old_messages = mail->messages;
903                                 }
904                                 if (fail) break;
905                         } else {
906                                 strncpy(sendbuf, "a3 logout\r\n", MAXDATASIZE);
907                                 if (send(sockfd, sendbuf, strlen(sendbuf), 0) == -1) {
908                                         perror("send a3");
909                                         fail++;
910                                         break;
911                                 }
912                                 fetchtimeout.tv_sec = 60;       // 60 second timeout i guess
913                                 fetchtimeout.tv_usec = 0;
914                                 FD_ZERO(&fdset);
915                                 FD_SET(sockfd, &fdset);
916                                 res = select(sockfd + 1, &fdset, NULL, NULL, &fetchtimeout);
917                                 if (res > 0) {
918                                         if ((numbytes = recv(sockfd, recvbuf, MAXDATASIZE - 1, 0)) == -1) {
919                                                 perror("recv a3");
920                                                 fail++;
921                                                 break;
922                                         }
923                                 }
924                                 recvbuf[numbytes] = '\0';
925                                 DBGP2("imap_thread() received: %s", recvbuf);
926                                 if (strstr(recvbuf, "a3 OK") == NULL) {
927                                         NORM_ERR("IMAP logout failed: %s", recvbuf);
928                                         fail++;
929                                         break;
930                                 }
931                         }
932                 } while (0);
933                 if ((fstat(sockfd, &stat_buf) == 0) && S_ISSOCK(stat_buf.st_mode)) {
934                         /* if a valid socket, close it */
935                         close(sockfd);
936                 }
937                 if (timed_thread_test(mail->p_timed_thread, 0)) {
938                         timed_thread_exit(mail->p_timed_thread);
939                 }
940         }
941         mail->unseen = 0;
942         mail->messages = 0;
943         return 0;
944 }
945
946 void print_imap_unseen(struct text_object *obj, char *p, int p_max_size)
947 {
948         struct mail_s *mail = obj->data.opaque;
949
950         if (!mail)
951                 return;
952
953         ensure_mail_thread(mail, imap_thread, "imap");
954
955         if (mail && mail->p_timed_thread) {
956                 timed_thread_lock(mail->p_timed_thread);
957                 snprintf(p, p_max_size, "%lu", mail->unseen);
958                 timed_thread_unlock(mail->p_timed_thread);
959         }
960 }
961
962 void print_imap_messages(struct text_object *obj, char *p, int p_max_size)
963 {
964         struct mail_s *mail = obj->data.opaque;
965
966         if (!mail)
967                 return;
968
969         ensure_mail_thread(mail, imap_thread, "imap");
970
971         if (mail && mail->p_timed_thread) {
972                 timed_thread_lock(mail->p_timed_thread);
973                 snprintf(p, p_max_size, "%lu", mail->messages);
974                 timed_thread_unlock(mail->p_timed_thread);
975         }
976 }
977
978 int pop3_command(int sockfd, const char *command, char *response, const char *verify)
979 {
980         struct timeval fetchtimeout;
981         fd_set fdset;
982         int res, numbytes = 0;
983         if (send(sockfd, command, strlen(command), 0) == -1) {
984                 perror("send");
985                 return -1;
986         }
987         fetchtimeout.tv_sec = 60;       // 60 second timeout i guess
988         fetchtimeout.tv_usec = 0;
989         FD_ZERO(&fdset);
990         FD_SET(sockfd, &fdset);
991         res = select(sockfd + 1, &fdset, NULL, NULL, &fetchtimeout);
992         if (res > 0) {
993                 if ((numbytes = recv(sockfd, response, MAXDATASIZE - 1, 0)) == -1) {
994                         perror("recv");
995                         return -1;
996                 }
997         }
998         DBGP2("pop3_command() received: %s", response);
999         response[numbytes] = '\0';
1000         if (strstr(response, verify) == NULL) {
1001                 return -1;
1002         }
1003         return 0;
1004 }
1005
1006 static void *pop3_thread(void *arg)
1007 {
1008         int sockfd, numbytes;
1009         char recvbuf[MAXDATASIZE];
1010         char sendbuf[MAXDATASIZE];
1011         char *reply;
1012         unsigned int fail = 0;
1013         unsigned long old_unseen = ULONG_MAX;
1014         struct stat stat_buf;
1015         struct hostent he, *he_res = 0;
1016         int he_errno;
1017         char hostbuff[2048];
1018         struct sockaddr_in their_addr;  // connector's address information
1019         struct mail_s *mail = (struct mail_s *)arg;
1020         char resolved_host = 0;
1021
1022         while (fail < mail->retries) {
1023                 struct timeval fetchtimeout;
1024                 int res;
1025                 fd_set fdset;
1026                 if (!resolved_host) {
1027 #ifdef HAVE_GETHOSTBYNAME_R
1028                         if (gethostbyname_r(mail->host, &he, hostbuff, sizeof(hostbuff), &he_res, &he_errno)) { // get the host info
1029                                 NORM_ERR("POP3 gethostbyname_r: %s", hstrerror(h_errno));
1030                                 fail++;
1031                                 break;
1032                         }
1033 #else /* HAVE_GETHOSTBYNAME_R */
1034                         if ((he_res = gethostbyname(mail->host)) == NULL) {     // get the host info
1035                                 herror("gethostbyname");
1036                 fail++;
1037                 break;
1038         }
1039 #endif /* HAVE_GETHOSTBYNAME_R */
1040         resolved_host = 1;
1041 }
1042                 if (fail > 0) {
1043                         NORM_ERR("Trying POP3 connection again for %s@%s (try %u/%u)",
1044                                         mail->user, mail->host, fail + 1, mail->retries);
1045                 }
1046                 do {
1047                         if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
1048                                 perror("socket");
1049                                 fail++;
1050                                 break;
1051                         }
1052
1053                         // host byte order
1054                         their_addr.sin_family = AF_INET;
1055                         // short, network byte order
1056                         their_addr.sin_port = htons(mail->port);
1057                         their_addr.sin_addr = *((struct in_addr *) he_res->h_addr);
1058                         // zero the rest of the struct
1059                         memset(&(their_addr.sin_zero), '\0', 8);
1060
1061                         if (connect(sockfd, (struct sockaddr *) &their_addr,
1062                                                 sizeof(struct sockaddr)) == -1) {
1063                                 perror("connect");
1064                                 fail++;
1065                                 break;
1066                         }
1067
1068                         fetchtimeout.tv_sec = 60;       // 60 second timeout i guess
1069                         fetchtimeout.tv_usec = 0;
1070                         FD_ZERO(&fdset);
1071                         FD_SET(sockfd, &fdset);
1072                         res = select(sockfd + 1, &fdset, NULL, NULL, &fetchtimeout);
1073                         if (res > 0) {
1074                                 if ((numbytes = recv(sockfd, recvbuf, MAXDATASIZE - 1, 0)) == -1) {
1075                                         perror("recv");
1076                                         fail++;
1077                                         break;
1078                                 }
1079                         } else {
1080                                 NORM_ERR("POP3 connection failed: timeout\n");
1081                                 fail++;
1082                                 break;
1083                         }
1084                         DBGP2("pop3_thread received: %s", recvbuf);
1085                         recvbuf[numbytes] = '\0';
1086                         if (strstr(recvbuf, "+OK ") != recvbuf) {
1087                                 NORM_ERR("POP3 connection failed, probably not a POP3 server");
1088                                 fail++;
1089                                 break;
1090                         }
1091                         strncpy(sendbuf, "USER ", MAXDATASIZE);
1092                         strncat(sendbuf, mail->user, MAXDATASIZE - strlen(sendbuf) - 1);
1093                         strncat(sendbuf, "\r\n", MAXDATASIZE - strlen(sendbuf) - 1);
1094                         if (pop3_command(sockfd, sendbuf, recvbuf, "+OK ")) {
1095                                 fail++;
1096                                 break;
1097                         }
1098
1099                         strncpy(sendbuf, "PASS ", MAXDATASIZE);
1100                         strncat(sendbuf, mail->pass, MAXDATASIZE - strlen(sendbuf) - 1);
1101                         strncat(sendbuf, "\r\n", MAXDATASIZE - strlen(sendbuf) - 1);
1102                         if (pop3_command(sockfd, sendbuf, recvbuf, "+OK ")) {
1103                                 NORM_ERR("POP3 server login failed: %s", recvbuf);
1104                                 fail++;
1105                                 break;
1106                         }
1107
1108                         strncpy(sendbuf, "STAT\r\n", MAXDATASIZE);
1109                         if (pop3_command(sockfd, sendbuf, recvbuf, "+OK ")) {
1110                                 perror("send STAT");
1111                                 fail++;
1112                                 break;
1113                         }
1114
1115                         // now we get the data
1116                         reply = recvbuf + 4;
1117                         if (reply == NULL) {
1118                                 NORM_ERR("Error parsing POP3 response: %s", recvbuf);
1119                                 fail++;
1120                                 break;
1121                         } else {
1122                                 timed_thread_lock(mail->p_timed_thread);
1123                                 sscanf(reply, "%lu %lu", &mail->unseen, &mail->used);
1124                                 timed_thread_unlock(mail->p_timed_thread);
1125                         }
1126
1127                         strncpy(sendbuf, "QUIT\r\n", MAXDATASIZE);
1128                         if (pop3_command(sockfd, sendbuf, recvbuf, "+OK")) {
1129                                 NORM_ERR("POP3 logout failed: %s", recvbuf);
1130                                 fail++;
1131                                 break;
1132                         }
1133
1134                         if (strlen(mail->command) > 1 && mail->unseen > old_unseen) {
1135                                 // new mail goodie
1136                                 if (system(mail->command) == -1) {
1137                                         perror("system()");
1138                                 }
1139                         }
1140                         fail = 0;
1141                         old_unseen = mail->unseen;
1142                 } while (0);
1143                 if ((fstat(sockfd, &stat_buf) == 0) && S_ISSOCK(stat_buf.st_mode)) {
1144                         /* if a valid socket, close it */
1145                         close(sockfd);
1146                 }
1147                 if (timed_thread_test(mail->p_timed_thread, 0)) {
1148                         timed_thread_exit(mail->p_timed_thread);
1149                 }
1150         }
1151         mail->unseen = 0;
1152         mail->used = 0;
1153         return 0;
1154 }
1155
1156 void print_pop3_unseen(struct text_object *obj, char *p, int p_max_size)
1157 {
1158         struct mail_s *mail = obj->data.opaque;
1159
1160         if (!mail)
1161                 return;
1162
1163         ensure_mail_thread(mail, pop3_thread, "pop3");
1164
1165         if (mail && mail->p_timed_thread) {
1166                 timed_thread_lock(mail->p_timed_thread);
1167                 snprintf(p, p_max_size, "%lu", mail->unseen);
1168                 timed_thread_unlock(mail->p_timed_thread);
1169         }
1170 }
1171
1172 void print_pop3_used(struct text_object *obj, char *p, int p_max_size)
1173 {
1174         struct mail_s *mail = obj->data.opaque;
1175
1176         if (!mail)
1177                 return;
1178
1179         ensure_mail_thread(mail, pop3_thread, "pop3");
1180
1181         if (mail && mail->p_timed_thread) {
1182                 timed_thread_lock(mail->p_timed_thread);
1183                 snprintf(p, p_max_size, "%.1f",
1184                                 mail->used / 1024.0 / 1024.0);
1185                 timed_thread_unlock(mail->p_timed_thread);
1186         }
1187 }