|
|
|
/* $Id$
|
|
|
|
*
|
|
|
|
* isync - IMAP4 to maildir mailbox synchronizer
|
|
|
|
* Copyright (C) 2000 Michael R. Elkins <me@mutt.org>
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <assert.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <ctype.h>
|
|
|
|
#include <sys/socket.h>
|
|
|
|
#include <netinet/in.h>
|
|
|
|
#include <arpa/inet.h>
|
|
|
|
#include <netdb.h>
|
|
|
|
#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)
|
|
|
|
{
|
|
|
|
puts ("Error, no server certificate");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
err = SSL_get_verify_result (ssl);
|
|
|
|
if (err == X509_V_OK)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
printf ("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));
|
|
|
|
printf ("\nSubject: %s\n", buf);
|
|
|
|
X509_NAME_oneline (X509_get_issuer_name (cert), buf, sizeof (buf));
|
|
|
|
printf ("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);
|
|
|
|
printf ("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);
|
|
|
|
printf (" to: %s\n", buf);
|
|
|
|
|
|
|
|
printf
|
|
|
|
("\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");
|
|
|
|
printf ("\nAccept this certificate anyway? [no]: ");
|
|
|
|
fflush (stdout);
|
|
|
|
if (fgets (buf, sizeof (buf), stdin) && (buf[0] == 'y' || buf[0] == 'Y'))
|
|
|
|
{
|
|
|
|
ret = 0;
|
|
|
|
puts ("\n*** Fine, but don't say I didn't warn you!\n");
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
init_ssl (config_t * conf)
|
|
|
|
{
|
|
|
|
if (!conf->cert_file)
|
|
|
|
{
|
|
|
|
puts ("Error, CertificateFile not defined");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
SSL_library_init ();
|
|
|
|
SSL_load_error_strings ();
|
|
|
|
SSLContext = SSL_CTX_new (SSLv23_client_method ());
|
|
|
|
if (access (conf->cert_file, F_OK))
|
|
|
|
{
|
|
|
|
if (errno != ENOENT)
|
|
|
|
{
|
|
|
|
perror ("access");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
puts
|
|
|
|
("*** Warning, CertificateFile doesn't exist, can't verify server certificates");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
if (!SSL_CTX_load_verify_locations
|
|
|
|
(SSLContext, conf->cert_file, NULL))
|
|
|
|
{
|
|
|
|
printf ("Error, SSL_CTX_load_verify_locations: %s\n",
|
|
|
|
ERR_error_string (ERR_get_error (), 0));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!conf->use_sslv2)
|
|
|
|
SSL_CTX_set_options (SSLContext, SSL_OP_NO_SSLv2);
|
|
|
|
if (!conf->use_sslv3)
|
|
|
|
SSL_CTX_set_options (SSLContext, SSL_OP_NO_SSLv3);
|
|
|
|
if (!conf->use_tlsv1)
|
|
|
|
SSL_CTX_set_options (SSLContext, SSL_OP_NO_TLSv1);
|
|
|
|
|
|
|
|
/* 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);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 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)
|
|
|
|
{
|
|
|
|
if (n == -1)
|
|
|
|
perror ("read");
|
|
|
|
else
|
|
|
|
puts ("EOF");
|
|
|
|
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 */
|
|
|
|
// assert (strchr (*s, '\r') == 0);
|
|
|
|
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
|
|
|
|
puts ("Error, unable to parse UID");
|
|
|
|
}
|
|
|
|
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
|
|
|
|
printf ("Warning, unknown flag %s\n",
|
|
|
|
flags->val);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
puts ("Error, unable to parse FLAGS list");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
puts ("Error, unable to parse FLAGS");
|
|
|
|
}
|
|
|
|
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
|
|
|
|
*/
|
|
|
|
fputs ("***ALERT*** ", stdout);
|
|
|
|
puts (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;
|
|
|
|
|
|
|
|
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);
|
|
|
|
socket_write (imap->sock, 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);
|
|
|
|
if (!arg)
|
|
|
|
{
|
|
|
|
puts ("Error, unable to parse untagged command");
|
|
|
|
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 ("PREAUTH", arg) ||
|
|
|
|
!strcmp ("BYE", arg))
|
|
|
|
{
|
|
|
|
parse_response_code (imap, cmd);
|
|
|
|
}
|
|
|
|
else if (!strcmp ("CAPABILITY", arg))
|
|
|
|
{
|
|
|
|
#if HAVE_LIBSSL
|
|
|
|
while ((arg = next_arg (&cmd)))
|
|
|
|
{
|
|
|
|
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 ((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
|
|
|
|
{
|
|
|
|
puts ("Error, unable to parse untagged command");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#if HAVE_LIBSSL
|
|
|
|
else if (*arg == '+')
|
|
|
|
{
|
|
|
|
char *resp;
|
|
|
|
|
|
|
|
if (!imap->cram)
|
|
|
|
{
|
|
|
|
puts ("Error, not doing CRAM-MD5 authentication");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
resp = cram (cmd, imap->box->user, imap->box->pass);
|
|
|
|
|
|
|
|
socket_write (imap->sock, resp, strlen (resp));
|
|
|
|
if (Verbose)
|
|
|
|
puts (resp);
|
|
|
|
socket_write (imap->sock, "\r\n", 2);
|
|
|
|
free (resp);
|
|
|
|
imap->cram = 0;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
else if ((size_t) atol (arg) != Tag)
|
|
|
|
{
|
|
|
|
puts ("wrong tag");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
arg = next_arg (&cmd);
|
|
|
|
parse_response_code (imap, cmd);
|
|
|
|
if (!strcmp ("OK", arg))
|
|
|
|
return 0;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* not reached */
|
|
|
|
}
|
|
|
|
|
|
|
|
/* `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 ret;
|
|
|
|
int s;
|
|
|
|
struct sockaddr_in sin;
|
|
|
|
struct hostent *he;
|
|
|
|
int reuse = 0;
|
|
|
|
#if HAVE_LIBSSL
|
|
|
|
int use_ssl = 0;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
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);
|
|
|
|
imap = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
reuse = 1;
|
|
|
|
/* reset mailbox-specific state info */
|
|
|
|
imap->recent = 0;
|
|
|
|
imap->deleted = 0;
|
|
|
|
imap->count = 0;
|
|
|
|
imap->maxuid = 0;
|
|
|
|
free_message (imap->msgs);
|
|
|
|
imap->msgs = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!imap)
|
|
|
|
{
|
|
|
|
imap = calloc (1, sizeof (imap_t));
|
|
|
|
imap->sock = calloc (1, sizeof (Socket_t));
|
|
|
|
imap->buf = calloc (1, sizeof (buffer_t));
|
|
|
|
imap->buf->sock = imap->sock;
|
|
|
|
}
|
|
|
|
|
|
|
|
imap->box = box;
|
|
|
|
imap->minuid = minuid;
|
|
|
|
imap->prefix = "";
|
|
|
|
|
|
|
|
if (!reuse)
|
|
|
|
{
|
|
|
|
/* 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->sock->fd = s;
|
|
|
|
}
|
|
|
|
|
|
|
|
do
|
|
|
|
{
|
|
|
|
/* if we are reusing the existing connection, we can skip the
|
|
|
|
* authentication steps.
|
|
|
|
*/
|
|
|
|
if (!reuse)
|
|
|
|
{
|
|
|
|
#if HAVE_LIBSSL
|
|
|
|
if (!box->use_imaps)
|
|
|
|
{
|
|
|
|
/* let's see what this puppy can do... */
|
|
|
|
if ((ret = imap_exec (imap, "CAPABILITY")))
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* always try to select SSL support if available */
|
|
|
|
if (imap->have_starttls)
|
|
|
|
{
|
|
|
|
if ((ret = imap_exec (imap, "STARTTLS")))
|
|
|
|
break;
|
|
|
|
use_ssl = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!use_ssl)
|
|
|
|
{
|
|
|
|
if (box->require_ssl)
|
|
|
|
{
|
|
|
|
puts ("Error, SSL support not available");
|
|
|
|
ret = -1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
puts ("Warning, SSL support not available");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
use_ssl = 1;
|
|
|
|
|
|
|
|
if (use_ssl)
|
|
|
|
{
|
|
|
|
/* initialize SSL */
|
|
|
|
if (init_ssl (box))
|
|
|
|
{
|
|
|
|
ret = -1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
imap->sock->ssl = SSL_new (SSLContext);
|
|
|
|
SSL_set_fd (imap->sock->ssl, imap->sock->fd);
|
|
|
|
ret = SSL_connect (imap->sock->ssl);
|
|
|
|
if (ret <= 0)
|
|
|
|
{
|
|
|
|
ret = SSL_get_error (imap->sock->ssl, ret);
|
|
|
|
printf ("Error, SSL_connect: %s\n",
|
|
|
|
ERR_error_string (ret, 0));
|
|
|
|
ret = -1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* verify the server certificate */
|
|
|
|
if ((ret = verify_cert (imap->sock->ssl)))
|
|
|
|
break;
|
|
|
|
|
|
|
|
imap->sock->use_ssl = 1;
|
|
|
|
puts ("SSL support enabled");
|
|
|
|
|
|
|
|
if (box->use_imaps)
|
|
|
|
if ((ret = imap_exec (imap, "CAPABILITY")))
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
if ((ret = imap_exec (imap, "CAPABILITY")))
|
|
|
|
break;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
puts ("Logging in...");
|
|
|
|
#if HAVE_LIBSSL
|
|
|
|
if (imap->have_cram)
|
|
|
|
{
|
|
|
|
puts ("Authenticating with CRAM-MD5");
|
|
|
|
imap->cram = 1;
|
|
|
|
if ((ret = imap_exec (imap, "AUTHENTICATE CRAM-MD5")))
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else if (imap->box->require_cram)
|
|
|
|
{
|
|
|
|
puts
|
|
|
|
("Error, CRAM-MD5 authentication is not supported by server");
|
|
|
|
ret = -1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
#endif
|
|
|
|
{
|
|
|
|
#if HAVE_LIBSSL
|
|
|
|
if (!use_ssl)
|
|
|
|
#endif
|
|
|
|
puts
|
|
|
|
("*** Warning *** Password is being sent in the clear");
|
|
|
|
if (
|
|
|
|
(ret =
|
|
|
|
imap_exec (imap, "LOGIN \"%s\" \"%s\"", box->user,
|
|
|
|
box->pass)))
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* get NAMESPACE info */
|
|
|
|
if (box->use_namespace && imap->have_namespace)
|
|
|
|
{
|
|
|
|
if ((ret = imap_exec (imap, "NAMESPACE")))
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} /* !reuse */
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
fputs ("Selecting mailbox... ", stdout);
|
|
|
|
fflush (stdout);
|
|
|
|
if ((ret = imap_exec (imap, "SELECT \"%s%s\"", imap->prefix, box->box)))
|
|
|
|
break;
|
|
|
|
printf ("%d messages, %d recent\n", imap->count, imap->recent);
|
|
|
|
|
|
|
|
puts ("Reading IMAP mailbox index");
|
|
|
|
if (imap->count > 0)
|
|
|
|
{
|
|
|
|
if ((ret = imap_exec (imap, "UID FETCH %d:* (FLAGS RFC822.SIZE)",
|
|
|
|
imap->minuid)))
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
while (0);
|
|
|
|
|
|
|
|
if (ret)
|
|
|
|
{
|
|
|
|
imap_close (imap);
|
|
|
|
imap = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return imap;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
imap_close (imap_t * imap)
|
|
|
|
{
|
|
|
|
puts ("Closing IMAP connection");
|
|
|
|
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;
|
|
|
|
|
|
|
|
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 (Socket_t * sock, 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);
|
|
|
|
socket_write (sock, 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->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> */
|
|
|
|
next_arg (&cmd); /* FETCH */
|
|
|
|
|
|
|
|
while ((arg = next_arg (&cmd)) && *arg != '{')
|
|
|
|
;
|
|
|
|
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 = socket_read (imap->sock, 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;
|
|
|
|
}
|
|
|
|
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 buf[1024];
|
|
|
|
size_t len;
|
|
|
|
size_t sofar = 0;
|
|
|
|
int lines = 0;
|
|
|
|
char flagstr[128];
|
|
|
|
char *s;
|
|
|
|
size_t i;
|
|
|
|
size_t start, end;
|
|
|
|
char *arg;
|
|
|
|
|
|
|
|
/* ugh, we need to count the number of newlines */
|
|
|
|
while (sofar < msg->size)
|
|
|
|
{
|
|
|
|
len = msg->size - sofar;
|
|
|
|
if (len > sizeof (buf))
|
|
|
|
len = sizeof (buf);
|
|
|
|
len = read (fd, buf, len);
|
|
|
|
if (len == (size_t) - 1)
|
|
|
|
{
|
|
|
|
perror ("read");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
for (i = 0; i < len; i++)
|
|
|
|
if (buf[i] == '\n')
|
|
|
|
lines++;
|
|
|
|
sofar += len;
|
|
|
|
}
|
|
|
|
|
|
|
|
flagstr[0] = 0;
|
|
|
|
if (msg->flags)
|
|
|
|
{
|
|
|
|
strcpy (flagstr, "(");
|
|
|
|
if (msg->flags & D_DELETED)
|
|
|
|
snprintf (flagstr + strlen (flagstr),
|
|
|
|
sizeof (flagstr) - strlen (flagstr), "%s\\Deleted",
|
|
|
|
flagstr[1] ? " " : "");
|
|
|
|
if (msg->flags & D_ANSWERED)
|
|
|
|
snprintf (flagstr + strlen (flagstr),
|
|
|
|
sizeof (flagstr) - strlen (flagstr), "%s\\Answered",
|
|
|
|
flagstr[1] ? " " : "");
|
|
|
|
if (msg->flags & D_SEEN)
|
|
|
|
snprintf (flagstr + strlen (flagstr),
|
|
|
|
sizeof (flagstr) - strlen (flagstr), "%s\\Seen",
|
|
|
|
flagstr[1] ? " " : "");
|
|
|
|
if (msg->flags & D_FLAGGED)
|
|
|
|
snprintf (flagstr + strlen (flagstr),
|
|
|
|
sizeof (flagstr) - strlen (flagstr), "%s\\Flagged",
|
|
|
|
flagstr[1] ? " " : "");
|
|
|
|
if (msg->flags & D_DRAFT)
|
|
|
|
snprintf (flagstr + strlen (flagstr),
|
|
|
|
sizeof (flagstr) - strlen (flagstr), "%s\\Draft",
|
|
|
|
flagstr[1] ? " " : "");
|
|
|
|
snprintf (flagstr + strlen (flagstr),
|
|
|
|
sizeof (flagstr) - strlen (flagstr), ") ");
|
|
|
|
}
|
|
|
|
|
|
|
|
send_server (imap->sock, "APPEND %s%s %s{%d}\r\n",
|
|
|
|
imap->prefix, imap->box->box, flagstr, msg->size + lines);
|
|
|
|
socket_write (imap->sock, buf, strlen (buf));
|
|
|
|
if (Verbose)
|
|
|
|
fputs (buf, stdout);
|
|
|
|
|
|
|
|
if (buffer_gets (imap->buf, &s))
|
|
|
|
return -1;
|
|
|
|
if (Verbose)
|
|
|
|
puts (s);
|
|
|
|
|
|
|
|
if (*s != '+')
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
/* rewind */
|
|
|
|
lseek (fd, 0, 0);
|
|
|
|
|
|
|
|
sofar = 0;
|
|
|
|
while (sofar < msg->size)
|
|
|
|
{
|
|
|
|
len = msg->size - sofar;
|
|
|
|
if (len > sizeof (buf))
|
|
|
|
len = sizeof (buf);
|
|
|
|
len = read (fd, buf, len);
|
|
|
|
if (len == (size_t) - 1)
|
|
|
|
return -1;
|
|
|
|
start = 0;
|
|
|
|
while (start < len)
|
|
|
|
{
|
|
|
|
end = start;
|
|
|
|
while (end < len && buf[end] != '\n')
|
|
|
|
end++;
|
|
|
|
if (start != end)
|
|
|
|
socket_write (imap->sock, buf + start, end - start);
|
|
|
|
/* if (Verbose)
|
|
|
|
{
|
|
|
|
buf[end] = 0;
|
|
|
|
puts (buf + start);
|
|
|
|
} */
|
|
|
|
socket_write (imap->sock, "\r\n", 2);
|
|
|
|
start = end + 1;
|
|
|
|
}
|
|
|
|
sofar += len;
|
|
|
|
}
|
|
|
|
socket_write (imap->sock, "\r\n", 2);
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
puts ("wrong tag");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int uid;
|
|
|
|
|
|
|
|
arg = next_arg (&s);
|
|
|
|
if (strcmp (arg, "OK"))
|
|
|
|
return -1;
|
|
|
|
arg = next_arg (&s);
|
|
|
|
if (*arg != '[')
|
|
|
|
break;
|
|
|
|
arg++;
|
|
|
|
if (strcasecmp ("APPENDUID", arg))
|
|
|
|
{
|
|
|
|
puts ("Error, expected APPENDUID");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
arg = next_arg (&s);
|
|
|
|
if (!arg)
|
|
|
|
break;
|
|
|
|
if (atoi (arg) != (int) imap->uidvalidity)
|
|
|
|
{
|
|
|
|
puts ("Error, UIDVALIDITY doesn't match APPENDUID");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
arg = next_arg (&s);
|
|
|
|
if (!arg)
|
|
|
|
break;
|
|
|
|
uid = strtol (arg, &s, 10);
|
|
|
|
if (*s != ']')
|
|
|
|
{
|
|
|
|
/* parse error */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return uid;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|