isync/imap.c
Michael Elkins 21bf53b2ab don't fetch deleted messages when expunging
display number of messages that are to be deleted

flags for \Recent messages were not properly fetched

local messages with updated flags were not corrected renamed
2000-12-21 00:30:53 +00:00

542 lines
11 KiB
C

/* $Id$
*
* isync - IMAP4 to maildir mailbox synchronizer
* Copyright (C) 2000 Michael R. Elkins <me@mutt.org>
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <assert.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include "isync.h"
const char *Flags[] = {
"\\Seen",
"\\Answered",
"\\Deleted",
"\\Flagged",
"\\Recent",
"\\Draft"
};
/* simple line buffering */
static int
buffer_gets (buffer_t * b, char **s)
{
int n;
int start = b->offset;
*s = b->buf + start;
for (;;)
{
if (b->offset + 2 > b->bytes)
{
/* shift down used bytes */
*s = b->buf;
assert (start <= b->bytes);
n = b->bytes - start;
if (n)
memmove (b->buf, b->buf + start, n);
b->offset = n;
start = 0;
n = read (b->fd, b->buf + b->offset, sizeof (b->buf) - b->offset);
if (n <= 0)
{
if (n == -1)
perror ("read");
else
puts ("EOF");
return -1;
}
b->bytes = b->offset + n;
// printf ("buffer_gets:read %d bytes\n", n);
}
if (b->buf[b->offset] == '\r')
{
if (b->buf[b->offset + 1] == '\n')
{
b->buf[b->offset] = 0; /* terminate the string */
b->offset += 2; /* next line */
return 0;
}
}
b->offset++;
}
/* not reached */
}
static int
imap_exec (imap_t * imap, const char *fmt, ...)
{
va_list ap;
char tmp[256];
char buf[256];
char *cmd;
char *arg;
char *arg1;
message_t **cur = 0;
message_t **rec = 0;
va_start (ap, fmt);
vsnprintf (tmp, sizeof (tmp), fmt, ap);
va_end (ap);
snprintf (buf, sizeof (buf), "%d %s\r\n", ++Tag, tmp);
if (Verbose)
fputs (buf, stdout);
write (imap->fd, buf, strlen (buf));
for (;;)
{
if (buffer_gets (imap->buf, &cmd))
return -1;
if (Verbose)
puts (cmd);
arg = next_arg (&cmd);
if (*arg == '*')
{
arg = next_arg (&cmd);
arg1 = next_arg (&cmd);
if (arg1 && !strcmp ("EXISTS", arg1))
imap->count = atoi (arg);
else if (arg1 && !strcmp ("RECENT", arg1))
imap->recent = atoi (arg);
else if (!strcmp ("SEARCH", arg))
{
if (!rec)
{
rec = &imap->recent_msgs;
while (*rec)
rec = &(*rec)->next;
}
/* need to add arg1 */
*rec = calloc (1, sizeof (message_t));
(*rec)->uid = atoi (arg1);
rec = &(*rec)->next;
/* parse rest of `cmd' */
while ((arg = next_arg (&cmd)))
{
*rec = calloc (1, sizeof (message_t));
(*rec)->uid = atoi (arg);
rec = &(*rec)->next;
}
}
else if (arg1 && !strcmp ("FETCH", arg1))
{
if (!cur)
{
cur = &imap->msgs;
while (*cur)
cur = &(*cur)->next;
}
/* new message
* * <N> FETCH (UID <uid> FLAGS (...))
*/
arg = next_arg (&cmd); /* (UID */
arg = next_arg (&cmd); /* <uid> */
*cur = calloc (1, sizeof (message_t));
(*cur)->uid = atoi (arg);
arg = next_arg (&cmd); /* FLAGS */
if (!arg || strcmp ("FLAGS", arg))
{
printf ("FETCH parse error: expected FLAGS at %s\n", arg);
return -1;
}
/* if we need to parse additional info, we should keep
* a copy of this `arg' pointer
*/
cmd++;
arg = strchr (cmd, ')');
if (!arg)
{
puts ("FETCH parse error");
return -1;
}
*arg = 0;
/* parse message flags */
while ((arg = next_arg (&cmd)))
{
if (!strcmp ("\\Seen", arg))
(*cur)->flags |= D_SEEN;
else if (!strcmp ("\\Flagged", arg))
(*cur)->flags |= D_FLAGGED;
else if (!strcmp ("\\Deleted", arg))
{
(*cur)->flags |= D_DELETED;
imap->deleted++;
}
else if (!strcmp ("\\Answered", arg))
(*cur)->flags |= D_ANSWERED;
else if (!strcmp ("\\Draft", arg))
(*cur)->flags |= D_DRAFT;
else if (!strcmp ("\\Recent", arg))
(*cur)->flags |= D_RECENT;
else
printf ("warning, unknown flag %s\n", arg);
}
cur = &(*cur)->next;
}
}
else if ((size_t) atol (arg) != Tag)
{
puts ("wrong tag");
return -1;
}
else
{
arg = next_arg (&cmd);
if (!strcmp ("OK", arg))
return 0;
puts ("IMAP command failed");
return -1;
}
}
/* not reached */
}
static int
fetch_recent_flags (imap_t * imap)
{
char buf[1024];
message_t **cur = &imap->recent_msgs;
message_t *tmp;
unsigned int start = -1;
unsigned int last = -1;
int ret = 0;
buf[0] = 0;
while (*cur)
{
tmp = *cur;
if (last == (unsigned int) -1)
{
/* init */
start = tmp->uid;
last = tmp->uid;
}
else if (tmp->uid == last + 1)
last++;
else
{
/* out of sequence */
if (start == last)
ret = imap_exec (imap, "UID FETCH %d (UID FLAGS)", start);
else
ret =
imap_exec (imap, "UID FETCH %d:%d (UID FLAGS)", start,
last);
start = tmp->uid;
last = tmp->uid;
}
free (tmp);
*cur = (*cur)->next;
}
if (start != (unsigned int) -1)
{
if (start == last)
ret = imap_exec (imap, "UID FETCH %d (UID FLAGS)", start);
else
ret =
imap_exec (imap, "UID FETCH %d:%d (UID FLAGS)", start, last);
}
return ret;
}
imap_t *
imap_open (config_t * box, int fast)
{
int ret;
imap_t *imap;
int s;
struct sockaddr_in sin;
struct hostent *he;
/* open connection to IMAP server */
memset (&sin, 0, sizeof (sin));
sin.sin_port = htons (box->port);
sin.sin_family = AF_INET;
printf ("Resolving %s... ", box->host);
fflush (stdout);
he = gethostbyname (box->host);
if (!he)
{
perror ("gethostbyname");
return 0;
}
puts ("ok");
sin.sin_addr.s_addr = *((int *) he->h_addr_list[0]);
s = socket (PF_INET, SOCK_STREAM, 0);
printf ("Connecting to %s:%hu... ", inet_ntoa (sin.sin_addr),
ntohs (sin.sin_port));
fflush (stdout);
if (connect (s, (struct sockaddr *) &sin, sizeof (sin)))
{
perror ("connect");
exit (1);
}
puts ("ok");
imap = calloc (1, sizeof (imap_t));
imap->fd = s;
//imap->state = imap_state_init;
imap->buf = calloc (1, sizeof (buffer_t));
imap->buf->fd = s;
imap->box = box;
puts ("Logging in...");
ret = imap_exec (imap, "LOGIN %s %s", box->user, box->pass);
if (!ret)
{
fputs ("Selecting mailbox... ", stdout);
fflush (stdout);
ret = imap_exec (imap, "SELECT %s", box->box);
if (!ret)
printf ("%d messages, %d recent\n", imap->count, imap->recent);
}
if (!ret)
{
if (fast)
{
if (imap->recent > 0)
{
puts ("Fetching info for recent messages");
ret = imap_exec (imap, "UID SEARCH RECENT");
if (!ret)
ret = fetch_recent_flags (imap);
}
}
else if (imap->count > 0)
{
puts ("Reading IMAP mailbox index");
ret = imap_exec (imap, "FETCH 1:%d (UID FLAGS)", imap->count);
}
}
if (ret)
{
imap_exec (imap, "LOGOUT");
close (s);
free (imap->buf);
free (imap);
imap = 0;
}
return imap;
}
void
imap_close (imap_t * imap)
{
puts ("Closing IMAP connection");
imap_exec (imap, "LOGOUT");
}
/* write a buffer stripping all \r bytes */
static int
write_strip (int fd, char *buf, size_t len)
{
size_t start = 0;
size_t end = 0;
while (start < len)
{
while (end < len && buf[end] != '\r')
end++;
write (fd, buf + start, end - start);
end++;
start = end;
}
return 0;
}
static void
send_server (int fd, const char *fmt, ...)
{
char buf[128];
char cmd[128];
va_list ap;
va_start (ap, fmt);
vsnprintf (buf, sizeof (buf), fmt, ap);
va_end (ap);
snprintf (cmd, sizeof (cmd), "%d %s\r\n", ++Tag, buf);
write (fd, cmd, strlen (cmd));
if (Verbose)
fputs (cmd, stdout);
}
int
imap_fetch_message (imap_t * imap, unsigned int uid, int fd)
{
char *cmd;
char *arg;
size_t bytes;
size_t n;
char buf[1024];
send_server (imap->fd, "UID FETCH %d RFC822.PEEK", uid);
for (;;)
{
if (buffer_gets (imap->buf, &cmd))
return -1;
if (Verbose)
puts (cmd);
if (*cmd == '*')
{
/* need to figure out how long the message is
* * <msgno> FETCH (RFC822 {<size>}
*/
next_arg (&cmd); /* * */
next_arg (&cmd); /* <msgno> */
next_arg (&cmd); /* FETCH */
next_arg (&cmd); /* (RFC822 */
arg = next_arg (&cmd);
if (*arg != '{')
{
puts ("parse error getting size");
return -1;
}
bytes = strtol (arg + 1, 0, 10);
// printf ("receiving %d byte message\n", bytes);
/* dump whats left over in the input buffer */
n = imap->buf->bytes - imap->buf->offset;
if (n > bytes)
{
/* the entire message fit in the buffer */
n = bytes;
}
/* ick. we have to strip out the \r\n line endings, so
* i can't just dump the raw bytes to disk.
*/
write_strip (fd, imap->buf->buf + imap->buf->offset, n);
bytes -= n;
// printf ("wrote %d buffered bytes\n", n);
/* mark that we used part of the buffer */
imap->buf->offset += n;
/* now read the rest of the message */
while (bytes > 0)
{
n = bytes;
if (n > sizeof (buf))
n = sizeof (buf);
n = read (imap->fd, buf, n);
if (n > 0)
{
// printf("imap_fetch_message:%d:read %d bytes\n", __LINE__, n);
write_strip (fd, buf, n);
bytes -= n;
}
else
{
if (n == (size_t) - 1)
perror ("read");
else
puts ("EOF");
return -1;
}
}
// puts ("finished fetching msg");
buffer_gets (imap->buf, &cmd);
if (Verbose)
puts (cmd); /* last part of line */
}
else
{
arg = next_arg (&cmd);
if (!arg || (size_t) atoi (arg) != Tag)
{
puts ("wrong tag");
return -1;
}
break;
}
}
return 0;
}
/* add flags to existing flags */
int
imap_set_flags (imap_t * imap, unsigned int uid, unsigned int flags)
{
char buf[256];
int i;
buf[0] = 0;
for (i = 0; i < D_MAX; i++)
{
if (flags & (1 << i))
snprintf (buf + strlen (buf),
sizeof (buf) - strlen (buf), "%s%s",
(buf[0] != 0) ? " " : "", Flags[i]);
}
return imap_exec (imap, "UID STORE %d +FLAGS.SILENT (%s)", uid, buf);
}
int
imap_expunge (imap_t * imap)
{
return imap_exec (imap, "EXPUNGE");
}