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