initial import
This commit is contained in:
commit
f47d0d7c11
4
Makefile.am
Normal file
4
Makefile.am
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
bin_PROGRAMS=isync
|
||||||
|
isync_SOURCES=main.c imap.c sync.c maildir.c
|
||||||
|
man_MANS=isync.1
|
||||||
|
EXTRA_DIST=sample.isyncrc $(man_MANS)
|
28
README
Normal file
28
README
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
_
|
||||||
|
(_)___ _ _ _ __ ___
|
||||||
|
| / __| | | | '_ \ / __|
|
||||||
|
| \__ \ |_| | | | | (__
|
||||||
|
|_|___/\__, |_| |_|\___|
|
||||||
|
|___/
|
||||||
|
isync - IMAP4 to maildir mailbox synchronization program
|
||||||
|
http://www.sigpipe.org/isync/
|
||||||
|
|
||||||
|
Author: Michael Elkins <me@mutt.org>
|
||||||
|
|
||||||
|
``isync'' is a command line application which synchronizes a local
|
||||||
|
maildir-style mailbox with a remote IMAP4 mailbox, suitable for use in
|
||||||
|
IMAP-disconnected mode. Multiple copies of the remote IMAP4 mailbox can be
|
||||||
|
maintained, and all flags are synchronized.
|
||||||
|
|
||||||
|
``isync'' has been tested with the following IMAP servers:
|
||||||
|
|
||||||
|
Microsoft Exchange 2000 IMAP4rev1 server version 6.0.4417.0
|
||||||
|
|
||||||
|
* INSTALLING
|
||||||
|
|
||||||
|
./configure
|
||||||
|
make install
|
||||||
|
|
||||||
|
* HELP
|
||||||
|
|
||||||
|
Please see the man page for complete documentation.
|
9
configure.in
Normal file
9
configure.in
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
AC_INIT(isync.h)
|
||||||
|
AM_INIT_AUTOMAKE(isync,0.1)
|
||||||
|
AM_PROG_CC_STDC
|
||||||
|
if test $CC = gcc; then
|
||||||
|
CFLAGS="$CFLAGS -pipe"
|
||||||
|
fi
|
||||||
|
AC_CHECK_FUNCS(getopt_long)
|
||||||
|
CFLAGS="$CFLAGS -W -Wall -pedantic -Wmissing-prototypes -Wmissing-declarations"
|
||||||
|
AC_OUTPUT(Makefile)
|
531
imap.c
Normal file
531
imap.c
Normal file
|
@ -0,0 +1,531 @@
|
||||||
|
/* 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->msgs;
|
||||||
|
while (*rec)
|
||||||
|
rec = &(*rec)->next;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
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");
|
||||||
|
}
|
184
isync.1
Normal file
184
isync.1
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
.ig
|
||||||
|
\" 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
|
||||||
|
..
|
||||||
|
.TH isync 1 "2000 Dec 20"
|
||||||
|
..
|
||||||
|
.SH NAME
|
||||||
|
isync - synchronize IMAP4 and maildir mailboxes
|
||||||
|
..
|
||||||
|
.SH SYNOPSIS
|
||||||
|
.B isync
|
||||||
|
[
|
||||||
|
.I options...
|
||||||
|
]
|
||||||
|
.I file
|
||||||
|
..
|
||||||
|
.SH DESCRIPTION
|
||||||
|
.B isync
|
||||||
|
is a command line application which synchronizes a local maildir-style
|
||||||
|
mailbox with a remote IMAP4 mailbox, suitable for use in IMAP-disconnected
|
||||||
|
mode. Multiple copies of the remote IMAP4 mailbox can be maintained, and
|
||||||
|
all flags are synchronized.
|
||||||
|
..
|
||||||
|
.SH OPTIONS
|
||||||
|
.TP
|
||||||
|
\fB-c\fR, \fB--config\fR \fIfile\fR
|
||||||
|
Read configuration from
|
||||||
|
.I file
|
||||||
|
By default, configuration is read from ~/.isyncrc if it exists.
|
||||||
|
.TP
|
||||||
|
.B -d, --delete
|
||||||
|
Causes
|
||||||
|
.B isync
|
||||||
|
to delete messages from the local maildir mailbox which do not exist on the
|
||||||
|
IMAP server. By default,
|
||||||
|
.I dead
|
||||||
|
messages are
|
||||||
|
.B not
|
||||||
|
deleted.
|
||||||
|
.TP
|
||||||
|
.B -e, --expunge
|
||||||
|
Causes
|
||||||
|
.B isync
|
||||||
|
to permanently remove all messages marked for deletion in both the local
|
||||||
|
maildir mailbox and the remote IMAP mailbox. By default, messages are
|
||||||
|
.B not
|
||||||
|
expunged.
|
||||||
|
.TP
|
||||||
|
.B -f, --fast
|
||||||
|
Causes
|
||||||
|
.B isync
|
||||||
|
to skip the step of synchronzing message flags between the local maildir
|
||||||
|
mailbox and the IMAP mailbox. Only new messages existing on the server will
|
||||||
|
be fetched into the local mailbox.
|
||||||
|
.B NOTE:
|
||||||
|
This command works by checking the \\Recent flag on messages in the IMAP
|
||||||
|
mailbox. If you access the IMAP mailbox from multiple locations, the
|
||||||
|
\\Recent flag will be lost between sessions, so you must do a full
|
||||||
|
synchronization to fetch the messages which do not exist in the local
|
||||||
|
mailbox.
|
||||||
|
.TP
|
||||||
|
.B -h, --help
|
||||||
|
Displays a summary of command line options
|
||||||
|
.TP
|
||||||
|
\fB-p\fR, \fB--port\fR \fIport\fR
|
||||||
|
Specifies the port on the IMAP server to connect to (default: 143)
|
||||||
|
.TP
|
||||||
|
\fB-r\fR, \fB--remote\fR \fIbox\fR
|
||||||
|
Specifies the name of the remote IMAP mailbox to synchronize with
|
||||||
|
(Default: INBOX)
|
||||||
|
.TP
|
||||||
|
\fB-s\fR, \fB--host\fR \fIhost\fR
|
||||||
|
.P
|
||||||
|
Specifies the hostname of the IMAP server
|
||||||
|
.TP
|
||||||
|
\fB-u\fR, \fB--user\fR \fIuser\fR
|
||||||
|
Specifies the login name to access the IMAP server (default: $USER)
|
||||||
|
.TP
|
||||||
|
.B -v, --version
|
||||||
|
Displays
|
||||||
|
.B isync
|
||||||
|
version information
|
||||||
|
.TP
|
||||||
|
.B -V, --verbose
|
||||||
|
Enables
|
||||||
|
.I verbose
|
||||||
|
mode, which disables the IMAP network traffic.
|
||||||
|
..
|
||||||
|
.SH CONFIGURATION
|
||||||
|
.B isync
|
||||||
|
reads
|
||||||
|
.I ~/.isyncrc
|
||||||
|
to load default configuration data. Each line of the configuration file
|
||||||
|
consists of a command. The following commands are understood:
|
||||||
|
.TP
|
||||||
|
\fBMailbox\fR \fIpath\fR
|
||||||
|
Defines a local maildir mailbox. All configuration commands following this
|
||||||
|
line, up until the next
|
||||||
|
.I Mailbox
|
||||||
|
command, apply to this mailbox only.
|
||||||
|
..
|
||||||
|
.TP
|
||||||
|
\fBHost\fR \fIname\fR
|
||||||
|
Defines the DNS name or IP address of the IMAP server
|
||||||
|
..
|
||||||
|
.TP
|
||||||
|
\fBPort\fR \fIport\fR
|
||||||
|
Defines the TCP port number on the IMAP server to use (Default: 143)
|
||||||
|
..
|
||||||
|
.TP
|
||||||
|
\fBBox\fR \fImailbox\fR
|
||||||
|
Defines the name of the remote IMAP mailbox associated with the local
|
||||||
|
maildir mailbox (Default: INBOX)
|
||||||
|
..
|
||||||
|
.TP
|
||||||
|
\fBUser\fR \fIusername\fR
|
||||||
|
Defines the login name on the IMAP server (Default: current user)
|
||||||
|
..
|
||||||
|
.TP
|
||||||
|
\fBPass\fR \fIpassword\fR
|
||||||
|
Defines the password for
|
||||||
|
.I username
|
||||||
|
on the IMAP server. Note that this option is
|
||||||
|
.B NOT
|
||||||
|
required. If no password is specified in the configuration file,
|
||||||
|
.B isync
|
||||||
|
will prompt you for it.
|
||||||
|
..
|
||||||
|
.TP
|
||||||
|
\fBAlias\fR \fIstring\fR
|
||||||
|
Defines an alias for the mailbox which can be used as a shortcut on the
|
||||||
|
command line.
|
||||||
|
.P
|
||||||
|
Configuration commands that appear prior to the first
|
||||||
|
.B Mailbox
|
||||||
|
command are considered to be
|
||||||
|
.I global
|
||||||
|
options which are used as defaults when those specific options are not
|
||||||
|
specifically set for a defined Mailbox. For example, if you use the same
|
||||||
|
login name for several IMAP servers, you can put a
|
||||||
|
.B User
|
||||||
|
command before the first
|
||||||
|
.B Mailbox
|
||||||
|
command, and then leave out the
|
||||||
|
.B User
|
||||||
|
command in the sections for each mailbox.
|
||||||
|
.B isync
|
||||||
|
will then use the global value by default.
|
||||||
|
..
|
||||||
|
.SH FILES
|
||||||
|
.TP
|
||||||
|
.B ~/.isyncrc
|
||||||
|
Default configuration file
|
||||||
|
..
|
||||||
|
.SH SEE ALSO
|
||||||
|
mutt(1), maildir(5)
|
||||||
|
.P
|
||||||
|
Up to date information on
|
||||||
|
.B isync
|
||||||
|
can be found at
|
||||||
|
http://www.sigpipe.org/isync/.
|
||||||
|
..
|
||||||
|
.SH AUTHOR
|
||||||
|
Written by Michael R. Elkins <me@mutt.org>.
|
||||||
|
..
|
||||||
|
.SH BUGS
|
||||||
|
SSL is currently not used when connecting to the IMAP server. A future
|
||||||
|
version of
|
||||||
|
.B isync
|
||||||
|
is expected to support this.
|
111
isync.h
Normal file
111
isync.h
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
/* 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 <stdarg.h>
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
int fd;
|
||||||
|
char buf[1024];
|
||||||
|
int bytes;
|
||||||
|
int offset;
|
||||||
|
}
|
||||||
|
buffer_t;
|
||||||
|
|
||||||
|
typedef struct config config_t;
|
||||||
|
typedef struct mailbox mailbox_t;
|
||||||
|
typedef struct message message_t;
|
||||||
|
|
||||||
|
struct config
|
||||||
|
{
|
||||||
|
char *path;
|
||||||
|
char *host;
|
||||||
|
int port;
|
||||||
|
char *user;
|
||||||
|
char *pass;
|
||||||
|
char *box;
|
||||||
|
char *alias;
|
||||||
|
config_t *next;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* struct representing local mailbox file */
|
||||||
|
struct mailbox
|
||||||
|
{
|
||||||
|
char *path;
|
||||||
|
message_t *msgs;
|
||||||
|
unsigned int changed:1;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* message dispositions */
|
||||||
|
#define D_SEEN (1<<0)
|
||||||
|
#define D_ANSWERED (1<<1)
|
||||||
|
#define D_DELETED (1<<2)
|
||||||
|
#define D_FLAGGED (1<<3)
|
||||||
|
#define D_RECENT (1<<4)
|
||||||
|
#define D_DRAFT (1<<5)
|
||||||
|
#define D_MAX 6
|
||||||
|
|
||||||
|
struct message
|
||||||
|
{
|
||||||
|
char *file;
|
||||||
|
unsigned int uid;
|
||||||
|
unsigned int flags;
|
||||||
|
message_t *next;
|
||||||
|
unsigned int processed:1; /* message has already been evaluated */
|
||||||
|
unsigned int new:1; /* message is in the new/ subdir */
|
||||||
|
unsigned int changed:1; /* flags changed */
|
||||||
|
unsigned int dead:1; /* message doesn't exist on the server */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* imap connection info */
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
int fd; /* server socket */
|
||||||
|
unsigned int count; /* # of msgs */
|
||||||
|
unsigned int recent; /* # of recent messages */
|
||||||
|
buffer_t *buf; /* input buffer for reading server output */
|
||||||
|
message_t *msgs; /* list of messages on the server */
|
||||||
|
config_t *box; /* mailbox to open */
|
||||||
|
message_t *recent_msgs; /* list of recent messages - only contains
|
||||||
|
* UID to be used in a FETCH FLAGS command
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
imap_t;
|
||||||
|
|
||||||
|
/* flags for sync_mailbox */
|
||||||
|
#define SYNC_FAST (1<<0) /* don't sync flags, only fetch new msgs */
|
||||||
|
#define SYNC_DELETE (1<<1) /* delete local that don't exist on server */
|
||||||
|
|
||||||
|
extern config_t global;
|
||||||
|
extern unsigned int Tag;
|
||||||
|
extern char Hostname[256];
|
||||||
|
extern int Verbose;
|
||||||
|
|
||||||
|
char *next_arg (char **);
|
||||||
|
|
||||||
|
int sync_mailbox (mailbox_t *, imap_t *, int);
|
||||||
|
|
||||||
|
void imap_close (imap_t *);
|
||||||
|
int imap_fetch_message (imap_t *, unsigned int, int);
|
||||||
|
int imap_set_flags (imap_t *, unsigned int, unsigned int);
|
||||||
|
int imap_expunge (imap_t *);
|
||||||
|
imap_t *imap_open (config_t *, int);
|
||||||
|
|
||||||
|
mailbox_t *maildir_open (const char *, int fast);
|
||||||
|
int maildir_expunge (mailbox_t *, int);
|
||||||
|
int maildir_sync (mailbox_t *);
|
28
isyncrc.sample
Normal file
28
isyncrc.sample
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
# Global configuration section
|
||||||
|
# Values here are used as defaults for any following Mailbox section that
|
||||||
|
# doesn't specify it.
|
||||||
|
|
||||||
|
# my default username, if different from the local username
|
||||||
|
User me
|
||||||
|
#Port 143
|
||||||
|
#Box INBOX
|
||||||
|
|
||||||
|
###
|
||||||
|
### work mailbox
|
||||||
|
###
|
||||||
|
|
||||||
|
Mailbox /home/me/Mail/work
|
||||||
|
Host work.host.com
|
||||||
|
Pass xxxxxxxx
|
||||||
|
# define a shortcut so I can just use "isync work" from the command line
|
||||||
|
Alias work
|
||||||
|
|
||||||
|
###
|
||||||
|
### personal mailbox
|
||||||
|
###
|
||||||
|
|
||||||
|
Mailbox /home/me/Mail/personal
|
||||||
|
Host host.play.com
|
||||||
|
# use a non-default port for this connection
|
||||||
|
Port 6789
|
||||||
|
Alias personal
|
208
maildir.c
Normal file
208
maildir.c
Normal file
|
@ -0,0 +1,208 @@
|
||||||
|
/* 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 <limits.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include "isync.h"
|
||||||
|
|
||||||
|
/* 2,<flags> */
|
||||||
|
static void
|
||||||
|
parse_info (message_t * m, char *s)
|
||||||
|
{
|
||||||
|
if (*s == '2' && *(s + 1) == ',')
|
||||||
|
{
|
||||||
|
s += 2;
|
||||||
|
while (*s)
|
||||||
|
{
|
||||||
|
if (*s == 'F')
|
||||||
|
m->flags |= D_FLAGGED;
|
||||||
|
else if (*s == 'R')
|
||||||
|
m->flags |= D_ANSWERED;
|
||||||
|
else if (*s == 'T')
|
||||||
|
m->flags |= D_DELETED;
|
||||||
|
else if (*s == 'S')
|
||||||
|
m->flags |= D_SEEN;
|
||||||
|
s++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* open a maildir mailbox. if `fast' is nonzero, we just check to make
|
||||||
|
* sure its a valid mailbox and don't actually parse it. any IMAP messages
|
||||||
|
* with the \Recent flag set are guaranteed not to be in the mailbox yet,
|
||||||
|
* so we can save a lot of time when the user just wants to fetch new messages
|
||||||
|
* without syncing the flags.
|
||||||
|
*/
|
||||||
|
mailbox_t *
|
||||||
|
maildir_open (const char *path, int fast)
|
||||||
|
{
|
||||||
|
char buf[_POSIX_PATH_MAX];
|
||||||
|
DIR *d;
|
||||||
|
struct dirent *e;
|
||||||
|
message_t **cur;
|
||||||
|
message_t *p;
|
||||||
|
mailbox_t *m;
|
||||||
|
char *s;
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
/* check to make sure this looks like a valid maildir box */
|
||||||
|
snprintf (buf, sizeof (buf), "%s/new", path);
|
||||||
|
if (access (buf, F_OK))
|
||||||
|
{
|
||||||
|
perror ("access");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
snprintf (buf, sizeof (buf), "%s/cur", path);
|
||||||
|
if (access (buf, F_OK))
|
||||||
|
{
|
||||||
|
perror ("access");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
m = calloc (1, sizeof (mailbox_t));
|
||||||
|
m->path = strdup (path);
|
||||||
|
|
||||||
|
if (fast)
|
||||||
|
return m;
|
||||||
|
|
||||||
|
cur = &m->msgs;
|
||||||
|
for (; count < 2; count++)
|
||||||
|
{
|
||||||
|
/* read the msgs from the new subdir */
|
||||||
|
snprintf (buf, sizeof (buf), "%s/%s", path,
|
||||||
|
(count == 0) ? "new" : "cur");
|
||||||
|
d = opendir (buf);
|
||||||
|
if (!d)
|
||||||
|
{
|
||||||
|
perror ("opendir");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
while ((e = readdir (d)))
|
||||||
|
{
|
||||||
|
if (*e->d_name == '.')
|
||||||
|
continue; /* skip dot-files */
|
||||||
|
*cur = calloc (1, sizeof (message_t));
|
||||||
|
p = *cur;
|
||||||
|
p->file = strdup (e->d_name);
|
||||||
|
p->uid = -1;
|
||||||
|
p->flags = (count == 1) ? D_SEEN : 0;
|
||||||
|
p->new = (count == 0);
|
||||||
|
|
||||||
|
/* filename format is something like:
|
||||||
|
* <unique-prefix>.UID<n>:2,<flags>
|
||||||
|
* This is completely non-standard, but in order for mail
|
||||||
|
* clients to understand the flags, we have to use the
|
||||||
|
* standard :info as described by the qmail spec
|
||||||
|
*/
|
||||||
|
s = strstr (p->file, "UID");
|
||||||
|
if (!s)
|
||||||
|
puts ("warning, no uid for message");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
p->uid = strtol (s + 3, &s, 10);
|
||||||
|
if (*s && *s != ':')
|
||||||
|
{
|
||||||
|
puts ("warning, unable to parse uid");
|
||||||
|
p->uid = -1; /* reset */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s = strchr (p->file, ':');
|
||||||
|
if (s)
|
||||||
|
parse_info (p, s + 1);
|
||||||
|
cur = &p->next;
|
||||||
|
}
|
||||||
|
closedir (d);
|
||||||
|
}
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* permanently remove messages from a maildir mailbox. if `dead' is nonzero,
|
||||||
|
* we only remove the messags marked dead.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
maildir_expunge (mailbox_t * mbox, int dead)
|
||||||
|
{
|
||||||
|
message_t **cur = &mbox->msgs;
|
||||||
|
message_t *tmp;
|
||||||
|
char path[_POSIX_PATH_MAX];
|
||||||
|
|
||||||
|
while (*cur)
|
||||||
|
{
|
||||||
|
if ((dead == 0 && (*cur)->flags & D_DELETED) ||
|
||||||
|
(dead && (*cur)->dead))
|
||||||
|
{
|
||||||
|
tmp = *cur;
|
||||||
|
*cur = (*cur)->next;
|
||||||
|
snprintf (path, sizeof (path), "%s/%s/%s",
|
||||||
|
mbox->path, tmp->new ? "new" : "cur", tmp->file);
|
||||||
|
if (unlink (path))
|
||||||
|
perror ("unlink");
|
||||||
|
free (tmp->file);
|
||||||
|
free (tmp);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
cur = &(*cur)->next;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
maildir_sync (mailbox_t * mbox)
|
||||||
|
{
|
||||||
|
message_t *cur = mbox->msgs;
|
||||||
|
char path[_POSIX_PATH_MAX];
|
||||||
|
char oldpath[_POSIX_PATH_MAX];
|
||||||
|
char *p;
|
||||||
|
|
||||||
|
if (mbox->changed)
|
||||||
|
{
|
||||||
|
for (; cur; cur = cur->next)
|
||||||
|
{
|
||||||
|
if (cur->changed)
|
||||||
|
{
|
||||||
|
/* generate old path */
|
||||||
|
snprintf (oldpath, sizeof (oldpath), "%s/%s/%s",
|
||||||
|
mbox->path, cur->new ? "new" : "cur", cur->file);
|
||||||
|
|
||||||
|
/* truncate old flags (if present) */
|
||||||
|
p = strchr (cur->file, ':');
|
||||||
|
if (p)
|
||||||
|
*p = 0;
|
||||||
|
|
||||||
|
p = strrchr (cur->file, '/');
|
||||||
|
|
||||||
|
/* generate new path */
|
||||||
|
snprintf (path, sizeof (path), "%s/%s%s:2,%s%s%s%s",
|
||||||
|
mbox->path, (cur->flags & D_SEEN) ? "cur" : "new",
|
||||||
|
cur->file, (cur->flags & D_FLAGGED) ? "F" : "",
|
||||||
|
(cur->flags & D_ANSWERED) ? "R" : "",
|
||||||
|
(cur->flags & D_SEEN) ? "S" : "",
|
||||||
|
(cur->flags & D_DELETED) ? "T" : "");
|
||||||
|
|
||||||
|
if (rename (oldpath, path))
|
||||||
|
perror ("rename");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
398
main.c
Normal file
398
main.c
Normal file
|
@ -0,0 +1,398 @@
|
||||||
|
/* 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 <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <pwd.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <termios.h>
|
||||||
|
#include "isync.h"
|
||||||
|
|
||||||
|
#if HAVE_GETOPT_LONG
|
||||||
|
#define _GNU_SOURCE
|
||||||
|
#include <getopt.h>
|
||||||
|
|
||||||
|
struct option Opts[] = {
|
||||||
|
{"config", 1, NULL, 'c'},
|
||||||
|
{"delete", 0, NULL, 'd'},
|
||||||
|
{"expunge", 0, NULL, 'e'},
|
||||||
|
{"fast", 0, NULL, 'f'},
|
||||||
|
{"help", 0, NULL, 'h'},
|
||||||
|
{"remote", 1, NULL, 'r'},
|
||||||
|
{"host", 1, NULL, 's'},
|
||||||
|
{"port", 1, NULL, 'p'},
|
||||||
|
{"user", 1, NULL, 'u'},
|
||||||
|
{"version", 0, NULL, 'v'},
|
||||||
|
{"verbose", 0, NULL, 'V'},
|
||||||
|
{0, 0, 0, 0}
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
config_t global;
|
||||||
|
unsigned int Tag = 0;
|
||||||
|
static config_t *box = 0;
|
||||||
|
char Hostname[256];
|
||||||
|
int Verbose = 0;
|
||||||
|
|
||||||
|
static void
|
||||||
|
version (void)
|
||||||
|
{
|
||||||
|
printf ("%s %s\n", PACKAGE, VERSION);
|
||||||
|
exit (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
usage (void)
|
||||||
|
{
|
||||||
|
printf ("%s %s IMAP4 to maildir synchronizer\n", PACKAGE, VERSION);
|
||||||
|
puts ("Copyright (C) 2000 Michael R. Elkins <me@mutt.org>");
|
||||||
|
printf ("usage: %s [ flags ] mailbox\n", PACKAGE);
|
||||||
|
puts
|
||||||
|
(" -c, --config CONFIG read an alternate config file (default: ~/.isyncrc)");
|
||||||
|
puts
|
||||||
|
(" -d, --delete delete local msgs that don't exist on the server");
|
||||||
|
puts
|
||||||
|
(" -e, --expunge expunge deleted messages from the server");
|
||||||
|
puts (" -f, --fast only fetch new messages");
|
||||||
|
puts (" -h, --help display this help message");
|
||||||
|
puts (" -p, --port PORT server IMAP port");
|
||||||
|
puts (" -r, --remote BOX remote mailbox");
|
||||||
|
puts (" -s, --host HOST IMAP server address");
|
||||||
|
puts (" -u, --user USER IMAP user name");
|
||||||
|
puts (" -v, --version display version");
|
||||||
|
puts
|
||||||
|
(" -V, --verbose verbose mode (display network traffic)");
|
||||||
|
exit (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *
|
||||||
|
enter_password (void)
|
||||||
|
{
|
||||||
|
struct termios t;
|
||||||
|
char pass[32];
|
||||||
|
|
||||||
|
tcgetattr (0, &t);
|
||||||
|
t.c_lflag &= ~ECHO;
|
||||||
|
tcsetattr (0, TCSANOW, &t);
|
||||||
|
printf ("Password: ");
|
||||||
|
fflush (stdout);
|
||||||
|
pass[sizeof (pass) - 1] = 0;
|
||||||
|
fgets (pass, sizeof (pass) - 1, stdin);
|
||||||
|
if (pass[0])
|
||||||
|
pass[strlen (pass) - 1] = 0; /* kill newline */
|
||||||
|
t.c_lflag |= ECHO;
|
||||||
|
tcsetattr (0, TCSANOW, &t);
|
||||||
|
puts ("");
|
||||||
|
return strdup (pass);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
load_config (char *where)
|
||||||
|
{
|
||||||
|
char path[_POSIX_PATH_MAX];
|
||||||
|
char buf[1024];
|
||||||
|
struct passwd *pw;
|
||||||
|
config_t **cur = &box;
|
||||||
|
char *p;
|
||||||
|
int line = 0;
|
||||||
|
FILE *fp;
|
||||||
|
|
||||||
|
if (!where)
|
||||||
|
{
|
||||||
|
pw = getpwuid (getuid ());
|
||||||
|
snprintf (path, sizeof (path), "%s/.isyncrc", pw->pw_dir);
|
||||||
|
where = path;
|
||||||
|
}
|
||||||
|
printf ("Reading %s\n", where);
|
||||||
|
|
||||||
|
fp = fopen (where, "r");
|
||||||
|
if (!fp)
|
||||||
|
{
|
||||||
|
if (errno != ENOENT)
|
||||||
|
{
|
||||||
|
perror ("fopen");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while ((fgets (buf, sizeof (buf) - 1, fp)))
|
||||||
|
{
|
||||||
|
if (buf[0])
|
||||||
|
buf[strlen (buf) - 1] = 0;
|
||||||
|
line++;
|
||||||
|
if (buf[0] == '#')
|
||||||
|
continue;
|
||||||
|
p = buf;
|
||||||
|
while (*p && !isspace (*p))
|
||||||
|
p++;
|
||||||
|
while (isspace (*p))
|
||||||
|
p++;
|
||||||
|
if (!strncmp ("mailbox", buf, 7))
|
||||||
|
{
|
||||||
|
if (*cur)
|
||||||
|
cur = &(*cur)->next;
|
||||||
|
*cur = calloc (1, sizeof (config_t));
|
||||||
|
(*cur)->path = strdup (p);
|
||||||
|
}
|
||||||
|
else if (!strncmp ("host", buf, 4))
|
||||||
|
{
|
||||||
|
if (*cur)
|
||||||
|
(*cur)->host = strdup (p);
|
||||||
|
else
|
||||||
|
global.host = strdup (p);
|
||||||
|
}
|
||||||
|
else if (!strncmp ("user", buf, 4))
|
||||||
|
{
|
||||||
|
if (*cur)
|
||||||
|
(*cur)->user = strdup (p);
|
||||||
|
else
|
||||||
|
global.user = strdup (p);
|
||||||
|
}
|
||||||
|
else if (!strncmp ("pass", buf, 4))
|
||||||
|
{
|
||||||
|
if (*cur)
|
||||||
|
(*cur)->pass = strdup (p);
|
||||||
|
else
|
||||||
|
global.pass = strdup (p);
|
||||||
|
}
|
||||||
|
else if (!strncmp ("port", buf, 4))
|
||||||
|
{
|
||||||
|
if (*cur)
|
||||||
|
(*cur)->port = atoi (p);
|
||||||
|
else
|
||||||
|
global.port = atoi (p);
|
||||||
|
}
|
||||||
|
else if (!strncmp ("box", buf, 3))
|
||||||
|
{
|
||||||
|
if (*cur)
|
||||||
|
(*cur)->box = strdup (p);
|
||||||
|
else
|
||||||
|
global.box = strdup (p);
|
||||||
|
}
|
||||||
|
else if (!strncmp ("alias", buf, 5))
|
||||||
|
{
|
||||||
|
if (*cur)
|
||||||
|
(*cur)->alias = strdup (p);
|
||||||
|
}
|
||||||
|
else if (buf[0])
|
||||||
|
printf ("%s:%d:unknown command:%s", path, line, buf);
|
||||||
|
}
|
||||||
|
fclose (fp);
|
||||||
|
}
|
||||||
|
|
||||||
|
static config_t *
|
||||||
|
find_box (const char *s)
|
||||||
|
{
|
||||||
|
config_t *p = box;
|
||||||
|
|
||||||
|
for (; p; p = p->next)
|
||||||
|
if (!strcmp (s, p->path) || (p->alias && !strcmp (s, p->alias)))
|
||||||
|
return p;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *
|
||||||
|
next_arg (char **s)
|
||||||
|
{
|
||||||
|
char *ret;
|
||||||
|
|
||||||
|
if (!s)
|
||||||
|
return 0;
|
||||||
|
if (!*s)
|
||||||
|
return 0;
|
||||||
|
while (isspace (**s))
|
||||||
|
(*s)++;
|
||||||
|
if (!**s)
|
||||||
|
{
|
||||||
|
*s = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
ret = *s;
|
||||||
|
while (**s && !isspace (**s))
|
||||||
|
(*s)++;
|
||||||
|
if (**s)
|
||||||
|
*(*s)++ = 0;
|
||||||
|
if (!**s)
|
||||||
|
*s = 0;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main (int argc, char **argv)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
config_t *box;
|
||||||
|
mailbox_t *mail;
|
||||||
|
imap_t *imap;
|
||||||
|
int expunge = 0; /* by default, don't delete anything */
|
||||||
|
int fast = 0;
|
||||||
|
int delete = 0;
|
||||||
|
char *config = 0;
|
||||||
|
struct passwd *pw;
|
||||||
|
|
||||||
|
pw = getpwuid (getuid ());
|
||||||
|
|
||||||
|
/* defaults */
|
||||||
|
memset (&global, 0, sizeof (global));
|
||||||
|
global.port = 143;
|
||||||
|
global.box = "INBOX";
|
||||||
|
global.user = strdup (pw->pw_name);
|
||||||
|
|
||||||
|
#if HAVE_GETOPT_LONG
|
||||||
|
while ((i = getopt_long (argc, argv, "defhp:u:r:s:vV", Opts, NULL)) != -1)
|
||||||
|
#else
|
||||||
|
while ((i = getopt (argc, argv, "defhp:u:r:s:vV")) != -1)
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
switch (i)
|
||||||
|
{
|
||||||
|
case 'c':
|
||||||
|
config = optarg;
|
||||||
|
break;
|
||||||
|
case 'd':
|
||||||
|
delete = 1;
|
||||||
|
break;
|
||||||
|
case 'e':
|
||||||
|
expunge = 1;
|
||||||
|
break;
|
||||||
|
case 'f':
|
||||||
|
fast = 1;
|
||||||
|
break;
|
||||||
|
case 'p':
|
||||||
|
global.port = atoi (optarg);
|
||||||
|
break;
|
||||||
|
case 'r':
|
||||||
|
global.box = optarg;
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
global.host = optarg;
|
||||||
|
break;
|
||||||
|
case 'u':
|
||||||
|
free (global.user);
|
||||||
|
global.user = optarg;
|
||||||
|
break;
|
||||||
|
case 'V':
|
||||||
|
Verbose = 1;
|
||||||
|
break;
|
||||||
|
case 'v':
|
||||||
|
version ();
|
||||||
|
default:
|
||||||
|
usage ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!argv[optind])
|
||||||
|
{
|
||||||
|
puts ("No box specified");
|
||||||
|
usage ();
|
||||||
|
}
|
||||||
|
|
||||||
|
gethostname (Hostname, sizeof (Hostname));
|
||||||
|
|
||||||
|
load_config (config);
|
||||||
|
|
||||||
|
box = find_box (argv[optind]);
|
||||||
|
if (!box)
|
||||||
|
{
|
||||||
|
/* if enough info is given on the command line, don't worry if
|
||||||
|
* the mailbox isn't defined.
|
||||||
|
*/
|
||||||
|
if (!global.host)
|
||||||
|
{
|
||||||
|
puts ("No such mailbox");
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
global.path = argv[optind];
|
||||||
|
box = &global;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* fill in missing info with defaults */
|
||||||
|
if (!box->pass)
|
||||||
|
{
|
||||||
|
if (!global.pass)
|
||||||
|
{
|
||||||
|
box->pass = enter_password ();
|
||||||
|
if (!box->pass)
|
||||||
|
{
|
||||||
|
puts ("Aborting, no password");
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
box->pass = global.pass;
|
||||||
|
}
|
||||||
|
if (!box->user)
|
||||||
|
box->user = global.user;
|
||||||
|
if (!box->port)
|
||||||
|
box->port = global.port;
|
||||||
|
if (!box->host)
|
||||||
|
box->host = global.host;
|
||||||
|
if (!box->box)
|
||||||
|
box->box = global.box;
|
||||||
|
|
||||||
|
printf ("Reading %s\n", box->path);
|
||||||
|
mail = maildir_open (box->path, fast);
|
||||||
|
if (!mail)
|
||||||
|
{
|
||||||
|
puts ("Unable to load mailbox");
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
imap = imap_open (box, fast);
|
||||||
|
if (!imap)
|
||||||
|
exit (1);
|
||||||
|
|
||||||
|
puts ("Synchronizing");
|
||||||
|
i = 0;
|
||||||
|
i |= (fast) ? SYNC_FAST : 0;
|
||||||
|
i |= (delete) ? SYNC_DELETE : 0;
|
||||||
|
if (sync_mailbox (mail, imap, i))
|
||||||
|
exit (1);
|
||||||
|
|
||||||
|
if (!fast)
|
||||||
|
{
|
||||||
|
if (expunge)
|
||||||
|
{
|
||||||
|
/* remove messages marked for deletion */
|
||||||
|
puts ("Expunging messages");
|
||||||
|
if (imap_expunge (imap))
|
||||||
|
exit (1);
|
||||||
|
if (maildir_expunge (mail, 0))
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
/* remove messages deleted from server. this can safely be an
|
||||||
|
* `else' clause since dead messages are marked as deleted by
|
||||||
|
* sync_mailbox.
|
||||||
|
*/
|
||||||
|
else if (delete)
|
||||||
|
maildir_expunge (mail, 1);
|
||||||
|
|
||||||
|
/* write changed flags back to the mailbox */
|
||||||
|
printf ("Committing changes to %s\n", mail->path);
|
||||||
|
if (maildir_sync (mail))
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* gracefully close connection to the IMAP server */
|
||||||
|
imap_close (imap);
|
||||||
|
|
||||||
|
exit (0);
|
||||||
|
}
|
134
sync.c
Normal file
134
sync.c
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
/* 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 <stdio.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "isync.h"
|
||||||
|
|
||||||
|
static unsigned int MaildirCount = 0;
|
||||||
|
|
||||||
|
static message_t *
|
||||||
|
find_msg (message_t * list, unsigned int uid)
|
||||||
|
{
|
||||||
|
for (; list; list = list->next)
|
||||||
|
if (list->uid == uid)
|
||||||
|
return list;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
sync_mailbox (mailbox_t * mbox, imap_t * imap, int flags)
|
||||||
|
{
|
||||||
|
message_t *cur;
|
||||||
|
message_t *tmp;
|
||||||
|
char path[_POSIX_PATH_MAX];
|
||||||
|
char newpath[_POSIX_PATH_MAX];
|
||||||
|
char *p;
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
for (cur = mbox->msgs; cur; cur = cur->next)
|
||||||
|
{
|
||||||
|
tmp = find_msg (imap->msgs, cur->uid);
|
||||||
|
if (!tmp)
|
||||||
|
{
|
||||||
|
printf ("warning, uid %d doesn't exist on server\n", cur->uid);
|
||||||
|
if (flags & SYNC_DELETE)
|
||||||
|
{
|
||||||
|
cur->flags |= D_DELETED;
|
||||||
|
cur->dead = 1;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
tmp->processed = 1;
|
||||||
|
|
||||||
|
if (!(flags & SYNC_FAST))
|
||||||
|
{
|
||||||
|
/* check if local flags are different from server flags.
|
||||||
|
* ignore \Recent and \Draft
|
||||||
|
*/
|
||||||
|
if (cur->flags != (tmp->flags & ~(D_RECENT | D_DRAFT)))
|
||||||
|
{
|
||||||
|
/* set local flags that don't exist on the server */
|
||||||
|
imap_set_flags (imap, cur->uid, cur->flags & ~tmp->flags);
|
||||||
|
|
||||||
|
/* update local flags */
|
||||||
|
cur->flags |= (tmp->flags & ~(D_RECENT | D_DRAFT));
|
||||||
|
cur->changed = 1;
|
||||||
|
mbox->changed = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fputs ("Fetching new messages", stdout);
|
||||||
|
fflush (stdout);
|
||||||
|
for (cur = imap->msgs; cur; cur = cur->next)
|
||||||
|
{
|
||||||
|
if (!cur->processed)
|
||||||
|
{
|
||||||
|
/* new message on server */
|
||||||
|
fputs (".", stdout);
|
||||||
|
fflush (stdout);
|
||||||
|
|
||||||
|
/* create new file */
|
||||||
|
snprintf (path, sizeof (path), "%s/tmp/%s.%ld_%d.%d.UID%d",
|
||||||
|
mbox->path, Hostname, time (0), MaildirCount++,
|
||||||
|
getpid (), cur->uid);
|
||||||
|
|
||||||
|
if (cur->flags)
|
||||||
|
{
|
||||||
|
/* append flags */
|
||||||
|
snprintf (path + strlen (path), sizeof (path) - strlen (path),
|
||||||
|
":2,%s%s%s%s",
|
||||||
|
(cur->flags & D_FLAGGED) ? "F" : "",
|
||||||
|
(cur->flags & D_ANSWERED) ? "R" : "",
|
||||||
|
(cur->flags & D_SEEN) ? "S" : "",
|
||||||
|
(cur->flags & D_DELETED) ? "T" : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
// printf("creating %s\n", path);
|
||||||
|
fd = open (path, O_WRONLY | O_CREAT | O_EXCL, 0600);
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
perror ("open");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
imap_fetch_message (imap, cur->uid, fd);
|
||||||
|
|
||||||
|
close (fd);
|
||||||
|
|
||||||
|
p = strrchr (path, '/');
|
||||||
|
|
||||||
|
snprintf (newpath, sizeof (newpath), "%s/%s%s", mbox->path,
|
||||||
|
(cur->flags & D_SEEN) ? "cur" : "new", p);
|
||||||
|
|
||||||
|
// printf ("moving %s to %s\n", path, newpath);
|
||||||
|
|
||||||
|
if (rename (path, newpath))
|
||||||
|
perror ("rename");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
puts ("");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user