mail: use a private common struct mail_s instead of info.mail
[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.mail = global_mail;
453                 global_mail_use++;
454                 return;
455         }
456         // proccss
457         obj->data.mail = 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.mail = global_mail;
474                 global_mail_use++;
475                 return;
476         }
477         // proccss
478         obj->data.mail = 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.mail)
494                 return;
495
496         if (obj->data.mail == global_mail) {
497                 if (--global_mail_use == 0) {
498                         free(global_mail);
499                         global_mail = 0;
500                 }
501         } else {
502                 free(obj->data.mail);
503                 obj->data.mail = 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 text_object *obj,
569                 void *thread(void *), const char *text)
570 {
571         if (!obj->data.mail)
572                 return;
573
574         if (obj->data.mail->p_timed_thread)
575                 return;
576
577         obj->data.mail->p_timed_thread =
578                 timed_thread_create(thread,
579                                 (void *) obj->data.mail,
580                                 obj->data.mail->interval * 1000000);
581         if (!obj->data.mail->p_timed_thread) {
582                 NORM_ERR("Error creating %s timed thread", text);
583         }
584         timed_thread_register(obj->data.mail->p_timed_thread,
585                         &obj->data.mail->p_timed_thread);
586         if (timed_thread_run(obj->data.mail->p_timed_thread)) {
587                 NORM_ERR("Error running %s timed thread", text);
588         }
589 }
590
591 static void *imap_thread(void *arg)
592 {
593         int sockfd, numbytes;
594         char recvbuf[MAXDATASIZE];
595         char sendbuf[MAXDATASIZE];
596         unsigned int fail = 0;
597         unsigned long old_unseen = ULONG_MAX;
598         unsigned long old_messages = ULONG_MAX;
599         struct stat stat_buf;
600         struct hostent he, *he_res = 0;
601         int he_errno;
602         char hostbuff[2048];
603         struct sockaddr_in their_addr;  // connector's address information
604         struct mail_s *mail = (struct mail_s *)arg;
605         int has_idle = 0;
606         int threadfd = timed_thread_readfd(mail->p_timed_thread);
607         char resolved_host = 0;
608
609         while (fail < mail->retries) {
610                 struct timeval fetchtimeout;
611                 int res;
612                 fd_set fdset;
613
614                 if (!resolved_host) {
615 #ifdef HAVE_GETHOSTBYNAME_R
616                         if (gethostbyname_r(mail->host, &he, hostbuff, sizeof(hostbuff), &he_res, &he_errno)) { // get the host info
617                                 NORM_ERR("IMAP gethostbyname_r: %s", hstrerror(h_errno));
618                                 fail++;
619                                 break;
620                         }
621 #else /* HAVE_GETHOSTBYNAME_R */
622                         if ((he_res = gethostbyname(mail->host)) == NULL) {     // get the host info
623                                 herror("gethostbyname");
624                                 fail++;
625                                 break;
626                         }
627 #endif /* HAVE_GETHOSTBYNAME_R */
628                         resolved_host = 1;
629                 }
630                 if (fail > 0) {
631                         NORM_ERR("Trying IMAP connection again for %s@%s (try %u/%u)",
632                                         mail->user, mail->host, fail + 1, mail->retries);
633                 }
634                 do {
635                         if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
636                                 perror("socket");
637                                 fail++;
638                                 break;
639                         }
640
641                         // host byte order
642                         their_addr.sin_family = AF_INET;
643                         // short, network byte order
644                         their_addr.sin_port = htons(mail->port);
645                         their_addr.sin_addr = *((struct in_addr *) he_res->h_addr);
646                         // zero the rest of the struct
647                         memset(&(their_addr.sin_zero), '\0', 8);
648
649                         if (connect(sockfd, (struct sockaddr *) &their_addr,
650                                                 sizeof(struct sockaddr)) == -1) {
651                                 perror("connect");
652                                 fail++;
653                                 break;
654                         }
655
656                         fetchtimeout.tv_sec = 60;       // 60 second timeout i guess
657                         fetchtimeout.tv_usec = 0;
658                         FD_ZERO(&fdset);
659                         FD_SET(sockfd, &fdset);
660                         res = select(sockfd + 1, &fdset, NULL, NULL, &fetchtimeout);
661                         if (res > 0) {
662                                 if ((numbytes = recv(sockfd, recvbuf, MAXDATASIZE - 1, 0)) == -1) {
663                                         perror("recv");
664                                         fail++;
665                                         break;
666                                 }
667                         } else {
668                                 NORM_ERR("IMAP connection failed: timeout");
669                                 fail++;
670                                 break;
671                         }
672                         recvbuf[numbytes] = '\0';
673                         DBGP2("imap_thread() received: %s", recvbuf);
674                         if (strstr(recvbuf, "* OK") != recvbuf) {
675                                 NORM_ERR("IMAP connection failed, probably not an IMAP server");
676                                 fail++;
677                                 break;
678                         }
679                         strncpy(sendbuf, "abc CAPABILITY\r\n", MAXDATASIZE);
680                         if (imap_command(sockfd, sendbuf, recvbuf, "abc OK")) {
681                                 fail++;
682                                 break;
683                         }
684                         if (strstr(recvbuf, " IDLE ") != NULL) {
685                                 has_idle = 1;
686                         }
687
688                         strncpy(sendbuf, "a1 login ", MAXDATASIZE);
689                         strncat(sendbuf, mail->user, MAXDATASIZE - strlen(sendbuf) - 1);
690                         strncat(sendbuf, " ", MAXDATASIZE - strlen(sendbuf) - 1);
691                         strncat(sendbuf, mail->pass, MAXDATASIZE - strlen(sendbuf) - 1);
692                         strncat(sendbuf, "\r\n", MAXDATASIZE - strlen(sendbuf) - 1);
693                         if (imap_command(sockfd, sendbuf, recvbuf, "a1 OK")) {
694                                 fail++;
695                                 break;
696                         }
697
698                         strncpy(sendbuf, "a2 STATUS \"", MAXDATASIZE);
699                         strncat(sendbuf, mail->folder, MAXDATASIZE - strlen(sendbuf) - 1);
700                         strncat(sendbuf, "\" (MESSAGES UNSEEN)\r\n",
701                                         MAXDATASIZE - strlen(sendbuf) - 1);
702                         if (imap_command(sockfd, sendbuf, recvbuf, "a2 OK")) {
703                                 fail++;
704                                 break;
705                         }
706
707                         if (imap_check_status(recvbuf, mail)) {
708                                 fail++;
709                                 break;
710                         }
711                         imap_unseen_command(mail, old_unseen, old_messages);
712                         fail = 0;
713                         old_unseen = mail->unseen;
714                         old_messages = mail->messages;
715
716                         if (has_idle) {
717                                 strncpy(sendbuf, "a4 SELECT \"", MAXDATASIZE);
718                                 strncat(sendbuf, mail->folder, MAXDATASIZE - strlen(sendbuf) - 1);
719                                 strncat(sendbuf, "\"\r\n", MAXDATASIZE - strlen(sendbuf) - 1);
720                                 if (imap_command(sockfd, sendbuf, recvbuf, "a4 OK")) {
721                                         fail++;
722                                         break;
723                                 }
724
725                                 strncpy(sendbuf, "a5 IDLE\r\n", MAXDATASIZE);
726                                 if (imap_command(sockfd, sendbuf, recvbuf, "+ idling")) {
727                                         fail++;
728                                         break;
729                                 }
730                                 recvbuf[0] = '\0';
731
732                                 while (1) {
733                                         /*
734                                          * RFC 2177 says we have to re-idle every 29 minutes.
735                                          * We'll do it every 20 minutes to be safe.
736                                          */
737                                         fetchtimeout.tv_sec = 1200;
738                                         fetchtimeout.tv_usec = 0;
739                                         DBGP2("idling...");
740                                         FD_ZERO(&fdset);
741                                         FD_SET(sockfd, &fdset);
742                                         FD_SET(threadfd, &fdset);
743                                         res = select(MAX(sockfd + 1, threadfd + 1), &fdset, NULL, NULL, &fetchtimeout);
744                                         if (timed_thread_test(mail->p_timed_thread, 1) || (res == -1 && errno == EINTR) || FD_ISSET(threadfd, &fdset)) {
745                                                 if ((fstat(sockfd, &stat_buf) == 0) && S_ISSOCK(stat_buf.st_mode)) {
746                                                         /* if a valid socket, close it */
747                                                         close(sockfd);
748                                                 }
749                                                 timed_thread_exit(mail->p_timed_thread);
750                                         } else if (res > 0) {
751                                                 if ((numbytes = recv(sockfd, recvbuf, MAXDATASIZE - 1, 0)) == -1) {
752                                                         perror("recv idling");
753                                                         fail++;
754                                                         break;
755                                                 }
756                                         } else {
757                                                 fail++;
758                                                 break;
759                                         }
760                                         recvbuf[numbytes] = '\0';
761                                         DBGP2("imap_thread() received: %s", recvbuf);
762                                         if (strlen(recvbuf) > 2) {
763                                                 unsigned long messages, recent;
764                                                 char *buf = recvbuf;
765                                                 char force_check = 0;
766                                                 buf = strstr(buf, "EXISTS");
767                                                 while (buf && strlen(buf) > 1 && strstr(buf + 1, "EXISTS")) {
768                                                         buf = strstr(buf + 1, "EXISTS");
769                                                 }
770                                                 if (buf) {
771                                                         // back up until we reach '*'
772                                                         while (buf >= recvbuf && buf[0] != '*') {
773                                                                 buf--;
774                                                         }
775                                                         if (sscanf(buf, "* %lu EXISTS\r\n", &messages) == 1) {
776                                                                 timed_thread_lock(mail->p_timed_thread);
777                                                                 if (mail->messages != messages) {
778                                                                         force_check = 1;
779                                                                         mail->messages = messages;
780                                                                 }
781                                                                 timed_thread_unlock(mail->p_timed_thread);
782                                                         }
783                                                 }
784                                                 buf = recvbuf;
785                                                 buf = strstr(buf, "RECENT");
786                                                 while (buf && strlen(buf) > 1 && strstr(buf + 1, "RECENT")) {
787                                                         buf = strstr(buf + 1, "RECENT");
788                                                 }
789                                                 if (buf) {
790                                                         // back up until we reach '*'
791                                                         while (buf >= recvbuf && buf[0] != '*') {
792                                                                 buf--;
793                                                         }
794                                                         if (sscanf(buf, "* %lu RECENT\r\n", &recent) != 1) {
795                                                                 recent = 0;
796                                                         }
797                                                 }
798                                                 /*
799                                                  * check if we got a FETCH from server, recent was
800                                                  * something other than 0, or we had a timeout
801                                                  */
802                                                 buf = recvbuf;
803                                                 if (recent > 0 || (buf && strstr(buf, " FETCH ")) || fetchtimeout.tv_sec == 0 || force_check) {
804                                                         // re-check messages and unseen
805                                                         if (imap_command(sockfd, "DONE\r\n", recvbuf, "a5 OK")) {
806                                                                 fail++;
807                                                                 break;
808                                                         }
809                                                         strncpy(sendbuf, "a2 STATUS \"", MAXDATASIZE);
810                                                         strncat(sendbuf, mail->folder, MAXDATASIZE - strlen(sendbuf) - 1);
811                                                         strncat(sendbuf, "\" (MESSAGES UNSEEN)\r\n",
812                                                                         MAXDATASIZE - strlen(sendbuf) - 1);
813                                                         if (imap_command(sockfd, sendbuf, recvbuf, "a2 OK")) {
814                                                                 fail++;
815                                                                 break;
816                                                         }
817                                                         if (imap_check_status(recvbuf, mail)) {
818                                                                 fail++;
819                                                                 break;
820                                                         }
821                                                         strncpy(sendbuf, "a5 IDLE\r\n", MAXDATASIZE);
822                                                         if (imap_command(sockfd, sendbuf, recvbuf, "+ idling")) {
823                                                                 fail++;
824                                                                 break;
825                                                         }
826                                                 }
827                                                 /*
828                                                  * check if we got a BYE from server
829                                                  */
830                                                 buf = recvbuf;
831                                                 if (buf && strstr(buf, "* BYE")) {
832                                                         // need to re-connect
833                                                         break;
834                                                 }
835                                         } else {
836                                                 fail++;
837                                                 break;
838                                         }
839                                         imap_unseen_command(mail, old_unseen, old_messages);
840                                         fail = 0;
841                                         old_unseen = mail->unseen;
842                                         old_messages = mail->messages;
843                                 }
844                                 if (fail) break;
845                         } else {
846                                 strncpy(sendbuf, "a3 logout\r\n", MAXDATASIZE);
847                                 if (send(sockfd, sendbuf, strlen(sendbuf), 0) == -1) {
848                                         perror("send a3");
849                                         fail++;
850                                         break;
851                                 }
852                                 fetchtimeout.tv_sec = 60;       // 60 second timeout i guess
853                                 fetchtimeout.tv_usec = 0;
854                                 FD_ZERO(&fdset);
855                                 FD_SET(sockfd, &fdset);
856                                 res = select(sockfd + 1, &fdset, NULL, NULL, &fetchtimeout);
857                                 if (res > 0) {
858                                         if ((numbytes = recv(sockfd, recvbuf, MAXDATASIZE - 1, 0)) == -1) {
859                                                 perror("recv a3");
860                                                 fail++;
861                                                 break;
862                                         }
863                                 }
864                                 recvbuf[numbytes] = '\0';
865                                 DBGP2("imap_thread() received: %s", recvbuf);
866                                 if (strstr(recvbuf, "a3 OK") == NULL) {
867                                         NORM_ERR("IMAP logout failed: %s", recvbuf);
868                                         fail++;
869                                         break;
870                                 }
871                         }
872                 } while (0);
873                 if ((fstat(sockfd, &stat_buf) == 0) && S_ISSOCK(stat_buf.st_mode)) {
874                         /* if a valid socket, close it */
875                         close(sockfd);
876                 }
877                 if (timed_thread_test(mail->p_timed_thread, 0)) {
878                         timed_thread_exit(mail->p_timed_thread);
879                 }
880         }
881         mail->unseen = 0;
882         mail->messages = 0;
883         return 0;
884 }
885
886 void print_imap_unseen(struct text_object *obj, char *p, int p_max_size)
887 {
888         struct mail_s *mail = ensure_mail_thread(obj, 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 = ensure_mail_thread(obj, imap_thread, "imap");
900
901         if (mail && mail->p_timed_thread) {
902                 timed_thread_lock(mail->p_timed_thread);
903                 snprintf(p, p_max_size, "%lu", mail->messages);
904                 timed_thread_unlock(mail->p_timed_thread);
905         }
906 }
907
908 int pop3_command(int sockfd, const char *command, char *response, const char *verify)
909 {
910         struct timeval fetchtimeout;
911         fd_set fdset;
912         int res, numbytes = 0;
913         if (send(sockfd, command, strlen(command), 0) == -1) {
914                 perror("send");
915                 return -1;
916         }
917         fetchtimeout.tv_sec = 60;       // 60 second timeout i guess
918         fetchtimeout.tv_usec = 0;
919         FD_ZERO(&fdset);
920         FD_SET(sockfd, &fdset);
921         res = select(sockfd + 1, &fdset, NULL, NULL, &fetchtimeout);
922         if (res > 0) {
923                 if ((numbytes = recv(sockfd, response, MAXDATASIZE - 1, 0)) == -1) {
924                         perror("recv");
925                         return -1;
926                 }
927         }
928         DBGP2("pop3_command() received: %s", response);
929         response[numbytes] = '\0';
930         if (strstr(response, verify) == NULL) {
931                 return -1;
932         }
933         return 0;
934 }
935
936 static void *pop3_thread(void *arg)
937 {
938         int sockfd, numbytes;
939         char recvbuf[MAXDATASIZE];
940         char sendbuf[MAXDATASIZE];
941         char *reply;
942         unsigned int fail = 0;
943         unsigned long old_unseen = ULONG_MAX;
944         struct stat stat_buf;
945         struct hostent he, *he_res = 0;
946         int he_errno;
947         char hostbuff[2048];
948         struct sockaddr_in their_addr;  // connector's address information
949         struct mail_s *mail = (struct mail_s *)arg;
950         char resolved_host = 0;
951
952         while (fail < mail->retries) {
953                 struct timeval fetchtimeout;
954                 int res;
955                 fd_set fdset;
956                 if (!resolved_host) {
957 #ifdef HAVE_GETHOSTBYNAME_R
958                         if (gethostbyname_r(mail->host, &he, hostbuff, sizeof(hostbuff), &he_res, &he_errno)) { // get the host info
959                                 NORM_ERR("POP3 gethostbyname_r: %s", hstrerror(h_errno));
960                                 fail++;
961                                 break;
962                         }
963 #else /* HAVE_GETHOSTBYNAME_R */
964                         if ((he_res = gethostbyname(mail->host)) == NULL) {     // get the host info
965                                 herror("gethostbyname");
966                 fail++;
967                 break;
968         }
969 #endif /* HAVE_GETHOSTBYNAME_R */
970         resolved_host = 1;
971 }
972                 if (fail > 0) {
973                         NORM_ERR("Trying POP3 connection again for %s@%s (try %u/%u)",
974                                         mail->user, mail->host, fail + 1, mail->retries);
975                 }
976                 do {
977                         if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
978                                 perror("socket");
979                                 fail++;
980                                 break;
981                         }
982
983                         // host byte order
984                         their_addr.sin_family = AF_INET;
985                         // short, network byte order
986                         their_addr.sin_port = htons(mail->port);
987                         their_addr.sin_addr = *((struct in_addr *) he_res->h_addr);
988                         // zero the rest of the struct
989                         memset(&(their_addr.sin_zero), '\0', 8);
990
991                         if (connect(sockfd, (struct sockaddr *) &their_addr,
992                                                 sizeof(struct sockaddr)) == -1) {
993                                 perror("connect");
994                                 fail++;
995                                 break;
996                         }
997
998                         fetchtimeout.tv_sec = 60;       // 60 second timeout i guess
999                         fetchtimeout.tv_usec = 0;
1000                         FD_ZERO(&fdset);
1001                         FD_SET(sockfd, &fdset);
1002                         res = select(sockfd + 1, &fdset, NULL, NULL, &fetchtimeout);
1003                         if (res > 0) {
1004                                 if ((numbytes = recv(sockfd, recvbuf, MAXDATASIZE - 1, 0)) == -1) {
1005                                         perror("recv");
1006                                         fail++;
1007                                         break;
1008                                 }
1009                         } else {
1010                                 NORM_ERR("POP3 connection failed: timeout\n");
1011                                 fail++;
1012                                 break;
1013                         }
1014                         DBGP2("pop3_thread received: %s", recvbuf);
1015                         recvbuf[numbytes] = '\0';
1016                         if (strstr(recvbuf, "+OK ") != recvbuf) {
1017                                 NORM_ERR("POP3 connection failed, probably not a POP3 server");
1018                                 fail++;
1019                                 break;
1020                         }
1021                         strncpy(sendbuf, "USER ", MAXDATASIZE);
1022                         strncat(sendbuf, mail->user, MAXDATASIZE - strlen(sendbuf) - 1);
1023                         strncat(sendbuf, "\r\n", MAXDATASIZE - strlen(sendbuf) - 1);
1024                         if (pop3_command(sockfd, sendbuf, recvbuf, "+OK ")) {
1025                                 fail++;
1026                                 break;
1027                         }
1028
1029                         strncpy(sendbuf, "PASS ", MAXDATASIZE);
1030                         strncat(sendbuf, mail->pass, MAXDATASIZE - strlen(sendbuf) - 1);
1031                         strncat(sendbuf, "\r\n", MAXDATASIZE - strlen(sendbuf) - 1);
1032                         if (pop3_command(sockfd, sendbuf, recvbuf, "+OK ")) {
1033                                 NORM_ERR("POP3 server login failed: %s", recvbuf);
1034                                 fail++;
1035                                 break;
1036                         }
1037
1038                         strncpy(sendbuf, "STAT\r\n", MAXDATASIZE);
1039                         if (pop3_command(sockfd, sendbuf, recvbuf, "+OK ")) {
1040                                 perror("send STAT");
1041                                 fail++;
1042                                 break;
1043                         }
1044
1045                         // now we get the data
1046                         reply = recvbuf + 4;
1047                         if (reply == NULL) {
1048                                 NORM_ERR("Error parsing POP3 response: %s", recvbuf);
1049                                 fail++;
1050                                 break;
1051                         } else {
1052                                 timed_thread_lock(mail->p_timed_thread);
1053                                 sscanf(reply, "%lu %lu", &mail->unseen, &mail->used);
1054                                 timed_thread_unlock(mail->p_timed_thread);
1055                         }
1056                         
1057                         strncpy(sendbuf, "QUIT\r\n", MAXDATASIZE);
1058                         if (pop3_command(sockfd, sendbuf, recvbuf, "+OK")) {
1059                                 NORM_ERR("POP3 logout failed: %s", recvbuf);
1060                                 fail++;
1061                                 break;
1062                         }
1063                         
1064                         if (strlen(mail->command) > 1 && mail->unseen > old_unseen) {
1065                                 // new mail goodie
1066                                 if (system(mail->command) == -1) {
1067                                         perror("system()");
1068                                 }
1069                         }
1070                         fail = 0;
1071                         old_unseen = mail->unseen;
1072                 } while (0);
1073                 if ((fstat(sockfd, &stat_buf) == 0) && S_ISSOCK(stat_buf.st_mode)) {
1074                         /* if a valid socket, close it */
1075                         close(sockfd);
1076                 }
1077                 if (timed_thread_test(mail->p_timed_thread, 0)) {
1078                         timed_thread_exit(mail->p_timed_thread);
1079                 }
1080         }
1081         mail->unseen = 0;
1082         mail->used = 0;
1083         return 0;
1084 }
1085
1086 void print_pop3_unseen(struct text_object *obj, char *p, int p_max_size)
1087 {
1088         struct mail_s *mail = ensure_mail_thread(obj, pop3_thread, "pop3");
1089
1090         if (mail && mail->p_timed_thread) {
1091                 timed_thread_lock(mail->p_timed_thread);
1092                 snprintf(p, p_max_size, "%lu", mail->unseen);
1093                 timed_thread_unlock(mail->p_timed_thread);
1094         }
1095 }
1096
1097 void print_pop3_used(struct text_object *obj, char *p, int p_max_size)
1098 {
1099         struct mail_s *mail = ensure_mail_thread(obj, pop3_thread, "pop3");
1100
1101         if (mail && mail->p_timed_thread) {
1102                 timed_thread_lock(mail->p_timed_thread);
1103                 snprintf(p, p_max_size, "%.1f",
1104                                 mail->used / 1024.0 / 1024.0);
1105                 timed_thread_unlock(mail->p_timed_thread);
1106         }
1107 }