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