1 /* Conky, a system monitor, based on torsmo
3 * Any original torsmo code is licensed under the BSD license
5 * All code written since the fork of torsmo is licensed under the GPL
7 * Please see COPYING for details
9 * Copyright (c) 2004, Hannu Saransaari and Lauri Hakkarainen
10 * Copyright (c) 2005-2008 Brenden Matthews, Philip Kovacs, et. al.
12 * All rights reserved.
14 * This program is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation, either version 3 of the License, or
17 * (at your option) any later version.
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 * You should have received a copy of the GNU General Public License
24 * along with this program. If not, see <http://www.gnu.org/licenses/>.
37 char *current_mail_spool;
39 void update_mail_count(struct local_mail_s *mail)
47 /* TODO: use that fine file modification notify on Linux 2.4 */
49 /* don't check mail so often (9.5s is minimum interval) */
50 if (current_update_time - mail->last_update < 9.5) {
53 mail->last_update = current_update_time;
56 if (stat(mail->box, &st)) {
60 ERR("can't stat %s: %s", mail->box, strerror(errno));
67 if (S_ISDIR(st.st_mode)) {
70 struct dirent *dirent;
72 mail->mail_count = mail->new_mail_count = 0;
73 dirname = (char *) malloc(sizeof(char) * (strlen(mail->box) + 5));
78 strcpy(dirname, mail->box);
80 /* checking the cur subdirectory */
81 strcat(dirname, "cur");
83 dir = opendir(dirname);
85 ERR("cannot open directory");
89 dirent = readdir(dir);
91 /* . and .. are skipped */
92 if (dirent->d_name[0] != '.') {
95 dirent = readdir(dir);
99 dirname[strlen(dirname) - 3] = '\0';
100 strcat(dirname, "new");
102 dir = opendir(dirname);
104 ERR("cannot open directory");
108 dirent = readdir(dir);
110 /* . and .. are skipped */
111 if (dirent->d_name[0] != '.') {
112 mail->new_mail_count++;
115 dirent = readdir(dir);
124 if (st.st_mtime != mail->last_mtime) {
125 /* yippee, modification time has changed, let's read mail count! */
128 int reading_status = 0;
130 /* could lock here but I don't think it's really worth it because
131 * this isn't going to write mail spool */
133 mail->new_mail_count = mail->mail_count = 0;
135 fp = open_file(mail->box, &rep);
140 /* NOTE: adds mail as new if there isn't Status-field at all */
145 if (fgets(buf, 128, fp) == NULL) {
149 if (strncmp(buf, "From ", 5) == 0) {
150 /* ignore MAILER-DAEMON */
151 if (strncmp(buf + 5, "MAILER-DAEMON ", 14) != 0) {
154 if (reading_status) {
155 mail->new_mail_count++;
162 && strncmp(buf, "X-Mozilla-Status:", 17) == 0) {
163 /* check that mail isn't already read */
164 if (strchr(buf + 21, '0')) {
165 mail->new_mail_count++;
171 if (reading_status && strncmp(buf, "Status:", 7) == 0) {
172 /* check that mail isn't already read */
173 if (strchr(buf + 7, 'R') == NULL) {
174 mail->new_mail_count++;
183 while (strchr(buf, '\n') == NULL && !feof(fp)) {
190 if (reading_status) {
191 mail->new_mail_count++;
194 mail->last_mtime = st.st_mtime;
198 #define MAXDATASIZE 1000
200 struct mail_s *parse_mail_args(char type, const char *arg)
205 mail = malloc(sizeof(struct mail_s));
206 memset(mail, 0, sizeof(struct mail_s));
208 if (sscanf(arg, "%128s %128s %128s", mail->host, mail->user, mail->pass)
210 if (type == POP3_TYPE) {
211 ERR("Scanning IMAP args failed");
212 } else if (type == IMAP_TYPE) {
213 ERR("Scanning POP3 args failed");
216 // see if password needs prompting
217 if (mail->pass[0] == '*' && mail->pass[1] == '\0') {
218 int fp = fileno(stdin);
221 tcgetattr(fp, &term);
222 term.c_lflag &= ~ECHO;
223 tcsetattr(fp, TCSANOW, &term);
224 printf("Enter mailbox password (%s@%s): ", mail->user, mail->host);
225 scanf("%128s", mail->pass);
227 term.c_lflag |= ECHO;
228 tcsetattr(fp, TCSANOW, &term);
230 // now we check for optional args
231 tmp = strstr(arg, "-r ");
234 sscanf(tmp, "%u", &mail->retries);
236 mail->retries = 5; // 5 retries after failure
238 tmp = strstr(arg, "-i ");
241 sscanf(tmp, "%f", &mail->interval);
243 mail->interval = 300; // 5 minutes
245 tmp = strstr(arg, "-p ");
248 sscanf(tmp, "%lu", &mail->port);
250 if (type == POP3_TYPE) {
251 mail->port = 110; // default pop3 port
252 } else if (type == IMAP_TYPE) {
253 mail->port = 143; // default imap port
256 if (type == IMAP_TYPE) {
257 tmp = strstr(arg, "-f ");
260 sscanf(tmp, "%s", mail->folder);
262 strncpy(mail->folder, "INBOX", 128); // default imap inbox
265 tmp = strstr(arg, "-e ");
270 if (tmp[0] == '\'') {
271 len = strstr(tmp + 1, "'") - tmp - 1;
276 strncpy(mail->command, tmp + 1, len);
278 mail->command[0] = '\0';
280 mail->p_timed_thread = NULL;
284 int imap_command(int sockfd, const char *command, char *response, const char *verify)
286 struct timeval timeout;
288 int res, numbytes = 0;
289 if (send(sockfd, command, strlen(command), 0) == -1) {
293 timeout.tv_sec = 60; // 60 second timeout i guess
296 FD_SET(sockfd, &fdset);
297 res = select(sockfd + 1, &fdset, NULL, NULL, &timeout);
299 if ((numbytes = recv(sockfd, response, MAXDATASIZE - 1, 0)) == -1) {
304 DBGP2("imap_command() received: %s", response);
305 response[numbytes] = '\0';
306 if (strstr(response, verify) == NULL) {
312 int imap_check_status(char *recvbuf, struct mail_s *mail)
315 reply = strstr(recvbuf, " (MESSAGES ");
316 if (!reply || strlen(reply) < 2) {
320 *strchr(reply, ')') = '\0';
322 ERR("Error parsing IMAP response: %s", recvbuf);
325 timed_thread_lock(mail->p_timed_thread);
326 sscanf(reply, "MESSAGES %lu UNSEEN %lu", &mail->messages,
328 timed_thread_unlock(mail->p_timed_thread);
333 void imap_unseen_command(struct mail_s *mail, unsigned long old_unseen, unsigned long old_messages)
335 if (strlen(mail->command) > 1 && (mail->unseen > old_unseen
336 || (mail->messages > old_messages && mail->unseen > 0))) {
338 if (system(mail->command) == -1) {
344 void *imap_thread(void *arg)
346 int sockfd, numbytes;
347 char recvbuf[MAXDATASIZE];
348 char sendbuf[MAXDATASIZE];
349 unsigned int fail = 0;
350 unsigned long old_unseen = ULONG_MAX;
351 unsigned long old_messages = ULONG_MAX;
352 struct stat stat_buf;
353 struct hostent he, *he_res = 0;
356 struct sockaddr_in their_addr; // connector's address information
357 struct mail_s *mail = (struct mail_s *)arg;
359 int threadfd = timed_thread_readfd(mail->p_timed_thread);
361 #ifdef HAVE_GETHOSTBYNAME_R
362 if (gethostbyname_r(mail->host, &he, hostbuff, sizeof(hostbuff), &he_res, &he_errno)) { // get the host info
363 ERR("IMAP gethostbyname_r: %s", hstrerror(h_errno));
366 #else /* HAVE_GETHOSTBYNAME_R */
367 if ((he_res = gethostbyname(mail->host)) == NULL) { // get the host info
368 herror("gethostbyname");
371 #endif /* HAVE_GETHOSTBYNAME_R */
372 while (fail < mail->retries) {
373 struct timeval timeout;
378 ERR("Trying IMAP connection again for %s@%s (try %u/%u)",
379 mail->user, mail->host, fail + 1, mail->retries);
382 if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
389 their_addr.sin_family = AF_INET;
390 // short, network byte order
391 their_addr.sin_port = htons(mail->port);
392 their_addr.sin_addr = *((struct in_addr *) he_res->h_addr);
393 // zero the rest of the struct
394 memset(&(their_addr.sin_zero), '\0', 8);
396 if (connect(sockfd, (struct sockaddr *) &their_addr,
397 sizeof(struct sockaddr)) == -1) {
403 timeout.tv_sec = 60; // 60 second timeout i guess
406 FD_SET(sockfd, &fdset);
407 res = select(sockfd + 1, &fdset, NULL, NULL, &timeout);
409 if ((numbytes = recv(sockfd, recvbuf, MAXDATASIZE - 1, 0)) == -1) {
415 ERR("IMAP connection failed: timeout");
419 recvbuf[numbytes] = '\0';
420 DBGP2("imap_thread() received: %s", recvbuf);
421 if (strstr(recvbuf, "* OK") != recvbuf) {
422 ERR("IMAP connection failed, probably not an IMAP server");
426 strncpy(sendbuf, "a1 login ", MAXDATASIZE);
427 strncat(sendbuf, mail->user, MAXDATASIZE - strlen(sendbuf) - 1);
428 strncat(sendbuf, " ", MAXDATASIZE - strlen(sendbuf) - 1);
429 strncat(sendbuf, mail->pass, MAXDATASIZE - strlen(sendbuf) - 1);
430 strncat(sendbuf, "\r\n", MAXDATASIZE - strlen(sendbuf) - 1);
431 if (imap_command(sockfd, sendbuf, recvbuf, "a1 OK")) {
435 if (strstr(recvbuf, " IDLE ") != NULL) {
439 strncpy(sendbuf, "a2 STATUS ", MAXDATASIZE);
440 strncat(sendbuf, mail->folder, MAXDATASIZE - strlen(sendbuf) - 1);
441 strncat(sendbuf, " (MESSAGES UNSEEN)\r\n",
442 MAXDATASIZE - strlen(sendbuf) - 1);
443 if (imap_command(sockfd, sendbuf, recvbuf, "a2 OK")) {
448 if (imap_check_status(recvbuf, mail)) {
452 imap_unseen_command(mail, old_unseen, old_messages);
454 old_unseen = mail->unseen;
455 old_messages = mail->messages;
458 strncpy(sendbuf, "a4 SELECT ", MAXDATASIZE);
459 strncat(sendbuf, mail->folder, MAXDATASIZE - strlen(sendbuf) - 1);
460 strncat(sendbuf, "\r\n", MAXDATASIZE - strlen(sendbuf) - 1);
461 if (imap_command(sockfd, sendbuf, recvbuf, "a4 OK")) {
466 strncpy(sendbuf, "a5 IDLE\r\n", MAXDATASIZE);
467 if (imap_command(sockfd, sendbuf, recvbuf, "+ idling")) {
475 * RFC 2177 says we have to re-idle every 29 minutes.
476 * We'll do it every 20 minutes to be safe.
478 timeout.tv_sec = 1200;
481 FD_SET(sockfd, &fdset);
482 FD_SET(threadfd, &fdset);
483 res = select(MAX(sockfd + 1, threadfd + 1), &fdset, NULL, NULL, NULL);
484 if (timed_thread_test(mail->p_timed_thread, 1) || (res == -1 && errno == EINTR) || FD_ISSET(threadfd, &fdset)) {
485 if ((fstat(sockfd, &stat_buf) == 0) && S_ISSOCK(stat_buf.st_mode)) {
486 /* if a valid socket, close it */
489 timed_thread_exit(mail->p_timed_thread);
490 } else if (res > 0) {
491 if ((numbytes = recv(sockfd, recvbuf, MAXDATASIZE - 1, 0)) == -1) {
492 perror("recv idling");
499 recvbuf[numbytes] = '\0';
500 DBGP2("imap_thread() received: %s", recvbuf);
501 if (strlen(recvbuf) > 2) {
502 unsigned long messages, recent;
504 buf = strstr(buf, "EXISTS");
505 while (buf && strlen(buf) > 1 && strstr(buf + 1, "EXISTS")) {
506 buf = strstr(buf + 1, "EXISTS");
509 // back up until we reach '*'
510 while (buf >= recvbuf && buf[0] != '*') {
513 if (sscanf(buf, "* %lu EXISTS\r\n", &messages) == 1) {
514 timed_thread_lock(mail->p_timed_thread);
515 mail->messages = messages;
516 timed_thread_unlock(mail->p_timed_thread);
520 buf = strstr(buf, "RECENT");
521 while (buf && strlen(buf) > 1 && strstr(buf + 1, "RECENT")) {
522 buf = strstr(buf + 1, "RECENT");
525 // back up until we reach '*'
526 while (buf >= recvbuf && buf[0] != '*') {
529 if (sscanf(buf, "* %lu RECENT\r\n", &recent) != 1) {
534 * check if we got a FETCH from server, recent was
535 * something other than 0, or we had a timeout
538 if (recent > 0 || (buf && strstr(buf, " FETCH ")) || timeout.tv_sec == 0) {
539 // re-check messages and unseen
540 if (imap_command(sockfd, "DONE\r\n", recvbuf, "a5 OK")) {
544 strncpy(sendbuf, "a2 STATUS ", MAXDATASIZE);
545 strncat(sendbuf, mail->folder, MAXDATASIZE - strlen(sendbuf) - 1);
546 strncat(sendbuf, " (MESSAGES UNSEEN)\r\n",
547 MAXDATASIZE - strlen(sendbuf) - 1);
548 if (imap_command(sockfd, sendbuf, recvbuf, "a2 OK")) {
552 if (imap_check_status(recvbuf, mail)) {
556 strncpy(sendbuf, "a5 IDLE\r\n", MAXDATASIZE);
557 if (imap_command(sockfd, sendbuf, recvbuf, "+ idling")) {
563 * check if we got a BYE from server
566 if (buf && strstr(buf, "* BYE")) {
567 // need to re-connect
571 imap_unseen_command(mail, old_unseen, old_messages);
573 old_unseen = mail->unseen;
574 old_messages = mail->messages;
577 strncpy(sendbuf, "a3 logout\r\n", MAXDATASIZE);
578 if (send(sockfd, sendbuf, strlen(sendbuf), 0) == -1) {
583 timeout.tv_sec = 60; // 60 second timeout i guess
586 FD_SET(sockfd, &fdset);
587 res = select(sockfd + 1, &fdset, NULL, NULL, &timeout);
589 if ((numbytes = recv(sockfd, recvbuf, MAXDATASIZE - 1, 0)) == -1) {
595 recvbuf[numbytes] = '\0';
596 DBGP2("imap_thread() received: %s", recvbuf);
597 if (strstr(recvbuf, "a3 OK") == NULL) {
598 ERR("IMAP logout failed: %s", recvbuf);
604 if ((fstat(sockfd, &stat_buf) == 0) && S_ISSOCK(stat_buf.st_mode)) {
605 /* if a valid socket, close it */
608 if (timed_thread_test(mail->p_timed_thread, 0)) {
609 timed_thread_exit(mail->p_timed_thread);
617 int pop3_command(int sockfd, const char *command, char *response, const char *verify)
619 struct timeval timeout;
621 int res, numbytes = 0;
622 if (send(sockfd, command, strlen(command), 0) == -1) {
626 timeout.tv_sec = 60; // 60 second timeout i guess
629 FD_SET(sockfd, &fdset);
630 res = select(sockfd + 1, &fdset, NULL, NULL, &timeout);
632 if ((numbytes = recv(sockfd, response, MAXDATASIZE - 1, 0)) == -1) {
637 DBGP2("pop3_command() received: %s", response);
638 response[numbytes] = '\0';
639 if (strstr(response, verify) == NULL) {
645 void *pop3_thread(void *arg)
647 int sockfd, numbytes;
648 char recvbuf[MAXDATASIZE];
649 char sendbuf[MAXDATASIZE];
651 unsigned int fail = 0;
652 unsigned long old_unseen = ULONG_MAX;
653 struct stat stat_buf;
654 struct hostent he, *he_res = 0;
657 struct sockaddr_in their_addr; // connector's address information
658 struct mail_s *mail = (struct mail_s *)arg;
660 #ifdef HAVE_GETHOSTBYNAME_R
661 if (gethostbyname_r(mail->host, &he, hostbuff, sizeof(hostbuff), &he_res, &he_errno)) { // get the host info
662 ERR("POP3 gethostbyname_r: %s", hstrerror(h_errno));
665 #else /* HAVE_GETHOSTBYNAME_R */
666 if ((he_res = gethostbyname(mail->host)) == NULL) { // get the host info
667 herror("gethostbyname");
670 #endif /* HAVE_GETHOSTBYNAME_R */
671 while (fail < mail->retries) {
672 struct timeval timeout;
677 ERR("Trying POP3 connection again for %s@%s (try %u/%u)",
678 mail->user, mail->host, fail + 1, mail->retries);
681 if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
688 their_addr.sin_family = AF_INET;
689 // short, network byte order
690 their_addr.sin_port = htons(mail->port);
691 their_addr.sin_addr = *((struct in_addr *) he_res->h_addr);
692 // zero the rest of the struct
693 memset(&(their_addr.sin_zero), '\0', 8);
695 if (connect(sockfd, (struct sockaddr *) &their_addr,
696 sizeof(struct sockaddr)) == -1) {
702 timeout.tv_sec = 60; // 60 second timeout i guess
705 FD_SET(sockfd, &fdset);
706 res = select(sockfd + 1, &fdset, NULL, NULL, &timeout);
708 if ((numbytes = recv(sockfd, recvbuf, MAXDATASIZE - 1, 0)) == -1) {
714 ERR("POP3 connection failed: timeout\n");
718 DBGP2("pop3_thread received: %s", recvbuf);
719 recvbuf[numbytes] = '\0';
720 if (strstr(recvbuf, "+OK ") != recvbuf) {
721 ERR("POP3 connection failed, probably not a POP3 server");
725 strncpy(sendbuf, "USER ", MAXDATASIZE);
726 strncat(sendbuf, mail->user, MAXDATASIZE - strlen(sendbuf) - 1);
727 strncat(sendbuf, "\r\n", MAXDATASIZE - strlen(sendbuf) - 1);
728 if (pop3_command(sockfd, sendbuf, recvbuf, "+OK ")) {
733 strncpy(sendbuf, "PASS ", MAXDATASIZE);
734 strncat(sendbuf, mail->pass, MAXDATASIZE - strlen(sendbuf) - 1);
735 strncat(sendbuf, "\r\n", MAXDATASIZE - strlen(sendbuf) - 1);
736 if (pop3_command(sockfd, sendbuf, recvbuf, "+OK ")) {
737 ERR("POP3 server login failed: %s", recvbuf);
742 strncpy(sendbuf, "STAT\r\n", MAXDATASIZE);
743 if (pop3_command(sockfd, sendbuf, recvbuf, "+OK ")) {
749 // now we get the data
752 ERR("Error parsing POP3 response: %s", recvbuf);
756 timed_thread_lock(mail->p_timed_thread);
757 sscanf(reply, "%lu %lu", &mail->unseen, &mail->used);
758 timed_thread_unlock(mail->p_timed_thread);
761 strncpy(sendbuf, "QUIT\r\n", MAXDATASIZE);
762 if (pop3_command(sockfd, sendbuf, recvbuf, "+OK")) {
763 ERR("POP3 logout failed: %s", recvbuf);
768 if (strlen(mail->command) > 1 && mail->unseen > old_unseen) {
770 if (system(mail->command) == -1) {
775 old_unseen = mail->unseen;
777 if ((fstat(sockfd, &stat_buf) == 0) && S_ISSOCK(stat_buf.st_mode)) {
778 /* if a valid socket, close it */
781 if (timed_thread_test(mail->p_timed_thread, 0)) {
782 timed_thread_exit(mail->p_timed_thread);