/* * 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. * * pop3.c: functions to handle POP3 mailboxes * * $Id: pop3.c,v 1.2 2000/07/03 08:49:24 bryan.chan Exp $ * */ #include #include #include #include #include #include #include #include "wmmail.h" #include "wmutil.h" #include "net.h" #ifdef POP3_SUPPORT /* * Supported key(s) in the Options dictionary: * * Hostname * Port (optional) * Username * Password * * 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; } Connection; /* internal functions */ static Connection *init_connection(Mailbox *); static int count_mail(Connection *, int *, 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 POP3_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 POP3 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"); 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) || (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 ? 110 : 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 (strncasecmp(buf, "+OK ", 4)) { /* 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 (Connection *) NULL; } sprintf(buf, "USER %s\n", PLGetString(username)); socket_write(sockfd, buf); socket_read(sockfd, buf, MAX_STRING_SIZE); if (strncasecmp(buf, "+OK ", 4)) { croak("Bad username specified for mailbox \"mailbox\"; ignored", mailbox->name); close(sockfd); return (Connection *) NULL; } sprintf(buf, "PASS %s\n", PLGetString(password)); socket_write(sockfd, buf); socket_read(sockfd, buf, MAX_STRING_SIZE); if (strncasecmp(buf, "+OK ", 4)) { /* if we can't login, then don't try any more */ croak("login failed for mailbox \"%s\"; ignored", mailbox->name); close(sockfd); return NULL; } connection = wmalloc(sizeof(Connection)); connection->socket = sockfd; return connection; } /* 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; { int i; char buf[MAX_STRING_SIZE + 1]; char cmd[MAX_STRING_SIZE + 1]; buf[MAX_STRING_SIZE] = 0; cmd[MAX_STRING_SIZE] = 0; sprintf(cmd, "STAT\n"); if (socket_write(connection->socket, cmd) && socket_read(connection->socket, buf, MAX_STRING_SIZE)) { if (!strncasecmp(buf, "+OK ", 4)) { sscanf(buf + 4, "%d", total_mail_count); } } for (i = 0, *new_mail_count = 0; i < *total_mail_count; i++) { sprintf(cmd, "TOP %d 0\n", i + 1); if (socket_write(connection->socket, cmd)) { int j = 1; while (socket_read(connection->socket, buf, MAX_STRING_SIZE) && strncmp(buf, ".", 1)) { if (!strncmp(buf, "Status:", 7) && index(buf + 7, 'R')) j = 0; } (*new_mail_count) += j; } } return True; } static void close_connection(Connection *connection) { char buf[MAX_STRING_SIZE]; sprintf(buf, "QUIT\n"); socket_write(connection->socket, buf); socket_read(connection->socket, buf, MAX_STRING_SIZE); close(connection->socket); wfree(connection); } #endif