/* * WMMail - Window Maker Mail * * Copyright (c) 1996, 1997, 1998 Per Liden * Copyright (c) 1997, 1998 Bryan Chan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * 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, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * imap.c: functions to handle IMAP4 mailboxes * * $Id: imap.c,v 1.1 2000/07/02 20:37:58 bryan.chan Exp $ * */ #include #include #include #include #include #include #include #include "wmmail.h" #include "wmutil.h" #include "net.h" #ifdef IMAP_SUPPORT /* * Supported key(s) in the Options dictionary: * * Hostname * Port (optional) * Username * Password * Folder (optional) * * See mbox.c for information on states and transitions. * * For a local mailbox, errors encountered during an update usually * cause the program to give up monitoring the mailbox ("ignored"). * * For remote mailboxes, network errors are ignored. Query will be * re-tried at the next update. Other types of errors (such as invalid * options) still cause the program to give up. * */ /* internal data structure */ typedef struct { char *folder_name; int socket; int cmd_seq; } Connection; /* internal functions */ static Connection *init_connection(Mailbox *); static int count_mail(Connection *, int *, int *); static int imap_ok(char *, int); static void close_connection(Connection *); #define MAX_STRING_SIZE 1024 #define GET_ENTRY(x,y,z) { proplist_t sub_key = PLMakeString(z); \ y = PLGetDictionaryEntry(x, sub_key); \ PLRelease(sub_key); } int IMAP_check(Mailbox *mailbox, int *beep, int *redraw, int *run) { Connection *connection; int prev_status, prev_new_mail_count; connection = init_connection(mailbox); mailbox->last_update = time(NULL); if (connection == NULL) return False; else if (connection == (Connection *) -1) return True; else { prev_status = mailbox->status; prev_new_mail_count = mailbox->new_mail_count; if (count_mail(connection, &mailbox->total_mail_count, &mailbox->new_mail_count)) { close_connection(connection); if (mailbox->total_mail_count == 0) { /* there is no mail in the mailbox */ mailbox->status = NO_MAIL; } else if (mailbox->new_mail_count == 0) { /* there are no new mails */ mailbox->status = OLD_MAIL; } else if (mailbox->new_mail_count > prev_new_mail_count) { /* new mails have arrived! */ if (mailbox->status == NEW_MAIL && always_new_mail_exec) *run |= True; else if (mailbox->status == NEW_MAIL) *run |= False; else *run |= True; *beep = True; mailbox->status = NEW_MAIL; } /* else no change */ } else { croak("cannot count messages in mailbox \"%s\"; ignored", mailbox->name); close_connection(connection); return False; } *redraw |= (prev_status != mailbox->status); return True; } } /* creates a connection to the IMAP server and logs in * * returns: -1 if recoverable error encountered * NULL if unrecoverable error encountered * pointer to Connection if successfully initiated */ static Connection *init_connection(Mailbox *mailbox) { Connection *connection; struct sockaddr_in remote_addr; struct hostent *host; char buf[MAX_STRING_SIZE]; int sockfd, port_no = -1; proplist_t hostname, port, username, password, folder; GET_ENTRY(mailbox->options, hostname, "Hostname"); GET_ENTRY(mailbox->options, port, "Port"); GET_ENTRY(mailbox->options, username, "Username"); GET_ENTRY(mailbox->options, password, "Password"); GET_ENTRY(mailbox->options, folder, "Folder"); if (!username || !password || !hostname) { croak("mailbox \"%s\" missing one of the options \"Username\", " "\"Password\", and \"Hostname\"; ignored", mailbox->name); return NULL; } else if ( !PLIsString(hostname) || !PLIsString(username) || !PLIsString(password) || (folder && !PLIsString(folder)) || (port && (!PLIsString(port) || sscanf(PLGetString(port), "%i", &port_no) != 1)) ) { croak("mailbox \"%s\" has invalid options; ignored", mailbox->name); return (Connection *) NULL; } if (( host = gethostbyname(PLGetString(hostname)) ) == NULL) { croak("gethostbyname() failed for mailbox \"%s\"; continuing", mailbox->name); return (Connection *) -1; } if (( sockfd = socket(AF_INET, SOCK_STREAM, 0) ) == -1) { croak("socket() failed for mailbox\"%s\"; continuing", mailbox->name); return (Connection *) -1; } remote_addr.sin_family = AF_INET; remote_addr.sin_port = htons(port_no == -1 ? 143 : port_no); remote_addr.sin_addr = *((struct in_addr *) host->h_addr); bzero(&remote_addr.sin_zero, 8); if (connect(sockfd, (struct sockaddr *) &remote_addr, sizeof(struct sockaddr)) == -1) { croak("connect() failed for mailbox\"%s\"; continuing", mailbox->name); close(sockfd); return (Connection *) -1; } /* are we welcome? */ socket_read(sockfd, buf, MAX_STRING_SIZE); if (!imap_ok(buf, -1)) { /* give up if not; we don't want to force open a door that won't */ croak("no service from remote server for mailbox \"%s\"; ignored", mailbox->name); close(sockfd); return NULL; } sprintf(buf, "A000 LOGIN %s %s\r\n", PLGetString(username), PLGetString(password)); socket_write(sockfd, buf); socket_read(sockfd, buf, MAX_STRING_SIZE); if (imap_ok(buf, 0)) { connection = wmalloc(sizeof(Connection)); connection->folder_name = folder ? PLGetString(folder) : "INBOX"; connection->socket = sockfd; connection->cmd_seq = 1; /* A000 is already issued, above */ return connection; } else { /* FIXME: remove this debug message before release! */ croak("%s", buf); /* if we can't login, then don't try any more */ croak("login failed for mailbox \"%s\"; ignored", mailbox->name); close(sockfd); return NULL; } } /* FIXME: socket errors are transparent! */ static int count_mail(connection, total_mail_count, new_mail_count) Connection *connection; int *total_mail_count, *new_mail_count; { char buf[MAX_STRING_SIZE + 1]; char cmd[MAX_STRING_SIZE + 1]; buf[MAX_STRING_SIZE] = 0; /* fix buffer overflow */ cmd[MAX_STRING_SIZE] = 0; sprintf(cmd, "A%03i SELECT %s\r\n", connection->cmd_seq++, connection->folder_name); if (socket_write(connection->socket, cmd)) do { if (!socket_read(connection->socket, buf, MAX_STRING_SIZE)) break; if (strstr(buf, "EXISTS")) *total_mail_count = atoi(strstr(buf, "*") + 1); } while (strncmp(buf, cmd, 4)); sprintf(cmd, "A%03i CHECK\r\n", connection->cmd_seq++); if (socket_write(connection->socket, cmd)) do if (!socket_read(connection->socket, buf, MAX_STRING_SIZE)) break; while (strncmp(buf, cmd, 4)); *new_mail_count = 0; sprintf(cmd, "A%03i SEARCH UNSEEN\r\n", connection->cmd_seq++); if (socket_write(connection->socket, cmd)) { socket_read(connection->socket, buf, MAX_STRING_SIZE); while (strncmp(buf, cmd, 4)) { char *c; /* From RFC1730: * * " SEARCH Response * * Data: zero or more numbers * * .... The number(s) refer to those messages that match the * search criteria. For SEARCH, these are message sequence * numbers;.... Each number is delimited by a space. * * Example: S: * SEARCH 2 3 6 10 " * */ /* chop trailing whitespace to avoid counting errors */ for (c = &buf[strlen(buf) - 1]; *c == ' '; c--) *c = '\0'; for (c = &buf[7]; *c != '\0'; c++) if (*c == ' ') (*new_mail_count)++; /* server closed connection? */ if (!socket_read(connection->socket, buf, MAX_STRING_SIZE)) break; } } return True; } /* verify that the return string from the server contains "OK"; * we should probably make use of this function more frequently */ static int imap_ok(char *buf, int cmd_seq) { /* make a slightly larger buffer just in case; * maybe we should just assert(cmd_seq <= 999) */ char s[10]; if (!buf) return False; /* assume untagged response if cmd_seq is negative */ if (cmd_seq < 0) sprintf(s, "* OK"); else sprintf(s, "A%03i OK", cmd_seq); if (strncasecmp(buf, s, strlen(s))) return False; else return True; } static void close_connection(Connection *connection) { char buf[MAX_STRING_SIZE]; sprintf(buf, "A%03i LOGOUT\r\n", connection->cmd_seq); socket_write(connection->socket, buf); socket_read(connection->socket, buf, MAX_STRING_SIZE); close(connection->socket); wfree(connection); } #endif