1db31aabd7
1 giving a path to a nonexistant rc-file with the -c argument dumps core The patch adds a check to ensure that the given rc-file is accessible 2 the error messages given from failed openssl calls are bogus The handles the error from SSL_connect () correctly. The bug is understndable since the error handling in openssl is quite obfuscated. Good news is that the documentation manapges has been greatly updated in the latest version (0.9.6). See in particular err(3), ERR_get_error(3) and SSL_get_error(3). Please note that possible SSL_ERROR_SSL type errors from SSL_read() and SSL_write() is not handled. This should also be fixed. 3 connecting using the STARTTLS command with an imap server that is configured only to accept the TLSv1 protocol gives an error because isync sends an SSLv2 Hello message for backwards compability. (This is the case with the uw-imap 2000 that ships with redhat-7.0) I've read RFC2595 several times to see if it says something about compability SSL2/SSL3 hello messages but can't find anything. IMHO the correct thing to do is change the default to not use SSL2/3 compability hello when using the STARTTLS command but use it if the imaps port is used. The patch implements this change 4 repeated calls to SSL_CTX_set_options overwrites the old settings (the values needs to be ORed together) fixed in the patch patch from me@mutt.org: \Recent messages were put in the cur/ directory instead of new/ give error message when the LOGIN command fails
1141 lines
23 KiB
C
1141 lines
23 KiB
C
/* $Id$
|
|
*
|
|
* isync - IMAP4 to maildir mailbox synchronizer
|
|
* Copyright (C) 2000-1 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)
|
|
{
|
|
SSL_METHOD *method;
|
|
int options = 0;
|
|
|
|
if (!conf->cert_file)
|
|
{
|
|
puts ("Error, CertificateFile not defined");
|
|
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;
|
|
}
|
|
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)
|
|
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;
|
|
}
|
|
#endif
|
|
perror (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 */
|
|
// 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;
|
|
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)
|
|
fputs (buf, stdout);
|
|
n = socket_write (imap->sock, buf, strlen (buf));
|
|
if (n <= 0)
|
|
{
|
|
socket_perror ("write", imap->sock, n);
|
|
return -1;
|
|
}
|
|
|
|
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);
|
|
|
|
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)
|
|
{
|
|
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)
|
|
use_ssl = 1;
|
|
else if (box->use_sslv2 || box->use_sslv3 || box->use_tlsv1)
|
|
{
|
|
/* 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
|
|
{
|
|
/* 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)
|
|
{
|
|
socket_perror ("connect", imap->sock, ret);
|
|
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)))
|
|
{
|
|
puts ("Error, LOGIN failed");
|
|
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)
|
|
{
|
|
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 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> */
|
|
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
|
|
{
|
|
socket_perror ("read", imap->sock, n);
|
|
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}",
|
|
imap->prefix, imap->box->box, flagstr, msg->size + lines);
|
|
|
|
if (buffer_gets (imap->buf, &s))
|
|
return -1;
|
|
if (Verbose)
|
|
puts (s);
|
|
|
|
if (*s != '+')
|
|
{
|
|
puts ("Error, expected `+' from server (aborting)");
|
|
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);
|
|
/* only send a crlf if we actually hit the end of a line. we
|
|
* might be in the middle of a line in which case we don't
|
|
* send one.
|
|
*/
|
|
if (end != len)
|
|
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;
|
|
}
|