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