/*
* 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 <stdio.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <time.h>
#include <proplist.h>
#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
syntax highlighted by Code2HTML, v. 0.9.1