isync/src/drv_imap.c
Oswald Buddenhagen b0bbd23512 replace DRV_STORE_BAD with a separate bad_callback()
that way we don't have to piggy-back (possibly asynchronous) fatal
errors to particular commands.

internally, the drivers still use synchronous return values as well,
so they don't try to access the invalidated store after calling back.
2012-07-30 01:21:31 +02:00

1923 lines
46 KiB
C

/*
* mbsync - mailbox synchronizer
* Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
* Copyright (C) 2002-2006,2008 Oswald Buddenhagen <ossi@users.sf.net>
* Copyright (C) 2004 Theodore Y. Ts'o <tytso@mit.edu>
*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*
* As a special exception, mbsync may be linked with the OpenSSL library,
* despite that library's more restrictive license.
*/
/* This must come before isync.h to avoid our #define S messing up
* blowfish.h on MacOS X. */
#include <config.h>
#ifdef HAVE_LIBSSL
# include <openssl/ssl.h>
# include <openssl/err.h>
# include <openssl/hmac.h>
#endif
#include "isync.h"
#include <assert.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <limits.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#ifdef HAVE_SYS_FILIO_H
# include <sys/filio.h>
#endif
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>
typedef struct imap_server_conf {
struct imap_server_conf *next;
char *name;
char *tunnel;
char *host;
int port;
char *user;
char *pass;
#ifdef HAVE_LIBSSL
char *cert_file;
unsigned use_imaps:1;
unsigned require_ssl:1;
unsigned use_sslv2:1;
unsigned use_sslv3:1;
unsigned use_tlsv1:1;
unsigned require_cram:1;
X509_STORE *cert_store;
#endif
} imap_server_conf_t;
typedef struct imap_store_conf {
store_conf_t gen;
imap_server_conf_t *server;
unsigned use_namespace:1;
} imap_store_conf_t;
typedef struct imap_message {
message_t gen;
/* int seq; will be needed when expunges are tracked */
} imap_message_t;
#define NIL (void*)0x1
#define LIST (void*)0x2
typedef struct _list {
struct _list *next, *child;
char *val;
int len;
} list_t;
typedef struct {
int fd;
#ifdef HAVE_LIBSSL
SSL *ssl;
#endif
} Socket_t;
typedef struct {
Socket_t sock;
int bytes;
int offset;
char buf[1024];
} buffer_t;
struct imap_cmd;
#define max_in_progress 50 /* make this configurable? */
typedef struct imap_store {
store_t gen;
const char *prefix;
int uidnext; /* from SELECT responses */
unsigned trashnc:1; /* trash folder's existence is not confirmed yet */
unsigned got_namespace:1;
list_t *ns_personal, *ns_other, *ns_shared; /* NAMESPACE info */
message_t **msgapp; /* FETCH results */
unsigned caps; /* CAPABILITY results */
/* command queue */
int nexttag, num_in_progress, literal_pending;
struct imap_cmd *in_progress, **in_progress_append;
#ifdef HAVE_LIBSSL
SSL_CTX *SSLContext;
#endif
buffer_t buf; /* this is BIG, so put it last */
} imap_store_t;
struct imap_cmd {
struct imap_cmd *next;
char *cmd;
int tag;
struct {
int (*cont)( imap_store_t *ctx, struct imap_cmd *cmd, const char *prompt );
void (*done)( imap_store_t *ctx, struct imap_cmd *cmd, int response );
void *aux;
char *data;
int data_len;
int uid; /* to identify fetch responses */
unsigned
to_trash:1, /* we are storing to trash, not current. */
create:1, /* create the mailbox if we get an error ... */
trycreate:1; /* ... but only if this is true or the server says so. */
} param;
};
#define CAP(cap) (ctx->caps & (1 << (cap)))
enum CAPABILITY {
NOLOGIN = 0,
UIDPLUS,
LITERALPLUS,
NAMESPACE,
#ifdef HAVE_LIBSSL
CRAM,
STARTTLS,
#endif
};
static const char *cap_list[] = {
"LOGINDISABLED",
"UIDPLUS",
"LITERAL+",
"NAMESPACE",
#ifdef HAVE_LIBSSL
"AUTH=CRAM-MD5",
"STARTTLS",
#endif
};
#define RESP_OK 0
#define RESP_NO 1
#define RESP_CANCEL 2
static int get_cmd_result( imap_store_t *ctx, struct imap_cmd *tcmd );
static void imap_invoke_bad_callback( imap_store_t *ctx );
static const char *Flags[] = {
"Draft",
"Flagged",
"Answered",
"Seen",
"Deleted",
};
#ifdef HAVE_LIBSSL
/* Some of this code is inspired by / lifted from mutt. */
static int
compare_certificates( X509 *cert, X509 *peercert,
unsigned char *peermd, unsigned peermdlen )
{
unsigned char md[EVP_MAX_MD_SIZE];
unsigned mdlen;
/* Avoid CPU-intensive digest calculation if the certificates are
* not even remotely equal. */
if (X509_subject_name_cmp( cert, peercert ) ||
X509_issuer_name_cmp( cert, peercert ))
return -1;
if (!X509_digest( cert, EVP_sha1(), md, &mdlen ) ||
peermdlen != mdlen || memcmp( peermd, md, mdlen ))
return -1;
return 0;
}
#if OPENSSL_VERSION_NUMBER >= 0x00904000L
#define READ_X509_KEY(fp, key) PEM_read_X509( fp, key, 0, 0 )
#else
#define READ_X509_KEY(fp, key) PEM_read_X509( fp, key, 0 )
#endif
/* this gets called when a certificate is to be verified */
static int
verify_cert( imap_store_t *ctx )
{
imap_server_conf_t *srvc = ((imap_store_conf_t *)ctx->gen.conf)->server;
SSL *ssl = ctx->buf.sock.ssl;
X509 *cert, *lcert;
BIO *bio;
FILE *fp;
int err;
unsigned n, i;
X509_STORE_CTX xsc;
char buf[256];
unsigned char md[EVP_MAX_MD_SIZE];
cert = SSL_get_peer_certificate( ssl );
if (!cert) {
error( "Error, no server certificate\n" );
return -1;
}
while (srvc->cert_file) { // So break works
if (X509_cmp_current_time( X509_get_notBefore( cert )) >= 0) {
error( "Server certificate is not yet valid" );
break;
}
if (X509_cmp_current_time( X509_get_notAfter( cert )) <= 0) {
error( "Server certificate has expired" );
break;
}
if (!X509_digest( cert, EVP_sha1(), md, &n )) {
error( "*** Unable to calculate digest\n" );
break;
}
if (!(fp = fopen( srvc->cert_file, "rt" ))) {
error( "Unable to load CertificateFile '%s': %s\n",
srvc->cert_file, strerror( errno ) );
return 0;
}
err = -1;
for (lcert = 0; READ_X509_KEY( fp, &lcert ); )
if (!(err = compare_certificates( lcert, cert, md, n )))
break;
X509_free( lcert );
fclose( fp );
if (!err)
return 0;
break;
}
if (!srvc->cert_store) {
if (!(srvc->cert_store = X509_STORE_new())) {
error( "Error creating certificate store\n" );
return -1;
}
if (!X509_STORE_set_default_paths( srvc->cert_store ))
warn( "Error while loading default certificate files: %s\n",
ERR_error_string( ERR_get_error(), 0 ) );
if (!srvc->cert_file) {
info( "Note: CertificateFile not defined\n" );
} else if (!X509_STORE_load_locations( srvc->cert_store, srvc->cert_file, 0 )) {
error( "Error while loading certificate file '%s': %s\n",
srvc->cert_file, ERR_error_string( ERR_get_error(), 0 ) );
return -1;
}
}
X509_STORE_CTX_init( &xsc, srvc->cert_store, cert, 0 );
err = X509_verify_cert( &xsc ) > 0 ? 0 : X509_STORE_CTX_get_error( &xsc );
X509_STORE_CTX_cleanup( &xsc );
if (!err)
return 0;
error( "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 );
if (!X509_digest( cert, EVP_md5(), md, &n )) {
error( "*** Unable to calculate fingerprint\n" );
} else {
info( "Fingerprint: " );
for (i = 0; i < n; i += 2)
info( "%02X%02X ", md[i], md[i + 1] );
info( "\n" );
}
fputs( "\nAccept certificate? [y/N]: ", stderr );
if (fgets( buf, sizeof(buf), stdin ) && (buf[0] == 'y' || buf[0] == 'Y'))
return 0;
return -1;
}
static int
init_ssl_ctx( imap_store_t *ctx )
{
imap_server_conf_t *srvc = ((imap_store_conf_t *)ctx->gen.conf)->server;
const SSL_METHOD *method;
int options = 0;
if (srvc->use_tlsv1 && !srvc->use_sslv2 && !srvc->use_sslv3)
method = TLSv1_client_method();
else
method = SSLv23_client_method();
ctx->SSLContext = SSL_CTX_new( method );
if (!srvc->use_sslv2)
options |= SSL_OP_NO_SSLv2;
if (!srvc->use_sslv3)
options |= SSL_OP_NO_SSLv3;
if (!srvc->use_tlsv1)
options |= SSL_OP_NO_TLSv1;
SSL_CTX_set_options( ctx->SSLContext, options );
/* we check the result of the verification after SSL_connect() */
SSL_CTX_set_verify( ctx->SSLContext, SSL_VERIFY_NONE, 0 );
return 0;
}
#endif /* HAVE_LIBSSL */
static void
socket_perror( const char *func, Socket_t *sock, int ret )
{
#ifdef HAVE_LIBSSL
int err;
if (sock->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)
error( "SSL_%s: got EOF\n", func );
else
error( "SSL_%s: %s\n", func, strerror(errno) );
} else
error( "SSL_%s: %s\n", func, ERR_error_string( err, 0 ) );
return;
default:
error( "SSL_%s: unhandled SSL error %d\n", func, err );
break;
}
return;
}
#else
(void)sock;
#endif
if (ret < 0)
perror( func );
else
error( "%s: unexpected EOF\n", func );
}
static int
socket_read( Socket_t *sock, char *buf, int len )
{
int n;
assert( sock->fd >= 0 );
n =
#ifdef HAVE_LIBSSL
sock->ssl ? SSL_read( sock->ssl, buf, len ) :
#endif
read( sock->fd, buf, len );
if (n <= 0) {
socket_perror( "read", sock, n );
close( sock->fd );
sock->fd = -1;
}
return n;
}
static int
socket_write( Socket_t *sock, char *buf, int len )
{
int n;
assert( sock->fd >= 0 );
n =
#ifdef HAVE_LIBSSL
sock->ssl ? SSL_write( sock->ssl, buf, len ) :
#endif
write( sock->fd, buf, len );
if (n != len) {
socket_perror( "write", sock, n );
close( sock->fd );
sock->fd = -1;
}
return n;
}
static int
socket_pending( Socket_t *sock )
{
int num = -1;
if (ioctl( sock->fd, FIONREAD, &num ) < 0)
return -1;
if (num > 0)
return num;
#ifdef HAVE_LIBSSL
if (sock->ssl)
return SSL_pending( sock->ssl );
#endif
return 0;
}
/* 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) {
/* 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)
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 */
if (DFlags & VERBOSE)
puts( *s );
return 0;
}
}
b->offset++;
}
/* not reached */
}
static struct imap_cmd *
new_imap_cmd( void )
{
struct imap_cmd *cmd = nfmalloc( sizeof(*cmd) );
memset( &cmd->param, 0, sizeof(cmd->param) );
return cmd;
}
static struct imap_cmd *
v_submit_imap_cmd( imap_store_t *ctx, struct imap_cmd *cmd,
const char *fmt, va_list ap )
{
int bufl, litplus;
const char *buffmt;
char buf[1024];
while (ctx->literal_pending)
if (get_cmd_result( ctx, 0 ) == RESP_CANCEL)
goto bail2;
if (!cmd)
cmd = new_imap_cmd();
cmd->tag = ++ctx->nexttag;
if (fmt)
nfvasprintf( &cmd->cmd, fmt, ap );
if (!cmd->param.data) {
buffmt = "%d %s\r\n";
litplus = 0;
} else if ((cmd->param.to_trash && ctx->trashnc) || !CAP(LITERALPLUS)) {
buffmt = "%d %s{%d}\r\n";
litplus = 0;
} else {
buffmt = "%d %s{%d+}\r\n";
litplus = 1;
}
bufl = nfsnprintf( buf, sizeof(buf), buffmt,
cmd->tag, cmd->cmd, cmd->param.data_len );
if (DFlags & VERBOSE) {
if (ctx->num_in_progress)
printf( "(%d in progress) ", ctx->num_in_progress );
if (memcmp( cmd->cmd, "LOGIN", 5 ))
printf( ">>> %s", buf );
else
printf( ">>> %d LOGIN <user> <pass>\n", cmd->tag );
}
if (socket_write( &ctx->buf.sock, buf, bufl ) != bufl)
goto bail;
if (litplus) {
if (socket_write( &ctx->buf.sock, cmd->param.data, cmd->param.data_len ) != cmd->param.data_len ||
socket_write( &ctx->buf.sock, "\r\n", 2 ) != 2)
goto bail;
free( cmd->param.data );
cmd->param.data = 0;
} else if (cmd->param.cont || cmd->param.data) {
ctx->literal_pending = 1;
}
cmd->next = 0;
*ctx->in_progress_append = cmd;
ctx->in_progress_append = &cmd->next;
ctx->num_in_progress++;
return cmd;
bail:
imap_invoke_bad_callback( ctx );
bail2:
free( cmd->param.data );
free( cmd->cmd );
free( cmd );
return NULL;
}
static struct imap_cmd *
submit_imap_cmd( imap_store_t *ctx, struct imap_cmd *cmd, const char *fmt, ... )
{
struct imap_cmd *ret;
va_list ap;
va_start( ap, fmt );
ret = v_submit_imap_cmd( ctx, cmd, fmt, ap );
va_end( ap );
return ret;
}
static int
imap_exec( imap_store_t *ctx, struct imap_cmd *cmdp, const char *fmt, ... )
{
va_list ap;
va_start( ap, fmt );
cmdp = v_submit_imap_cmd( ctx, cmdp, fmt, ap );
va_end( ap );
if (!cmdp)
return RESP_CANCEL;
return get_cmd_result( ctx, cmdp );
}
static int
imap_exec_b( imap_store_t *ctx, struct imap_cmd *cmdp, const char *fmt, ... )
{
va_list ap;
va_start( ap, fmt );
cmdp = v_submit_imap_cmd( ctx, cmdp, fmt, ap );
va_end( ap );
if (!cmdp)
return DRV_CANCELED;
switch (get_cmd_result( ctx, cmdp )) {
case RESP_CANCEL: return DRV_CANCELED;
case RESP_NO: return DRV_BOX_BAD;
default: return DRV_OK;
}
}
static int
imap_exec_m( imap_store_t *ctx, struct imap_cmd *cmdp, const char *fmt, ... )
{
va_list ap;
va_start( ap, fmt );
cmdp = v_submit_imap_cmd( ctx, cmdp, fmt, ap );
va_end( ap );
if (!cmdp)
return DRV_CANCELED;
switch (get_cmd_result( ctx, cmdp )) {
case RESP_CANCEL: return DRV_CANCELED;
case RESP_NO: return DRV_MSG_BAD;
default: return DRV_OK;
}
}
/*
static void
drain_imap_replies( imap_store_t *ctx )
{
while (ctx->num_in_progress)
get_cmd_result( ctx, 0 );
}
*/
static int
process_imap_replies( imap_store_t *ctx )
{
while (ctx->num_in_progress > max_in_progress ||
socket_pending( &ctx->buf.sock ))
if (get_cmd_result( ctx, 0 ) == RESP_CANCEL)
return RESP_CANCEL;
return RESP_OK;
}
static int
is_atom( list_t *list )
{
return list && list->val && list->val != NIL && list->val != LIST;
}
static int
is_list( list_t *list )
{
return list && list->val == LIST;
}
static void
free_list( list_t *list )
{
list_t *tmp;
for (; list; list = tmp) {
tmp = list->next;
if (is_list( list ))
free_list( list->child );
else if (is_atom( list ))
free( list->val );
free( list );
}
}
static int
parse_imap_list_l( imap_store_t *ctx, char **sp, list_t **curp, int level )
{
list_t *cur;
char *s = *sp, *p;
int n, bytes;
for (;;) {
while (isspace( (unsigned char)*s ))
s++;
if (level && *s == ')') {
s++;
break;
}
*curp = cur = nfmalloc( sizeof(*cur) );
curp = &cur->next;
cur->val = 0; /* for clean bail */
if (*s == '(') {
/* sublist */
s++;
cur->val = LIST;
if (parse_imap_list_l( ctx, &s, &cur->child, level + 1 ))
goto bail;
} else if (ctx && *s == '{') {
/* literal */
bytes = cur->len = strtol( s + 1, &s, 10 );
if (*s != '}')
goto bail;
s = cur->val = nfmalloc( cur->len );
/* dump whats left over in the input buffer */
n = ctx->buf.bytes - ctx->buf.offset;
if (n > bytes)
/* the entire message fit in the buffer */
n = bytes;
memcpy( s, ctx->buf.buf + ctx->buf.offset, n );
s += n;
bytes -= n;
/* mark that we used part of the buffer */
ctx->buf.offset += n;
/* now read the rest of the message */
while (bytes > 0) {
if ((n = socket_read( &ctx->buf.sock, s, bytes )) <= 0)
goto bail;
s += n;
bytes -= n;
}
if (DFlags & XVERBOSE) {
puts( "=========" );
fwrite( cur->val, cur->len, 1, stdout );
puts( "=========" );
}
if (buffer_gets( &ctx->buf, &s ))
goto bail;
} else if (*s == '"') {
/* quoted string */
s++;
p = s;
for (; *s != '"'; s++)
if (!*s)
goto bail;
cur->len = s - p;
s++;
cur->val = nfmalloc( cur->len + 1 );
memcpy( cur->val, p, cur->len );
cur->val[cur->len] = 0;
} else {
/* atom */
p = s;
for (; *s && !isspace( (unsigned char)*s ); s++)
if (level && *s == ')')
break;
cur->len = s - p;
if (cur->len == 3 && !memcmp ("NIL", p, 3))
cur->val = NIL;
else {
cur->val = nfmalloc( cur->len + 1 );
memcpy( cur->val, p, cur->len );
cur->val[cur->len] = 0;
}
}
if (!level)
break;
if (!*s)
goto bail;
}
*sp = s;
*curp = 0;
return 0;
bail:
*curp = 0;
return -1;
}
static list_t *
parse_imap_list( imap_store_t *ctx, char **sp )
{
list_t *head;
if (!parse_imap_list_l( ctx, sp, &head, 0 ))
return head;
free_list( head );
return NULL;
}
static list_t *
parse_list( char **sp )
{
return parse_imap_list( 0, sp );
}
static int
parse_fetch( imap_store_t *ctx, char *cmd ) /* move this down */
{
list_t *tmp, *list, *flags;
char *body = 0;
imap_message_t *cur;
msg_data_t *msgdata;
struct imap_cmd *cmdp;
int uid = 0, mask = 0, status = 0, size = 0;
unsigned i;
list = parse_imap_list( ctx, &cmd );
if (!is_list( list )) {
error( "IMAP error: bogus FETCH response\n" );
free_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 );
else
error( "IMAP error: unable to parse UID\n" );
} else if (!strcmp( "FLAGS", tmp->val )) {
tmp = tmp->next;
if (is_list( tmp )) {
for (flags = tmp->child; flags; flags = flags->next) {
if (is_atom( flags )) {
if (flags->val[0] == '\\') { /* ignore user-defined flags for now */
if (!strcmp( "Recent", flags->val + 1)) {
status |= M_RECENT;
goto flagok;
}
for (i = 0; i < as(Flags); i++)
if (!strcmp( Flags[i], flags->val + 1 )) {
mask |= 1 << i;
goto flagok;
}
if (flags->val[1] == 'X' && flags->val[2] == '-')
goto flagok; /* ignore system flag extensions */
error( "IMAP warning: unknown system flag %s\n", flags->val );
}
flagok: ;
} else
error( "IMAP error: unable to parse FLAGS list\n" );
}
status |= M_FLAGS;
} else
error( "IMAP error: unable to parse FLAGS\n" );
} else if (!strcmp( "RFC822.SIZE", tmp->val )) {
tmp = tmp->next;
if (is_atom( tmp ))
size = atoi( tmp->val );
else
error( "IMAP error: unable to parse RFC822.SIZE\n" );
} else if (!strcmp( "BODY[]", tmp->val )) {
tmp = tmp->next;
if (is_atom( tmp )) {
body = tmp->val;
tmp->val = 0; /* don't free together with list */
size = tmp->len;
} else
error( "IMAP error: unable to parse BODY[]\n" );
}
}
}
if (body) {
for (cmdp = ctx->in_progress; cmdp; cmdp = cmdp->next)
if (cmdp->param.uid == uid)
goto gotuid;
error( "IMAP error: unexpected FETCH response (UID %d)\n", uid );
free_list( list );
return -1;
gotuid:
msgdata = (msg_data_t *)cmdp->param.aux;
msgdata->data = body;
msgdata->len = size;
if (status & M_FLAGS)
msgdata->flags = mask;
} else if (uid) { /* ignore async flag updates for now */
/* XXX this will need sorting for out-of-order (multiple queries) */
cur = nfcalloc( sizeof(*cur) );
*ctx->msgapp = &cur->gen;
ctx->msgapp = &cur->gen.next;
cur->gen.next = 0;
cur->gen.uid = uid;
cur->gen.flags = mask;
cur->gen.status = status;
cur->gen.size = size;
}
free_list( list );
return 0;
}
static void
parse_capability( imap_store_t *ctx, char *cmd )
{
char *arg;
unsigned i;
ctx->caps = 0x80000000;
while ((arg = next_arg( &cmd )))
for (i = 0; i < as(cap_list); i++)
if (!strcmp( cap_list[i], arg ))
ctx->caps |= 1 << i;
}
static int
parse_response_code( imap_store_t *ctx, struct imap_cmd *cmd, char *s )
{
char *arg, *earg, *p;
if (*s != '[')
return RESP_OK; /* no response code */
s++;
if (!(p = strchr( s, ']' ))) {
error( "IMAP error: malformed response code\n" );
return RESP_CANCEL;
}
*p++ = 0;
arg = next_arg( &s );
if (!strcmp( "UIDVALIDITY", arg )) {
if (!(arg = next_arg( &s )) ||
(ctx->gen.uidvalidity = strtoll( arg, &earg, 10 ), *earg))
{
error( "IMAP error: malformed UIDVALIDITY status\n" );
return RESP_CANCEL;
}
} else if (!strcmp( "UIDNEXT", arg )) {
if (!(arg = next_arg( &s )) || (ctx->uidnext = strtol( arg, &p, 10 ), *p)) {
error( "IMAP error: malformed NEXTUID status\n" );
return RESP_CANCEL;
}
} else if (!strcmp( "CAPABILITY", arg )) {
parse_capability( ctx, s );
} else if (!strcmp( "ALERT", arg )) {
/* RFC2060 says that these messages MUST be displayed
* to the user
*/
for (; isspace( (unsigned char)*p ); p++);
error( "*** IMAP ALERT *** %s\n", p );
} else if (cmd && cmd->param.aux && !strcmp( "APPENDUID", arg )) {
if (!(arg = next_arg( &s )) ||
(ctx->gen.uidvalidity = strtoll( arg, &earg, 10 ), *earg) ||
!(arg = next_arg( &s )) || !(*(int *)cmd->param.aux = atoi( arg )))
{
error( "IMAP error: malformed APPENDUID status\n" );
return RESP_CANCEL;
}
}
return RESP_OK;
}
static void
parse_search( imap_store_t *ctx, char *cmd )
{
char *arg;
struct imap_cmd *cmdp;
int uid;
if (!(arg = next_arg( &cmd )))
uid = -1;
else if (!(uid = atoi( arg ))) {
error( "IMAP error: malformed SEARCH response\n" );
return;
} else if (next_arg( &cmd )) {
warn( "IMAP warning: SEARCH returns multiple matches\n" );
uid = -1; /* to avoid havoc */
}
/* Find the first command that expects a UID - this is guaranteed
* to come in-order, as there are no other means to identify which
* SEARCH response belongs to which request.
*/
for (cmdp = ctx->in_progress; cmdp; cmdp = cmdp->next)
if (cmdp->param.uid == -1) {
*(int *)cmdp->param.aux = uid;
return;
}
error( "IMAP error: unexpected SEARCH response (UID %u)\n", uid );
}
static void
parse_list_rsp( imap_store_t *ctx, char *cmd )
{
char *arg;
list_t *list, *lp;
int l;
list = parse_list( &cmd );
if (list->val == LIST)
for (lp = list->child; lp; lp = lp->next)
if (is_atom( lp ) && !strcasecmp( lp->val, "\\NoSelect" )) {
free_list( list );
return;
}
free_list( list );
(void) next_arg( &cmd ); /* skip delimiter */
arg = next_arg( &cmd );
l = strlen( ctx->gen.conf->path );
if (memcmp( arg, ctx->gen.conf->path, l ))
return;
arg += l;
if (!memcmp( arg + strlen( arg ) - 5, ".lock", 5 )) /* workaround broken servers */
return;
add_string_list( &ctx->gen.boxes, arg );
}
static int
get_cmd_result( imap_store_t *ctx, struct imap_cmd *tcmd )
{
struct imap_cmd *cmdp, **pcmdp;
char *cmd, *arg, *arg1, *p;
int n, resp, resp2, tag;
for (;;) {
if (buffer_gets( &ctx->buf, &cmd ))
break;
arg = next_arg( &cmd );
if (*arg == '*') {
arg = next_arg( &cmd );
if (!arg) {
error( "IMAP error: unable to parse untagged response\n" );
break;
}
if (!strcmp( "NAMESPACE", arg )) {
ctx->ns_personal = parse_list( &cmd );
ctx->ns_other = parse_list( &cmd );
ctx->ns_shared = parse_list( &cmd );
} else if (!strcmp( "OK", arg ) || !strcmp( "BAD", arg ) ||
!strcmp( "NO", arg ) || !strcmp( "BYE", arg )) {
parse_response_code( ctx, 0, cmd );
} else if (!strcmp( "CAPABILITY", arg ))
parse_capability( ctx, cmd );
else if (!strcmp( "LIST", arg ))
parse_list_rsp( ctx, cmd );
else if (!strcmp( "SEARCH", arg ))
parse_search( ctx, cmd );
else if ((arg1 = next_arg( &cmd ))) {
if (!strcmp( "EXISTS", arg1 ))
ctx->gen.count = atoi( arg );
else if (!strcmp( "RECENT", arg1 ))
ctx->gen.recent = atoi( arg );
else if(!strcmp ( "FETCH", arg1 )) {
if (parse_fetch( ctx, cmd ))
break; /* stream is likely to be useless now */
}
} else {
error( "IMAP error: unrecognized untagged response '%s'\n", arg );
break; /* this may mean anything, so prefer not to spam the log */
}
} else if (!ctx->in_progress) {
error( "IMAP error: unexpected reply: %s %s\n", arg, cmd ? cmd : "" );
break; /* this may mean anything, so prefer not to spam the log */
} else if (*arg == '+') {
/* This can happen only with the last command underway, as
it enforces a round-trip. */
cmdp = ctx->in_progress;
if (cmdp->param.data) {
if (cmdp->param.to_trash)
ctx->trashnc = 0; /* Can't get NO [TRYCREATE] any more. */
n = socket_write( &ctx->buf.sock, cmdp->param.data, cmdp->param.data_len );
free( cmdp->param.data );
cmdp->param.data = 0;
if (n != (int)cmdp->param.data_len)
break;
} else if (cmdp->param.cont) {
if (cmdp->param.cont( ctx, cmdp, cmd ))
break;
} else {
error( "IMAP error: unexpected command continuation request\n" );
break;
}
if (socket_write( &ctx->buf.sock, "\r\n", 2 ) != 2)
break;
if (!cmdp->param.cont)
ctx->literal_pending = 0;
if (!tcmd)
return RESP_OK;
} else {
tag = atoi( arg );
for (pcmdp = &ctx->in_progress; (cmdp = *pcmdp); pcmdp = &cmdp->next)
if (cmdp->tag == tag)
goto gottag;
error( "IMAP error: unexpected tag %s\n", arg );
break;
gottag:
if (!(*pcmdp = cmdp->next))
ctx->in_progress_append = pcmdp;
ctx->num_in_progress--;
if (cmdp->param.cont || cmdp->param.data)
ctx->literal_pending = 0;
arg = next_arg( &cmd );
if (!strcmp( "OK", arg )) {
if (cmdp->param.to_trash)
ctx->trashnc = 0; /* Can't get NO [TRYCREATE] any more. */
resp = RESP_OK;
} else {
if (!strcmp( "NO", arg )) {
if (cmdp->param.create && cmd && (cmdp->param.trycreate || !memcmp( cmd, "[TRYCREATE]", 11 ))) { /* SELECT, APPEND or UID COPY */
p = strchr( cmdp->cmd, '"' );
if (!submit_imap_cmd( ctx, 0, "CREATE %.*s", strchr( p + 1, '"' ) - p + 1, p )) {
resp = RESP_CANCEL;
goto normal;
}
/* not waiting here violates the spec, but a server that does not
grok this nonetheless violates it too. */
cmdp->param.create = 0;
if (!submit_imap_cmd( ctx, cmdp, 0 )) {
resp = RESP_CANCEL;
goto abnormal;
}
if (!tcmd)
return 0; /* ignored */
continue;
}
resp = RESP_NO;
} else /*if (!strcmp( "BAD", arg ))*/
resp = RESP_CANCEL;
error( "IMAP command '%s' returned an error: %s %s\n",
memcmp( cmdp->cmd, "LOGIN", 5 ) ? cmdp->cmd : "LOGIN <user> <pass>",
arg, cmd ? cmd : "" );
}
if ((resp2 = parse_response_code( ctx, cmdp, cmd )) > resp)
resp = resp2;
if (resp == RESP_CANCEL)
imap_invoke_bad_callback( ctx );
normal:
if (cmdp->param.done)
cmdp->param.done( ctx, cmdp, resp );
free( cmdp->param.data );
free( cmdp->cmd );
free( cmdp );
abnormal:
if (!tcmd || tcmd == cmdp)
return resp;
}
}
imap_invoke_bad_callback( ctx );
return RESP_CANCEL;
}
static void
imap_cancel_store( store_t *gctx )
{
imap_store_t *ctx = (imap_store_t *)gctx;
free_generic_messages( gctx->msgs );
free_string_list( ctx->gen.boxes );
if (ctx->buf.sock.fd >= 0)
close( ctx->buf.sock.fd );
#ifdef HAVE_LIBSSL
if (ctx->buf.sock.ssl)
SSL_free( ctx->buf.sock.ssl );
if (ctx->SSLContext)
SSL_CTX_free( ctx->SSLContext );
#endif
free_list( ctx->ns_personal );
free_list( ctx->ns_other );
free_list( ctx->ns_shared );
free( ctx );
}
static void
imap_invoke_bad_callback( imap_store_t *ctx )
{
if (ctx->gen.bad_callback)
ctx->gen.bad_callback( ctx->gen.bad_callback_aux );
}
static store_t *unowned;
static void
imap_cancel_unowned( void *gctx )
{
store_t *store, **storep;
for (storep = &unowned; (store = *storep); storep = &store->next)
if (store == gctx) {
*storep = store->next;
break;
}
imap_cancel_store( gctx );
}
static void
imap_disown_store( store_t *gctx )
{
free_generic_messages( gctx->msgs );
gctx->msgs = 0;
set_bad_callback( gctx, imap_cancel_unowned, gctx );
gctx->next = unowned;
unowned = gctx;
}
static store_t *
imap_own_store( store_conf_t *conf )
{
store_t *store, **storep;
for (storep = &unowned; (store = *storep); storep = &store->next)
if (store->conf == conf) {
*storep = store->next;
return store;
}
return 0;
}
static void
imap_cleanup( void )
{
store_t *ctx, *nctx;
for (ctx = unowned; ctx; ctx = nctx) {
nctx = ctx->next;
set_bad_callback( ctx, (void (*)(void *))imap_cancel_store, ctx );
if (imap_exec( (imap_store_t *)ctx, 0, "LOGOUT" ) == RESP_CANCEL)
continue;
imap_cancel_store( ctx );
}
}
#ifdef HAVE_LIBSSL
static int
start_tls( imap_store_t *ctx )
{
int ret;
static int ssl_inited;
if (!ssl_inited) {
SSL_library_init();
SSL_load_error_strings();
ssl_inited = 1;
}
if (init_ssl_ctx( ctx ))
return 1;
ctx->buf.sock.ssl = SSL_new( ctx->SSLContext );
SSL_set_fd( ctx->buf.sock.ssl, ctx->buf.sock.fd );
if ((ret = SSL_connect( ctx->buf.sock.ssl )) <= 0) {
socket_perror( "connect", &ctx->buf.sock, ret );
return 1;
}
/* verify the server certificate */
if (verify_cert( ctx ))
return 1;
info( "Connection is now encrypted\n" );
return 0;
}
#define ENCODED_SIZE(n) (4*((n+2)/3))
static char
hexchar( unsigned int b )
{
if (b < 10)
return '0' + b;
return 'a' + (b - 10);
}
static void
cram( const char *challenge, const char *user, const char *pass, char **_final, int *_finallen )
{
unsigned char *response, *final;
unsigned hashlen;
int i, clen, rlen, blen, flen, olen;
unsigned char hash[16];
char buf[256], hex[33];
HMAC_CTX hmac;
HMAC_Init( &hmac, (unsigned char *)pass, strlen( pass ), EVP_md5() );
clen = strlen( challenge );
/* response will always be smaller than challenge because we are decoding. */
response = nfcalloc( 1 + clen );
rlen = EVP_DecodeBlock( response, (unsigned char *)challenge, clen );
HMAC_Update( &hmac, response, rlen );
free( response );
hashlen = sizeof(hash);
HMAC_Final( &hmac, hash, &hashlen );
assert( hashlen == sizeof(hash) );
hex[32] = 0;
for (i = 0; i < 16; i++) {
hex[2 * i] = hexchar( (hash[i] >> 4) & 0xf );
hex[2 * i + 1] = hexchar( hash[i] & 0xf );
}
blen = nfsnprintf( buf, sizeof(buf), "%s %s", user, hex );
flen = ENCODED_SIZE( blen );
final = nfmalloc( flen + 1 );
final[flen] = 0;
olen = EVP_EncodeBlock( (unsigned char *)final, (unsigned char *)buf, blen );
assert( olen == flen );
*_final = (char *)final;
*_finallen = flen;
}
static int
do_cram_auth( imap_store_t *ctx, struct imap_cmd *cmdp, const char *prompt )
{
imap_server_conf_t *srvc = ((imap_store_conf_t *)ctx->gen.conf)->server;
char *resp;
int n, l;
cram( prompt, srvc->user, srvc->pass, &resp, &l );
if (DFlags & VERBOSE)
printf( ">+> %s\n", resp );
n = socket_write( &ctx->buf.sock, resp, l );
free( resp );
if (n != l)
return -1;
cmdp->param.cont = 0;
return 0;
}
#endif
static void
imap_open_store( store_conf_t *conf,
void (*cb)( store_t *srv, void *aux ), void *aux )
{
imap_store_conf_t *cfg = (imap_store_conf_t *)conf;
imap_server_conf_t *srvc = cfg->server;
imap_store_t *ctx;
store_t **ctxp;
char *arg, *rsp;
struct hostent *he;
struct sockaddr_in addr;
int s, a[2], preauth;
for (ctxp = &unowned; (ctx = (imap_store_t *)*ctxp); ctxp = &ctx->gen.next)
if (((imap_store_conf_t *)ctx->gen.conf)->server == srvc) {
*ctxp = ctx->gen.next;
/* One could ping the server here, but given that the idle timeout
* is at least 30 minutes, this sounds pretty pointless. */
free_string_list( ctx->gen.boxes );
ctx->gen.boxes = 0;
ctx->gen.listed = 0;
ctx->gen.conf = conf;
set_bad_callback( &ctx->gen, 0, 0 );
goto final;
}
ctx = nfcalloc( sizeof(*ctx) );
ctx->gen.conf = conf;
ctx->buf.sock.fd = -1;
ctx->in_progress_append = &ctx->in_progress;
/* open connection to IMAP server */
if (srvc->tunnel) {
infon( "Starting tunnel '%s'... ", srvc->tunnel );
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", srvc->tunnel, (char *)0 );
_exit( 127 );
}
close (a[0]);
ctx->buf.sock.fd = a[1];
info( "ok\n" );
} else {
memset( &addr, 0, sizeof(addr) );
addr.sin_port = srvc->port ? htons( srvc->port ) :
#ifdef HAVE_LIBSSL
srvc->use_imaps ? htons( 993 ) :
#endif
htons( 143 );
addr.sin_family = AF_INET;
infon( "Resolving %s... ", srvc->host );
he = gethostbyname( srvc->host );
if (!he) {
error( "IMAP error: Cannot resolve server '%s'\n", srvc->host );
goto bail;
}
info( "ok\n" );
addr.sin_addr.s_addr = *((int *) he->h_addr_list[0]);
s = socket( PF_INET, SOCK_STREAM, 0 );
if (s < 0) {
perror( "socket" );
exit( 1 );
}
infon( "Connecting to %s:%hu... ", inet_ntoa( addr.sin_addr ), ntohs( addr.sin_port ) );
if (connect( s, (struct sockaddr *)&addr, sizeof(addr) )) {
close( s );
perror( "connect" );
goto bail;
}
info( "ok\n" );
ctx->buf.sock.fd = s;
}
#ifdef HAVE_LIBSSL
if (srvc->use_imaps) {
if (start_tls( ctx ))
goto ssl_bail;
}
#endif
/* read the greeting string */
if (buffer_gets( &ctx->buf, &rsp ))
goto bail;
arg = next_arg( &rsp );
if (!arg || *arg != '*' || (arg = next_arg( &rsp )) == NULL) {
error( "IMAP error: invalid greeting response\n" );
goto bail;
}
preauth = 0;
if (!strcmp( "PREAUTH", arg ))
preauth = 1;
else if (strcmp( "OK", arg ) != 0) {
error( "IMAP error: unknown greeting response\n" );
goto bail;
}
parse_response_code( ctx, 0, rsp );
if (!ctx->caps && imap_exec( ctx, 0, "CAPABILITY" ) != RESP_OK)
goto bail;
if (!preauth) {
#ifdef HAVE_LIBSSL
if (!srvc->use_imaps && (srvc->use_sslv2 || srvc->use_sslv3 || srvc->use_tlsv1)) {
/* always try to select SSL support if available */
if (CAP(STARTTLS)) {
if (imap_exec( ctx, 0, "STARTTLS" ) != RESP_OK)
goto bail;
if (start_tls( ctx ))
goto ssl_bail;
if (imap_exec( ctx, 0, "CAPABILITY" ) != RESP_OK)
goto bail;
} else {
if (srvc->require_ssl) {
error( "IMAP error: SSL support not available\n" );
goto bail;
} else
warn( "IMAP warning: SSL support not available\n" );
}
}
#endif
info ("Logging in...\n");
if (!srvc->user) {
error( "Skipping account %s, no user\n", srvc->name );
goto bail;
}
if (!srvc->pass) {
char prompt[80];
sprintf( prompt, "Password (%s): ", srvc->name );
arg = getpass( prompt );
if (!arg) {
perror( "getpass" );
exit( 1 );
}
if (!*arg) {
error( "Skipping account %s, no password\n", srvc->name );
goto bail;
}
/*
* getpass() returns a pointer to a static buffer. make a copy
* for long term storage.
*/
srvc->pass = nfstrdup( arg );
}
#ifdef HAVE_LIBSSL
if (CAP(CRAM)) {
struct imap_cmd *cmd = new_imap_cmd();
info( "Authenticating with CRAM-MD5\n" );
cmd->param.cont = do_cram_auth;
if (imap_exec( ctx, cmd, "AUTHENTICATE CRAM-MD5" ) != RESP_OK)
goto bail;
} else if (srvc->require_cram) {
error( "IMAP error: CRAM-MD5 authentication is not supported by server\n" );
goto bail;
} else
#endif
{
if (CAP(NOLOGIN)) {
error( "Skipping account %s, server forbids LOGIN\n", srvc->name );
goto bail;
}
#ifdef HAVE_LIBSSL
if (!ctx->buf.sock.ssl)
#endif
warn( "*** IMAP Warning *** Password is being sent in the clear\n" );
if (imap_exec( ctx, 0, "LOGIN \"%s\" \"%s\"", srvc->user, srvc->pass ) != RESP_OK) {
error( "IMAP error: LOGIN failed\n" );
goto bail;
}
}
} /* !preauth */
final:
ctx->prefix = "";
if (*conf->path)
ctx->prefix = conf->path;
else if (cfg->use_namespace && CAP(NAMESPACE)) {
/* get NAMESPACE info */
if (!ctx->got_namespace) {
if (imap_exec( ctx, 0, "NAMESPACE" ) != RESP_OK) {
cb( 0, aux );
return;
}
ctx->got_namespace = 1;
}
/* XXX for now assume personal namespace */
if (is_list( ctx->ns_personal ) &&
is_list( ctx->ns_personal->child ) &&
is_atom( ctx->ns_personal->child->child ))
ctx->prefix = ctx->ns_personal->child->child->val;
}
ctx->trashnc = 1;
cb( &ctx->gen, aux );
return;
#ifdef HAVE_LIBSSL
ssl_bail:
/* This avoids that we try to send LOGOUT to an unusable socket. */
close( ctx->buf.sock.fd );
ctx->buf.sock.fd = -1;
#endif
bail:
imap_cancel_store( &ctx->gen );
cb( 0, aux );
}
static void
imap_prepare_paths( store_t *gctx )
{
free_generic_messages( gctx->msgs );
gctx->msgs = 0;
}
static void
imap_prepare_opts( store_t *gctx, int opts )
{
gctx->opts = opts;
}
static int
imap_select( store_t *gctx, int minuid, int maxuid, int *excs, int nexcs,
int (*cb)( int sts, void *aux ), void *aux )
{
imap_store_t *ctx = (imap_store_t *)gctx;
struct imap_cmd *cmd = new_imap_cmd();
const char *prefix;
int ret, i, j, bl;
char buf[1000];
if (!strcmp( gctx->name, "INBOX" )) {
prefix = "";
} else {
prefix = ctx->prefix;
}
ctx->uidnext = -1;
cmd->param.create = (gctx->opts & OPEN_CREATE) != 0;
cmd->param.trycreate = 1;
if ((ret = imap_exec_b( ctx, cmd, "SELECT \"%s%s\"", prefix, gctx->name )) != DRV_OK)
goto bail;
if (gctx->count) {
ctx->msgapp = &gctx->msgs;
sort_ints( excs, nexcs );
for (i = 0; i < nexcs; ) {
for (bl = 0; i < nexcs && bl < 960; i++) {
if (bl)
buf[bl++] = ',';
bl += sprintf( buf + bl, "%d", excs[i] );
j = i;
for (; i + 1 < nexcs && excs[i + 1] == excs[i] + 1; i++);
if (i != j)
bl += sprintf( buf + bl, ":%d", excs[i] );
}
if ((ret = imap_exec_b( ctx, 0, "UID FETCH %s (UID%s%s)", buf,
(gctx->opts & OPEN_FLAGS) ? " FLAGS" : "",
(gctx->opts & OPEN_SIZE) ? " RFC822.SIZE" : "" )) != DRV_OK)
goto bail;
}
if (maxuid == INT_MAX)
maxuid = ctx->uidnext >= 0 ? ctx->uidnext - 1 : 1000000000;
if (maxuid >= minuid &&
(ret = imap_exec_b( ctx, 0, "UID FETCH %d:%d (UID%s%s)", minuid, maxuid,
(gctx->opts & OPEN_FLAGS) ? " FLAGS" : "",
(gctx->opts & OPEN_SIZE) ? " RFC822.SIZE" : "" )) != DRV_OK)
goto bail;
}
ret = DRV_OK;
bail:
free( excs );
return cb( ret, aux );
}
static int
imap_fetch_msg( store_t *ctx, message_t *msg, msg_data_t *data,
int (*cb)( int sts, void *aux ), void *aux )
{
struct imap_cmd *cmd = new_imap_cmd();
cmd->param.uid = msg->uid;
cmd->param.aux = data;
return cb( imap_exec_m( (imap_store_t *)ctx, cmd, "UID FETCH %d (%sBODY.PEEK[])",
msg->uid, (msg->status & M_FLAGS) ? "" : "FLAGS " ), aux );
}
static int
imap_make_flags( int flags, char *buf )
{
const char *s;
unsigned i, d;
for (i = d = 0; i < as(Flags); i++)
if (flags & (1 << i)) {
buf[d++] = ' ';
buf[d++] = '\\';
for (s = Flags[i]; *s; s++)
buf[d++] = *s;
}
buf[0] = '(';
buf[d++] = ')';
return d;
}
static int
imap_flags_helper( imap_store_t *ctx, int uid, char what, int flags)
{
char buf[256];
buf[imap_make_flags( flags, buf )] = 0;
if (!submit_imap_cmd( ctx, 0, "UID STORE %d %cFLAGS.SILENT %s", uid, what, buf ))
return DRV_CANCELED;
return process_imap_replies( ctx ) == RESP_CANCEL ? DRV_CANCELED : DRV_OK;
}
static int
imap_set_flags( store_t *gctx, message_t *msg, int uid, int add, int del,
int (*cb)( int sts, void *aux ), void *aux )
{
imap_store_t *ctx = (imap_store_t *)gctx;
int ret;
if (msg) {
uid = msg->uid;
add &= ~msg->flags;
del &= msg->flags;
msg->flags |= add;
msg->flags &= ~del;
}
if ((!add || (ret = imap_flags_helper( ctx, uid, '+', add )) == DRV_OK) &&
(!del || (ret = imap_flags_helper( ctx, uid, '-', del )) == DRV_OK))
ret = DRV_OK;
return cb( ret, aux );
}
static int
imap_close( store_t *ctx,
int (*cb)( int sts, void *aux ), void *aux )
{
return cb( imap_exec_b( (imap_store_t *)ctx, 0, "CLOSE" ), aux );
}
static int
imap_trash_msg( store_t *gctx, message_t *msg,
int (*cb)( int sts, void *aux ), void *aux )
{
imap_store_t *ctx = (imap_store_t *)gctx;
struct imap_cmd *cmd = new_imap_cmd();
cmd->param.create = 1;
cmd->param.to_trash = 1;
return cb( imap_exec_m( ctx, cmd, "UID COPY %d \"%s%s\"",
msg->uid, ctx->prefix, gctx->conf->trash ), aux );
}
static int
imap_store_msg( store_t *gctx, msg_data_t *data, int to_trash,
int (*cb)( int sts, int uid, void *aux ), void *aux )
{
imap_store_t *ctx = (imap_store_t *)gctx;
struct imap_cmd *cmd = new_imap_cmd();
const char *prefix, *box;
int ret, d, uid;
char flagstr[128];
d = 0;
if (data->flags) {
d = imap_make_flags( data->flags, flagstr );
flagstr[d++] = ' ';
}
flagstr[d] = 0;
cmd->param.data_len = data->len;
cmd->param.data = data->data;
cmd->param.aux = &uid;
uid = -2;
if (to_trash) {
box = gctx->conf->trash;
prefix = ctx->prefix;
cmd->param.create = 1;
cmd->param.to_trash = 1;
} else {
box = gctx->name;
prefix = !strcmp( box, "INBOX" ) ? "" : ctx->prefix;
}
ret = imap_exec_m( ctx, cmd, "APPEND \"%s%s\" %s", prefix, box, flagstr );
if (ret != DRV_OK)
return cb( ret, -1, aux );
return cb( DRV_OK, uid, aux );
}
static int
imap_find_msg( store_t *gctx, const char *tuid,
int (*cb)( int sts, int uid, void *aux ), void *aux )
{
imap_store_t *ctx = (imap_store_t *)gctx;
struct imap_cmd *cmd = new_imap_cmd();
int ret, uid;
cmd->param.uid = -1; /* we're looking for a UID */
cmd->param.aux = &uid;
uid = -1; /* in case we get no SEARCH response at all */
if ((ret = imap_exec_m( ctx, cmd, "UID SEARCH HEADER X-TUID %." stringify(TUIDL) "s", tuid )) != DRV_OK)
return cb( ret, -1, aux );
else
return cb( uid <= 0 ? DRV_MSG_BAD : DRV_OK, uid, aux );
}
static void
imap_list( store_t *gctx,
void (*cb)( int sts, void *aux ), void *aux )
{
imap_store_t *ctx = (imap_store_t *)gctx;
cb( imap_exec_b( ctx, 0, "LIST \"\" \"%s%%\"", ctx->prefix ), aux );
}
static void
imap_cancel( store_t *gctx,
void (*cb)( void *aux ), void *aux )
{
(void)gctx;
cb( aux );
}
static void
imap_commit( store_t *gctx )
{
(void)gctx;
}
imap_server_conf_t *servers, **serverapp = &servers;
static int
imap_parse_store( conffile_t *cfg, store_conf_t **storep, int *err )
{
imap_store_conf_t *store;
imap_server_conf_t *server, *srv, sserver;
int acc_opt = 0;
if (!strcasecmp( "IMAPAccount", cfg->cmd )) {
server = nfcalloc( sizeof(*server) );
server->name = nfstrdup( cfg->val );
*serverapp = server;
serverapp = &server->next;
store = 0;
*storep = 0;
} else if (!strcasecmp( "IMAPStore", cfg->cmd )) {
store = nfcalloc( sizeof(*store) );
store->gen.driver = &imap_driver;
store->gen.name = nfstrdup( cfg->val );
store->use_namespace = 1;
*storep = &store->gen;
memset( &sserver, 0, sizeof(sserver) );
server = &sserver;
} else
return 0;
#ifdef HAVE_LIBSSL
/* this will probably annoy people, but its the best default just in
* case people forget to turn it on
*/
server->require_ssl = 1;
server->use_tlsv1 = 1;
#endif
while (getcline( cfg ) && cfg->cmd) {
if (!strcasecmp( "Host", cfg->cmd )) {
/* The imap[s]: syntax is just a backwards compat hack. */
#ifdef HAVE_LIBSSL
if (!memcmp( "imaps:", cfg->val, 6 )) {
cfg->val += 6;
server->use_imaps = 1;
server->use_sslv2 = 1;
server->use_sslv3 = 1;
} else
#endif
{
if (!memcmp( "imap:", cfg->val, 5 ))
cfg->val += 5;
}
if (!memcmp( "//", cfg->val, 2 ))
cfg->val += 2;
server->host = nfstrdup( cfg->val );
}
else if (!strcasecmp( "User", cfg->cmd ))
server->user = nfstrdup( cfg->val );
else if (!strcasecmp( "Pass", cfg->cmd ))
server->pass = nfstrdup( cfg->val );
else if (!strcasecmp( "Port", cfg->cmd ))
server->port = parse_int( cfg );
#ifdef HAVE_LIBSSL
else if (!strcasecmp( "CertificateFile", cfg->cmd )) {
server->cert_file = expand_strdup( cfg->val );
if (access( server->cert_file, R_OK )) {
error( "%s:%d: CertificateFile '%s': %s\n",
cfg->file, cfg->line, server->cert_file, strerror( errno ) );
*err = 1;
}
} else if (!strcasecmp( "RequireSSL", cfg->cmd ))
server->require_ssl = parse_bool( cfg );
else if (!strcasecmp( "UseIMAPS", cfg->cmd ))
server->use_imaps = parse_bool( cfg );
else if (!strcasecmp( "UseSSLv2", cfg->cmd ))
server->use_sslv2 = parse_bool( cfg );
else if (!strcasecmp( "UseSSLv3", cfg->cmd ))
server->use_sslv3 = parse_bool( cfg );
else if (!strcasecmp( "UseTLSv1", cfg->cmd ))
server->use_tlsv1 = parse_bool( cfg );
else if (!strcasecmp( "RequireCRAM", cfg->cmd ))
server->require_cram = parse_bool( cfg );
#endif
else if (!strcasecmp( "Tunnel", cfg->cmd ))
server->tunnel = nfstrdup( cfg->val );
else if (store) {
if (!strcasecmp( "Account", cfg->cmd )) {
for (srv = servers; srv; srv = srv->next)
if (srv->name && !strcmp( srv->name, cfg->val ))
goto gotsrv;
error( "%s:%d: unknown IMAP account '%s'\n", cfg->file, cfg->line, cfg->val );
*err = 1;
continue;
gotsrv:
store->server = srv;
} else if (!strcasecmp( "UseNamespace", cfg->cmd ))
store->use_namespace = parse_bool( cfg );
else if (!strcasecmp( "Path", cfg->cmd ))
store->gen.path = nfstrdup( cfg->val );
else
parse_generic_store( &store->gen, cfg, err );
continue;
} else {
error( "%s:%d: unknown/misplaced keyword '%s'\n", cfg->file, cfg->line, cfg->cmd );
*err = 1;
continue;
}
acc_opt = 1;
}
if (!store || !store->server) {
if (!server->tunnel && !server->host) {
if (store)
error( "IMAP store '%s' has incomplete/missing connection details\n", store->gen.name );
else
error( "IMAP account '%s' has incomplete/missing connection details\n", server->name );
*err = 1;
return 1;
}
}
if (store) {
if (!store->server) {
store->server = nfmalloc( sizeof(sserver) );
memcpy( store->server, &sserver, sizeof(sserver) );
store->server->name = store->gen.name;
} else if (acc_opt) {
error( "IMAP store '%s' has both Account and account-specific options\n", store->gen.name );
*err = 1;
}
}
return 1;
}
struct driver imap_driver = {
DRV_CRLF,
imap_parse_store,
imap_cleanup,
imap_open_store,
imap_disown_store,
imap_own_store,
imap_cancel_store,
imap_list,
imap_prepare_paths,
imap_prepare_opts,
imap_select,
imap_fetch_msg,
imap_store_msg,
imap_find_msg,
imap_set_flags,
imap_trash_msg,
imap_close,
imap_cancel,
imap_commit,
};