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