mail: put imap and pop3 code to where it belongs
[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 #define MAXDATASIZE 1000
298
299 struct mail_s *parse_mail_args(char type, const char *arg)
300 {
301         struct mail_s *mail;
302         char *tmp;
303
304         mail = malloc(sizeof(struct mail_s));
305         memset(mail, 0, sizeof(struct mail_s));
306
307         if (sscanf(arg, "%128s %128s %128s", mail->host, mail->user, mail->pass)
308                         != 3) {
309                 if (type == POP3_TYPE) {
310                         NORM_ERR("Scanning POP3 args failed");
311                 } else if (type == IMAP_TYPE) {
312                         NORM_ERR("Scanning IMAP args failed");
313                 }
314                 return 0;
315         }
316         // see if password needs prompting
317         if (mail->pass[0] == '*' && mail->pass[1] == '\0') {
318                 int fp = fileno(stdin);
319                 struct termios term;
320
321                 tcgetattr(fp, &term);
322                 term.c_lflag &= ~ECHO;
323                 tcsetattr(fp, TCSANOW, &term);
324                 printf("Enter mailbox password (%s@%s): ", mail->user, mail->host);
325                 scanf("%128s", mail->pass);
326                 printf("\n");
327                 term.c_lflag |= ECHO;
328                 tcsetattr(fp, TCSANOW, &term);
329         }
330         // now we check for optional args
331         tmp = strstr(arg, "-r ");
332         if (tmp) {
333                 tmp += 3;
334                 sscanf(tmp, "%u", &mail->retries);
335         } else {
336                 mail->retries = 5;      // 5 retries after failure
337         }
338         tmp = strstr(arg, "-i ");
339         if (tmp) {
340                 tmp += 3;
341                 sscanf(tmp, "%f", &mail->interval);
342         } else {
343                 mail->interval = 300;   // 5 minutes
344         }
345         tmp = strstr(arg, "-p ");
346         if (tmp) {
347                 tmp += 3;
348                 sscanf(tmp, "%lu", &mail->port);
349         } else {
350                 if (type == POP3_TYPE) {
351                         mail->port = 110;       // default pop3 port
352                 } else if (type == IMAP_TYPE) {
353                         mail->port = 143;       // default imap port
354                 }
355         }
356         if (type == IMAP_TYPE) {
357                 tmp = strstr(arg, "-f ");
358                 if (tmp) {
359                         int len = 1024;
360                         tmp += 3;
361                         if (tmp[0] == '\'') {
362                                 len = strstr(tmp + 1, "'") - tmp - 1;
363                                 if (len > 1024) {
364                                         len = 1024;
365                                 }
366                         }
367                         strncpy(mail->folder, tmp + 1, len);
368                 } else {
369                         strncpy(mail->folder, "INBOX", 128);    // default imap inbox
370                 }
371         }
372         tmp = strstr(arg, "-e ");
373         if (tmp) {
374                 int len = 1024;
375                 tmp += 3;
376
377                 if (tmp[0] == '\'') {
378                         len = strstr(tmp + 1, "'") - tmp - 1;
379                         if (len > 1024) {
380                                 len = 1024;
381                         }
382                 }
383                 strncpy(mail->command, tmp + 1, len);
384         } else {
385                 mail->command[0] = '\0';
386         }
387         mail->p_timed_thread = NULL;
388         return mail;
389 }
390
391 void parse_imap_mail_args(struct text_object *obj, const char *arg)
392 {
393         if (!arg) {
394                 obj->char_b = 1;
395                 return;
396         }
397         // proccss
398         obj->data.mail = parse_mail_args(IMAP_TYPE, arg);
399         obj->char_b = 0;
400 }
401
402 void parse_pop3_mail_args(struct text_object *obj, const char *arg)
403 {
404         if (!arg) {
405                 obj->char_b = 1;
406                 return;
407         }
408         // proccss
409         obj->data.mail = parse_mail_args(POP3_TYPE, arg);
410         obj->char_b = 0;
411 }
412
413 void free_mail_obj(struct text_object *obj)
414 {
415         if (!obj->char_b) {
416                 free(obj->data.mail);
417         }
418 }
419
420 int imap_command(int sockfd, const char *command, char *response, const char *verify)
421 {
422         struct timeval fetchtimeout;
423         fd_set fdset;
424         int res, numbytes = 0;
425         if (send(sockfd, command, strlen(command), 0) == -1) {
426                 perror("send");
427                 return -1;
428         }
429         fetchtimeout.tv_sec = 60;       // 60 second timeout i guess
430         fetchtimeout.tv_usec = 0;
431         FD_ZERO(&fdset);
432         FD_SET(sockfd, &fdset);
433         res = select(sockfd + 1, &fdset, NULL, NULL, &fetchtimeout);
434         if (res > 0) {
435                 if ((numbytes = recv(sockfd, response, MAXDATASIZE - 1, 0)) == -1) {
436                         perror("recv");
437                         return -1;
438                 }
439         }
440         DBGP2("imap_command()  command: %s", command);
441         DBGP2("imap_command() received: %s", response);
442         response[numbytes] = '\0';
443         if (strstr(response, verify) == NULL) {
444                 return -1;
445         }
446         return 0;
447 }
448
449 int imap_check_status(char *recvbuf, struct mail_s *mail)
450 {
451         char *reply;
452         reply = strstr(recvbuf, " (MESSAGES ");
453         if (!reply || strlen(reply) < 2) {
454                 return -1;
455         }
456         reply += 2;
457         *strchr(reply, ')') = '\0';
458         if (reply == NULL) {
459                 NORM_ERR("Error parsing IMAP response: %s", recvbuf);
460                 return -1;
461         } else {
462                 timed_thread_lock(mail->p_timed_thread);
463                 sscanf(reply, "MESSAGES %lu UNSEEN %lu", &mail->messages,
464                                 &mail->unseen);
465                 timed_thread_unlock(mail->p_timed_thread);
466         }
467         return 0;
468 }
469
470 void imap_unseen_command(struct mail_s *mail, unsigned long old_unseen, unsigned long old_messages)
471 {
472         if (strlen(mail->command) > 1 && (mail->unseen > old_unseen
473                                 || (mail->messages > old_messages && mail->unseen > 0))) {
474                 // new mail goodie
475                 if (system(mail->command) == -1) {
476                         perror("system()");
477                 }
478         }
479 }
480
481 static inline struct mail_s *ensure_mail_thread(struct text_object *obj,
482                 void *thread(void *), const char *text)
483 {
484         if (obj->char_b && info.mail) {
485                 // this means we use info
486                 if (!info.mail->p_timed_thread) {
487                         info.mail->p_timed_thread =
488                                 timed_thread_create(thread,
489                                                 (void *) info.mail, info.mail->interval * 1000000);
490                         if (!info.mail->p_timed_thread) {
491                                 NORM_ERR("Error creating %s timed thread", text);
492                         }
493                         timed_thread_register(info.mail->p_timed_thread,
494                                         &info.mail->p_timed_thread);
495                         if (timed_thread_run(info.mail->p_timed_thread)) {
496                                 NORM_ERR("Error running %s timed thread", text);
497                         }
498                 }
499                 return info.mail;
500         } else if (obj->data.mail) {
501                 // this means we use obj
502                 if (!obj->data.mail->p_timed_thread) {
503                         obj->data.mail->p_timed_thread =
504                                 timed_thread_create(thread,
505                                                 (void *) obj->data.mail,
506                                                 obj->data.mail->interval * 1000000);
507                         if (!obj->data.mail->p_timed_thread) {
508                                 NORM_ERR("Error creating %s timed thread", text);
509                         }
510                         timed_thread_register(obj->data.mail->p_timed_thread,
511                                         &obj->data.mail->p_timed_thread);
512                         if (timed_thread_run(obj->data.mail->p_timed_thread)) {
513                                 NORM_ERR("Error running %s timed thread", text);
514                         }
515                 }
516                 return obj->data.mail;
517         } else if (!obj->a) {
518                 // something is wrong, warn once then stop
519                 NORM_ERR("There's a problem with your mail settings.  "
520                                 "Check that the global mail settings are properly defined"
521                                 " (line %li).", obj->line);
522                 obj->a++;
523         }
524         return NULL;
525 }
526
527 static void *imap_thread(void *arg)
528 {
529         int sockfd, numbytes;
530         char recvbuf[MAXDATASIZE];
531         char sendbuf[MAXDATASIZE];
532         unsigned int fail = 0;
533         unsigned long old_unseen = ULONG_MAX;
534         unsigned long old_messages = ULONG_MAX;
535         struct stat stat_buf;
536         struct hostent he, *he_res = 0;
537         int he_errno;
538         char hostbuff[2048];
539         struct sockaddr_in their_addr;  // connector's address information
540         struct mail_s *mail = (struct mail_s *)arg;
541         int has_idle = 0;
542         int threadfd = timed_thread_readfd(mail->p_timed_thread);
543         char resolved_host = 0;
544
545         while (fail < mail->retries) {
546                 struct timeval fetchtimeout;
547                 int res;
548                 fd_set fdset;
549
550                 if (!resolved_host) {
551 #ifdef HAVE_GETHOSTBYNAME_R
552                         if (gethostbyname_r(mail->host, &he, hostbuff, sizeof(hostbuff), &he_res, &he_errno)) { // get the host info
553                                 NORM_ERR("IMAP gethostbyname_r: %s", hstrerror(h_errno));
554                                 fail++;
555                                 break;
556                         }
557 #else /* HAVE_GETHOSTBYNAME_R */
558                         if ((he_res = gethostbyname(mail->host)) == NULL) {     // get the host info
559                                 herror("gethostbyname");
560                                 fail++;
561                                 break;
562                         }
563 #endif /* HAVE_GETHOSTBYNAME_R */
564                         resolved_host = 1;
565                 }
566                 if (fail > 0) {
567                         NORM_ERR("Trying IMAP connection again for %s@%s (try %u/%u)",
568                                         mail->user, mail->host, fail + 1, mail->retries);
569                 }
570                 do {
571                         if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
572                                 perror("socket");
573                                 fail++;
574                                 break;
575                         }
576
577                         // host byte order
578                         their_addr.sin_family = AF_INET;
579                         // short, network byte order
580                         their_addr.sin_port = htons(mail->port);
581                         their_addr.sin_addr = *((struct in_addr *) he_res->h_addr);
582                         // zero the rest of the struct
583                         memset(&(their_addr.sin_zero), '\0', 8);
584
585                         if (connect(sockfd, (struct sockaddr *) &their_addr,
586                                                 sizeof(struct sockaddr)) == -1) {
587                                 perror("connect");
588                                 fail++;
589                                 break;
590                         }
591
592                         fetchtimeout.tv_sec = 60;       // 60 second timeout i guess
593                         fetchtimeout.tv_usec = 0;
594                         FD_ZERO(&fdset);
595                         FD_SET(sockfd, &fdset);
596                         res = select(sockfd + 1, &fdset, NULL, NULL, &fetchtimeout);
597                         if (res > 0) {
598                                 if ((numbytes = recv(sockfd, recvbuf, MAXDATASIZE - 1, 0)) == -1) {
599                                         perror("recv");
600                                         fail++;
601                                         break;
602                                 }
603                         } else {
604                                 NORM_ERR("IMAP connection failed: timeout");
605                                 fail++;
606                                 break;
607                         }
608                         recvbuf[numbytes] = '\0';
609                         DBGP2("imap_thread() received: %s", recvbuf);
610                         if (strstr(recvbuf, "* OK") != recvbuf) {
611                                 NORM_ERR("IMAP connection failed, probably not an IMAP server");
612                                 fail++;
613                                 break;
614                         }
615                         strncpy(sendbuf, "abc CAPABILITY\r\n", MAXDATASIZE);
616                         if (imap_command(sockfd, sendbuf, recvbuf, "abc OK")) {
617                                 fail++;
618                                 break;
619                         }
620                         if (strstr(recvbuf, " IDLE ") != NULL) {
621                                 has_idle = 1;
622                         }
623
624                         strncpy(sendbuf, "a1 login ", MAXDATASIZE);
625                         strncat(sendbuf, mail->user, MAXDATASIZE - strlen(sendbuf) - 1);
626                         strncat(sendbuf, " ", MAXDATASIZE - strlen(sendbuf) - 1);
627                         strncat(sendbuf, mail->pass, MAXDATASIZE - strlen(sendbuf) - 1);
628                         strncat(sendbuf, "\r\n", MAXDATASIZE - strlen(sendbuf) - 1);
629                         if (imap_command(sockfd, sendbuf, recvbuf, "a1 OK")) {
630                                 fail++;
631                                 break;
632                         }
633
634                         strncpy(sendbuf, "a2 STATUS \"", MAXDATASIZE);
635                         strncat(sendbuf, mail->folder, MAXDATASIZE - strlen(sendbuf) - 1);
636                         strncat(sendbuf, "\" (MESSAGES UNSEEN)\r\n",
637                                         MAXDATASIZE - strlen(sendbuf) - 1);
638                         if (imap_command(sockfd, sendbuf, recvbuf, "a2 OK")) {
639                                 fail++;
640                                 break;
641                         }
642
643                         if (imap_check_status(recvbuf, mail)) {
644                                 fail++;
645                                 break;
646                         }
647                         imap_unseen_command(mail, old_unseen, old_messages);
648                         fail = 0;
649                         old_unseen = mail->unseen;
650                         old_messages = mail->messages;
651
652                         if (has_idle) {
653                                 strncpy(sendbuf, "a4 SELECT \"", MAXDATASIZE);
654                                 strncat(sendbuf, mail->folder, MAXDATASIZE - strlen(sendbuf) - 1);
655                                 strncat(sendbuf, "\"\r\n", MAXDATASIZE - strlen(sendbuf) - 1);
656                                 if (imap_command(sockfd, sendbuf, recvbuf, "a4 OK")) {
657                                         fail++;
658                                         break;
659                                 }
660
661                                 strncpy(sendbuf, "a5 IDLE\r\n", MAXDATASIZE);
662                                 if (imap_command(sockfd, sendbuf, recvbuf, "+ idling")) {
663                                         fail++;
664                                         break;
665                                 }
666                                 recvbuf[0] = '\0';
667
668                                 while (1) {
669                                         /*
670                                          * RFC 2177 says we have to re-idle every 29 minutes.
671                                          * We'll do it every 20 minutes to be safe.
672                                          */
673                                         fetchtimeout.tv_sec = 1200;
674                                         fetchtimeout.tv_usec = 0;
675                                         DBGP2("idling...");
676                                         FD_ZERO(&fdset);
677                                         FD_SET(sockfd, &fdset);
678                                         FD_SET(threadfd, &fdset);
679                                         res = select(MAX(sockfd + 1, threadfd + 1), &fdset, NULL, NULL, &fetchtimeout);
680                                         if (timed_thread_test(mail->p_timed_thread, 1) || (res == -1 && errno == EINTR) || FD_ISSET(threadfd, &fdset)) {
681                                                 if ((fstat(sockfd, &stat_buf) == 0) && S_ISSOCK(stat_buf.st_mode)) {
682                                                         /* if a valid socket, close it */
683                                                         close(sockfd);
684                                                 }
685                                                 timed_thread_exit(mail->p_timed_thread);
686                                         } else if (res > 0) {
687                                                 if ((numbytes = recv(sockfd, recvbuf, MAXDATASIZE - 1, 0)) == -1) {
688                                                         perror("recv idling");
689                                                         fail++;
690                                                         break;
691                                                 }
692                                         } else {
693                                                 fail++;
694                                                 break;
695                                         }
696                                         recvbuf[numbytes] = '\0';
697                                         DBGP2("imap_thread() received: %s", recvbuf);
698                                         if (strlen(recvbuf) > 2) {
699                                                 unsigned long messages, recent;
700                                                 char *buf = recvbuf;
701                                                 char force_check = 0;
702                                                 buf = strstr(buf, "EXISTS");
703                                                 while (buf && strlen(buf) > 1 && strstr(buf + 1, "EXISTS")) {
704                                                         buf = strstr(buf + 1, "EXISTS");
705                                                 }
706                                                 if (buf) {
707                                                         // back up until we reach '*'
708                                                         while (buf >= recvbuf && buf[0] != '*') {
709                                                                 buf--;
710                                                         }
711                                                         if (sscanf(buf, "* %lu EXISTS\r\n", &messages) == 1) {
712                                                                 timed_thread_lock(mail->p_timed_thread);
713                                                                 if (mail->messages != messages) {
714                                                                         force_check = 1;
715                                                                         mail->messages = messages;
716                                                                 }
717                                                                 timed_thread_unlock(mail->p_timed_thread);
718                                                         }
719                                                 }
720                                                 buf = recvbuf;
721                                                 buf = strstr(buf, "RECENT");
722                                                 while (buf && strlen(buf) > 1 && strstr(buf + 1, "RECENT")) {
723                                                         buf = strstr(buf + 1, "RECENT");
724                                                 }
725                                                 if (buf) {
726                                                         // back up until we reach '*'
727                                                         while (buf >= recvbuf && buf[0] != '*') {
728                                                                 buf--;
729                                                         }
730                                                         if (sscanf(buf, "* %lu RECENT\r\n", &recent) != 1) {
731                                                                 recent = 0;
732                                                         }
733                                                 }
734                                                 /*
735                                                  * check if we got a FETCH from server, recent was
736                                                  * something other than 0, or we had a timeout
737                                                  */
738                                                 buf = recvbuf;
739                                                 if (recent > 0 || (buf && strstr(buf, " FETCH ")) || fetchtimeout.tv_sec == 0 || force_check) {
740                                                         // re-check messages and unseen
741                                                         if (imap_command(sockfd, "DONE\r\n", recvbuf, "a5 OK")) {
742                                                                 fail++;
743                                                                 break;
744                                                         }
745                                                         strncpy(sendbuf, "a2 STATUS \"", MAXDATASIZE);
746                                                         strncat(sendbuf, mail->folder, MAXDATASIZE - strlen(sendbuf) - 1);
747                                                         strncat(sendbuf, "\" (MESSAGES UNSEEN)\r\n",
748                                                                         MAXDATASIZE - strlen(sendbuf) - 1);
749                                                         if (imap_command(sockfd, sendbuf, recvbuf, "a2 OK")) {
750                                                                 fail++;
751                                                                 break;
752                                                         }
753                                                         if (imap_check_status(recvbuf, mail)) {
754                                                                 fail++;
755                                                                 break;
756                                                         }
757                                                         strncpy(sendbuf, "a5 IDLE\r\n", MAXDATASIZE);
758                                                         if (imap_command(sockfd, sendbuf, recvbuf, "+ idling")) {
759                                                                 fail++;
760                                                                 break;
761                                                         }
762                                                 }
763                                                 /*
764                                                  * check if we got a BYE from server
765                                                  */
766                                                 buf = recvbuf;
767                                                 if (buf && strstr(buf, "* BYE")) {
768                                                         // need to re-connect
769                                                         break;
770                                                 }
771                                         } else {
772                                                 fail++;
773                                                 break;
774                                         }
775                                         imap_unseen_command(mail, old_unseen, old_messages);
776                                         fail = 0;
777                                         old_unseen = mail->unseen;
778                                         old_messages = mail->messages;
779                                 }
780                                 if (fail) break;
781                         } else {
782                                 strncpy(sendbuf, "a3 logout\r\n", MAXDATASIZE);
783                                 if (send(sockfd, sendbuf, strlen(sendbuf), 0) == -1) {
784                                         perror("send a3");
785                                         fail++;
786                                         break;
787                                 }
788                                 fetchtimeout.tv_sec = 60;       // 60 second timeout i guess
789                                 fetchtimeout.tv_usec = 0;
790                                 FD_ZERO(&fdset);
791                                 FD_SET(sockfd, &fdset);
792                                 res = select(sockfd + 1, &fdset, NULL, NULL, &fetchtimeout);
793                                 if (res > 0) {
794                                         if ((numbytes = recv(sockfd, recvbuf, MAXDATASIZE - 1, 0)) == -1) {
795                                                 perror("recv a3");
796                                                 fail++;
797                                                 break;
798                                         }
799                                 }
800                                 recvbuf[numbytes] = '\0';
801                                 DBGP2("imap_thread() received: %s", recvbuf);
802                                 if (strstr(recvbuf, "a3 OK") == NULL) {
803                                         NORM_ERR("IMAP logout failed: %s", recvbuf);
804                                         fail++;
805                                         break;
806                                 }
807                         }
808                 } while (0);
809                 if ((fstat(sockfd, &stat_buf) == 0) && S_ISSOCK(stat_buf.st_mode)) {
810                         /* if a valid socket, close it */
811                         close(sockfd);
812                 }
813                 if (timed_thread_test(mail->p_timed_thread, 0)) {
814                         timed_thread_exit(mail->p_timed_thread);
815                 }
816         }
817         mail->unseen = 0;
818         mail->messages = 0;
819         return 0;
820 }
821
822 void print_imap_unseen(struct text_object *obj, char *p, int p_max_size)
823 {
824         struct mail_s *mail = ensure_mail_thread(obj, imap_thread, "imap");
825
826         if (mail && mail->p_timed_thread) {
827                 timed_thread_lock(mail->p_timed_thread);
828                 snprintf(p, p_max_size, "%lu", mail->unseen);
829                 timed_thread_unlock(mail->p_timed_thread);
830         }
831 }
832
833 void print_imap_messages(struct text_object *obj, char *p, int p_max_size)
834 {
835         struct mail_s *mail = ensure_mail_thread(obj, imap_thread, "imap");
836
837         if (mail && mail->p_timed_thread) {
838                 timed_thread_lock(mail->p_timed_thread);
839                 snprintf(p, p_max_size, "%lu", mail->messages);
840                 timed_thread_unlock(mail->p_timed_thread);
841         }
842 }
843
844 int pop3_command(int sockfd, const char *command, char *response, const char *verify)
845 {
846         struct timeval fetchtimeout;
847         fd_set fdset;
848         int res, numbytes = 0;
849         if (send(sockfd, command, strlen(command), 0) == -1) {
850                 perror("send");
851                 return -1;
852         }
853         fetchtimeout.tv_sec = 60;       // 60 second timeout i guess
854         fetchtimeout.tv_usec = 0;
855         FD_ZERO(&fdset);
856         FD_SET(sockfd, &fdset);
857         res = select(sockfd + 1, &fdset, NULL, NULL, &fetchtimeout);
858         if (res > 0) {
859                 if ((numbytes = recv(sockfd, response, MAXDATASIZE - 1, 0)) == -1) {
860                         perror("recv");
861                         return -1;
862                 }
863         }
864         DBGP2("pop3_command() received: %s", response);
865         response[numbytes] = '\0';
866         if (strstr(response, verify) == NULL) {
867                 return -1;
868         }
869         return 0;
870 }
871
872 static void *pop3_thread(void *arg)
873 {
874         int sockfd, numbytes;
875         char recvbuf[MAXDATASIZE];
876         char sendbuf[MAXDATASIZE];
877         char *reply;
878         unsigned int fail = 0;
879         unsigned long old_unseen = ULONG_MAX;
880         struct stat stat_buf;
881         struct hostent he, *he_res = 0;
882         int he_errno;
883         char hostbuff[2048];
884         struct sockaddr_in their_addr;  // connector's address information
885         struct mail_s *mail = (struct mail_s *)arg;
886         char resolved_host = 0;
887
888         while (fail < mail->retries) {
889                 struct timeval fetchtimeout;
890                 int res;
891                 fd_set fdset;
892                 if (!resolved_host) {
893 #ifdef HAVE_GETHOSTBYNAME_R
894                         if (gethostbyname_r(mail->host, &he, hostbuff, sizeof(hostbuff), &he_res, &he_errno)) { // get the host info
895                                 NORM_ERR("POP3 gethostbyname_r: %s", hstrerror(h_errno));
896                                 fail++;
897                                 break;
898                         }
899 #else /* HAVE_GETHOSTBYNAME_R */
900                         if ((he_res = gethostbyname(mail->host)) == NULL) {     // get the host info
901                                 herror("gethostbyname");
902                 fail++;
903                 break;
904         }
905 #endif /* HAVE_GETHOSTBYNAME_R */
906         resolved_host = 1;
907 }
908                 if (fail > 0) {
909                         NORM_ERR("Trying POP3 connection again for %s@%s (try %u/%u)",
910                                         mail->user, mail->host, fail + 1, mail->retries);
911                 }
912                 do {
913                         if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
914                                 perror("socket");
915                                 fail++;
916                                 break;
917                         }
918
919                         // host byte order
920                         their_addr.sin_family = AF_INET;
921                         // short, network byte order
922                         their_addr.sin_port = htons(mail->port);
923                         their_addr.sin_addr = *((struct in_addr *) he_res->h_addr);
924                         // zero the rest of the struct
925                         memset(&(their_addr.sin_zero), '\0', 8);
926
927                         if (connect(sockfd, (struct sockaddr *) &their_addr,
928                                                 sizeof(struct sockaddr)) == -1) {
929                                 perror("connect");
930                                 fail++;
931                                 break;
932                         }
933
934                         fetchtimeout.tv_sec = 60;       // 60 second timeout i guess
935                         fetchtimeout.tv_usec = 0;
936                         FD_ZERO(&fdset);
937                         FD_SET(sockfd, &fdset);
938                         res = select(sockfd + 1, &fdset, NULL, NULL, &fetchtimeout);
939                         if (res > 0) {
940                                 if ((numbytes = recv(sockfd, recvbuf, MAXDATASIZE - 1, 0)) == -1) {
941                                         perror("recv");
942                                         fail++;
943                                         break;
944                                 }
945                         } else {
946                                 NORM_ERR("POP3 connection failed: timeout\n");
947                                 fail++;
948                                 break;
949                         }
950                         DBGP2("pop3_thread received: %s", recvbuf);
951                         recvbuf[numbytes] = '\0';
952                         if (strstr(recvbuf, "+OK ") != recvbuf) {
953                                 NORM_ERR("POP3 connection failed, probably not a POP3 server");
954                                 fail++;
955                                 break;
956                         }
957                         strncpy(sendbuf, "USER ", MAXDATASIZE);
958                         strncat(sendbuf, mail->user, MAXDATASIZE - strlen(sendbuf) - 1);
959                         strncat(sendbuf, "\r\n", MAXDATASIZE - strlen(sendbuf) - 1);
960                         if (pop3_command(sockfd, sendbuf, recvbuf, "+OK ")) {
961                                 fail++;
962                                 break;
963                         }
964
965                         strncpy(sendbuf, "PASS ", MAXDATASIZE);
966                         strncat(sendbuf, mail->pass, MAXDATASIZE - strlen(sendbuf) - 1);
967                         strncat(sendbuf, "\r\n", MAXDATASIZE - strlen(sendbuf) - 1);
968                         if (pop3_command(sockfd, sendbuf, recvbuf, "+OK ")) {
969                                 NORM_ERR("POP3 server login failed: %s", recvbuf);
970                                 fail++;
971                                 break;
972                         }
973
974                         strncpy(sendbuf, "STAT\r\n", MAXDATASIZE);
975                         if (pop3_command(sockfd, sendbuf, recvbuf, "+OK ")) {
976                                 perror("send STAT");
977                                 fail++;
978                                 break;
979                         }
980
981                         // now we get the data
982                         reply = recvbuf + 4;
983                         if (reply == NULL) {
984                                 NORM_ERR("Error parsing POP3 response: %s", recvbuf);
985                                 fail++;
986                                 break;
987                         } else {
988                                 timed_thread_lock(mail->p_timed_thread);
989                                 sscanf(reply, "%lu %lu", &mail->unseen, &mail->used);
990                                 timed_thread_unlock(mail->p_timed_thread);
991                         }
992                         
993                         strncpy(sendbuf, "QUIT\r\n", MAXDATASIZE);
994                         if (pop3_command(sockfd, sendbuf, recvbuf, "+OK")) {
995                                 NORM_ERR("POP3 logout failed: %s", recvbuf);
996                                 fail++;
997                                 break;
998                         }
999                         
1000                         if (strlen(mail->command) > 1 && mail->unseen > old_unseen) {
1001                                 // new mail goodie
1002                                 if (system(mail->command) == -1) {
1003                                         perror("system()");
1004                                 }
1005                         }
1006                         fail = 0;
1007                         old_unseen = mail->unseen;
1008                 } while (0);
1009                 if ((fstat(sockfd, &stat_buf) == 0) && S_ISSOCK(stat_buf.st_mode)) {
1010                         /* if a valid socket, close it */
1011                         close(sockfd);
1012                 }
1013                 if (timed_thread_test(mail->p_timed_thread, 0)) {
1014                         timed_thread_exit(mail->p_timed_thread);
1015                 }
1016         }
1017         mail->unseen = 0;
1018         mail->used = 0;
1019         return 0;
1020 }
1021
1022 void print_pop3_unseen(struct text_object *obj, char *p, int p_max_size)
1023 {
1024         struct mail_s *mail = ensure_mail_thread(obj, pop3_thread, "pop3");
1025
1026         if (mail && mail->p_timed_thread) {
1027                 timed_thread_lock(mail->p_timed_thread);
1028                 snprintf(p, p_max_size, "%lu", mail->unseen);
1029                 timed_thread_unlock(mail->p_timed_thread);
1030         }
1031 }
1032
1033 void print_pop3_used(struct text_object *obj, char *p, int p_max_size)
1034 {
1035         struct mail_s *mail = ensure_mail_thread(obj, pop3_thread, "pop3");
1036
1037         if (mail && mail->p_timed_thread) {
1038                 timed_thread_lock(mail->p_timed_thread);
1039                 snprintf(p, p_max_size, "%.1f",
1040                                 mail->used / 1024.0 / 1024.0);
1041                 timed_thread_unlock(mail->p_timed_thread);
1042         }
1043 }