mirror of https://git.code.sf.net/p/isync/isync
Michael Elkins
24 years ago
commit
f47d0d7c11
14 changed files with 1640 additions and 0 deletions
@ -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)
|
@ -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. |
@ -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) |
@ -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"); |
||||||
|
} |
@ -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. |
@ -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 *); |
@ -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 |
@ -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; |
||||||
|
} |
@ -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); |
||||||
|
} |
@ -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