7cd74a1179
i implemented some cool stuff (tm). first, the long missing "create server-side missing mailboxes". -C now creates both local and remote boxes; -L and -R create only local/remote. second, i implemented a 1:1 remote:local folder mapping (-1) with an optional INBOX exception (inbox/-I). the remote folder is specified with the folder keyword (or -F switch) and takes precedence over the namespace setting. the local directory with the mailboxes can now be specified on the command line, too (-M). another patch: - made the -1 switch settable permanently (OneToOne). after all, you usually define your mailbox layout once forever. removed -A, as it is semantically -a modified by -1. - cleaned up message output a bit. still, the quiet variable should be used throughout the program. at best, create some generic output function, which obeys a global verbosity level variable. - optimized + cleaned up configuration parser slightly - minor cleanups add an (almost) unique id to every uploaded message and search for it right after. i thought about using the message-id, but a) it is not guaranteed to be unique in a mailbox (imagine you edit a mail and store the dupe in the same box) and b) some mails (e.g., postponed) don't even have one. a downside of the current implementation is, that this id-header remains in the mailbox, but given that it wastes only 27 bytes per mail and removing it would mean several roundtrips more, this seems acceptable. i changed the line-counting loop to use a mmapped file instead of reading it in chunks, as it makes things simpler and is probably even faster for big mails. the amount of goto statements in my code may be scary, but c is simply lacking a multi-level break statement. :) this is the "shut up" patch. :) it makes the -q option consequent, so to say. additionally it adds an -l option which gathers all defined/found mailboxes and just outputs the list. don't ask what i need it for. ;)
1370 lines
28 KiB
C
1370 lines
28 KiB
C
/* $Id$
|
|
*
|
|
* isync - IMAP4 to maildir mailbox synchronizer
|
|
* Copyright (C) 2000-2 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 <sys/mman.h>
|
|
#include <sys/time.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>
|
|
#if HAVE_LIBSSL
|
|
#include <openssl/err.h>
|
|
#endif
|
|
#include "isync.h"
|
|
|
|
const char *Flags[] = {
|
|
"\\Seen",
|
|
"\\Answered",
|
|
"\\Deleted",
|
|
"\\Flagged",
|
|
"\\Recent",
|
|
"\\Draft"
|
|
};
|
|
|
|
void
|
|
free_message (message_t * msg)
|
|
{
|
|
message_t *tmp;
|
|
|
|
while (msg)
|
|
{
|
|
tmp = msg;
|
|
msg = msg->next;
|
|
if (tmp->file)
|
|
free (tmp->file);
|
|
free (tmp);
|
|
}
|
|
}
|
|
|
|
#if HAVE_LIBSSL
|
|
|
|
#define MAX_DEPTH 1
|
|
|
|
SSL_CTX *SSLContext = 0;
|
|
|
|
/* this gets called when a certificate is to be verified */
|
|
static int
|
|
verify_cert (SSL * ssl)
|
|
{
|
|
X509 *cert;
|
|
int err;
|
|
char buf[256];
|
|
int ret = -1;
|
|
BIO *bio;
|
|
|
|
cert = SSL_get_peer_certificate (ssl);
|
|
if (!cert)
|
|
{
|
|
fprintf (stderr, "Error, no server certificate\n");
|
|
return -1;
|
|
}
|
|
|
|
err = SSL_get_verify_result (ssl);
|
|
if (err == X509_V_OK)
|
|
return 0;
|
|
|
|
fprintf (stderr, "Error, can't verify certificate: %s (%d)\n",
|
|
X509_verify_cert_error_string (err), err);
|
|
|
|
X509_NAME_oneline (X509_get_subject_name (cert), buf, sizeof (buf));
|
|
info ("\nSubject: %s\n", buf);
|
|
X509_NAME_oneline (X509_get_issuer_name (cert), buf, sizeof (buf));
|
|
info ("Issuer: %s\n", buf);
|
|
bio = BIO_new (BIO_s_mem ());
|
|
ASN1_TIME_print (bio, X509_get_notBefore (cert));
|
|
memset (buf, 0, sizeof (buf));
|
|
BIO_read (bio, buf, sizeof (buf) - 1);
|
|
info ("Valid from: %s\n", buf);
|
|
ASN1_TIME_print (bio, X509_get_notAfter (cert));
|
|
memset (buf, 0, sizeof (buf));
|
|
BIO_read (bio, buf, sizeof (buf) - 1);
|
|
BIO_free (bio);
|
|
info (" to: %s\n", buf);
|
|
|
|
fprintf (stderr,
|
|
"\n*** WARNING *** There is no way to verify this certificate. It is\n"
|
|
" possible that a hostile attacker has replaced the\n"
|
|
" server certificate. Continue at your own risk!\n"
|
|
"\nAccept this certificate anyway? [no]: ");
|
|
if (fgets (buf, sizeof (buf), stdin) && (buf[0] == 'y' || buf[0] == 'Y'))
|
|
{
|
|
ret = 0;
|
|
fprintf (stderr, "\n*** Fine, but don't say I didn't warn you!\n\n");
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
init_ssl (config_t * conf)
|
|
{
|
|
SSL_METHOD *method;
|
|
int options = 0;
|
|
|
|
if (!conf->cert_file)
|
|
{
|
|
fprintf (stderr, "Error, CertificateFile not defined\n");
|
|
return -1;
|
|
}
|
|
SSL_library_init ();
|
|
SSL_load_error_strings ();
|
|
|
|
if (conf->use_tlsv1 && !conf->use_sslv2 && !conf->use_sslv3)
|
|
method = TLSv1_client_method ();
|
|
else
|
|
method = SSLv23_client_method ();
|
|
|
|
SSLContext = SSL_CTX_new (method);
|
|
|
|
if (access (conf->cert_file, F_OK))
|
|
{
|
|
if (errno != ENOENT)
|
|
{
|
|
perror ("access");
|
|
return -1;
|
|
}
|
|
fprintf (stderr,
|
|
"*** Warning, CertificateFile doesn't exist, can't verify server certificates\n");
|
|
}
|
|
else
|
|
if (!SSL_CTX_load_verify_locations
|
|
(SSLContext, conf->cert_file, NULL))
|
|
{
|
|
fprintf (stderr, "Error, SSL_CTX_load_verify_locations: %s\n",
|
|
ERR_error_string (ERR_get_error (), 0));
|
|
return -1;
|
|
}
|
|
|
|
if (!conf->use_sslv2)
|
|
options |= SSL_OP_NO_SSLv2;
|
|
if (!conf->use_sslv3)
|
|
options |= SSL_OP_NO_SSLv3;
|
|
if (!conf->use_tlsv1)
|
|
options |= SSL_OP_NO_TLSv1;
|
|
|
|
SSL_CTX_set_options (SSLContext, options);
|
|
|
|
/* we check the result of the verification after SSL_connect() */
|
|
SSL_CTX_set_verify (SSLContext, SSL_VERIFY_NONE, 0);
|
|
return 0;
|
|
}
|
|
#endif /* HAVE_LIBSSL */
|
|
|
|
static int
|
|
socket_read (Socket_t * sock, char *buf, size_t len)
|
|
{
|
|
#if HAVE_LIBSSL
|
|
if (sock->use_ssl)
|
|
return SSL_read (sock->ssl, buf, len);
|
|
#endif
|
|
return read (sock->fd, buf, len);
|
|
}
|
|
|
|
static int
|
|
socket_write (Socket_t * sock, char *buf, size_t len)
|
|
{
|
|
#if HAVE_LIBSSL
|
|
if (sock->use_ssl)
|
|
return SSL_write (sock->ssl, buf, len);
|
|
#endif
|
|
return write (sock->fd, buf, len);
|
|
}
|
|
|
|
static void
|
|
socket_perror (const char *func, Socket_t *sock, int ret)
|
|
{
|
|
#if HAVE_LIBSSL
|
|
int err;
|
|
|
|
if (sock->use_ssl)
|
|
{
|
|
switch ((err = SSL_get_error (sock->ssl, ret)))
|
|
{
|
|
case SSL_ERROR_SYSCALL:
|
|
case SSL_ERROR_SSL:
|
|
if ((err = ERR_get_error ()) == 0)
|
|
{
|
|
if (ret == 0)
|
|
fprintf (stderr, "SSL_%s:got EOF\n", func);
|
|
else
|
|
fprintf (stderr, "SSL_%s:%d:%s\n", func,
|
|
errno, strerror (errno));
|
|
}
|
|
else
|
|
fprintf (stderr, "SSL_%s:%d:%s\n", func, err,
|
|
ERR_error_string (err, 0));
|
|
return;
|
|
default:
|
|
fprintf (stderr, "SSL_%s:%d:unhandled SSL error\n", func, err);
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
#else
|
|
(void) sock;
|
|
#endif
|
|
if (ret)
|
|
perror (func);
|
|
else
|
|
fprintf (stderr, "%s: unexpected EOF\n", func);
|
|
}
|
|
|
|
/* simple line buffering */
|
|
static int
|
|
buffer_gets (buffer_t * b, char **s)
|
|
{
|
|
int n;
|
|
int start = b->offset;
|
|
|
|
*s = b->buf + start;
|
|
|
|
for (;;)
|
|
{
|
|
/* make sure we have enough data to read the \r\n sequence */
|
|
if (b->offset + 1 >= b->bytes)
|
|
{
|
|
if (start != 0)
|
|
{
|
|
/* 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 -= start;
|
|
b->bytes = n;
|
|
start = 0;
|
|
}
|
|
|
|
n =
|
|
socket_read (b->sock, b->buf + b->bytes,
|
|
sizeof (b->buf) - b->bytes);
|
|
|
|
if (n <= 0)
|
|
{
|
|
socket_perror ("read", b->sock, n);
|
|
return -1;
|
|
}
|
|
|
|
b->bytes += n;
|
|
}
|
|
|
|
if (b->buf[b->offset] == '\r')
|
|
{
|
|
assert (b->offset + 1 < b->bytes);
|
|
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
|
|
parse_fetch (imap_t * imap, list_t * list)
|
|
{
|
|
list_t *tmp;
|
|
unsigned int uid = 0;
|
|
unsigned int mask = 0;
|
|
unsigned int size = 0;
|
|
message_t *cur;
|
|
|
|
if (!is_list (list))
|
|
return -1;
|
|
|
|
for (tmp = list->child; tmp; tmp = tmp->next)
|
|
{
|
|
if (is_atom (tmp))
|
|
{
|
|
if (!strcmp ("UID", tmp->val))
|
|
{
|
|
tmp = tmp->next;
|
|
if (is_atom (tmp))
|
|
{
|
|
uid = atoi (tmp->val);
|
|
if (uid < imap->minuid)
|
|
{
|
|
/* already saw this message */
|
|
return 0;
|
|
}
|
|
else if (uid > imap->maxuid)
|
|
imap->maxuid = uid;
|
|
}
|
|
else
|
|
fprintf (stderr, "IMAP error: unable to parse UID\n");
|
|
}
|
|
else if (!strcmp ("FLAGS", tmp->val))
|
|
{
|
|
tmp = tmp->next;
|
|
if (is_list (tmp))
|
|
{
|
|
list_t *flags = tmp->child;
|
|
|
|
for (; flags; flags = flags->next)
|
|
{
|
|
if (is_atom (flags))
|
|
{
|
|
if (!strcmp ("\\Seen", flags->val))
|
|
mask |= D_SEEN;
|
|
else if (!strcmp ("\\Flagged", flags->val))
|
|
mask |= D_FLAGGED;
|
|
else if (!strcmp ("\\Deleted", flags->val))
|
|
mask |= D_DELETED;
|
|
else if (!strcmp ("\\Answered", flags->val))
|
|
mask |= D_ANSWERED;
|
|
else if (!strcmp ("\\Draft", flags->val))
|
|
mask |= D_DRAFT;
|
|
else if (!strcmp ("\\Recent", flags->val))
|
|
mask |= D_RECENT;
|
|
else
|
|
fprintf (stderr, "IMAP error: unknown flag %s\n",
|
|
flags->val);
|
|
}
|
|
else
|
|
fprintf (stderr, "IMAP error: unable to parse FLAGS list\n");
|
|
}
|
|
}
|
|
else
|
|
fprintf (stderr, "IMAP error: unable to parse FLAGS\n");
|
|
}
|
|
else if (!strcmp ("RFC822.SIZE", tmp->val))
|
|
{
|
|
tmp = tmp->next;
|
|
if (is_atom (tmp))
|
|
size = atol (tmp->val);
|
|
}
|
|
}
|
|
}
|
|
|
|
cur = calloc (1, sizeof (message_t));
|
|
cur->next = imap->msgs;
|
|
imap->msgs = cur;
|
|
|
|
if (mask & D_DELETED)
|
|
imap->deleted++;
|
|
|
|
cur->uid = uid;
|
|
cur->flags = mask;
|
|
cur->size = size;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
parse_response_code (imap_t * imap, char *s)
|
|
{
|
|
char *arg;
|
|
|
|
if (*s != '[')
|
|
return; /* no response code */
|
|
s++;
|
|
|
|
arg = next_arg (&s);
|
|
|
|
if (!strcmp ("UIDVALIDITY", arg))
|
|
{
|
|
arg = next_arg (&s);
|
|
imap->uidvalidity = atol (arg);
|
|
}
|
|
else if (!strcmp ("ALERT", arg))
|
|
{
|
|
/* RFC2060 says that these messages MUST be displayed
|
|
* to the user
|
|
*/
|
|
fprintf (stderr, "*** IMAP ALERT *** %s\n", s);
|
|
}
|
|
}
|
|
|
|
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;
|
|
config_t *box;
|
|
int n;
|
|
|
|
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)
|
|
printf (">>> %s", buf);
|
|
n = socket_write (imap->sock, buf, strlen (buf));
|
|
if (n <= 0)
|
|
{
|
|
socket_perror ("write", imap->sock, n);
|
|
return -1;
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
next:
|
|
if (buffer_gets (imap->buf, &cmd))
|
|
return -1;
|
|
if (Verbose)
|
|
puts (cmd);
|
|
|
|
arg = next_arg (&cmd);
|
|
if (*arg == '*')
|
|
{
|
|
arg = next_arg (&cmd);
|
|
if (!arg)
|
|
{
|
|
fprintf (stderr, "IMAP error: unable to parse untagged response\n");
|
|
return -1;
|
|
}
|
|
|
|
if (!strcmp ("NAMESPACE", arg))
|
|
{
|
|
imap->ns_personal = parse_list (cmd, &cmd);
|
|
imap->ns_other = parse_list (cmd, &cmd);
|
|
imap->ns_shared = parse_list (cmd, 0);
|
|
}
|
|
else if (!strcmp ("OK", arg) || !strcmp ("BAD", arg) ||
|
|
!strcmp ("NO", arg) || !strcmp ("BYE", arg) ||
|
|
!strcmp ("PREAUTH", arg))
|
|
{
|
|
parse_response_code (imap, cmd);
|
|
}
|
|
else if (!strcmp ("CAPABILITY", arg))
|
|
{
|
|
while ((arg = next_arg (&cmd)))
|
|
{
|
|
if (!strcmp ("UIDPLUS", arg))
|
|
imap->have_uidplus = 1;
|
|
#if HAVE_LIBSSL
|
|
else if (!strcmp ("STARTTLS", arg))
|
|
imap->have_starttls = 1;
|
|
else if (!strcmp ("AUTH=CRAM-MD5", arg))
|
|
imap->have_cram = 1;
|
|
else if (!strcmp ("NAMESPACE", arg))
|
|
imap->have_namespace = 1;
|
|
#endif
|
|
}
|
|
}
|
|
else if (!strcmp ("LIST", arg))
|
|
{
|
|
list_t *list, *lp;
|
|
int l;
|
|
|
|
list = parse_list (cmd, &cmd);
|
|
if (list->val == LIST)
|
|
for (lp = list->child; lp; lp = lp->next)
|
|
if (is_atom (lp) &&
|
|
!strcasecmp (lp->val, "\\NoSelect"))
|
|
{
|
|
free_list (list);
|
|
goto next;
|
|
}
|
|
free_list (list);
|
|
(void) next_arg (&cmd); /* skip delimiter */
|
|
arg = next_arg (&cmd);
|
|
l = strlen (global.folder);
|
|
if (memcmp (arg, global.folder, l))
|
|
goto next;
|
|
arg += l;
|
|
for (box = boxes; box; box = box->next)
|
|
if (!strcmp (box->box, arg))
|
|
goto next;
|
|
box = malloc (sizeof (config_t));
|
|
memcpy (box, &global, sizeof (config_t));
|
|
box->path = strdup (arg);
|
|
box->box = box->path;
|
|
box->next = boxes;
|
|
boxes = box;
|
|
}
|
|
else if ((arg1 = next_arg (&cmd)))
|
|
{
|
|
if (!strcmp ("EXISTS", arg1))
|
|
imap->count = atoi (arg);
|
|
else if (!strcmp ("RECENT", arg1))
|
|
imap->recent = atoi (arg);
|
|
else if (!strcmp ("FETCH", arg1))
|
|
{
|
|
list_t *list;
|
|
|
|
list = parse_list (cmd, 0);
|
|
|
|
if (parse_fetch (imap, list))
|
|
{
|
|
free_list (list);
|
|
return -1;
|
|
}
|
|
|
|
free_list (list);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fprintf (stderr, "IMAP error: unable to parse untagged response\n");
|
|
return -1;
|
|
}
|
|
}
|
|
#if HAVE_LIBSSL
|
|
else if (*arg == '+')
|
|
{
|
|
char *resp;
|
|
|
|
if (!imap->cram)
|
|
{
|
|
fprintf (stderr, "IMAP error, not doing CRAM-MD5 authentication\n");
|
|
return -1;
|
|
}
|
|
resp = cram (cmd, imap->box->user, imap->box->pass);
|
|
|
|
n = socket_write (imap->sock, resp, strlen (resp));
|
|
if (n <= 0)
|
|
{
|
|
socket_perror ("write", imap->sock, n);
|
|
return -1;
|
|
}
|
|
if (Verbose)
|
|
puts (resp);
|
|
n = socket_write (imap->sock, "\r\n", 2);
|
|
if (n <= 0)
|
|
{
|
|
socket_perror ("write", imap->sock, n);
|
|
return -1;
|
|
}
|
|
free (resp);
|
|
imap->cram = 0;
|
|
}
|
|
#endif
|
|
else if ((size_t) atol (arg) != Tag)
|
|
{
|
|
fprintf (stderr, "IMAP error: wrong tag\n");
|
|
return -1;
|
|
}
|
|
else
|
|
{
|
|
arg = next_arg (&cmd);
|
|
parse_response_code (imap, cmd);
|
|
if (!strcmp ("OK", arg))
|
|
return 0;
|
|
return -1;
|
|
}
|
|
}
|
|
/* not reached */
|
|
}
|
|
|
|
imap_t *
|
|
imap_connect (config_t * cfg)
|
|
{
|
|
int s, ret;
|
|
struct sockaddr_in addr;
|
|
struct hostent *he;
|
|
imap_t *imap;
|
|
char *arg, *rsp;
|
|
int preauth = 0;
|
|
#if HAVE_LIBSSL
|
|
int use_ssl = 0;
|
|
#endif
|
|
int a[2];
|
|
|
|
imap = calloc (1, sizeof (imap_t));
|
|
imap->box = cfg;
|
|
imap->sock = calloc (1, sizeof (Socket_t));
|
|
imap->buf = calloc (1, sizeof (buffer_t));
|
|
imap->buf->sock = imap->sock;
|
|
imap->sock->fd = -1;
|
|
|
|
/* open connection to IMAP server */
|
|
|
|
if (cfg->tunnel)
|
|
{
|
|
info ("Starting tunnel '%s'...", cfg->tunnel);
|
|
fflush (stdout);
|
|
|
|
if (socketpair (PF_UNIX, SOCK_STREAM, 0, a))
|
|
{
|
|
perror ("socketpair");
|
|
exit (1);
|
|
}
|
|
|
|
if (fork () == 0)
|
|
{
|
|
if (dup2 (a[0], 0) == -1 || dup2 (a[0], 1) == -1)
|
|
{
|
|
_exit (127);
|
|
}
|
|
close (a[0]);
|
|
close (a[1]);
|
|
execl ("/bin/sh", "sh", "-c", cfg->tunnel, 0);
|
|
_exit (127);
|
|
}
|
|
|
|
close (a[0]);
|
|
|
|
imap->sock->fd = a[1];
|
|
|
|
info ("ok\n");
|
|
}
|
|
else
|
|
{
|
|
memset (&addr, 0, sizeof (addr));
|
|
addr.sin_port = htons (cfg->port);
|
|
addr.sin_family = AF_INET;
|
|
|
|
info ("Resolving %s... ", cfg->host);
|
|
fflush (stdout);
|
|
he = gethostbyname (cfg->host);
|
|
if (!he)
|
|
{
|
|
perror ("gethostbyname");
|
|
goto bail;
|
|
}
|
|
info ("ok\n");
|
|
|
|
addr.sin_addr.s_addr = *((int *) he->h_addr_list[0]);
|
|
|
|
s = socket (PF_INET, SOCK_STREAM, 0);
|
|
|
|
info ("Connecting to %s:%hu... ", inet_ntoa (addr.sin_addr),
|
|
ntohs (addr.sin_port));
|
|
fflush (stdout);
|
|
if (connect (s, (struct sockaddr *) &addr, sizeof (addr)))
|
|
{
|
|
close (s);
|
|
perror ("connect");
|
|
goto bail;
|
|
}
|
|
info ("ok\n");
|
|
|
|
imap->sock->fd = s;
|
|
}
|
|
|
|
/* read the greeting string */
|
|
if (buffer_gets (imap->buf, &rsp))
|
|
{
|
|
fprintf (stderr, "IMAP error: no greeting response\n");
|
|
goto bail;
|
|
}
|
|
if (Verbose)
|
|
puts (rsp);
|
|
arg = next_arg (&rsp);
|
|
if (!arg || *arg != '*' || (arg = next_arg (&rsp)) == NULL)
|
|
{
|
|
fprintf (stderr, "IMAP error: invalid greeting response\n");
|
|
goto bail;
|
|
}
|
|
if (!strcmp ("PREAUTH", arg))
|
|
preauth = 1;
|
|
else if (strcmp ("OK", arg) != 0)
|
|
{
|
|
fprintf (stderr, "IMAP error: unknown greeting response\n");
|
|
goto bail;
|
|
}
|
|
|
|
#if HAVE_LIBSSL
|
|
if (cfg->use_imaps)
|
|
use_ssl = 1;
|
|
else
|
|
{
|
|
/* let's see what this puppy can do... */
|
|
if (imap_exec (imap, "CAPABILITY"))
|
|
goto bail;
|
|
|
|
if (cfg->use_sslv2 || cfg->use_sslv3 || cfg->use_tlsv1)
|
|
{
|
|
/* always try to select SSL support if available */
|
|
if (imap->have_starttls)
|
|
{
|
|
if (imap_exec (imap, "STARTTLS"))
|
|
goto bail;
|
|
use_ssl = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!use_ssl)
|
|
{
|
|
if (cfg->require_ssl)
|
|
{
|
|
fprintf (stderr, "IMAP error: SSL support not available\n");
|
|
goto bail;
|
|
}
|
|
else if (cfg->use_sslv2 || cfg->use_sslv3 || cfg->use_tlsv1)
|
|
fprintf (stderr, "IMAP warning: SSL support not available\n");
|
|
}
|
|
else
|
|
{
|
|
/* initialize SSL */
|
|
if (init_ssl (cfg))
|
|
goto bail;
|
|
|
|
imap->sock->ssl = SSL_new (SSLContext);
|
|
SSL_set_fd (imap->sock->ssl, imap->sock->fd);
|
|
if ((ret = SSL_connect (imap->sock->ssl)) <= 0)
|
|
{
|
|
socket_perror ("connect", imap->sock, ret);
|
|
goto bail;
|
|
}
|
|
|
|
/* verify the server certificate */
|
|
if (verify_cert (imap->sock->ssl))
|
|
goto bail;
|
|
|
|
/* to conform to RFC2595 we need to forget all information
|
|
* retrieved from CAPABILITY invocations before STARTTLS.
|
|
*/
|
|
imap->have_uidplus = 0;
|
|
imap->have_namespace = 0;
|
|
imap->have_cram = 0;
|
|
imap->have_starttls = 0;
|
|
|
|
imap->sock->use_ssl = 1;
|
|
puts ("SSL support enabled");
|
|
|
|
if (imap_exec (imap, "CAPABILITY"))
|
|
goto bail;
|
|
}
|
|
#else
|
|
if (imap_exec (imap, "CAPABILITY"))
|
|
goto bail;
|
|
#endif
|
|
|
|
if (!preauth)
|
|
{
|
|
info ("Logging in...\n");
|
|
|
|
if (!cfg->pass)
|
|
{
|
|
/*
|
|
* if we don't have a global password set, prompt the user for
|
|
* it now.
|
|
*/
|
|
if (!global.pass)
|
|
{
|
|
global.pass = getpass ("Password:");
|
|
if (!global.pass)
|
|
{
|
|
perror ("getpass");
|
|
exit (1);
|
|
}
|
|
if (!*global.pass)
|
|
{
|
|
fprintf (stderr, "Skipping %s, no password\n", cfg->path);
|
|
global.pass = NULL; /* force retry */
|
|
goto bail;
|
|
}
|
|
/*
|
|
* getpass() returns a pointer to a static buffer. make a copy
|
|
* for long term storage.
|
|
*/
|
|
global.pass = strdup (global.pass);
|
|
}
|
|
cfg->pass = strdup (global.pass);
|
|
}
|
|
|
|
#if HAVE_LIBSSL
|
|
if (imap->have_cram)
|
|
{
|
|
info ("Authenticating with CRAM-MD5\n");
|
|
imap->cram = 1;
|
|
if (imap_exec (imap, "AUTHENTICATE CRAM-MD5"))
|
|
goto bail;
|
|
}
|
|
else if (imap->box->require_cram)
|
|
{
|
|
fprintf (stderr, "IMAP error: CRAM-MD5 authentication is not supported by server\n");
|
|
goto bail;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
#if HAVE_LIBSSL
|
|
if (!use_ssl)
|
|
#endif
|
|
fprintf (stderr, "*** IMAP Warning *** Password is being sent in the clear\n");
|
|
if (imap_exec (imap, "LOGIN \"%s\" \"%s\"", cfg->user, cfg->pass))
|
|
{
|
|
fprintf (stderr, "IMAP error: LOGIN failed\n");
|
|
goto bail;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* get NAMESPACE info */
|
|
if (cfg->use_namespace && imap->have_namespace)
|
|
{
|
|
if (imap_exec (imap, "NAMESPACE"))
|
|
goto bail;
|
|
}
|
|
return imap;
|
|
|
|
bail:
|
|
imap_close (imap);
|
|
return 0;
|
|
}
|
|
|
|
/* `box' is the config info for the maildrop to sync. `minuid' is the
|
|
* minimum UID to consider. in normal mode this will be 1, but in --fast
|
|
* mode we only fetch messages newer than the last one seen in the local
|
|
* mailbox.
|
|
*/
|
|
imap_t *
|
|
imap_open (config_t * box, unsigned int minuid, imap_t * imap, int imap_create)
|
|
{
|
|
if (imap)
|
|
{
|
|
/* determine whether or not we can reuse the existing session */
|
|
if (strcmp (box->host, imap->box->host) ||
|
|
strcmp (box->user, imap->box->user) ||
|
|
box->port != imap->box->port
|
|
#if HAVE_LIBSSL
|
|
/* ensure that security requirements are met */
|
|
|| (box->require_ssl ^ imap->box->require_ssl)
|
|
|| (box->require_cram ^ imap->box->require_cram)
|
|
#endif
|
|
)
|
|
{
|
|
/* can't reuse */
|
|
imap_close (imap);
|
|
}
|
|
else
|
|
{
|
|
/* reset mailbox-specific state info */
|
|
imap->box = box;
|
|
imap->recent = 0;
|
|
imap->deleted = 0;
|
|
imap->count = 0;
|
|
imap->maxuid = 0;
|
|
free_message (imap->msgs);
|
|
imap->msgs = 0;
|
|
goto gotimap;
|
|
}
|
|
}
|
|
if (!(imap = imap_connect (box)))
|
|
return 0;
|
|
gotimap:
|
|
|
|
if (global.folder)
|
|
imap->prefix = !strcmp (box->box, "INBOX") ? "" : global.folder;
|
|
else
|
|
{
|
|
imap->prefix = "";
|
|
/* XXX for now assume personal namespace */
|
|
if (imap->box->use_namespace &&
|
|
is_list (imap->ns_personal) &&
|
|
is_list (imap->ns_personal->child) &&
|
|
is_atom (imap->ns_personal->child->child))
|
|
imap->prefix = imap->ns_personal->child->child->val;
|
|
}
|
|
|
|
info ("Selecting IMAP mailbox... ");
|
|
fflush (stdout);
|
|
if (imap_exec (imap, "SELECT \"%s%s\"", imap->prefix, box->box)) {
|
|
if (imap_create) {
|
|
if (imap_exec (imap, "CREATE \"%s%s\"", imap->prefix, box->box))
|
|
goto bail;
|
|
if (imap_exec (imap, "SELECT \"%s%s\"", imap->prefix, box->box))
|
|
goto bail;
|
|
} else
|
|
goto bail;
|
|
}
|
|
info ("%d messages, %d recent\n", imap->count, imap->recent);
|
|
|
|
info ("Reading IMAP mailbox index\n");
|
|
imap->minuid = minuid;
|
|
if (imap->count > 0)
|
|
{
|
|
if (imap_exec (imap, "UID FETCH %d:* (FLAGS RFC822.SIZE)", minuid))
|
|
goto bail;
|
|
}
|
|
|
|
return imap;
|
|
|
|
bail:
|
|
imap_close (imap);
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
imap_close (imap_t * imap)
|
|
{
|
|
if (imap)
|
|
{
|
|
if (imap->sock->fd != -1)
|
|
{
|
|
imap_exec (imap, "LOGOUT");
|
|
close (imap->sock->fd);
|
|
}
|
|
free (imap->sock);
|
|
free (imap->buf);
|
|
free_message (imap->msgs);
|
|
memset (imap, 0xff, sizeof (imap_t));
|
|
free (imap);
|
|
}
|
|
}
|
|
|
|
/* 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;
|
|
ssize_t n;
|
|
|
|
while (start < len)
|
|
{
|
|
while (end < len && buf[end] != '\r')
|
|
end++;
|
|
n = write (fd, buf + start, end - start);
|
|
if (n == -1)
|
|
{
|
|
perror ("write");
|
|
return -1;
|
|
}
|
|
else if ((size_t) n != end - start)
|
|
{
|
|
/* short write, try again */
|
|
start += n;
|
|
}
|
|
else
|
|
{
|
|
/* write complete */
|
|
end++;
|
|
start = end;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
send_server (Socket_t * sock, const char *fmt, ...)
|
|
{
|
|
char buf[128];
|
|
char cmd[128];
|
|
va_list ap;
|
|
int n;
|
|
|
|
va_start (ap, fmt);
|
|
vsnprintf (buf, sizeof (buf), fmt, ap);
|
|
va_end (ap);
|
|
|
|
snprintf (cmd, sizeof (cmd), "%d %s\r\n", ++Tag, buf);
|
|
n = socket_write (sock, cmd, strlen (cmd));
|
|
if (n <= 0)
|
|
{
|
|
socket_perror ("write", sock, n);
|
|
return -1;
|
|
}
|
|
|
|
if (Verbose)
|
|
fputs (cmd, stdout);
|
|
|
|
return 0;
|
|
}
|
|
|
|
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->sock, "UID FETCH %d BODY.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> */
|
|
arg = next_arg (&cmd); /* FETCH */
|
|
|
|
if (strcasecmp ("FETCH", arg) != 0)
|
|
{
|
|
/* this is likely an untagged response, such as when new
|
|
* mail arrives in the middle of the session. just skip
|
|
* it for now.
|
|
*
|
|
* eg.,
|
|
* "* 4000 EXISTS"
|
|
* "* 2 RECENT"
|
|
*
|
|
*/
|
|
info ("IMAP info: skipping untagged response: %s\n", arg);
|
|
continue;
|
|
}
|
|
|
|
while ((arg = next_arg (&cmd)) && *arg != '{')
|
|
;
|
|
if (!arg)
|
|
{
|
|
fprintf (stderr, "IMAP error: parse error getting size\n");
|
|
return -1;
|
|
}
|
|
bytes = strtol (arg + 1, 0, 10);
|
|
|
|
/* 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.
|
|
*/
|
|
if (write_strip (fd, imap->buf->buf + imap->buf->offset, n))
|
|
{
|
|
/* write failed, message is not delivered */
|
|
return -1;
|
|
}
|
|
|
|
bytes -= 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 = socket_read (imap->sock, buf, n);
|
|
if (n > 0)
|
|
{
|
|
if (write_strip (fd, buf, n))
|
|
{
|
|
/* write failed */
|
|
return -1;
|
|
}
|
|
bytes -= n;
|
|
}
|
|
else
|
|
{
|
|
socket_perror ("read", imap->sock, n);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
fprintf (stderr, "IMAP error: wrong tag\n");
|
|
return -1;
|
|
}
|
|
arg = next_arg (&cmd);
|
|
if (!strcmp ("OK", arg))
|
|
return 0;
|
|
return -1;
|
|
}
|
|
}
|
|
/* not reached */
|
|
}
|
|
|
|
/* 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");
|
|
}
|
|
|
|
int
|
|
imap_copy_message (imap_t * imap, unsigned int uid, const char *mailbox)
|
|
{
|
|
return imap_exec (imap, "UID COPY %u \"%s%s\"", uid, imap->prefix,
|
|
mailbox);
|
|
}
|
|
|
|
int
|
|
imap_append_message (imap_t * imap, int fd, message_t * msg)
|
|
{
|
|
char *fmap;
|
|
int extra, uid, tuidl = 0;
|
|
char flagstr[128], tuid[128];
|
|
char *s;
|
|
size_t i;
|
|
size_t start;
|
|
size_t len, sbreak = 0, ebreak = 0;
|
|
char *arg;
|
|
struct timeval tv;
|
|
pid_t pid = getpid();
|
|
|
|
len = msg->size;
|
|
/* ugh, we need to count the number of newlines */
|
|
fmap = (char *)mmap (0, len, PROT_READ, MAP_PRIVATE, fd, 0);
|
|
if (!fmap)
|
|
{
|
|
perror ("mmap");
|
|
return -1;
|
|
}
|
|
|
|
extra = 0, i = 0;
|
|
if (!imap->have_uidplus)
|
|
{
|
|
nloop:
|
|
start = i;
|
|
while (i < len)
|
|
if (fmap[i++] == '\n')
|
|
{
|
|
extra++;
|
|
if (i - 1 == start)
|
|
{
|
|
sbreak = ebreak = i - 1;
|
|
goto mktid;
|
|
}
|
|
if (!memcmp (fmap + start, "X-TUID: ", 8))
|
|
{
|
|
extra -= (ebreak = i) - (sbreak = start) + 1;
|
|
goto mktid;
|
|
}
|
|
goto nloop;
|
|
}
|
|
/* invalid mesasge */
|
|
goto bail;
|
|
mktid:
|
|
gettimeofday (&tv, 0);
|
|
tuidl = sprintf (tuid, "X-TUID: %08lx%05lx%04x\r\n",
|
|
tv.tv_sec, tv.tv_usec, pid);
|
|
extra += tuidl;
|
|
}
|
|
for (; i < len; i++)
|
|
if (fmap[i] == '\n')
|
|
extra++;
|
|
|
|
flagstr[0] = 0;
|
|
if (msg->flags)
|
|
{
|
|
if (msg->flags & D_DELETED)
|
|
strcat (flagstr," \\Deleted");
|
|
if (msg->flags & D_ANSWERED)
|
|
strcat (flagstr," \\Answered");
|
|
if (msg->flags & D_SEEN)
|
|
strcat (flagstr," \\Seen");
|
|
if (msg->flags & D_FLAGGED)
|
|
strcat (flagstr," \\Flagged");
|
|
if (msg->flags & D_DRAFT)
|
|
strcat (flagstr," \\Draft");
|
|
flagstr[0] = '(';
|
|
strcat (flagstr,") ");
|
|
}
|
|
|
|
send_server (imap->sock, "APPEND %s%s %s{%d}",
|
|
imap->prefix, imap->box->box, flagstr, len + extra);
|
|
|
|
if (buffer_gets (imap->buf, &s))
|
|
goto bail;
|
|
if (Verbose)
|
|
puts (s);
|
|
|
|
if (*s != '+')
|
|
{
|
|
fprintf (stderr, "IMAP error: expected `+' from server (aborting)\n");
|
|
goto bail;
|
|
}
|
|
|
|
i = 0;
|
|
if (!imap->have_uidplus)
|
|
{
|
|
n1loop:
|
|
start = i;
|
|
while (i < sbreak)
|
|
if (fmap[i++] == '\n')
|
|
{
|
|
socket_write (imap->sock, fmap + start, i - 1 - start);
|
|
socket_write (imap->sock, "\r\n", 2);
|
|
goto n1loop;
|
|
}
|
|
socket_write (imap->sock, tuid, tuidl);
|
|
i = ebreak;
|
|
}
|
|
n2loop:
|
|
start = i;
|
|
while (i < len)
|
|
if (fmap[i++] == '\n')
|
|
{
|
|
socket_write (imap->sock, fmap + start, i - 1 - start);
|
|
socket_write (imap->sock, "\r\n", 2);
|
|
goto n2loop;
|
|
}
|
|
socket_write (imap->sock, fmap + start, len - start);
|
|
socket_write (imap->sock, "\r\n", 2);
|
|
|
|
munmap (fmap, len);
|
|
|
|
for (;;)
|
|
{
|
|
if (buffer_gets (imap->buf, &s))
|
|
return -1;
|
|
|
|
if (Verbose)
|
|
puts (s);
|
|
|
|
arg = next_arg (&s);
|
|
if (*arg == '*')
|
|
{
|
|
/* XXX just ignore it for now */
|
|
}
|
|
else if (atoi (arg) != (int) Tag)
|
|
{
|
|
fprintf (stderr, "IMAP error: wrong tag\n");
|
|
return -1;
|
|
}
|
|
else
|
|
{
|
|
arg = next_arg (&s);
|
|
if (strcmp (arg, "OK"))
|
|
return -1;
|
|
arg = next_arg (&s);
|
|
if (*arg != '[')
|
|
break;
|
|
arg++;
|
|
if (strcasecmp ("APPENDUID", arg))
|
|
{
|
|
fprintf (stderr, "IMAP error: expected APPENDUID\n");
|
|
break;
|
|
}
|
|
arg = next_arg (&s);
|
|
if (!arg)
|
|
break;
|
|
if (atoi (arg) != (int) imap->uidvalidity)
|
|
{
|
|
fprintf (stderr, "IMAP error: UIDVALIDITY doesn't match APPENDUID\n");
|
|
return -1;
|
|
}
|
|
arg = next_arg (&s);
|
|
if (!arg)
|
|
break;
|
|
uid = strtol (arg, &s, 10);
|
|
if (*s != ']')
|
|
{
|
|
/* parse error */
|
|
break;
|
|
}
|
|
return uid;
|
|
}
|
|
}
|
|
|
|
/* didn't receive an APPENDUID */
|
|
send_server (imap->sock,
|
|
"UID SEARCH HEADER X-TUID %08lx%05lx%04x",
|
|
tv.tv_sec, tv.tv_usec, pid);
|
|
uid = 0;
|
|
for (;;)
|
|
{
|
|
if (buffer_gets (imap->buf, &s))
|
|
return -1;
|
|
|
|
if (Verbose)
|
|
puts (s);
|
|
|
|
arg = next_arg (&s);
|
|
if (*arg == '*')
|
|
{
|
|
arg = next_arg (&s);
|
|
if (!strcmp (arg, "SEARCH"))
|
|
{
|
|
arg = next_arg (&s);
|
|
if (!arg)
|
|
{
|
|
fprintf (stderr, "IMAP error: incomplete SEARCH response\n");
|
|
return -1;
|
|
}
|
|
uid = atoi (arg);
|
|
}
|
|
}
|
|
else if (atoi (arg) != (int) Tag)
|
|
{
|
|
fprintf (stderr, "IMAP error: wrong tag\n");
|
|
return -1;
|
|
}
|
|
else
|
|
{
|
|
arg = next_arg (&s);
|
|
if (strcmp (arg, "OK"))
|
|
return -1;
|
|
return uid;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
bail:
|
|
munmap (fmap, len);
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
imap_list (imap_t * imap)
|
|
{
|
|
return imap_exec (imap, "LIST \"\" \"%s*\"", global.folder);
|
|
}
|
|
|