Check for deleted and not expunged emails
[monky] / src / mail.c
1 /* Conky, a system monitor, based on torsmo
2  *
3  * Any original torsmo code is licensed under the BSD license
4  *
5  * All code written since the fork of torsmo is licensed under the GPL
6  *
7  * Please see COPYING for details
8  *
9  * Copyright (c) 2004, Hannu Saransaari and Lauri Hakkarainen
10  * Copyright (c) 2005-2009 Brenden Matthews, Philip Kovacs, et. al.
11  *      (see AUTHORS)
12  * All rights reserved.
13  *
14  * This program is free software: you can redistribute it and/or modify
15  * it under the terms of the GNU General Public License as published by
16  * the Free Software Foundation, either version 3 of the License, or
17  * (at your option) any later version.
18  *
19  * This program is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  * You should have received a copy of the GNU General Public License
24  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
25  *
26  */
27
28 #include "config.h"
29 #include "conky.h"
30 #include "common.h"
31 #include "logging.h"
32 #include "mail.h"
33
34 #include <errno.h>
35 #include <stdio.h>
36 #include <string.h>
37 #include <unistd.h>
38 #include <limits.h>
39 #include <netinet/in.h>
40 #include <netdb.h>
41 #include <sys/socket.h>
42 #include <sys/stat.h>
43 #include <sys/time.h>
44 #include <sys/param.h>
45
46 #include <dirent.h>
47 #include <errno.h>
48 #include <termios.h>
49
50 /* MAX() is defined by a header included from conky.h
51  * maybe once this is not true anymore, so have an alternative
52  * waiting to drop in.
53  *
54  * #define MAX(a, b)  ((a > b) ? a : b)
55  */
56
57 char *current_mail_spool;
58
59 void update_mail_count(struct local_mail_s *mail)
60 {
61         struct stat st;
62
63         if (mail == NULL) {
64                 return;
65         }
66
67         /* TODO: use that fine file modification notify on Linux 2.4 */
68
69         /* don't check mail so often (9.5s is minimum interval) */
70         if (current_update_time - mail->last_update < 9.5) {
71                 return;
72         } else {
73                 mail->last_update = current_update_time;
74         }
75
76         if (stat(mail->box, &st)) {
77                 static int rep = 0;
78
79                 if (!rep) {
80                         ERR("can't stat %s: %s", mail->box, strerror(errno));
81                         rep = 1;
82                 }
83                 return;
84         }
85 #if HAVE_DIRENT_H
86         /* maildir format */
87         if (S_ISDIR(st.st_mode)) {
88                 DIR *dir;
89                 char *dirname;
90                 struct dirent *dirent;
91                 char *mailflags;
92
93                 mail->mail_count = mail->new_mail_count = 0;
94                 mail->seen_mail_count = mail->unseen_mail_count = 0;
95                 mail->flagged_mail_count = mail->unflagged_mail_count = 0;
96                 mail->forwarded_mail_count = mail->unforwarded_mail_count = 0;
97                 mail->replied_mail_count = mail->unreplied_mail_count = 0;
98                 mail->draft_mail_count = mail->trashed_mail_count = 0;
99                 dirname = (char *) malloc(sizeof(char) * (strlen(mail->box) + 5));
100                 if (!dirname) {
101                         ERR("malloc");
102                         return;
103                 }
104                 strcpy(dirname, mail->box);
105                 strcat(dirname, "/");
106                 /* checking the cur subdirectory */
107                 strcat(dirname, "cur");
108
109                 dir = opendir(dirname);
110                 if (!dir) {
111                         ERR("cannot open directory");
112                         free(dirname);
113                         return;
114                 }
115                 dirent = readdir(dir);
116                 while (dirent) {
117                         /* . and .. are skipped */
118                         if (dirent->d_name[0] != '.') {
119                                 mail->mail_count++;
120                                 mailflags = (char *) malloc(sizeof(char) * strlen(strrchr(dirent->d_name, ',')));
121                                 if (!mailflags) {
122                                         ERR("malloc");
123                                         free(dirname);
124                                         return;
125                                 }
126                                 strcpy(mailflags, strrchr(dirent->d_name, ','));
127                                 if (!strchr(mailflags, 'T')) { /* The message is not in the trash */
128                                         if (strchr(mailflags, 'S')) { /*The message has been seen */
129                                                 mail->seen_mail_count++;
130                                         } else {
131                                                 mail->unseen_mail_count++;
132                                         }
133                                         if (strchr(mailflags, 'F')) { /*The message was flagged */
134                                                 mail->flagged_mail_count++;
135                                         } else {
136                                                 mail->unflagged_mail_count++;
137                                         }
138                                         if (strchr(mailflags, 'P')) { /*The message was forwarded */
139                                                 mail->forwarded_mail_count++;
140                                         } else {
141                                                 mail->unforwarded_mail_count++;
142                                         }
143                                         if (strchr(mailflags, 'R')) { /*The message was replied */
144                                                 mail->replied_mail_count++;
145                                         } else {
146                                                 mail->unreplied_mail_count++;
147                                         }
148                                         if (strchr(mailflags, 'D')) { /*The message is a draft */
149                                                 mail->draft_mail_count++;
150                                         }
151                                 } else {
152                                         mail->trashed_mail_count++;
153                                 }
154                                 free(mailflags);
155                         }
156                         dirent = readdir(dir);
157                 }
158                 closedir(dir);
159
160                 dirname[strlen(dirname) - 3] = '\0';
161                 strcat(dirname, "new");
162
163                 dir = opendir(dirname);
164                 if (!dir) {
165                         ERR("cannot open directory");
166                         free(dirname);
167                         return;
168                 }
169                 dirent = readdir(dir);
170                 while (dirent) {
171                         /* . and .. are skipped */
172                         if (dirent->d_name[0] != '.') {
173                                 mail->new_mail_count++;
174                                 mail->mail_count++;
175                                 mail->unseen_mail_count++;  /* new messages cannot have been seen */
176                         }
177                         dirent = readdir(dir);
178                 }
179                 closedir(dir);
180
181                 free(dirname);
182                 return;
183         }
184 #endif
185         /* mbox format */
186         if (st.st_mtime != mail->last_mtime) {
187                 /* yippee, modification time has changed, let's read mail count! */
188                 static int rep;
189                 FILE *fp;
190                 int reading_status = 0;
191
192                 /* could lock here but I don't think it's really worth it because
193                  * this isn't going to write mail spool */
194
195                 mail->new_mail_count = mail->mail_count = 0;
196
197                 /* these flags are not supported for mbox */
198                 mail->seen_mail_count = mail->unseen_mail_count = -1;
199                 mail->flagged_mail_count = mail->unflagged_mail_count = -1;
200                 mail->forwarded_mail_count = mail->unforwarded_mail_count = -1;
201                 mail->replied_mail_count = mail->unreplied_mail_count = -1;
202                 mail->draft_mail_count = mail->trashed_mail_count = -1;
203
204                 fp = open_file(mail->box, &rep);
205                 if (!fp) {
206                         return;
207                 }
208
209                 /* NOTE: adds mail as new if there isn't Status-field at all */
210
211                 while (!feof(fp)) {
212                         char buf[128];
213                         int was_new = 0;
214
215                         if (fgets(buf, 128, fp) == NULL) {
216                                 break;
217                         }
218
219                         if (strncmp(buf, "From ", 5) == 0) {
220                                 /* ignore MAILER-DAEMON */
221                                 if (strncmp(buf + 5, "MAILER-DAEMON ", 14) != 0) {
222                                         mail->mail_count++;
223                                         was_new = 0;
224
225                                         if (reading_status == 1) {
226                                                 mail->new_mail_count++;
227                                         } else {
228                                                 reading_status = 1;
229                                         }
230                                 }
231                         } else {
232                                 if (reading_status == 1
233                                                 && strncmp(buf, "X-Mozilla-Status:", 17) == 0) {
234                                         int xms = strtol(buf + 17, NULL, 16);
235                                         /* check that mail isn't marked for deletion */
236                                         if (xms & 0x0008) {
237                                                 mail->trashed_mail_count++;
238                                                 reading_status = 0;
239                                                 /* Don't check whether the trashed email is unread */
240                                                 continue;
241                                         }
242                                         /* check that mail isn't already read */
243                                         if (!(xms & 0x0001)) {
244                                                 mail->new_mail_count++;
245                                                 was_new = 1;
246                                         }
247
248                                         /* check for an additional X-Status header */
249                                         reading_status = 2;
250                                         continue;
251                                 }
252                                 if (reading_status == 1 && strncmp(buf, "Status:", 7) == 0) {
253                                         /* check that mail isn't already read */
254                                         if (strchr(buf + 7, 'R') == NULL) {
255                                                 mail->new_mail_count++;
256                                                 was_new = 1;
257                                         }
258
259                                         reading_status = 2;
260                                         continue;
261                                 }
262                                 if (reading_status >= 1 && strncmp(buf, "X-Status:", 9) == 0) {
263                                         /* check that mail isn't marked for deletion */
264                                         if (strchr(buf + 9, 'D') != NULL) {
265                                                 mail->trashed_mail_count++;
266                                                 /* If the mail was previously detected as new,
267                                                    subtract it from the new mail count */
268                                                 if (was_new)
269                                                         mail->new_mail_count--;
270                                         }
271
272                                         reading_status = 0;
273                                         continue;
274                                 }
275                         }
276
277                         /* skip until \n */
278                         while (strchr(buf, '\n') == NULL && !feof(fp)) {
279                                 fgets(buf, 128, fp);
280                         }
281                 }
282
283                 fclose(fp);
284
285                 if (reading_status) {
286                         mail->new_mail_count++;
287                 }
288
289                 mail->last_mtime = st.st_mtime;
290         }
291 }
292
293 #define MAXDATASIZE 1000
294
295 struct mail_s *parse_mail_args(char type, const char *arg)
296 {
297         struct mail_s *mail;
298         char *tmp;
299
300         mail = malloc(sizeof(struct mail_s));
301         memset(mail, 0, sizeof(struct mail_s));
302
303         if (sscanf(arg, "%128s %128s %128s", mail->host, mail->user, mail->pass)
304                         != 3) {
305                 if (type == POP3_TYPE) {
306                         ERR("Scanning POP3 args failed");
307                 } else if (type == IMAP_TYPE) {
308                         ERR("Scanning IMAP args failed");
309                 }
310                 return 0;
311         }
312         // see if password needs prompting
313         if (mail->pass[0] == '*' && mail->pass[1] == '\0') {
314                 int fp = fileno(stdin);
315                 struct termios term;
316
317                 tcgetattr(fp, &term);
318                 term.c_lflag &= ~ECHO;
319                 tcsetattr(fp, TCSANOW, &term);
320                 printf("Enter mailbox password (%s@%s): ", mail->user, mail->host);
321                 scanf("%128s", mail->pass);
322                 printf("\n");
323                 term.c_lflag |= ECHO;
324                 tcsetattr(fp, TCSANOW, &term);
325         }
326         // now we check for optional args
327         tmp = strstr(arg, "-r ");
328         if (tmp) {
329                 tmp += 3;
330                 sscanf(tmp, "%u", &mail->retries);
331         } else {
332                 mail->retries = 5;      // 5 retries after failure
333         }
334         tmp = strstr(arg, "-i ");
335         if (tmp) {
336                 tmp += 3;
337                 sscanf(tmp, "%f", &mail->interval);
338         } else {
339                 mail->interval = 300;   // 5 minutes
340         }
341         tmp = strstr(arg, "-p ");
342         if (tmp) {
343                 tmp += 3;
344                 sscanf(tmp, "%lu", &mail->port);
345         } else {
346                 if (type == POP3_TYPE) {
347                         mail->port = 110;       // default pop3 port
348                 } else if (type == IMAP_TYPE) {
349                         mail->port = 143;       // default imap port
350                 }
351         }
352         if (type == IMAP_TYPE) {
353                 tmp = strstr(arg, "-f ");
354                 if (tmp) {
355                         tmp += 3;
356                         sscanf(tmp, "%s", mail->folder);
357                 } else {
358                         strncpy(mail->folder, "INBOX", 128);    // default imap inbox
359                 }
360         }
361         tmp = strstr(arg, "-e ");
362         if (tmp) {
363                 int len = 1024;
364                 tmp += 3;
365
366                 if (tmp[0] == '\'') {
367                         len = strstr(tmp + 1, "'") - tmp - 1;
368                         if (len > 1024) {
369                                 len = 1024;
370                         }
371                 }
372                 strncpy(mail->command, tmp + 1, len);
373         } else {
374                 mail->command[0] = '\0';
375         }
376         mail->p_timed_thread = NULL;
377         return mail;
378 }
379
380 int imap_command(int sockfd, const char *command, char *response, const char *verify)
381 {
382         struct timeval timeout;
383         fd_set fdset;
384         int res, numbytes = 0;
385         if (send(sockfd, command, strlen(command), 0) == -1) {
386                 perror("send");
387                 return -1;
388         }
389         timeout.tv_sec = 60;    // 60 second timeout i guess
390         timeout.tv_usec = 0;
391         FD_ZERO(&fdset);
392         FD_SET(sockfd, &fdset);
393         res = select(sockfd + 1, &fdset, NULL, NULL, &timeout);
394         if (res > 0) {
395                 if ((numbytes = recv(sockfd, response, MAXDATASIZE - 1, 0)) == -1) {
396                         perror("recv");
397                         return -1;
398                 }
399         }
400         DBGP2("imap_command()  command: %s", command);
401         DBGP2("imap_command() received: %s", response);
402         response[numbytes] = '\0';
403         if (strstr(response, verify) == NULL) {
404                 return -1;
405         }
406         return 0;
407 }
408
409 int imap_check_status(char *recvbuf, struct mail_s *mail)
410 {
411         char *reply;
412         reply = strstr(recvbuf, " (MESSAGES ");
413         if (!reply || strlen(reply) < 2) {
414                 return -1;
415         }
416         reply += 2;
417         *strchr(reply, ')') = '\0';
418         if (reply == NULL) {
419                 ERR("Error parsing IMAP response: %s", recvbuf);
420                 return -1;
421         } else {
422                 timed_thread_lock(mail->p_timed_thread);
423                 sscanf(reply, "MESSAGES %lu UNSEEN %lu", &mail->messages,
424                                 &mail->unseen);
425                 timed_thread_unlock(mail->p_timed_thread);
426         }
427         return 0;
428 }
429
430 void imap_unseen_command(struct mail_s *mail, unsigned long old_unseen, unsigned long old_messages)
431 {
432         if (strlen(mail->command) > 1 && (mail->unseen > old_unseen
433                                 || (mail->messages > old_messages && mail->unseen > 0))) {
434                 // new mail goodie
435                 if (system(mail->command) == -1) {
436                         perror("system()");
437                 }
438         }
439 }
440
441 void *imap_thread(void *arg)
442 {
443         int sockfd, numbytes;
444         char recvbuf[MAXDATASIZE];
445         char sendbuf[MAXDATASIZE];
446         unsigned int fail = 0;
447         unsigned long old_unseen = ULONG_MAX;
448         unsigned long old_messages = ULONG_MAX;
449         struct stat stat_buf;
450         struct hostent he, *he_res = 0;
451         int he_errno;
452         char hostbuff[2048];
453         struct sockaddr_in their_addr;  // connector's address information
454         struct mail_s *mail = (struct mail_s *)arg;
455         int has_idle = 0;
456         int threadfd = timed_thread_readfd(mail->p_timed_thread);
457
458 #ifdef HAVE_GETHOSTBYNAME_R
459         if (gethostbyname_r(mail->host, &he, hostbuff, sizeof(hostbuff), &he_res, &he_errno)) { // get the host info
460                 ERR("IMAP gethostbyname_r: %s", hstrerror(h_errno));
461                 exit(1);
462         }
463 #else /* HAVE_GETHOSTBYNAME_R */
464         if ((he_res = gethostbyname(mail->host)) == NULL) {     // get the host info
465                 herror("gethostbyname");
466                 exit(1);
467         }
468 #endif /* HAVE_GETHOSTBYNAME_R */
469         while (fail < mail->retries) {
470                 struct timeval timeout;
471                 int res;
472                 fd_set fdset;
473
474                 if (fail > 0) {
475                         ERR("Trying IMAP connection again for %s@%s (try %u/%u)",
476                                         mail->user, mail->host, fail + 1, mail->retries);
477                 }
478                 do {
479                         if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
480                                 perror("socket");
481                                 fail++;
482                                 break;
483                         }
484
485                         // host byte order
486                         their_addr.sin_family = AF_INET;
487                         // short, network byte order
488                         their_addr.sin_port = htons(mail->port);
489                         their_addr.sin_addr = *((struct in_addr *) he_res->h_addr);
490                         // zero the rest of the struct
491                         memset(&(their_addr.sin_zero), '\0', 8);
492
493                         if (connect(sockfd, (struct sockaddr *) &their_addr,
494                                                 sizeof(struct sockaddr)) == -1) {
495                                 perror("connect");
496                                 fail++;
497                                 break;
498                         }
499
500                         timeout.tv_sec = 60;    // 60 second timeout i guess
501                         timeout.tv_usec = 0;
502                         FD_ZERO(&fdset);
503                         FD_SET(sockfd, &fdset);
504                         res = select(sockfd + 1, &fdset, NULL, NULL, &timeout);
505                         if (res > 0) {
506                                 if ((numbytes = recv(sockfd, recvbuf, MAXDATASIZE - 1, 0)) == -1) {
507                                         perror("recv");
508                                         fail++;
509                                         break;
510                                 }
511                         } else {
512                                 ERR("IMAP connection failed: timeout");
513                                 fail++;
514                                 break;
515                         }
516                         recvbuf[numbytes] = '\0';
517                         DBGP2("imap_thread() received: %s", recvbuf);
518                         if (strstr(recvbuf, "* OK") != recvbuf) {
519                                 ERR("IMAP connection failed, probably not an IMAP server");
520                                 fail++;
521                                 break;
522                         }
523                         strncpy(sendbuf, "a1 login ", MAXDATASIZE);
524                         strncat(sendbuf, mail->user, MAXDATASIZE - strlen(sendbuf) - 1);
525                         strncat(sendbuf, " ", MAXDATASIZE - strlen(sendbuf) - 1);
526                         strncat(sendbuf, mail->pass, MAXDATASIZE - strlen(sendbuf) - 1);
527                         strncat(sendbuf, "\r\n", MAXDATASIZE - strlen(sendbuf) - 1);
528                         if (imap_command(sockfd, sendbuf, recvbuf, "a1 OK")) {
529                                 fail++;
530                                 break;
531                         }
532                         if (strstr(recvbuf, " IDLE ") != NULL) {
533                                 has_idle = 1;
534                         }
535
536                         strncpy(sendbuf, "a2 STATUS ", MAXDATASIZE);
537                         strncat(sendbuf, mail->folder, MAXDATASIZE - strlen(sendbuf) - 1);
538                         strncat(sendbuf, " (MESSAGES UNSEEN)\r\n",
539                                         MAXDATASIZE - strlen(sendbuf) - 1);
540                         if (imap_command(sockfd, sendbuf, recvbuf, "a2 OK")) {
541                                 fail++;
542                                 break;
543                         }
544
545                         if (imap_check_status(recvbuf, mail)) {
546                                 fail++;
547                                 break;
548                         }
549                         imap_unseen_command(mail, old_unseen, old_messages);
550                         fail = 0;
551                         old_unseen = mail->unseen;
552                         old_messages = mail->messages;
553
554                         if (has_idle) {
555                                 strncpy(sendbuf, "a4 SELECT ", MAXDATASIZE);
556                                 strncat(sendbuf, mail->folder, MAXDATASIZE - strlen(sendbuf) - 1);
557                                 strncat(sendbuf, "\r\n", MAXDATASIZE - strlen(sendbuf) - 1);
558                                 if (imap_command(sockfd, sendbuf, recvbuf, "a4 OK")) {
559                                         fail++;
560                                         break;
561                                 }
562
563                                 strncpy(sendbuf, "a5 IDLE\r\n", MAXDATASIZE);
564                                 if (imap_command(sockfd, sendbuf, recvbuf, "+ idling")) {
565                                         fail++;
566                                         break;
567                                 }
568                                 recvbuf[0] = '\0';
569
570                                 while (1) {
571                                         /*
572                                          * RFC 2177 says we have to re-idle every 29 minutes.
573                                          * We'll do it every 20 minutes to be safe.
574                                          */
575                                         timeout.tv_sec = 1200;
576                                         timeout.tv_usec = 0;
577                                         DBGP2("idling...");
578                                         FD_ZERO(&fdset);
579                                         FD_SET(sockfd, &fdset);
580                                         FD_SET(threadfd, &fdset);
581                                         res = select(MAX(sockfd + 1, threadfd + 1), &fdset, NULL, NULL, NULL);
582                                         if (timed_thread_test(mail->p_timed_thread, 1) || (res == -1 && errno == EINTR) || FD_ISSET(threadfd, &fdset)) {
583                                                 if ((fstat(sockfd, &stat_buf) == 0) && S_ISSOCK(stat_buf.st_mode)) {
584                                                         /* if a valid socket, close it */
585                                                         close(sockfd);
586                                                 }
587                                                 timed_thread_exit(mail->p_timed_thread);
588                                         } else if (res > 0) {
589                                                 if ((numbytes = recv(sockfd, recvbuf, MAXDATASIZE - 1, 0)) == -1) {
590                                                         perror("recv idling");
591                                                         fail++;
592                                                         break;
593                                                 }
594                                         } else {
595                                                 fail++;
596                                                 break;
597                                         }
598                                         recvbuf[numbytes] = '\0';
599                                         DBGP2("imap_thread() received: %s", recvbuf);
600                                         if (strlen(recvbuf) > 2) {
601                                                 unsigned long messages, recent;
602                                                 char *buf = recvbuf;
603                                                 char force_check = 0;
604                                                 buf = strstr(buf, "EXISTS");
605                                                 while (buf && strlen(buf) > 1 && strstr(buf + 1, "EXISTS")) {
606                                                         buf = strstr(buf + 1, "EXISTS");
607                                                 }
608                                                 if (buf) {
609                                                         // back up until we reach '*'
610                                                         while (buf >= recvbuf && buf[0] != '*') {
611                                                                 buf--;
612                                                         }
613                                                         if (sscanf(buf, "* %lu EXISTS\r\n", &messages) == 1) {
614                                                                 timed_thread_lock(mail->p_timed_thread);
615                                                                 if (mail->messages != messages) {
616                                                                         force_check = 1;
617                                                                         mail->messages = messages;
618                                                                 }
619                                                                 timed_thread_unlock(mail->p_timed_thread);
620                                                         }
621                                                 }
622                                                 buf = recvbuf;
623                                                 buf = strstr(buf, "RECENT");
624                                                 while (buf && strlen(buf) > 1 && strstr(buf + 1, "RECENT")) {
625                                                         buf = strstr(buf + 1, "RECENT");
626                                                 }
627                                                 if (buf) {
628                                                         // back up until we reach '*'
629                                                         while (buf >= recvbuf && buf[0] != '*') {
630                                                                 buf--;
631                                                         }
632                                                         if (sscanf(buf, "* %lu RECENT\r\n", &recent) != 1) {
633                                                                 recent = 0;
634                                                         }
635                                                 }
636                                                 /*
637                                                  * check if we got a FETCH from server, recent was
638                                                  * something other than 0, or we had a timeout
639                                                  */
640                                                 buf = recvbuf;
641                                                 if (recent > 0 || (buf && strstr(buf, " FETCH ")) || timeout.tv_sec == 0 || force_check) {
642                                                         // re-check messages and unseen
643                                                         if (imap_command(sockfd, "DONE\r\n", recvbuf, "a5 OK")) {
644                                                                 fail++;
645                                                                 break;
646                                                         }
647                                                         strncpy(sendbuf, "a2 STATUS ", MAXDATASIZE);
648                                                         strncat(sendbuf, mail->folder, MAXDATASIZE - strlen(sendbuf) - 1);
649                                                         strncat(sendbuf, " (MESSAGES UNSEEN)\r\n",
650                                                                         MAXDATASIZE - strlen(sendbuf) - 1);
651                                                         if (imap_command(sockfd, sendbuf, recvbuf, "a2 OK")) {
652                                                                 fail++;
653                                                                 break;
654                                                         }
655                                                         if (imap_check_status(recvbuf, mail)) {
656                                                                 fail++;
657                                                                 break;
658                                                         }
659                                                         strncpy(sendbuf, "a5 IDLE\r\n", MAXDATASIZE);
660                                                         if (imap_command(sockfd, sendbuf, recvbuf, "+ idling")) {
661                                                                 fail++;
662                                                                 break;
663                                                         }
664                                                 }
665                                                 /*
666                                                  * check if we got a BYE from server
667                                                  */
668                                                 buf = recvbuf;
669                                                 if (buf && strstr(buf, "* BYE")) {
670                                                         // need to re-connect
671                                                         break;
672                                                 }
673                                         } else {
674                                                 fail++;
675                                                 break;
676                                         }
677                                         imap_unseen_command(mail, old_unseen, old_messages);
678                                         fail = 0;
679                                         old_unseen = mail->unseen;
680                                         old_messages = mail->messages;
681                                 }
682                                 if (fail) break;
683                         } else {
684                                 strncpy(sendbuf, "a3 logout\r\n", MAXDATASIZE);
685                                 if (send(sockfd, sendbuf, strlen(sendbuf), 0) == -1) {
686                                         perror("send a3");
687                                         fail++;
688                                         break;
689                                 }
690                                 timeout.tv_sec = 60;    // 60 second timeout i guess
691                                 timeout.tv_usec = 0;
692                                 FD_ZERO(&fdset);
693                                 FD_SET(sockfd, &fdset);
694                                 res = select(sockfd + 1, &fdset, NULL, NULL, &timeout);
695                                 if (res > 0) {
696                                         if ((numbytes = recv(sockfd, recvbuf, MAXDATASIZE - 1, 0)) == -1) {
697                                                 perror("recv a3");
698                                                 fail++;
699                                                 break;
700                                         }
701                                 }
702                                 recvbuf[numbytes] = '\0';
703                                 DBGP2("imap_thread() received: %s", recvbuf);
704                                 if (strstr(recvbuf, "a3 OK") == NULL) {
705                                         ERR("IMAP logout failed: %s", recvbuf);
706                                         fail++;
707                                         break;
708                                 }
709                         }
710                 } while (0);
711                 if ((fstat(sockfd, &stat_buf) == 0) && S_ISSOCK(stat_buf.st_mode)) {
712                         /* if a valid socket, close it */
713                         close(sockfd);
714                 }
715                 if (timed_thread_test(mail->p_timed_thread, 0)) {
716                         timed_thread_exit(mail->p_timed_thread);
717                 }
718         }
719         mail->unseen = 0;
720         mail->messages = 0;
721         return 0;
722 }
723
724 int pop3_command(int sockfd, const char *command, char *response, const char *verify)
725 {
726         struct timeval timeout;
727         fd_set fdset;
728         int res, numbytes = 0;
729         if (send(sockfd, command, strlen(command), 0) == -1) {
730                 perror("send");
731                 return -1;
732         }
733         timeout.tv_sec = 60;    // 60 second timeout i guess
734         timeout.tv_usec = 0;
735         FD_ZERO(&fdset);
736         FD_SET(sockfd, &fdset);
737         res = select(sockfd + 1, &fdset, NULL, NULL, &timeout);
738         if (res > 0) {
739                 if ((numbytes = recv(sockfd, response, MAXDATASIZE - 1, 0)) == -1) {
740                         perror("recv");
741                         return -1;
742                 }
743         }
744         DBGP2("pop3_command() received: %s", response);
745         response[numbytes] = '\0';
746         if (strstr(response, verify) == NULL) {
747                 return -1;
748         }
749         return 0;
750 }
751
752 void *pop3_thread(void *arg)
753 {
754         int sockfd, numbytes;
755         char recvbuf[MAXDATASIZE];
756         char sendbuf[MAXDATASIZE];
757         char *reply;
758         unsigned int fail = 0;
759         unsigned long old_unseen = ULONG_MAX;
760         struct stat stat_buf;
761         struct hostent he, *he_res = 0;
762         int he_errno;
763         char hostbuff[2048];
764         struct sockaddr_in their_addr;  // connector's address information
765         struct mail_s *mail = (struct mail_s *)arg;
766
767 #ifdef HAVE_GETHOSTBYNAME_R
768         if (gethostbyname_r(mail->host, &he, hostbuff, sizeof(hostbuff), &he_res, &he_errno)) { // get the host info
769                 ERR("POP3 gethostbyname_r: %s", hstrerror(h_errno));
770                 exit(1);
771         }
772 #else /* HAVE_GETHOSTBYNAME_R */
773         if ((he_res = gethostbyname(mail->host)) == NULL) {     // get the host info
774                 herror("gethostbyname");
775                 exit(1);
776         }
777 #endif /* HAVE_GETHOSTBYNAME_R */
778         while (fail < mail->retries) {
779                 struct timeval timeout;
780                 int res;
781                 fd_set fdset;
782
783                 if (fail > 0) {
784                         ERR("Trying POP3 connection again for %s@%s (try %u/%u)",
785                                         mail->user, mail->host, fail + 1, mail->retries);
786                 }
787                 do {
788                         if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
789                                 perror("socket");
790                                 fail++;
791                                 break;
792                         }
793
794                         // host byte order
795                         their_addr.sin_family = AF_INET;
796                         // short, network byte order
797                         their_addr.sin_port = htons(mail->port);
798                         their_addr.sin_addr = *((struct in_addr *) he_res->h_addr);
799                         // zero the rest of the struct
800                         memset(&(their_addr.sin_zero), '\0', 8);
801
802                         if (connect(sockfd, (struct sockaddr *) &their_addr,
803                                                 sizeof(struct sockaddr)) == -1) {
804                                 perror("connect");
805                                 fail++;
806                                 break;
807                         }
808
809                         timeout.tv_sec = 60;    // 60 second timeout i guess
810                         timeout.tv_usec = 0;
811                         FD_ZERO(&fdset);
812                         FD_SET(sockfd, &fdset);
813                         res = select(sockfd + 1, &fdset, NULL, NULL, &timeout);
814                         if (res > 0) {
815                                 if ((numbytes = recv(sockfd, recvbuf, MAXDATASIZE - 1, 0)) == -1) {
816                                         perror("recv");
817                                         fail++;
818                                         break;
819                                 }
820                         } else {
821                                 ERR("POP3 connection failed: timeout\n");
822                                 fail++;
823                                 break;
824                         }
825                         DBGP2("pop3_thread received: %s", recvbuf);
826                         recvbuf[numbytes] = '\0';
827                         if (strstr(recvbuf, "+OK ") != recvbuf) {
828                                 ERR("POP3 connection failed, probably not a POP3 server");
829                                 fail++;
830                                 break;
831                         }
832                         strncpy(sendbuf, "USER ", MAXDATASIZE);
833                         strncat(sendbuf, mail->user, MAXDATASIZE - strlen(sendbuf) - 1);
834                         strncat(sendbuf, "\r\n", MAXDATASIZE - strlen(sendbuf) - 1);
835                         if (pop3_command(sockfd, sendbuf, recvbuf, "+OK ")) {
836                                 fail++;
837                                 break;
838                         }
839
840                         strncpy(sendbuf, "PASS ", MAXDATASIZE);
841                         strncat(sendbuf, mail->pass, MAXDATASIZE - strlen(sendbuf) - 1);
842                         strncat(sendbuf, "\r\n", MAXDATASIZE - strlen(sendbuf) - 1);
843                         if (pop3_command(sockfd, sendbuf, recvbuf, "+OK ")) {
844                                 ERR("POP3 server login failed: %s", recvbuf);
845                                 fail++;
846                                 break;
847                         }
848
849                         strncpy(sendbuf, "STAT\r\n", MAXDATASIZE);
850                         if (pop3_command(sockfd, sendbuf, recvbuf, "+OK ")) {
851                                 perror("send STAT");
852                                 fail++;
853                                 break;
854                         }
855
856                         // now we get the data
857                         reply = recvbuf + 4;
858                         if (reply == NULL) {
859                                 ERR("Error parsing POP3 response: %s", recvbuf);
860                                 fail++;
861                                 break;
862                         } else {
863                                 timed_thread_lock(mail->p_timed_thread);
864                                 sscanf(reply, "%lu %lu", &mail->unseen, &mail->used);
865                                 timed_thread_unlock(mail->p_timed_thread);
866                         }
867                         
868                         strncpy(sendbuf, "QUIT\r\n", MAXDATASIZE);
869                         if (pop3_command(sockfd, sendbuf, recvbuf, "+OK")) {
870                                 ERR("POP3 logout failed: %s", recvbuf);
871                                 fail++;
872                                 break;
873                         }
874                         
875                         if (strlen(mail->command) > 1 && mail->unseen > old_unseen) {
876                                 // new mail goodie
877                                 if (system(mail->command) == -1) {
878                                         perror("system()");
879                                 }
880                         }
881                         fail = 0;
882                         old_unseen = mail->unseen;
883                 } while (0);
884                 if ((fstat(sockfd, &stat_buf) == 0) && S_ISSOCK(stat_buf.st_mode)) {
885                         /* if a valid socket, close it */
886                         close(sockfd);
887                 }
888                 if (timed_thread_test(mail->p_timed_thread, 0)) {
889                         timed_thread_exit(mail->p_timed_thread);
890                 }
891         }
892         mail->unseen = 0;
893         mail->used = 0;
894         return 0;
895 }
896