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