5db57b66d70e88b445367408f4d57389076ad151
[monky] / src / mail.c
1 /* -*- mode: c; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: t -*-
2  *
3  * Conky, a system monitor, based on torsmo
4  *
5  * Any original torsmo code is licensed under the BSD license
6  *
7  * All code written since the fork of torsmo is licensed under the GPL
8  *
9  * Please see COPYING for details
10  *
11  * Copyright (c) 2004, Hannu Saransaari and Lauri Hakkarainen
12  * Copyright (c) 2005-2009 Brenden Matthews, Philip Kovacs, et. al.
13  *      (see AUTHORS)
14  * All rights reserved.
15  *
16  * This program is free software: you can redistribute it and/or modify
17  * it under the terms of the GNU General Public License as published by
18  * the Free Software Foundation, either version 3 of the License, or
19  * (at your option) any later version.
20  *
21  * This program is distributed in the hope that it will be useful,
22  * but WITHOUT ANY WARRANTY; without even the implied warranty of
23  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24  * GNU General Public License for more details.
25  * You should have received a copy of the GNU General Public License
26  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
27  *
28  * vim: ts=4 sw=4 noet ai cindent syntax=c
29  *
30  */
31
32 #include "config.h"
33 #include "conky.h"
34 #include "common.h"
35 #include "logging.h"
36 #include "mail.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                         tmp += 3;
360                         sscanf(tmp, "%s", mail->folder);
361                 } else {
362                         strncpy(mail->folder, "INBOX", 128);    // default imap inbox
363                 }
364         }
365         tmp = strstr(arg, "-e ");
366         if (tmp) {
367                 int len = 1024;
368                 tmp += 3;
369
370                 if (tmp[0] == '\'') {
371                         len = strstr(tmp + 1, "'") - tmp - 1;
372                         if (len > 1024) {
373                                 len = 1024;
374                         }
375                 }
376                 strncpy(mail->command, tmp + 1, len);
377         } else {
378                 mail->command[0] = '\0';
379         }
380         mail->p_timed_thread = NULL;
381         return mail;
382 }
383
384 int imap_command(int sockfd, const char *command, char *response, const char *verify)
385 {
386         struct timeval timeout;
387         fd_set fdset;
388         int res, numbytes = 0;
389         if (send(sockfd, command, strlen(command), 0) == -1) {
390                 perror("send");
391                 return -1;
392         }
393         timeout.tv_sec = 60;    // 60 second timeout i guess
394         timeout.tv_usec = 0;
395         FD_ZERO(&fdset);
396         FD_SET(sockfd, &fdset);
397         res = select(sockfd + 1, &fdset, NULL, NULL, &timeout);
398         if (res > 0) {
399                 if ((numbytes = recv(sockfd, response, MAXDATASIZE - 1, 0)) == -1) {
400                         perror("recv");
401                         return -1;
402                 }
403         }
404         DBGP2("imap_command()  command: %s", command);
405         DBGP2("imap_command() received: %s", response);
406         response[numbytes] = '\0';
407         if (strstr(response, verify) == NULL) {
408                 return -1;
409         }
410         return 0;
411 }
412
413 int imap_check_status(char *recvbuf, struct mail_s *mail)
414 {
415         char *reply;
416         reply = strstr(recvbuf, " (MESSAGES ");
417         if (!reply || strlen(reply) < 2) {
418                 return -1;
419         }
420         reply += 2;
421         *strchr(reply, ')') = '\0';
422         if (reply == NULL) {
423                 NORM_ERR("Error parsing IMAP response: %s", recvbuf);
424                 return -1;
425         } else {
426                 timed_thread_lock(mail->p_timed_thread);
427                 sscanf(reply, "MESSAGES %lu UNSEEN %lu", &mail->messages,
428                                 &mail->unseen);
429                 timed_thread_unlock(mail->p_timed_thread);
430         }
431         return 0;
432 }
433
434 void imap_unseen_command(struct mail_s *mail, unsigned long old_unseen, unsigned long old_messages)
435 {
436         if (strlen(mail->command) > 1 && (mail->unseen > old_unseen
437                                 || (mail->messages > old_messages && mail->unseen > 0))) {
438                 // new mail goodie
439                 if (system(mail->command) == -1) {
440                         perror("system()");
441                 }
442         }
443 }
444
445 void *imap_thread(void *arg)
446 {
447         int sockfd, numbytes;
448         char recvbuf[MAXDATASIZE];
449         char sendbuf[MAXDATASIZE];
450         unsigned int fail = 0;
451         unsigned long old_unseen = ULONG_MAX;
452         unsigned long old_messages = ULONG_MAX;
453         struct stat stat_buf;
454         struct hostent he, *he_res = 0;
455         int he_errno;
456         char hostbuff[2048];
457         struct sockaddr_in their_addr;  // connector's address information
458         struct mail_s *mail = (struct mail_s *)arg;
459         int has_idle = 0;
460         int threadfd = timed_thread_readfd(mail->p_timed_thread);
461
462 #ifdef HAVE_GETHOSTBYNAME_R
463         if (gethostbyname_r(mail->host, &he, hostbuff, sizeof(hostbuff), &he_res, &he_errno)) { // get the host info
464                 NORM_ERR("IMAP gethostbyname_r: %s", hstrerror(h_errno));
465                 exit(EXIT_FAILURE);
466         }
467 #else /* HAVE_GETHOSTBYNAME_R */
468         if ((he_res = gethostbyname(mail->host)) == NULL) {     // get the host info
469                 herror("gethostbyname");
470                 exit(EXIT_FAILURE);
471         }
472 #endif /* HAVE_GETHOSTBYNAME_R */
473         while (fail < mail->retries) {
474                 struct timeval timeout;
475                 int res;
476                 fd_set fdset;
477
478                 if (fail > 0) {
479                         NORM_ERR("Trying IMAP connection again for %s@%s (try %u/%u)",
480                                         mail->user, mail->host, fail + 1, mail->retries);
481                 }
482                 do {
483                         if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
484                                 perror("socket");
485                                 fail++;
486                                 break;
487                         }
488
489                         // host byte order
490                         their_addr.sin_family = AF_INET;
491                         // short, network byte order
492                         their_addr.sin_port = htons(mail->port);
493                         their_addr.sin_addr = *((struct in_addr *) he_res->h_addr);
494                         // zero the rest of the struct
495                         memset(&(their_addr.sin_zero), '\0', 8);
496
497                         if (connect(sockfd, (struct sockaddr *) &their_addr,
498                                                 sizeof(struct sockaddr)) == -1) {
499                                 perror("connect");
500                                 fail++;
501                                 break;
502                         }
503
504                         timeout.tv_sec = 60;    // 60 second timeout i guess
505                         timeout.tv_usec = 0;
506                         FD_ZERO(&fdset);
507                         FD_SET(sockfd, &fdset);
508                         res = select(sockfd + 1, &fdset, NULL, NULL, &timeout);
509                         if (res > 0) {
510                                 if ((numbytes = recv(sockfd, recvbuf, MAXDATASIZE - 1, 0)) == -1) {
511                                         perror("recv");
512                                         fail++;
513                                         break;
514                                 }
515                         } else {
516                                 NORM_ERR("IMAP connection failed: timeout");
517                                 fail++;
518                                 break;
519                         }
520                         recvbuf[numbytes] = '\0';
521                         DBGP2("imap_thread() received: %s", recvbuf);
522                         if (strstr(recvbuf, "* OK") != recvbuf) {
523                                 NORM_ERR("IMAP connection failed, probably not an IMAP server");
524                                 fail++;
525                                 break;
526                         }
527                         strncpy(sendbuf, "a1 login ", MAXDATASIZE);
528                         strncat(sendbuf, mail->user, MAXDATASIZE - strlen(sendbuf) - 1);
529                         strncat(sendbuf, " ", MAXDATASIZE - strlen(sendbuf) - 1);
530                         strncat(sendbuf, mail->pass, MAXDATASIZE - strlen(sendbuf) - 1);
531                         strncat(sendbuf, "\r\n", MAXDATASIZE - strlen(sendbuf) - 1);
532                         if (imap_command(sockfd, sendbuf, recvbuf, "a1 OK")) {
533                                 fail++;
534                                 break;
535                         }
536                         if (strstr(recvbuf, " IDLE ") != NULL) {
537                                 has_idle = 1;
538                         }
539
540                         strncpy(sendbuf, "a2 STATUS ", MAXDATASIZE);
541                         strncat(sendbuf, mail->folder, MAXDATASIZE - strlen(sendbuf) - 1);
542                         strncat(sendbuf, " (MESSAGES UNSEEN)\r\n",
543                                         MAXDATASIZE - strlen(sendbuf) - 1);
544                         if (imap_command(sockfd, sendbuf, recvbuf, "a2 OK")) {
545                                 fail++;
546                                 break;
547                         }
548
549                         if (imap_check_status(recvbuf, mail)) {
550                                 fail++;
551                                 break;
552                         }
553                         imap_unseen_command(mail, old_unseen, old_messages);
554                         fail = 0;
555                         old_unseen = mail->unseen;
556                         old_messages = mail->messages;
557
558                         if (has_idle) {
559                                 strncpy(sendbuf, "a4 SELECT ", MAXDATASIZE);
560                                 strncat(sendbuf, mail->folder, MAXDATASIZE - strlen(sendbuf) - 1);
561                                 strncat(sendbuf, "\r\n", MAXDATASIZE - strlen(sendbuf) - 1);
562                                 if (imap_command(sockfd, sendbuf, recvbuf, "a4 OK")) {
563                                         fail++;
564                                         break;
565                                 }
566
567                                 strncpy(sendbuf, "a5 IDLE\r\n", MAXDATASIZE);
568                                 if (imap_command(sockfd, sendbuf, recvbuf, "+ idling")) {
569                                         fail++;
570                                         break;
571                                 }
572                                 recvbuf[0] = '\0';
573
574                                 while (1) {
575                                         /*
576                                          * RFC 2177 says we have to re-idle every 29 minutes.
577                                          * We'll do it every 20 minutes to be safe.
578                                          */
579                                         timeout.tv_sec = 1200;
580                                         timeout.tv_usec = 0;
581                                         DBGP2("idling...");
582                                         FD_ZERO(&fdset);
583                                         FD_SET(sockfd, &fdset);
584                                         FD_SET(threadfd, &fdset);
585                                         res = select(MAX(sockfd + 1, threadfd + 1), &fdset, NULL, NULL, NULL);
586                                         if (timed_thread_test(mail->p_timed_thread, 1) || (res == -1 && errno == EINTR) || FD_ISSET(threadfd, &fdset)) {
587                                                 if ((fstat(sockfd, &stat_buf) == 0) && S_ISSOCK(stat_buf.st_mode)) {
588                                                         /* if a valid socket, close it */
589                                                         close(sockfd);
590                                                 }
591                                                 timed_thread_exit(mail->p_timed_thread);
592                                         } else if (res > 0) {
593                                                 if ((numbytes = recv(sockfd, recvbuf, MAXDATASIZE - 1, 0)) == -1) {
594                                                         perror("recv idling");
595                                                         fail++;
596                                                         break;
597                                                 }
598                                         } else {
599                                                 fail++;
600                                                 break;
601                                         }
602                                         recvbuf[numbytes] = '\0';
603                                         DBGP2("imap_thread() received: %s", recvbuf);
604                                         if (strlen(recvbuf) > 2) {
605                                                 unsigned long messages, recent;
606                                                 char *buf = recvbuf;
607                                                 char force_check = 0;
608                                                 buf = strstr(buf, "EXISTS");
609                                                 while (buf && strlen(buf) > 1 && strstr(buf + 1, "EXISTS")) {
610                                                         buf = strstr(buf + 1, "EXISTS");
611                                                 }
612                                                 if (buf) {
613                                                         // back up until we reach '*'
614                                                         while (buf >= recvbuf && buf[0] != '*') {
615                                                                 buf--;
616                                                         }
617                                                         if (sscanf(buf, "* %lu EXISTS\r\n", &messages) == 1) {
618                                                                 timed_thread_lock(mail->p_timed_thread);
619                                                                 if (mail->messages != messages) {
620                                                                         force_check = 1;
621                                                                         mail->messages = messages;
622                                                                 }
623                                                                 timed_thread_unlock(mail->p_timed_thread);
624                                                         }
625                                                 }
626                                                 buf = recvbuf;
627                                                 buf = strstr(buf, "RECENT");
628                                                 while (buf && strlen(buf) > 1 && strstr(buf + 1, "RECENT")) {
629                                                         buf = strstr(buf + 1, "RECENT");
630                                                 }
631                                                 if (buf) {
632                                                         // back up until we reach '*'
633                                                         while (buf >= recvbuf && buf[0] != '*') {
634                                                                 buf--;
635                                                         }
636                                                         if (sscanf(buf, "* %lu RECENT\r\n", &recent) != 1) {
637                                                                 recent = 0;
638                                                         }
639                                                 }
640                                                 /*
641                                                  * check if we got a FETCH from server, recent was
642                                                  * something other than 0, or we had a timeout
643                                                  */
644                                                 buf = recvbuf;
645                                                 if (recent > 0 || (buf && strstr(buf, " FETCH ")) || timeout.tv_sec == 0 || force_check) {
646                                                         // re-check messages and unseen
647                                                         if (imap_command(sockfd, "DONE\r\n", recvbuf, "a5 OK")) {
648                                                                 fail++;
649                                                                 break;
650                                                         }
651                                                         strncpy(sendbuf, "a2 STATUS ", MAXDATASIZE);
652                                                         strncat(sendbuf, mail->folder, MAXDATASIZE - strlen(sendbuf) - 1);
653                                                         strncat(sendbuf, " (MESSAGES UNSEEN)\r\n",
654                                                                         MAXDATASIZE - strlen(sendbuf) - 1);
655                                                         if (imap_command(sockfd, sendbuf, recvbuf, "a2 OK")) {
656                                                                 fail++;
657                                                                 break;
658                                                         }
659                                                         if (imap_check_status(recvbuf, mail)) {
660                                                                 fail++;
661                                                                 break;
662                                                         }
663                                                         strncpy(sendbuf, "a5 IDLE\r\n", MAXDATASIZE);
664                                                         if (imap_command(sockfd, sendbuf, recvbuf, "+ idling")) {
665                                                                 fail++;
666                                                                 break;
667                                                         }
668                                                 }
669                                                 /*
670                                                  * check if we got a BYE from server
671                                                  */
672                                                 buf = recvbuf;
673                                                 if (buf && strstr(buf, "* BYE")) {
674                                                         // need to re-connect
675                                                         break;
676                                                 }
677                                         } else {
678                                                 fail++;
679                                                 break;
680                                         }
681                                         imap_unseen_command(mail, old_unseen, old_messages);
682                                         fail = 0;
683                                         old_unseen = mail->unseen;
684                                         old_messages = mail->messages;
685                                 }
686                                 if (fail) break;
687                         } else {
688                                 strncpy(sendbuf, "a3 logout\r\n", MAXDATASIZE);
689                                 if (send(sockfd, sendbuf, strlen(sendbuf), 0) == -1) {
690                                         perror("send a3");
691                                         fail++;
692                                         break;
693                                 }
694                                 timeout.tv_sec = 60;    // 60 second timeout i guess
695                                 timeout.tv_usec = 0;
696                                 FD_ZERO(&fdset);
697                                 FD_SET(sockfd, &fdset);
698                                 res = select(sockfd + 1, &fdset, NULL, NULL, &timeout);
699                                 if (res > 0) {
700                                         if ((numbytes = recv(sockfd, recvbuf, MAXDATASIZE - 1, 0)) == -1) {
701                                                 perror("recv a3");
702                                                 fail++;
703                                                 break;
704                                         }
705                                 }
706                                 recvbuf[numbytes] = '\0';
707                                 DBGP2("imap_thread() received: %s", recvbuf);
708                                 if (strstr(recvbuf, "a3 OK") == NULL) {
709                                         NORM_ERR("IMAP logout failed: %s", recvbuf);
710                                         fail++;
711                                         break;
712                                 }
713                         }
714                 } while (0);
715                 if ((fstat(sockfd, &stat_buf) == 0) && S_ISSOCK(stat_buf.st_mode)) {
716                         /* if a valid socket, close it */
717                         close(sockfd);
718                 }
719                 if (timed_thread_test(mail->p_timed_thread, 0)) {
720                         timed_thread_exit(mail->p_timed_thread);
721                 }
722         }
723         mail->unseen = 0;
724         mail->messages = 0;
725         return 0;
726 }
727
728 int pop3_command(int sockfd, const char *command, char *response, const char *verify)
729 {
730         struct timeval timeout;
731         fd_set fdset;
732         int res, numbytes = 0;
733         if (send(sockfd, command, strlen(command), 0) == -1) {
734                 perror("send");
735                 return -1;
736         }
737         timeout.tv_sec = 60;    // 60 second timeout i guess
738         timeout.tv_usec = 0;
739         FD_ZERO(&fdset);
740         FD_SET(sockfd, &fdset);
741         res = select(sockfd + 1, &fdset, NULL, NULL, &timeout);
742         if (res > 0) {
743                 if ((numbytes = recv(sockfd, response, MAXDATASIZE - 1, 0)) == -1) {
744                         perror("recv");
745                         return -1;
746                 }
747         }
748         DBGP2("pop3_command() received: %s", response);
749         response[numbytes] = '\0';
750         if (strstr(response, verify) == NULL) {
751                 return -1;
752         }
753         return 0;
754 }
755
756 void *pop3_thread(void *arg)
757 {
758         int sockfd, numbytes;
759         char recvbuf[MAXDATASIZE];
760         char sendbuf[MAXDATASIZE];
761         char *reply;
762         unsigned int fail = 0;
763         unsigned long old_unseen = ULONG_MAX;
764         struct stat stat_buf;
765         struct hostent he, *he_res = 0;
766         int he_errno;
767         char hostbuff[2048];
768         struct sockaddr_in their_addr;  // connector's address information
769         struct mail_s *mail = (struct mail_s *)arg;
770
771 #ifdef HAVE_GETHOSTBYNAME_R
772         if (gethostbyname_r(mail->host, &he, hostbuff, sizeof(hostbuff), &he_res, &he_errno)) { // get the host info
773                 NORM_ERR("POP3 gethostbyname_r: %s", hstrerror(h_errno));
774                 exit(EXIT_FAILURE);
775         }
776 #else /* HAVE_GETHOSTBYNAME_R */
777         if ((he_res = gethostbyname(mail->host)) == NULL) {     // get the host info
778                 herror("gethostbyname");
779                 exit(EXIT_FAILURE);
780         }
781 #endif /* HAVE_GETHOSTBYNAME_R */
782         while (fail < mail->retries) {
783                 struct timeval timeout;
784                 int res;
785                 fd_set fdset;
786
787                 if (fail > 0) {
788                         NORM_ERR("Trying POP3 connection again for %s@%s (try %u/%u)",
789                                         mail->user, mail->host, fail + 1, mail->retries);
790                 }
791                 do {
792                         if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
793                                 perror("socket");
794                                 fail++;
795                                 break;
796                         }
797
798                         // host byte order
799                         their_addr.sin_family = AF_INET;
800                         // short, network byte order
801                         their_addr.sin_port = htons(mail->port);
802                         their_addr.sin_addr = *((struct in_addr *) he_res->h_addr);
803                         // zero the rest of the struct
804                         memset(&(their_addr.sin_zero), '\0', 8);
805
806                         if (connect(sockfd, (struct sockaddr *) &their_addr,
807                                                 sizeof(struct sockaddr)) == -1) {
808                                 perror("connect");
809                                 fail++;
810                                 break;
811                         }
812
813                         timeout.tv_sec = 60;    // 60 second timeout i guess
814                         timeout.tv_usec = 0;
815                         FD_ZERO(&fdset);
816                         FD_SET(sockfd, &fdset);
817                         res = select(sockfd + 1, &fdset, NULL, NULL, &timeout);
818                         if (res > 0) {
819                                 if ((numbytes = recv(sockfd, recvbuf, MAXDATASIZE - 1, 0)) == -1) {
820                                         perror("recv");
821                                         fail++;
822                                         break;
823                                 }
824                         } else {
825                                 NORM_ERR("POP3 connection failed: timeout\n");
826                                 fail++;
827                                 break;
828                         }
829                         DBGP2("pop3_thread received: %s", recvbuf);
830                         recvbuf[numbytes] = '\0';
831                         if (strstr(recvbuf, "+OK ") != recvbuf) {
832                                 NORM_ERR("POP3 connection failed, probably not a POP3 server");
833                                 fail++;
834                                 break;
835                         }
836                         strncpy(sendbuf, "USER ", MAXDATASIZE);
837                         strncat(sendbuf, mail->user, MAXDATASIZE - strlen(sendbuf) - 1);
838                         strncat(sendbuf, "\r\n", MAXDATASIZE - strlen(sendbuf) - 1);
839                         if (pop3_command(sockfd, sendbuf, recvbuf, "+OK ")) {
840                                 fail++;
841                                 break;
842                         }
843
844                         strncpy(sendbuf, "PASS ", MAXDATASIZE);
845                         strncat(sendbuf, mail->pass, MAXDATASIZE - strlen(sendbuf) - 1);
846                         strncat(sendbuf, "\r\n", MAXDATASIZE - strlen(sendbuf) - 1);
847                         if (pop3_command(sockfd, sendbuf, recvbuf, "+OK ")) {
848                                 NORM_ERR("POP3 server login failed: %s", recvbuf);
849                                 fail++;
850                                 break;
851                         }
852
853                         strncpy(sendbuf, "STAT\r\n", MAXDATASIZE);
854                         if (pop3_command(sockfd, sendbuf, recvbuf, "+OK ")) {
855                                 perror("send STAT");
856                                 fail++;
857                                 break;
858                         }
859
860                         // now we get the data
861                         reply = recvbuf + 4;
862                         if (reply == NULL) {
863                                 NORM_ERR("Error parsing POP3 response: %s", recvbuf);
864                                 fail++;
865                                 break;
866                         } else {
867                                 timed_thread_lock(mail->p_timed_thread);
868                                 sscanf(reply, "%lu %lu", &mail->unseen, &mail->used);
869                                 timed_thread_unlock(mail->p_timed_thread);
870                         }
871                         
872                         strncpy(sendbuf, "QUIT\r\n", MAXDATASIZE);
873                         if (pop3_command(sockfd, sendbuf, recvbuf, "+OK")) {
874                                 NORM_ERR("POP3 logout failed: %s", recvbuf);
875                                 fail++;
876                                 break;
877                         }
878                         
879                         if (strlen(mail->command) > 1 && mail->unseen > old_unseen) {
880                                 // new mail goodie
881                                 if (system(mail->command) == -1) {
882                                         perror("system()");
883                                 }
884                         }
885                         fail = 0;
886                         old_unseen = mail->unseen;
887                 } while (0);
888                 if ((fstat(sockfd, &stat_buf) == 0) && S_ISSOCK(stat_buf.st_mode)) {
889                         /* if a valid socket, close it */
890                         close(sockfd);
891                 }
892                 if (timed_thread_test(mail->p_timed_thread, 0)) {
893                         timed_thread_exit(mail->p_timed_thread);
894                 }
895         }
896         mail->unseen = 0;
897         mail->used = 0;
898         return 0;
899 }
900