Fix segfault in mail folder (SF: 3007493)
[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 MAXFOLDERSIZE 128
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[128];
76         char user[128];
77         char pass[128];
78         char command[1024];
79         char folder[MAXFOLDERSIZE];
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         if (sscanf(arg, "%128s %128s %128s", mail->host, mail->user, mail->pass)
419                         != 3) {
420                 if (type == POP3_TYPE) {
421                         NORM_ERR("Scanning POP3 args failed");
422                 } else if (type == IMAP_TYPE) {
423                         NORM_ERR("Scanning IMAP args failed");
424                 }
425                 return 0;
426         }
427         // see if password needs prompting
428         if (mail->pass[0] == '*' && mail->pass[1] == '\0') {
429                 int fp = fileno(stdin);
430                 struct termios term;
431
432                 tcgetattr(fp, &term);
433                 term.c_lflag &= ~ECHO;
434                 tcsetattr(fp, TCSANOW, &term);
435                 printf("Enter mailbox password (%s@%s): ", mail->user, mail->host);
436                 scanf("%128s", mail->pass);
437                 printf("\n");
438                 term.c_lflag |= ECHO;
439                 tcsetattr(fp, TCSANOW, &term);
440         }
441         // now we check for optional args
442         tmp = strstr(arg, "-r ");
443         if (tmp) {
444                 tmp += 3;
445                 sscanf(tmp, "%u", &mail->retries);
446         } else {
447                 mail->retries = 5;      // 5 retries after failure
448         }
449         tmp = strstr(arg, "-i ");
450         if (tmp) {
451                 tmp += 3;
452                 sscanf(tmp, "%f", &mail->interval);
453         } else {
454                 mail->interval = 300;   // 5 minutes
455         }
456         tmp = strstr(arg, "-p ");
457         if (tmp) {
458                 tmp += 3;
459                 sscanf(tmp, "%lu", &mail->port);
460         } else {
461                 if (type == POP3_TYPE) {
462                         mail->port = 110;       // default pop3 port
463                 } else if (type == IMAP_TYPE) {
464                         mail->port = 143;       // default imap port
465                 }
466         }
467         if (type == IMAP_TYPE) {
468                 tmp = strstr(arg, "-f ");
469                 if (tmp) {
470                         int len = MAXFOLDERSIZE-1;
471                         tmp += 3;
472                         if (tmp[0] == '\'') {
473                                 len = strstr(tmp + 1, "'") - tmp - 1;
474                                 if (len > MAXFOLDERSIZE-1) {
475                                         len = MAXFOLDERSIZE-1;
476                                 }
477                                 tmp++;
478                         }
479                         strncpy(mail->folder, tmp, len);
480                         mail->folder[len-1] = 0;
481                 } else {
482                         strncpy(mail->folder, "INBOX", MAXFOLDERSIZE-1);        // default imap inbox
483                         mail->folder[MAXFOLDERSIZE-1] = 0;
484                 }
485         }
486         tmp = strstr(arg, "-e ");
487         if (tmp) {
488                 int len = 1024;
489                 tmp += 3;
490
491                 if (tmp[0] == '\'') {
492                         len = strstr(tmp + 1, "'") - tmp - 1;
493                         if (len > 1024) {
494                                 len = 1024;
495                         }
496                 }
497                 strncpy(mail->command, tmp + 1, len);
498         } else {
499                 mail->command[0] = '\0';
500         }
501         mail->p_timed_thread = NULL;
502         return mail;
503 }
504
505 void parse_imap_mail_args(struct text_object *obj, const char *arg)
506 {
507         static int rep = 0;
508
509         if (!arg) {
510                 if (!global_mail && !rep) {
511                         // something is wrong, warn once then stop
512                         NORM_ERR("There's a problem with your mail settings.  "
513                                         "Check that the global mail settings are properly defined"
514                                         " (line %li).", obj->line);
515                         rep = 1;
516                         return;
517                 }
518                 obj->data.opaque = global_mail;
519                 global_mail_use++;
520                 return;
521         }
522         // proccss
523         obj->data.opaque = parse_mail_args(IMAP_TYPE, arg);
524 }
525
526 void parse_pop3_mail_args(struct text_object *obj, const char *arg)
527 {
528         static int rep = 0;
529
530         if (!arg) {
531                 if (!global_mail && !rep) {
532                         // something is wrong, warn once then stop
533                         NORM_ERR("There's a problem with your mail settings.  "
534                                         "Check that the global mail settings are properly defined"
535                                         " (line %li).", obj->line);
536                         rep = 1;
537                         return;
538                 }
539                 obj->data.opaque = global_mail;
540                 global_mail_use++;
541                 return;
542         }
543         // proccss
544         obj->data.opaque = parse_mail_args(POP3_TYPE, arg);
545 }
546
547 void parse_global_imap_mail_args(const char *value)
548 {
549         global_mail = parse_mail_args(IMAP_TYPE, value);
550 }
551
552 void parse_global_pop3_mail_args(const char *value)
553 {
554         global_mail = parse_mail_args(POP3_TYPE, value);
555 }
556
557 void free_mail_obj(struct text_object *obj)
558 {
559         if (!obj->data.opaque)
560                 return;
561
562         if (obj->data.opaque == global_mail) {
563                 if (--global_mail_use == 0) {
564                         free(global_mail);
565                         global_mail = 0;
566                 }
567         } else {
568                 free(obj->data.opaque);
569                 obj->data.opaque = 0;
570         }
571 }
572
573 int imap_command(int sockfd, const char *command, char *response, const char *verify)
574 {
575         struct timeval fetchtimeout;
576         fd_set fdset;
577         int res, numbytes = 0;
578         if (send(sockfd, command, strlen(command), 0) == -1) {
579                 perror("send");
580                 return -1;
581         }
582         fetchtimeout.tv_sec = 60;       // 60 second timeout i guess
583         fetchtimeout.tv_usec = 0;
584         FD_ZERO(&fdset);
585         FD_SET(sockfd, &fdset);
586         res = select(sockfd + 1, &fdset, NULL, NULL, &fetchtimeout);
587         if (res > 0) {
588                 if ((numbytes = recv(sockfd, response, MAXDATASIZE - 1, 0)) == -1) {
589                         perror("recv");
590                         return -1;
591                 }
592         }
593         DBGP2("imap_command()  command: %s", command);
594         DBGP2("imap_command() received: %s", response);
595         response[numbytes] = '\0';
596         if (strstr(response, verify) == NULL) {
597                 return -1;
598         }
599         return 0;
600 }
601
602 int imap_check_status(char *recvbuf, struct mail_s *mail)
603 {
604         char *reply;
605         reply = strstr(recvbuf, " (MESSAGES ");
606         if (!reply || strlen(reply) < 2) {
607                 return -1;
608         }
609         reply += 2;
610         *strchr(reply, ')') = '\0';
611         if (reply == NULL) {
612                 NORM_ERR("Error parsing IMAP response: %s", recvbuf);
613                 return -1;
614         } else {
615                 timed_thread_lock(mail->p_timed_thread);
616                 sscanf(reply, "MESSAGES %lu UNSEEN %lu", &mail->messages,
617                                 &mail->unseen);
618                 timed_thread_unlock(mail->p_timed_thread);
619         }
620         return 0;
621 }
622
623 void imap_unseen_command(struct mail_s *mail, unsigned long old_unseen, unsigned long old_messages)
624 {
625         if (strlen(mail->command) > 1 && (mail->unseen > old_unseen
626                                 || (mail->messages > old_messages && mail->unseen > 0))) {
627                 // new mail goodie
628                 if (system(mail->command) == -1) {
629                         perror("system()");
630                 }
631         }
632 }
633
634 static void ensure_mail_thread(struct mail_s *mail,
635                 void *thread(void *), const char *text)
636 {
637         if (mail->p_timed_thread)
638                 return;
639
640         mail->p_timed_thread = timed_thread_create(thread,
641                                 mail, mail->interval * 1000000);
642         if (!mail->p_timed_thread) {
643                 NORM_ERR("Error creating %s timed thread", text);
644         }
645         timed_thread_register(mail->p_timed_thread,
646                         &mail->p_timed_thread);
647         if (timed_thread_run(mail->p_timed_thread)) {
648                 NORM_ERR("Error running %s timed thread", text);
649         }
650 }
651
652 static void *imap_thread(void *arg)
653 {
654         int sockfd, numbytes;
655         char recvbuf[MAXDATASIZE];
656         char sendbuf[MAXDATASIZE];
657         unsigned int fail = 0;
658         unsigned long old_unseen = ULONG_MAX;
659         unsigned long old_messages = ULONG_MAX;
660         struct stat stat_buf;
661         struct hostent he, *he_res = 0;
662         int he_errno;
663         char hostbuff[2048];
664         struct sockaddr_in their_addr;  // connector's address information
665         struct mail_s *mail = (struct mail_s *)arg;
666         int has_idle = 0;
667         int threadfd = timed_thread_readfd(mail->p_timed_thread);
668         char resolved_host = 0;
669
670         while (fail < mail->retries) {
671                 struct timeval fetchtimeout;
672                 int res;
673                 fd_set fdset;
674
675                 if (!resolved_host) {
676 #ifdef HAVE_GETHOSTBYNAME_R
677                         if (gethostbyname_r(mail->host, &he, hostbuff, sizeof(hostbuff), &he_res, &he_errno)) { // get the host info
678                                 NORM_ERR("IMAP gethostbyname_r: %s", hstrerror(h_errno));
679                                 fail++;
680                                 break;
681                         }
682 #else /* HAVE_GETHOSTBYNAME_R */
683                         if ((he_res = gethostbyname(mail->host)) == NULL) {     // get the host info
684                                 herror("gethostbyname");
685                                 fail++;
686                                 break;
687                         }
688 #endif /* HAVE_GETHOSTBYNAME_R */
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                         if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
697                                 perror("socket");
698                                 fail++;
699                                 break;
700                         }
701
702                         // host byte order
703                         their_addr.sin_family = AF_INET;
704                         // short, network byte order
705                         their_addr.sin_port = htons(mail->port);
706                         their_addr.sin_addr = *((struct in_addr *) he_res->h_addr);
707                         // zero the rest of the struct
708                         memset(&(their_addr.sin_zero), '\0', 8);
709
710                         if (connect(sockfd, (struct sockaddr *) &their_addr,
711                                                 sizeof(struct sockaddr)) == -1) {
712                                 perror("connect");
713                                 fail++;
714                                 break;
715                         }
716
717                         fetchtimeout.tv_sec = 60;       // 60 second timeout i guess
718                         fetchtimeout.tv_usec = 0;
719                         FD_ZERO(&fdset);
720                         FD_SET(sockfd, &fdset);
721                         res = select(sockfd + 1, &fdset, NULL, NULL, &fetchtimeout);
722                         if (res > 0) {
723                                 if ((numbytes = recv(sockfd, recvbuf, MAXDATASIZE - 1, 0)) == -1) {
724                                         perror("recv");
725                                         fail++;
726                                         break;
727                                 }
728                         } else {
729                                 NORM_ERR("IMAP connection failed: timeout");
730                                 fail++;
731                                 break;
732                         }
733                         recvbuf[numbytes] = '\0';
734                         DBGP2("imap_thread() received: %s", recvbuf);
735                         if (strstr(recvbuf, "* OK") != recvbuf) {
736                                 NORM_ERR("IMAP connection failed, probably not an IMAP server");
737                                 fail++;
738                                 break;
739                         }
740                         strncpy(sendbuf, "abc CAPABILITY\r\n", MAXDATASIZE);
741                         if (imap_command(sockfd, sendbuf, recvbuf, "abc OK")) {
742                                 fail++;
743                                 break;
744                         }
745                         if (strstr(recvbuf, " IDLE ") != NULL) {
746                                 has_idle = 1;
747                         }
748
749                         strncpy(sendbuf, "a1 login ", MAXDATASIZE);
750                         strncat(sendbuf, mail->user, MAXDATASIZE - strlen(sendbuf) - 1);
751                         strncat(sendbuf, " ", MAXDATASIZE - strlen(sendbuf) - 1);
752                         strncat(sendbuf, mail->pass, MAXDATASIZE - strlen(sendbuf) - 1);
753                         strncat(sendbuf, "\r\n", MAXDATASIZE - strlen(sendbuf) - 1);
754                         if (imap_command(sockfd, sendbuf, recvbuf, "a1 OK")) {
755                                 fail++;
756                                 break;
757                         }
758
759                         strncpy(sendbuf, "a2 STATUS \"", MAXDATASIZE);
760                         strncat(sendbuf, mail->folder, MAXDATASIZE - strlen(sendbuf) - 1);
761                         strncat(sendbuf, "\" (MESSAGES UNSEEN)\r\n",
762                                         MAXDATASIZE - strlen(sendbuf) - 1);
763                         if (imap_command(sockfd, sendbuf, recvbuf, "a2 OK")) {
764                                 fail++;
765                                 break;
766                         }
767
768                         if (imap_check_status(recvbuf, mail)) {
769                                 fail++;
770                                 break;
771                         }
772                         imap_unseen_command(mail, old_unseen, old_messages);
773                         fail = 0;
774                         old_unseen = mail->unseen;
775                         old_messages = mail->messages;
776
777                         if (has_idle) {
778                                 strncpy(sendbuf, "a4 SELECT \"", MAXDATASIZE);
779                                 strncat(sendbuf, mail->folder, MAXDATASIZE - strlen(sendbuf) - 1);
780                                 strncat(sendbuf, "\"\r\n", MAXDATASIZE - strlen(sendbuf) - 1);
781                                 if (imap_command(sockfd, sendbuf, recvbuf, "a4 OK")) {
782                                         fail++;
783                                         break;
784                                 }
785
786                                 strncpy(sendbuf, "a5 IDLE\r\n", MAXDATASIZE);
787                                 if (imap_command(sockfd, sendbuf, recvbuf, "+ idling")) {
788                                         fail++;
789                                         break;
790                                 }
791                                 recvbuf[0] = '\0';
792
793                                 while (1) {
794                                         /*
795                                          * RFC 2177 says we have to re-idle every 29 minutes.
796                                          * We'll do it every 20 minutes to be safe.
797                                          */
798                                         fetchtimeout.tv_sec = 1200;
799                                         fetchtimeout.tv_usec = 0;
800                                         DBGP2("idling...");
801                                         FD_ZERO(&fdset);
802                                         FD_SET(sockfd, &fdset);
803                                         FD_SET(threadfd, &fdset);
804                                         res = select(MAX(sockfd + 1, threadfd + 1), &fdset, NULL, NULL, &fetchtimeout);
805                                         if (timed_thread_test(mail->p_timed_thread, 1) || (res == -1 && errno == EINTR) || FD_ISSET(threadfd, &fdset)) {
806                                                 if ((fstat(sockfd, &stat_buf) == 0) && S_ISSOCK(stat_buf.st_mode)) {
807                                                         /* if a valid socket, close it */
808                                                         close(sockfd);
809                                                 }
810                                                 timed_thread_exit(mail->p_timed_thread);
811                                         } else if (res > 0) {
812                                                 if ((numbytes = recv(sockfd, recvbuf, MAXDATASIZE - 1, 0)) == -1) {
813                                                         perror("recv idling");
814                                                         fail++;
815                                                         break;
816                                                 }
817                                         } else {
818                                                 fail++;
819                                                 break;
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                                                                         mail->messages = messages;
841                                                                 }
842                                                                 timed_thread_unlock(mail->p_timed_thread);
843                                                         }
844                                                 }
845                                                 buf = recvbuf;
846                                                 buf = strstr(buf, "RECENT");
847                                                 while (buf && strlen(buf) > 1 && strstr(buf + 1, "RECENT")) {
848                                                         buf = strstr(buf + 1, "RECENT");
849                                                 }
850                                                 if (buf) {
851                                                         // back up until we reach '*'
852                                                         while (buf >= recvbuf && buf[0] != '*') {
853                                                                 buf--;
854                                                         }
855                                                         if (sscanf(buf, "* %lu RECENT\r\n", &recent) != 1) {
856                                                                 recent = 0;
857                                                         }
858                                                 }
859                                                 /*
860                                                  * check if we got a FETCH from server, recent was
861                                                  * something other than 0, or we had a timeout
862                                                  */
863                                                 buf = recvbuf;
864                                                 if (recent > 0 || (buf && strstr(buf, " FETCH ")) || fetchtimeout.tv_sec == 0 || force_check) {
865                                                         // re-check messages and unseen
866                                                         if (imap_command(sockfd, "DONE\r\n", recvbuf, "a5 OK")) {
867                                                                 fail++;
868                                                                 break;
869                                                         }
870                                                         strncpy(sendbuf, "a2 STATUS \"", MAXDATASIZE);
871                                                         strncat(sendbuf, mail->folder, MAXDATASIZE - strlen(sendbuf) - 1);
872                                                         strncat(sendbuf, "\" (MESSAGES UNSEEN)\r\n",
873                                                                         MAXDATASIZE - strlen(sendbuf) - 1);
874                                                         if (imap_command(sockfd, sendbuf, recvbuf, "a2 OK")) {
875                                                                 fail++;
876                                                                 break;
877                                                         }
878                                                         if (imap_check_status(recvbuf, mail)) {
879                                                                 fail++;
880                                                                 break;
881                                                         }
882                                                         strncpy(sendbuf, "a5 IDLE\r\n", MAXDATASIZE);
883                                                         if (imap_command(sockfd, sendbuf, recvbuf, "+ idling")) {
884                                                                 fail++;
885                                                                 break;
886                                                         }
887                                                 }
888                                                 /*
889                                                  * check if we got a BYE from server
890                                                  */
891                                                 buf = recvbuf;
892                                                 if (buf && strstr(buf, "* BYE")) {
893                                                         // need to re-connect
894                                                         break;
895                                                 }
896                                         } else {
897                                                 fail++;
898                                                 break;
899                                         }
900                                         imap_unseen_command(mail, old_unseen, old_messages);
901                                         fail = 0;
902                                         old_unseen = mail->unseen;
903                                         old_messages = mail->messages;
904                                 }
905                                 if (fail) break;
906                         } else {
907                                 strncpy(sendbuf, "a3 logout\r\n", MAXDATASIZE);
908                                 if (send(sockfd, sendbuf, strlen(sendbuf), 0) == -1) {
909                                         perror("send a3");
910                                         fail++;
911                                         break;
912                                 }
913                                 fetchtimeout.tv_sec = 60;       // 60 second timeout i guess
914                                 fetchtimeout.tv_usec = 0;
915                                 FD_ZERO(&fdset);
916                                 FD_SET(sockfd, &fdset);
917                                 res = select(sockfd + 1, &fdset, NULL, NULL, &fetchtimeout);
918                                 if (res > 0) {
919                                         if ((numbytes = recv(sockfd, recvbuf, MAXDATASIZE - 1, 0)) == -1) {
920                                                 perror("recv a3");
921                                                 fail++;
922                                                 break;
923                                         }
924                                 }
925                                 recvbuf[numbytes] = '\0';
926                                 DBGP2("imap_thread() received: %s", recvbuf);
927                                 if (strstr(recvbuf, "a3 OK") == NULL) {
928                                         NORM_ERR("IMAP logout failed: %s", recvbuf);
929                                         fail++;
930                                         break;
931                                 }
932                         }
933                 } while (0);
934                 if ((fstat(sockfd, &stat_buf) == 0) && S_ISSOCK(stat_buf.st_mode)) {
935                         /* if a valid socket, close it */
936                         close(sockfd);
937                 }
938                 if (timed_thread_test(mail->p_timed_thread, 0)) {
939                         timed_thread_exit(mail->p_timed_thread);
940                 }
941         }
942         mail->unseen = 0;
943         mail->messages = 0;
944         return 0;
945 }
946
947 void print_imap_unseen(struct text_object *obj, char *p, int p_max_size)
948 {
949         struct mail_s *mail = obj->data.opaque;
950
951         if (!mail)
952                 return;
953
954         ensure_mail_thread(mail, imap_thread, "imap");
955
956         if (mail && mail->p_timed_thread) {
957                 timed_thread_lock(mail->p_timed_thread);
958                 snprintf(p, p_max_size, "%lu", mail->unseen);
959                 timed_thread_unlock(mail->p_timed_thread);
960         }
961 }
962
963 void print_imap_messages(struct text_object *obj, char *p, int p_max_size)
964 {
965         struct mail_s *mail = obj->data.opaque;
966
967         if (!mail)
968                 return;
969
970         ensure_mail_thread(mail, imap_thread, "imap");
971
972         if (mail && mail->p_timed_thread) {
973                 timed_thread_lock(mail->p_timed_thread);
974                 snprintf(p, p_max_size, "%lu", mail->messages);
975                 timed_thread_unlock(mail->p_timed_thread);
976         }
977 }
978
979 int pop3_command(int sockfd, const char *command, char *response, const char *verify)
980 {
981         struct timeval fetchtimeout;
982         fd_set fdset;
983         int res, numbytes = 0;
984         if (send(sockfd, command, strlen(command), 0) == -1) {
985                 perror("send");
986                 return -1;
987         }
988         fetchtimeout.tv_sec = 60;       // 60 second timeout i guess
989         fetchtimeout.tv_usec = 0;
990         FD_ZERO(&fdset);
991         FD_SET(sockfd, &fdset);
992         res = select(sockfd + 1, &fdset, NULL, NULL, &fetchtimeout);
993         if (res > 0) {
994                 if ((numbytes = recv(sockfd, response, MAXDATASIZE - 1, 0)) == -1) {
995                         perror("recv");
996                         return -1;
997                 }
998         }
999         DBGP2("pop3_command() received: %s", response);
1000         response[numbytes] = '\0';
1001         if (strstr(response, verify) == NULL) {
1002                 return -1;
1003         }
1004         return 0;
1005 }
1006
1007 static void *pop3_thread(void *arg)
1008 {
1009         int sockfd, numbytes;
1010         char recvbuf[MAXDATASIZE];
1011         char sendbuf[MAXDATASIZE];
1012         char *reply;
1013         unsigned int fail = 0;
1014         unsigned long old_unseen = ULONG_MAX;
1015         struct stat stat_buf;
1016         struct hostent he, *he_res = 0;
1017         int he_errno;
1018         char hostbuff[2048];
1019         struct sockaddr_in their_addr;  // connector's address information
1020         struct mail_s *mail = (struct mail_s *)arg;
1021         char resolved_host = 0;
1022
1023         while (fail < mail->retries) {
1024                 struct timeval fetchtimeout;
1025                 int res;
1026                 fd_set fdset;
1027                 if (!resolved_host) {
1028 #ifdef HAVE_GETHOSTBYNAME_R
1029                         if (gethostbyname_r(mail->host, &he, hostbuff, sizeof(hostbuff), &he_res, &he_errno)) { // get the host info
1030                                 NORM_ERR("POP3 gethostbyname_r: %s", hstrerror(h_errno));
1031                                 fail++;
1032                                 break;
1033                         }
1034 #else /* HAVE_GETHOSTBYNAME_R */
1035                         if ((he_res = gethostbyname(mail->host)) == NULL) {     // get the host info
1036                                 herror("gethostbyname");
1037                 fail++;
1038                 break;
1039         }
1040 #endif /* HAVE_GETHOSTBYNAME_R */
1041         resolved_host = 1;
1042 }
1043                 if (fail > 0) {
1044                         NORM_ERR("Trying POP3 connection again for %s@%s (try %u/%u)",
1045                                         mail->user, mail->host, fail + 1, mail->retries);
1046                 }
1047                 do {
1048                         if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
1049                                 perror("socket");
1050                                 fail++;
1051                                 break;
1052                         }
1053
1054                         // host byte order
1055                         their_addr.sin_family = AF_INET;
1056                         // short, network byte order
1057                         their_addr.sin_port = htons(mail->port);
1058                         their_addr.sin_addr = *((struct in_addr *) he_res->h_addr);
1059                         // zero the rest of the struct
1060                         memset(&(their_addr.sin_zero), '\0', 8);
1061
1062                         if (connect(sockfd, (struct sockaddr *) &their_addr,
1063                                                 sizeof(struct sockaddr)) == -1) {
1064                                 perror("connect");
1065                                 fail++;
1066                                 break;
1067                         }
1068
1069                         fetchtimeout.tv_sec = 60;       // 60 second timeout i guess
1070                         fetchtimeout.tv_usec = 0;
1071                         FD_ZERO(&fdset);
1072                         FD_SET(sockfd, &fdset);
1073                         res = select(sockfd + 1, &fdset, NULL, NULL, &fetchtimeout);
1074                         if (res > 0) {
1075                                 if ((numbytes = recv(sockfd, recvbuf, MAXDATASIZE - 1, 0)) == -1) {
1076                                         perror("recv");
1077                                         fail++;
1078                                         break;
1079                                 }
1080                         } else {
1081                                 NORM_ERR("POP3 connection failed: timeout\n");
1082                                 fail++;
1083                                 break;
1084                         }
1085                         DBGP2("pop3_thread received: %s", recvbuf);
1086                         recvbuf[numbytes] = '\0';
1087                         if (strstr(recvbuf, "+OK ") != recvbuf) {
1088                                 NORM_ERR("POP3 connection failed, probably not a POP3 server");
1089                                 fail++;
1090                                 break;
1091                         }
1092                         strncpy(sendbuf, "USER ", MAXDATASIZE);
1093                         strncat(sendbuf, mail->user, MAXDATASIZE - strlen(sendbuf) - 1);
1094                         strncat(sendbuf, "\r\n", MAXDATASIZE - strlen(sendbuf) - 1);
1095                         if (pop3_command(sockfd, sendbuf, recvbuf, "+OK ")) {
1096                                 fail++;
1097                                 break;
1098                         }
1099
1100                         strncpy(sendbuf, "PASS ", MAXDATASIZE);
1101                         strncat(sendbuf, mail->pass, MAXDATASIZE - strlen(sendbuf) - 1);
1102                         strncat(sendbuf, "\r\n", MAXDATASIZE - strlen(sendbuf) - 1);
1103                         if (pop3_command(sockfd, sendbuf, recvbuf, "+OK ")) {
1104                                 NORM_ERR("POP3 server login failed: %s", recvbuf);
1105                                 fail++;
1106                                 break;
1107                         }
1108
1109                         strncpy(sendbuf, "STAT\r\n", MAXDATASIZE);
1110                         if (pop3_command(sockfd, sendbuf, recvbuf, "+OK ")) {
1111                                 perror("send STAT");
1112                                 fail++;
1113                                 break;
1114                         }
1115
1116                         // now we get the data
1117                         reply = recvbuf + 4;
1118                         if (reply == NULL) {
1119                                 NORM_ERR("Error parsing POP3 response: %s", recvbuf);
1120                                 fail++;
1121                                 break;
1122                         } else {
1123                                 timed_thread_lock(mail->p_timed_thread);
1124                                 sscanf(reply, "%lu %lu", &mail->unseen, &mail->used);
1125                                 timed_thread_unlock(mail->p_timed_thread);
1126                         }
1127
1128                         strncpy(sendbuf, "QUIT\r\n", MAXDATASIZE);
1129                         if (pop3_command(sockfd, sendbuf, recvbuf, "+OK")) {
1130                                 NORM_ERR("POP3 logout failed: %s", recvbuf);
1131                                 fail++;
1132                                 break;
1133                         }
1134
1135                         if (strlen(mail->command) > 1 && mail->unseen > old_unseen) {
1136                                 // new mail goodie
1137                                 if (system(mail->command) == -1) {
1138                                         perror("system()");
1139                                 }
1140                         }
1141                         fail = 0;
1142                         old_unseen = mail->unseen;
1143                 } while (0);
1144                 if ((fstat(sockfd, &stat_buf) == 0) && S_ISSOCK(stat_buf.st_mode)) {
1145                         /* if a valid socket, close it */
1146                         close(sockfd);
1147                 }
1148                 if (timed_thread_test(mail->p_timed_thread, 0)) {
1149                         timed_thread_exit(mail->p_timed_thread);
1150                 }
1151         }
1152         mail->unseen = 0;
1153         mail->used = 0;
1154         return 0;
1155 }
1156
1157 void print_pop3_unseen(struct text_object *obj, char *p, int p_max_size)
1158 {
1159         struct mail_s *mail = obj->data.opaque;
1160
1161         if (!mail)
1162                 return;
1163
1164         ensure_mail_thread(mail, pop3_thread, "pop3");
1165
1166         if (mail && mail->p_timed_thread) {
1167                 timed_thread_lock(mail->p_timed_thread);
1168                 snprintf(p, p_max_size, "%lu", mail->unseen);
1169                 timed_thread_unlock(mail->p_timed_thread);
1170         }
1171 }
1172
1173 void print_pop3_used(struct text_object *obj, char *p, int p_max_size)
1174 {
1175         struct mail_s *mail = obj->data.opaque;
1176
1177         if (!mail)
1178                 return;
1179
1180         ensure_mail_thread(mail, pop3_thread, "pop3");
1181
1182         if (mail && mail->p_timed_thread) {
1183                 timed_thread_lock(mail->p_timed_thread);
1184                 snprintf(p, p_max_size, "%.1f",
1185                                 mail->used / 1024.0 / 1024.0);
1186                 timed_thread_unlock(mail->p_timed_thread);
1187         }
1188 }