X-Git-Url: https://vcs.maemo.org/git/?a=blobdiff_plain;f=src%2Fmail.c;h=60249b35fbf8090e743870bed04e6ad01eaaff81;hb=a78d9538fcaa1ba01cccac507c4a1940105c8979;hp=00b10a9c2b1db4f88de3f4d404b077f8a5684090;hpb=27b4c8550cfae4fd0f2169962e33f4907a8e7d71;p=monky diff --git a/src/mail.c b/src/mail.c index 00b10a9..60249b3 100644 --- a/src/mail.c +++ b/src/mail.c @@ -1,4 +1,6 @@ -/* +/* -*- mode: c; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: t -*- + * vim: ts=4 sw=4 noet ai cindent syntax=c + * * Conky, a system monitor, based on torsmo * * Any original torsmo code is licensed under the BSD license @@ -8,7 +10,8 @@ * Please see COPYING for details * * Copyright (c) 2004, Hannu Saransaari and Lauri Hakkarainen - * Copyright (c) 2005-2007 Brenden Matthews, Philip Kovacs, et. al. (see AUTHORS) + * Copyright (c) 2005-2010 Brenden Matthews, Philip Kovacs, et. al. + * (see AUTHORS) * All rights reserved. * * This program is free software: you can redistribute it and/or modify @@ -21,74 +24,140 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * along with this program. If not, see . * - * $Id$ */ +#include "config.h" +#include "conky.h" +#include "common.h" +#include "logging.h" +#include "text_object.h" +#include "timed_thread.h" + +#include +#include +#include +#include +#include +#include +#include +#include #include #include +#include #include #include -#include -#include +#include -#include "conky.h" +/* MAX() is defined by a header included from conky.h + * maybe once this is not true anymore, so have an alternative + * waiting to drop in. + * + * #define MAX(a, b) ((a > b) ? a : b) + */ + +#define POP3_TYPE 1 +#define IMAP_TYPE 2 + +#define MAXFOLDERSIZE 128 + +struct mail_s { // for imap and pop3 + unsigned long unseen; + unsigned long messages; + unsigned long used; + unsigned long quota; + unsigned long port; + unsigned int retries; + float interval; + double last_update; + char host[128]; + char user[128]; + char pass[128]; + char command[1024]; + char folder[MAXFOLDERSIZE]; + timed_thread *p_timed_thread; + char secure; +}; + +struct local_mail_s { + char *mbox; + int mail_count; + int new_mail_count; + int seen_mail_count; + int unseen_mail_count; + int flagged_mail_count; + int unflagged_mail_count; + int forwarded_mail_count; + int unforwarded_mail_count; + int replied_mail_count; + int unreplied_mail_count; + int draft_mail_count; + int trashed_mail_count; + float interval; + time_t last_mtime; + double last_update; +}; char *current_mail_spool; -static time_t last_mail_mtime; -static double last_mail_update; +static struct mail_s *global_mail; +static int global_mail_use = 0; -void update_mail_count() +static void update_mail_count(struct local_mail_s *mail) { - struct stat buf; + struct stat st; - if (current_mail_spool == NULL) + if (mail == NULL) { return; + } /* TODO: use that fine file modification notify on Linux 2.4 */ /* don't check mail so often (9.5s is minimum interval) */ - if (current_update_time - last_mail_update < 9.5) + if (current_update_time - mail->last_update < 9.5) { return; - else - last_mail_update = current_update_time; + } else { + mail->last_update = current_update_time; + } + + if (stat(mail->mbox, &st)) { + static int rep = 0; - if (stat(current_mail_spool, &buf)) { - static int rep; if (!rep) { - ERR("can't stat %s: %s", current_mail_spool, - strerror(errno)); + NORM_ERR("can't stat %s: %s", mail->mbox, strerror(errno)); rep = 1; } return; } #if HAVE_DIRENT_H /* maildir format */ - if (S_ISDIR(buf.st_mode)) { + if (S_ISDIR(st.st_mode)) { DIR *dir; char *dirname; struct dirent *dirent; - info.mail_count = 0; - info.new_mail_count = 0; + char *mailflags; - dirname = - (char *) malloc(sizeof(char) * - (strlen(current_mail_spool) + 5)); + mail->mail_count = mail->new_mail_count = 0; + mail->seen_mail_count = mail->unseen_mail_count = 0; + mail->flagged_mail_count = mail->unflagged_mail_count = 0; + mail->forwarded_mail_count = mail->unforwarded_mail_count = 0; + mail->replied_mail_count = mail->unreplied_mail_count = 0; + mail->draft_mail_count = mail->trashed_mail_count = 0; + dirname = (char *) malloc(sizeof(char) * (strlen(mail->mbox) + 5)); if (!dirname) { - ERR("malloc"); + NORM_ERR("malloc"); return; } - strcpy(dirname, current_mail_spool); + strcpy(dirname, mail->mbox); strcat(dirname, "/"); /* checking the cur subdirectory */ strcat(dirname, "cur"); dir = opendir(dirname); if (!dir) { - ERR("cannot open directory"); + NORM_ERR("cannot open directory"); free(dirname); return; } @@ -96,7 +165,42 @@ void update_mail_count() while (dirent) { /* . and .. are skipped */ if (dirent->d_name[0] != '.') { - info.mail_count++; + mail->mail_count++; + mailflags = (char *) malloc(sizeof(char) * strlen(strrchr(dirent->d_name, ','))); + if (!mailflags) { + NORM_ERR("malloc"); + free(dirname); + return; + } + strcpy(mailflags, strrchr(dirent->d_name, ',')); + if (!strchr(mailflags, 'T')) { /* The message is not in the trash */ + if (strchr(mailflags, 'S')) { /*The message has been seen */ + mail->seen_mail_count++; + } else { + mail->unseen_mail_count++; + } + if (strchr(mailflags, 'F')) { /*The message was flagged */ + mail->flagged_mail_count++; + } else { + mail->unflagged_mail_count++; + } + if (strchr(mailflags, 'P')) { /*The message was forwarded */ + mail->forwarded_mail_count++; + } else { + mail->unforwarded_mail_count++; + } + if (strchr(mailflags, 'R')) { /*The message was replied */ + mail->replied_mail_count++; + } else { + mail->unreplied_mail_count++; + } + if (strchr(mailflags, 'D')) { /*The message is a draft */ + mail->draft_mail_count++; + } + } else { + mail->trashed_mail_count++; + } + free(mailflags); } dirent = readdir(dir); } @@ -107,7 +211,7 @@ void update_mail_count() dir = opendir(dirname); if (!dir) { - ERR("cannot open directory"); + NORM_ERR("cannot open directory"); free(dirname); return; } @@ -115,8 +219,9 @@ void update_mail_count() while (dirent) { /* . and .. are skipped */ if (dirent->d_name[0] != '.') { - info.new_mail_count++; - info.mail_count++; + mail->new_mail_count++; + mail->mail_count++; + mail->unseen_mail_count++; /* new messages cannot have been seen */ } dirent = readdir(dir); } @@ -127,7 +232,7 @@ void update_mail_count() } #endif /* mbox format */ - if (buf.st_mtime != last_mail_mtime) { + if (st.st_mtime != mail->last_mtime) { /* yippee, modification time has changed, let's read mail count! */ static int rep; FILE *fp; @@ -136,47 +241,82 @@ void update_mail_count() /* could lock here but I don't think it's really worth it because * this isn't going to write mail spool */ - info.new_mail_count = 0; - info.mail_count = 0; + mail->new_mail_count = mail->mail_count = 0; - fp = open_file(current_mail_spool, &rep); - if (!fp) + /* these flags are not supported for mbox */ + mail->seen_mail_count = mail->unseen_mail_count = -1; + mail->flagged_mail_count = mail->unflagged_mail_count = -1; + mail->forwarded_mail_count = mail->unforwarded_mail_count = -1; + mail->replied_mail_count = mail->unreplied_mail_count = -1; + mail->draft_mail_count = mail->trashed_mail_count = -1; + + fp = open_file(mail->mbox, &rep); + if (!fp) { return; + } /* NOTE: adds mail as new if there isn't Status-field at all */ while (!feof(fp)) { char buf[128]; - if (fgets(buf, 128, fp) == NULL) + int was_new = 0; + + if (fgets(buf, 128, fp) == NULL) { break; + } if (strncmp(buf, "From ", 5) == 0) { /* ignore MAILER-DAEMON */ - if (strncmp(buf + 5, "MAILER-DAEMON ", 14) - != 0) { - info.mail_count++; + if (strncmp(buf + 5, "MAILER-DAEMON ", 14) != 0) { + mail->mail_count++; + was_new = 0; - if (reading_status) - info.new_mail_count++; - else + if (reading_status == 1) { + mail->new_mail_count++; + } else { reading_status = 1; + } } } else { - if (reading_status - && strncmp(buf, "X-Mozilla-Status:", - 17) == 0) { + if (reading_status == 1 + && strncmp(buf, "X-Mozilla-Status:", 17) == 0) { + int xms = strtol(buf + 17, NULL, 16); + /* check that mail isn't marked for deletion */ + if (xms & 0x0008) { + mail->trashed_mail_count++; + reading_status = 0; + /* Don't check whether the trashed email is unread */ + continue; + } /* check that mail isn't already read */ - if (strchr(buf + 21, '0')) - info.new_mail_count++; + if (!(xms & 0x0001)) { + mail->new_mail_count++; + was_new = 1; + } - reading_status = 0; + /* check for an additional X-Status header */ + reading_status = 2; continue; } - if (reading_status - && strncmp(buf, "Status:", 7) == 0) { + if (reading_status == 1 && strncmp(buf, "Status:", 7) == 0) { /* check that mail isn't already read */ - if (strchr(buf + 7, 'R') == NULL) - info.new_mail_count++; + if (strchr(buf + 7, 'R') == NULL) { + mail->new_mail_count++; + was_new = 1; + } + + reading_status = 2; + continue; + } + if (reading_status >= 1 && strncmp(buf, "X-Status:", 9) == 0) { + /* check that mail isn't marked for deletion */ + if (strchr(buf + 9, 'D') != NULL) { + mail->trashed_mail_count++; + /* If the mail was previously detected as new, + subtract it from the new mail count */ + if (was_new) + mail->new_mail_count--; + } reading_status = 0; continue; @@ -184,15 +324,865 @@ void update_mail_count() } /* skip until \n */ - while (strchr(buf, '\n') == NULL && !feof(fp)) + while (strchr(buf, '\n') == NULL && !feof(fp)) { fgets(buf, 128, fp); + } } fclose(fp); - if (reading_status) - info.new_mail_count++; + if (reading_status) { + mail->new_mail_count++; + } + + mail->last_mtime = st.st_mtime; + } +} + +void parse_local_mail_args(struct text_object *obj, const char *arg) +{ + float n1; + char mbox[256], dst[256]; + struct local_mail_s *locmail; + + if (!arg) { + n1 = 9.5; + /* Kapil: Changed from MAIL_FILE to + current_mail_spool since the latter + is a copy of the former if undefined + but the latter should take precedence + if defined */ + strncpy(mbox, current_mail_spool, sizeof(mbox)); + } else { + if (sscanf(arg, "%s %f", mbox, &n1) != 2) { + n1 = 9.5; + strncpy(mbox, arg, sizeof(mbox)); + } + } + + variable_substitute(mbox, dst, sizeof(dst)); + + locmail = malloc(sizeof(struct local_mail_s)); + memset(locmail, 0, sizeof(struct local_mail_s)); + locmail->mbox = strndup(dst, text_buffer_size); + locmail->interval = n1; + obj->data.opaque = locmail; +} + +#define PRINT_MAILS_GENERATOR(x) \ +void print_##x##mails(struct text_object *obj, char *p, int p_max_size) \ +{ \ + struct local_mail_s *locmail = obj->data.opaque; \ + if (!locmail) \ + return; \ + update_mail_count(locmail); \ + snprintf(p, p_max_size, "%d", locmail->x##mail_count); \ +} + +PRINT_MAILS_GENERATOR() +PRINT_MAILS_GENERATOR(new_) +PRINT_MAILS_GENERATOR(seen_) +PRINT_MAILS_GENERATOR(unseen_) +PRINT_MAILS_GENERATOR(flagged_) +PRINT_MAILS_GENERATOR(unflagged_) +PRINT_MAILS_GENERATOR(forwarded_) +PRINT_MAILS_GENERATOR(unforwarded_) +PRINT_MAILS_GENERATOR(replied_) +PRINT_MAILS_GENERATOR(unreplied_) +PRINT_MAILS_GENERATOR(draft_) +PRINT_MAILS_GENERATOR(trashed_) + +void free_local_mails(struct text_object *obj) +{ + struct local_mail_s *locmail = obj->data.opaque; + + if (!locmail) + return; + + if (locmail->mbox) + free(locmail->mbox); + free(obj->data.opaque); + obj->data.opaque = 0; +} + +#define MAXDATASIZE 1000 + +struct mail_s *parse_mail_args(char type, const char *arg) +{ + struct mail_s *mail; + char *tmp; + + mail = malloc(sizeof(struct mail_s)); + memset(mail, 0, sizeof(struct mail_s)); + + if (sscanf(arg, "%128s %128s %128s", mail->host, mail->user, mail->pass) + != 3) { + if (type == POP3_TYPE) { + NORM_ERR("Scanning POP3 args failed"); + } else if (type == IMAP_TYPE) { + NORM_ERR("Scanning IMAP args failed"); + } + return 0; + } + // see if password needs prompting + if (mail->pass[0] == '*' && mail->pass[1] == '\0') { + int fp = fileno(stdin); + struct termios term; + + tcgetattr(fp, &term); + term.c_lflag &= ~ECHO; + tcsetattr(fp, TCSANOW, &term); + printf("Enter mailbox password (%s@%s): ", mail->user, mail->host); + scanf("%128s", mail->pass); + printf("\n"); + term.c_lflag |= ECHO; + tcsetattr(fp, TCSANOW, &term); + } + // now we check for optional args + tmp = strstr(arg, "-r "); + if (tmp) { + tmp += 3; + sscanf(tmp, "%u", &mail->retries); + } else { + mail->retries = 5; // 5 retries after failure + } + tmp = strstr(arg, "-i "); + if (tmp) { + tmp += 3; + sscanf(tmp, "%f", &mail->interval); + } else { + mail->interval = 300; // 5 minutes + } + tmp = strstr(arg, "-p "); + if (tmp) { + tmp += 3; + sscanf(tmp, "%lu", &mail->port); + } else { + if (type == POP3_TYPE) { + mail->port = 110; // default pop3 port + } else if (type == IMAP_TYPE) { + mail->port = 143; // default imap port + } + } + if (type == IMAP_TYPE) { + tmp = strstr(arg, "-f "); + if (tmp) { + int len = MAXFOLDERSIZE-1; + tmp += 3; + if (tmp[0] == '\'') { + len = strstr(tmp + 1, "'") - tmp - 1; + if (len > MAXFOLDERSIZE-1) { + len = MAXFOLDERSIZE-1; + } + tmp++; + } + strncpy(mail->folder, tmp, len); + mail->folder[len-1] = 0; + } else { + strncpy(mail->folder, "INBOX", MAXFOLDERSIZE-1); // default imap inbox + mail->folder[MAXFOLDERSIZE-1] = 0; + } + } + tmp = strstr(arg, "-e "); + if (tmp) { + int len = 1024; + tmp += 3; + + if (tmp[0] == '\'') { + len = strstr(tmp + 1, "'") - tmp - 1; + if (len > 1024) { + len = 1024; + } + } + strncpy(mail->command, tmp + 1, len); + } else { + mail->command[0] = '\0'; + } + mail->p_timed_thread = NULL; + return mail; +} + +void parse_imap_mail_args(struct text_object *obj, const char *arg) +{ + static int rep = 0; + + if (!arg) { + if (!global_mail && !rep) { + // something is wrong, warn once then stop + NORM_ERR("There's a problem with your mail settings. " + "Check that the global mail settings are properly defined" + " (line %li).", obj->line); + rep = 1; + return; + } + obj->data.opaque = global_mail; + global_mail_use++; + return; + } + // proccss + obj->data.opaque = parse_mail_args(IMAP_TYPE, arg); +} + +void parse_pop3_mail_args(struct text_object *obj, const char *arg) +{ + static int rep = 0; + + if (!arg) { + if (!global_mail && !rep) { + // something is wrong, warn once then stop + NORM_ERR("There's a problem with your mail settings. " + "Check that the global mail settings are properly defined" + " (line %li).", obj->line); + rep = 1; + return; + } + obj->data.opaque = global_mail; + global_mail_use++; + return; + } + // proccss + obj->data.opaque = parse_mail_args(POP3_TYPE, arg); +} + +void parse_global_imap_mail_args(const char *value) +{ + global_mail = parse_mail_args(IMAP_TYPE, value); +} + +void parse_global_pop3_mail_args(const char *value) +{ + global_mail = parse_mail_args(POP3_TYPE, value); +} + +void free_mail_obj(struct text_object *obj) +{ + if (!obj->data.opaque) + return; + + if (obj->data.opaque == global_mail) { + if (--global_mail_use == 0) { + free(global_mail); + global_mail = 0; + } + } else { + free(obj->data.opaque); + obj->data.opaque = 0; + } +} + +int imap_command(int sockfd, const char *command, char *response, const char *verify) +{ + struct timeval fetchtimeout; + fd_set fdset; + int res, numbytes = 0; + if (send(sockfd, command, strlen(command), 0) == -1) { + perror("send"); + return -1; + } + fetchtimeout.tv_sec = 60; // 60 second timeout i guess + fetchtimeout.tv_usec = 0; + FD_ZERO(&fdset); + FD_SET(sockfd, &fdset); + res = select(sockfd + 1, &fdset, NULL, NULL, &fetchtimeout); + if (res > 0) { + if ((numbytes = recv(sockfd, response, MAXDATASIZE - 1, 0)) == -1) { + perror("recv"); + return -1; + } + } + DBGP2("imap_command() command: %s", command); + DBGP2("imap_command() received: %s", response); + response[numbytes] = '\0'; + if (strstr(response, verify) == NULL) { + return -1; + } + return 0; +} + +int imap_check_status(char *recvbuf, struct mail_s *mail) +{ + char *reply; + reply = strstr(recvbuf, " (MESSAGES "); + if (!reply || strlen(reply) < 2) { + return -1; + } + reply += 2; + *strchr(reply, ')') = '\0'; + if (reply == NULL) { + NORM_ERR("Error parsing IMAP response: %s", recvbuf); + return -1; + } else { + timed_thread_lock(mail->p_timed_thread); + sscanf(reply, "MESSAGES %lu UNSEEN %lu", &mail->messages, + &mail->unseen); + timed_thread_unlock(mail->p_timed_thread); + } + return 0; +} + +void imap_unseen_command(struct mail_s *mail, unsigned long old_unseen, unsigned long old_messages) +{ + if (strlen(mail->command) > 1 && (mail->unseen > old_unseen + || (mail->messages > old_messages && mail->unseen > 0))) { + // new mail goodie + if (system(mail->command) == -1) { + perror("system()"); + } + } +} + +static void ensure_mail_thread(struct mail_s *mail, + void *thread(void *), const char *text) +{ + if (mail->p_timed_thread) + return; + + mail->p_timed_thread = timed_thread_create(thread, + mail, mail->interval * 1000000); + if (!mail->p_timed_thread) { + NORM_ERR("Error creating %s timed thread", text); + } + timed_thread_register(mail->p_timed_thread, + &mail->p_timed_thread); + if (timed_thread_run(mail->p_timed_thread)) { + NORM_ERR("Error running %s timed thread", text); + } +} + +static void *imap_thread(void *arg) +{ + int sockfd, numbytes; + char recvbuf[MAXDATASIZE]; + char sendbuf[MAXDATASIZE]; + unsigned int fail = 0; + unsigned long old_unseen = ULONG_MAX; + unsigned long old_messages = ULONG_MAX; + struct stat stat_buf; + struct hostent he, *he_res = 0; + int he_errno; + char hostbuff[2048]; + struct sockaddr_in their_addr; // connector's address information + struct mail_s *mail = (struct mail_s *)arg; + int has_idle = 0; + int threadfd = timed_thread_readfd(mail->p_timed_thread); + char resolved_host = 0; + + while (fail < mail->retries) { + struct timeval fetchtimeout; + int res; + fd_set fdset; + + if (!resolved_host) { +#ifdef HAVE_GETHOSTBYNAME_R + if (gethostbyname_r(mail->host, &he, hostbuff, sizeof(hostbuff), &he_res, &he_errno)) { // get the host info + NORM_ERR("IMAP gethostbyname_r: %s", hstrerror(h_errno)); + fail++; + break; + } +#else /* HAVE_GETHOSTBYNAME_R */ + if ((he_res = gethostbyname(mail->host)) == NULL) { // get the host info + herror("gethostbyname"); + fail++; + break; + } +#endif /* HAVE_GETHOSTBYNAME_R */ + resolved_host = 1; + } + if (fail > 0) { + NORM_ERR("Trying IMAP connection again for %s@%s (try %u/%u)", + mail->user, mail->host, fail + 1, mail->retries); + } + do { + if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) { + perror("socket"); + fail++; + break; + } + + // host byte order + their_addr.sin_family = AF_INET; + // short, network byte order + their_addr.sin_port = htons(mail->port); + their_addr.sin_addr = *((struct in_addr *) he_res->h_addr); + // zero the rest of the struct + memset(&(their_addr.sin_zero), '\0', 8); + + if (connect(sockfd, (struct sockaddr *) &their_addr, + sizeof(struct sockaddr)) == -1) { + perror("connect"); + fail++; + break; + } + + fetchtimeout.tv_sec = 60; // 60 second timeout i guess + fetchtimeout.tv_usec = 0; + FD_ZERO(&fdset); + FD_SET(sockfd, &fdset); + res = select(sockfd + 1, &fdset, NULL, NULL, &fetchtimeout); + if (res > 0) { + if ((numbytes = recv(sockfd, recvbuf, MAXDATASIZE - 1, 0)) == -1) { + perror("recv"); + fail++; + break; + } + } else { + NORM_ERR("IMAP connection failed: timeout"); + fail++; + break; + } + recvbuf[numbytes] = '\0'; + DBGP2("imap_thread() received: %s", recvbuf); + if (strstr(recvbuf, "* OK") != recvbuf) { + NORM_ERR("IMAP connection failed, probably not an IMAP server"); + fail++; + break; + } + strncpy(sendbuf, "abc CAPABILITY\r\n", MAXDATASIZE); + if (imap_command(sockfd, sendbuf, recvbuf, "abc OK")) { + fail++; + break; + } + if (strstr(recvbuf, " IDLE ") != NULL) { + has_idle = 1; + } + + strncpy(sendbuf, "a1 login ", MAXDATASIZE); + strncat(sendbuf, mail->user, MAXDATASIZE - strlen(sendbuf) - 1); + strncat(sendbuf, " ", MAXDATASIZE - strlen(sendbuf) - 1); + strncat(sendbuf, mail->pass, MAXDATASIZE - strlen(sendbuf) - 1); + strncat(sendbuf, "\r\n", MAXDATASIZE - strlen(sendbuf) - 1); + if (imap_command(sockfd, sendbuf, recvbuf, "a1 OK")) { + fail++; + break; + } + + strncpy(sendbuf, "a2 STATUS \"", MAXDATASIZE); + strncat(sendbuf, mail->folder, MAXDATASIZE - strlen(sendbuf) - 1); + strncat(sendbuf, "\" (MESSAGES UNSEEN)\r\n", + MAXDATASIZE - strlen(sendbuf) - 1); + if (imap_command(sockfd, sendbuf, recvbuf, "a2 OK")) { + fail++; + break; + } + + if (imap_check_status(recvbuf, mail)) { + fail++; + break; + } + imap_unseen_command(mail, old_unseen, old_messages); + fail = 0; + old_unseen = mail->unseen; + old_messages = mail->messages; + + if (has_idle) { + strncpy(sendbuf, "a4 SELECT \"", MAXDATASIZE); + strncat(sendbuf, mail->folder, MAXDATASIZE - strlen(sendbuf) - 1); + strncat(sendbuf, "\"\r\n", MAXDATASIZE - strlen(sendbuf) - 1); + if (imap_command(sockfd, sendbuf, recvbuf, "a4 OK")) { + fail++; + break; + } + + strncpy(sendbuf, "a5 IDLE\r\n", MAXDATASIZE); + if (imap_command(sockfd, sendbuf, recvbuf, "+ idling")) { + fail++; + break; + } + recvbuf[0] = '\0'; + + while (1) { + /* + * RFC 2177 says we have to re-idle every 29 minutes. + * We'll do it every 20 minutes to be safe. + */ + fetchtimeout.tv_sec = 1200; + fetchtimeout.tv_usec = 0; + DBGP2("idling..."); + FD_ZERO(&fdset); + FD_SET(sockfd, &fdset); + FD_SET(threadfd, &fdset); + res = select(MAX(sockfd + 1, threadfd + 1), &fdset, NULL, NULL, &fetchtimeout); + if (timed_thread_test(mail->p_timed_thread, 1) || (res == -1 && errno == EINTR) || FD_ISSET(threadfd, &fdset)) { + if ((fstat(sockfd, &stat_buf) == 0) && S_ISSOCK(stat_buf.st_mode)) { + /* if a valid socket, close it */ + close(sockfd); + } + timed_thread_exit(mail->p_timed_thread); + } else if (res > 0) { + if ((numbytes = recv(sockfd, recvbuf, MAXDATASIZE - 1, 0)) == -1) { + perror("recv idling"); + fail++; + break; + } + } else { + fail++; + break; + } + recvbuf[numbytes] = '\0'; + DBGP2("imap_thread() received: %s", recvbuf); + if (strlen(recvbuf) > 2) { + unsigned long messages, recent = 0; + char *buf = recvbuf; + char force_check = 0; + buf = strstr(buf, "EXISTS"); + while (buf && strlen(buf) > 1 && strstr(buf + 1, "EXISTS")) { + buf = strstr(buf + 1, "EXISTS"); + } + if (buf) { + // back up until we reach '*' + while (buf >= recvbuf && buf[0] != '*') { + buf--; + } + if (sscanf(buf, "* %lu EXISTS\r\n", &messages) == 1) { + timed_thread_lock(mail->p_timed_thread); + if (mail->messages != messages) { + force_check = 1; + mail->messages = messages; + } + timed_thread_unlock(mail->p_timed_thread); + } + } + buf = recvbuf; + buf = strstr(buf, "RECENT"); + while (buf && strlen(buf) > 1 && strstr(buf + 1, "RECENT")) { + buf = strstr(buf + 1, "RECENT"); + } + if (buf) { + // back up until we reach '*' + while (buf >= recvbuf && buf[0] != '*') { + buf--; + } + if (sscanf(buf, "* %lu RECENT\r\n", &recent) != 1) { + recent = 0; + } + } + /* + * check if we got a FETCH from server, recent was + * something other than 0, or we had a timeout + */ + buf = recvbuf; + if (recent > 0 || (buf && strstr(buf, " FETCH ")) || fetchtimeout.tv_sec == 0 || force_check) { + // re-check messages and unseen + if (imap_command(sockfd, "DONE\r\n", recvbuf, "a5 OK")) { + fail++; + break; + } + strncpy(sendbuf, "a2 STATUS \"", MAXDATASIZE); + strncat(sendbuf, mail->folder, MAXDATASIZE - strlen(sendbuf) - 1); + strncat(sendbuf, "\" (MESSAGES UNSEEN)\r\n", + MAXDATASIZE - strlen(sendbuf) - 1); + if (imap_command(sockfd, sendbuf, recvbuf, "a2 OK")) { + fail++; + break; + } + if (imap_check_status(recvbuf, mail)) { + fail++; + break; + } + strncpy(sendbuf, "a5 IDLE\r\n", MAXDATASIZE); + if (imap_command(sockfd, sendbuf, recvbuf, "+ idling")) { + fail++; + break; + } + } + /* + * check if we got a BYE from server + */ + buf = recvbuf; + if (buf && strstr(buf, "* BYE")) { + // need to re-connect + break; + } + } else { + fail++; + break; + } + imap_unseen_command(mail, old_unseen, old_messages); + fail = 0; + old_unseen = mail->unseen; + old_messages = mail->messages; + } + if (fail) break; + } else { + strncpy(sendbuf, "a3 logout\r\n", MAXDATASIZE); + if (send(sockfd, sendbuf, strlen(sendbuf), 0) == -1) { + perror("send a3"); + fail++; + break; + } + fetchtimeout.tv_sec = 60; // 60 second timeout i guess + fetchtimeout.tv_usec = 0; + FD_ZERO(&fdset); + FD_SET(sockfd, &fdset); + res = select(sockfd + 1, &fdset, NULL, NULL, &fetchtimeout); + if (res > 0) { + if ((numbytes = recv(sockfd, recvbuf, MAXDATASIZE - 1, 0)) == -1) { + perror("recv a3"); + fail++; + break; + } + } + recvbuf[numbytes] = '\0'; + DBGP2("imap_thread() received: %s", recvbuf); + if (strstr(recvbuf, "a3 OK") == NULL) { + NORM_ERR("IMAP logout failed: %s", recvbuf); + fail++; + break; + } + } + } while (0); + if ((fstat(sockfd, &stat_buf) == 0) && S_ISSOCK(stat_buf.st_mode)) { + /* if a valid socket, close it */ + close(sockfd); + } + if (timed_thread_test(mail->p_timed_thread, 0)) { + timed_thread_exit(mail->p_timed_thread); + } + } + mail->unseen = 0; + mail->messages = 0; + return 0; +} + +void print_imap_unseen(struct text_object *obj, char *p, int p_max_size) +{ + struct mail_s *mail = obj->data.opaque; + + if (!mail) + return; + + ensure_mail_thread(mail, imap_thread, "imap"); + + if (mail && mail->p_timed_thread) { + timed_thread_lock(mail->p_timed_thread); + snprintf(p, p_max_size, "%lu", mail->unseen); + timed_thread_unlock(mail->p_timed_thread); + } +} + +void print_imap_messages(struct text_object *obj, char *p, int p_max_size) +{ + struct mail_s *mail = obj->data.opaque; + + if (!mail) + return; + + ensure_mail_thread(mail, imap_thread, "imap"); + + if (mail && mail->p_timed_thread) { + timed_thread_lock(mail->p_timed_thread); + snprintf(p, p_max_size, "%lu", mail->messages); + timed_thread_unlock(mail->p_timed_thread); + } +} + +int pop3_command(int sockfd, const char *command, char *response, const char *verify) +{ + struct timeval fetchtimeout; + fd_set fdset; + int res, numbytes = 0; + if (send(sockfd, command, strlen(command), 0) == -1) { + perror("send"); + return -1; + } + fetchtimeout.tv_sec = 60; // 60 second timeout i guess + fetchtimeout.tv_usec = 0; + FD_ZERO(&fdset); + FD_SET(sockfd, &fdset); + res = select(sockfd + 1, &fdset, NULL, NULL, &fetchtimeout); + if (res > 0) { + if ((numbytes = recv(sockfd, response, MAXDATASIZE - 1, 0)) == -1) { + perror("recv"); + return -1; + } + } + DBGP2("pop3_command() received: %s", response); + response[numbytes] = '\0'; + if (strstr(response, verify) == NULL) { + return -1; + } + return 0; +} + +static void *pop3_thread(void *arg) +{ + int sockfd, numbytes; + char recvbuf[MAXDATASIZE]; + char sendbuf[MAXDATASIZE]; + char *reply; + unsigned int fail = 0; + unsigned long old_unseen = ULONG_MAX; + struct stat stat_buf; + struct hostent he, *he_res = 0; + int he_errno; + char hostbuff[2048]; + struct sockaddr_in their_addr; // connector's address information + struct mail_s *mail = (struct mail_s *)arg; + char resolved_host = 0; + + while (fail < mail->retries) { + struct timeval fetchtimeout; + int res; + fd_set fdset; + if (!resolved_host) { +#ifdef HAVE_GETHOSTBYNAME_R + if (gethostbyname_r(mail->host, &he, hostbuff, sizeof(hostbuff), &he_res, &he_errno)) { // get the host info + NORM_ERR("POP3 gethostbyname_r: %s", hstrerror(h_errno)); + fail++; + break; + } +#else /* HAVE_GETHOSTBYNAME_R */ + if ((he_res = gethostbyname(mail->host)) == NULL) { // get the host info + herror("gethostbyname"); + fail++; + break; + } +#endif /* HAVE_GETHOSTBYNAME_R */ + resolved_host = 1; +} + if (fail > 0) { + NORM_ERR("Trying POP3 connection again for %s@%s (try %u/%u)", + mail->user, mail->host, fail + 1, mail->retries); + } + do { + if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) { + perror("socket"); + fail++; + break; + } + + // host byte order + their_addr.sin_family = AF_INET; + // short, network byte order + their_addr.sin_port = htons(mail->port); + their_addr.sin_addr = *((struct in_addr *) he_res->h_addr); + // zero the rest of the struct + memset(&(their_addr.sin_zero), '\0', 8); + + if (connect(sockfd, (struct sockaddr *) &their_addr, + sizeof(struct sockaddr)) == -1) { + perror("connect"); + fail++; + break; + } + + fetchtimeout.tv_sec = 60; // 60 second timeout i guess + fetchtimeout.tv_usec = 0; + FD_ZERO(&fdset); + FD_SET(sockfd, &fdset); + res = select(sockfd + 1, &fdset, NULL, NULL, &fetchtimeout); + if (res > 0) { + if ((numbytes = recv(sockfd, recvbuf, MAXDATASIZE - 1, 0)) == -1) { + perror("recv"); + fail++; + break; + } + } else { + NORM_ERR("POP3 connection failed: timeout\n"); + fail++; + break; + } + DBGP2("pop3_thread received: %s", recvbuf); + recvbuf[numbytes] = '\0'; + if (strstr(recvbuf, "+OK ") != recvbuf) { + NORM_ERR("POP3 connection failed, probably not a POP3 server"); + fail++; + break; + } + strncpy(sendbuf, "USER ", MAXDATASIZE); + strncat(sendbuf, mail->user, MAXDATASIZE - strlen(sendbuf) - 1); + strncat(sendbuf, "\r\n", MAXDATASIZE - strlen(sendbuf) - 1); + if (pop3_command(sockfd, sendbuf, recvbuf, "+OK ")) { + fail++; + break; + } + + strncpy(sendbuf, "PASS ", MAXDATASIZE); + strncat(sendbuf, mail->pass, MAXDATASIZE - strlen(sendbuf) - 1); + strncat(sendbuf, "\r\n", MAXDATASIZE - strlen(sendbuf) - 1); + if (pop3_command(sockfd, sendbuf, recvbuf, "+OK ")) { + NORM_ERR("POP3 server login failed: %s", recvbuf); + fail++; + break; + } + + strncpy(sendbuf, "STAT\r\n", MAXDATASIZE); + if (pop3_command(sockfd, sendbuf, recvbuf, "+OK ")) { + perror("send STAT"); + fail++; + break; + } + + // now we get the data + reply = recvbuf + 4; + if (reply == NULL) { + NORM_ERR("Error parsing POP3 response: %s", recvbuf); + fail++; + break; + } else { + timed_thread_lock(mail->p_timed_thread); + sscanf(reply, "%lu %lu", &mail->unseen, &mail->used); + timed_thread_unlock(mail->p_timed_thread); + } + + strncpy(sendbuf, "QUIT\r\n", MAXDATASIZE); + if (pop3_command(sockfd, sendbuf, recvbuf, "+OK")) { + NORM_ERR("POP3 logout failed: %s", recvbuf); + fail++; + break; + } + + if (strlen(mail->command) > 1 && mail->unseen > old_unseen) { + // new mail goodie + if (system(mail->command) == -1) { + perror("system()"); + } + } + fail = 0; + old_unseen = mail->unseen; + } while (0); + if ((fstat(sockfd, &stat_buf) == 0) && S_ISSOCK(stat_buf.st_mode)) { + /* if a valid socket, close it */ + close(sockfd); + } + if (timed_thread_test(mail->p_timed_thread, 0)) { + timed_thread_exit(mail->p_timed_thread); + } + } + mail->unseen = 0; + mail->used = 0; + return 0; +} + +void print_pop3_unseen(struct text_object *obj, char *p, int p_max_size) +{ + struct mail_s *mail = obj->data.opaque; + + if (!mail) + return; + + ensure_mail_thread(mail, pop3_thread, "pop3"); + + if (mail && mail->p_timed_thread) { + timed_thread_lock(mail->p_timed_thread); + snprintf(p, p_max_size, "%lu", mail->unseen); + timed_thread_unlock(mail->p_timed_thread); + } +} + +void print_pop3_used(struct text_object *obj, char *p, int p_max_size) +{ + struct mail_s *mail = obj->data.opaque; + + if (!mail) + return; + + ensure_mail_thread(mail, pop3_thread, "pop3"); - last_mail_mtime = buf.st_mtime; + if (mail && mail->p_timed_thread) { + timed_thread_lock(mail->p_timed_thread); + snprintf(p, p_max_size, "%.1f", + mail->used / 1024.0 / 1024.0); + timed_thread_unlock(mail->p_timed_thread); } }