commit 8b3bb9ade881d203b54f7495feb2c9530dcbf5be Author: Oswald Buddenhagen <o...@kde.org> Date: Sun Mar 13 11:34:04 2011 +0100
make imap_exec() result reporting callback-based this makes the IMAP command submission interface asynchronous. the imap context is now refcounted, so it can be kept alive after imap_cancel_store() is called from a callback, so it can hold a cancelation indicator which can be queried by loops (which must hold a reference, obviously). as a "side effect", properly sequence commands after CREATE resulting from [TRYCREATE]. src/drv_imap.c | 917 +++++++++++++++++++++++++++++++++++------------- 1 files changed, 677 insertions(+), 240 deletions(-) diff --git a/src/drv_imap.c b/src/drv_imap.c index 92777b7..be083a5 100644 --- a/src/drv_imap.c +++ b/src/drv_imap.c @@ -121,12 +121,22 @@ typedef struct imap_store { list_t *ns_personal, *ns_other, *ns_shared; /* NAMESPACE info */ message_t **msgapp; /* FETCH results */ unsigned caps; /* CAPABILITY results */ + int ref_count; /* for ordered destruction */ + int store_canceled; /* context is invalid, only ref_count keeps it alive */ /* command queue */ int nexttag, num_in_progress, literal_pending; struct imap_cmd *in_progress, **in_progress_append; #if HAVE_LIBSSL SSL_CTX *SSLContext; #endif + + /* Used during sequential operations like connect */ + int preauth; + union { + void (*imap_open)( store_t *srv, void *aux ); + } callbacks; + void *callback_aux; + buffer_t buf; /* this is BIG, so put it last */ } imap_store_t; @@ -138,7 +148,6 @@ struct imap_cmd { 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 */ @@ -149,6 +158,36 @@ struct imap_cmd { } param; }; +struct imap_cmd_simple { + struct imap_cmd gen; + void (*callback)( int sts, void *aux ); + void *callback_aux; +}; + +struct imap_cmd_fetch_msg { + struct imap_cmd_simple gen; + msg_data_t *msg_data; +}; + +struct imap_cmd_out_uid { + struct imap_cmd gen; + void (*callback)( int sts, int uid, void *aux ); + void *callback_aux; + int out_uid; +}; + +struct imap_cmd_refcounted_state { + void (*callback)( int sts, void *aux ); + void *callback_aux; + int ref_count; + int ret_val; +}; + +struct imap_cmd_refcounted { + struct imap_cmd gen; + struct imap_cmd_refcounted_state *state; +}; + #define CAP(cap) (ctx->caps & (1 << (cap))) enum CAPABILITY { @@ -173,11 +212,12 @@ static const char *cap_list[] = { #endif }; -#define RESP_OK 0 -#define RESP_NO 1 -#define RESP_BAD 2 +#define RESP_OK 0 +#define RESP_NO 1 +#define RESP_BAD 2 +#define RESP_CANCEL 3 -static int get_cmd_result( imap_store_t *ctx, struct imap_cmd *tcmd ); +static void get_cmd_result( imap_store_t *ctx, struct imap_cmd *tcmd ); static const char *Flags[] = { @@ -487,14 +527,31 @@ buffer_gets( buffer_t * b, char **s ) /* not reached */ } +static void +deref_store( imap_store_t *ctx ) +{ + if (!--ctx->ref_count) + free( ctx ); +} + static struct imap_cmd * -new_imap_cmd( void ) +new_imap_cmd( int size ) { - struct imap_cmd *cmd = nfmalloc( sizeof(*cmd) ); + struct imap_cmd *cmd = nfmalloc( size ); memset( &cmd->param, 0, sizeof(cmd->param) ); return cmd; } +#define INIT_IMAP_CMD(type, cmdp, cb, aux) \ + cmdp = (struct type *)new_imap_cmd( sizeof(*cmdp) ); \ + cmdp->callback = cb; \ + cmdp->callback_aux = aux; + +#define INIT_IMAP_CMD_X(type, cmdp, cb, aux) \ + cmdp = (struct type *)new_imap_cmd( sizeof(*cmdp) ); \ + cmdp->gen.callback = cb; \ + cmdp->gen.callback_aux = aux; + static struct imap_cmd * v_submit_imap_cmd( imap_store_t *ctx, struct imap_cmd *cmd, const char *fmt, va_list ap ) @@ -503,11 +560,24 @@ v_submit_imap_cmd( imap_store_t *ctx, struct imap_cmd *cmd, const char *buffmt; char buf[1024]; - while (ctx->literal_pending) + assert( ctx ); + assert( cmd ); + assert( cmd->param.done ); + + ctx->ref_count++; + + while (ctx->literal_pending) { get_cmd_result( ctx, 0 ); + if (ctx->store_canceled) { + /* ctx (and thus the queue) is invalid by now. */ + cmd->param.done( 0, cmd, RESP_CANCEL ); + goto bail2; + } + } + + if (ctx->buf.sock.fd < 0) + goto bail; /* We got disconnected and had no chance to report it yet. */ - if (!cmd) - cmd = new_imap_cmd(); cmd->tag = ++ctx->nexttag; if (fmt) nfvasprintf( &cmd->cmd, fmt, ap ); @@ -552,8 +622,11 @@ v_submit_imap_cmd( imap_store_t *ctx, struct imap_cmd *cmd, return cmd; bail: + cmd->param.done( ctx, cmd, RESP_BAD ); + bail2: free( cmd->cmd ); free( cmd ); + deref_store( ctx ); return NULL; } @@ -569,54 +642,72 @@ submit_imap_cmd( imap_store_t *ctx, struct imap_cmd *cmd, const char *fmt, ... ) return ret; } -static int -imap_exec( imap_store_t *ctx, struct imap_cmd *cmdp, const char *fmt, ... ) +static void +imap_exec( imap_store_t *ctx, struct imap_cmd *cmdp, + void (*done)( imap_store_t *ctx, struct imap_cmd *cmd, int response ), + const char *fmt, ... ) { va_list ap; + if (!cmdp) + cmdp = new_imap_cmd( sizeof(*cmdp) ); + cmdp->param.done = done; va_start( ap, fmt ); cmdp = v_submit_imap_cmd( ctx, cmdp, fmt, ap ); va_end( ap ); if (!cmdp) - return RESP_BAD; + return; - return get_cmd_result( ctx, cmdp ); + get_cmd_result( ctx, cmdp ); } -static int -imap_exec_b( imap_store_t *ctx, struct imap_cmd *cmdp, const char *fmt, ... ) +static void +transform_box_response( int *response ) { - va_list ap; + switch (*response) { + case RESP_CANCEL: *response = DRV_CANCELED; break; + case RESP_BAD: *response = DRV_STORE_BAD; break; + case RESP_NO: *response = DRV_BOX_BAD; break; + default: *response = DRV_OK; break; + } +} - va_start( ap, fmt ); - cmdp = v_submit_imap_cmd( ctx, cmdp, fmt, ap ); - va_end( ap ); - if (!cmdp) - return DRV_STORE_BAD; +static void +imap_done_simple_box( imap_store_t *ctx ATTR_UNUSED, + struct imap_cmd *cmd, int response ) +{ + struct imap_cmd_simple *cmdp = (struct imap_cmd_simple *)cmd; - switch (get_cmd_result( ctx, cmdp )) { - case RESP_BAD: return DRV_STORE_BAD; - case RESP_NO: return DRV_BOX_BAD; - default: return DRV_OK; + transform_box_response( &response ); + cmdp->callback( response, cmdp->callback_aux ); +} + +static void +transform_msg_response( int *response ) +{ + switch (*response) { + case RESP_CANCEL: *response = DRV_CANCELED; break; + case RESP_BAD: *response = DRV_STORE_BAD; break; + case RESP_NO: *response = DRV_MSG_BAD; break; + default: *response = DRV_OK; break; } } -static int -imap_exec_m( imap_store_t *ctx, struct imap_cmd *cmdp, const char *fmt, ... ) +static void +imap_done_simple_msg( imap_store_t *ctx ATTR_UNUSED, + struct imap_cmd *cmd, int response ) { - va_list ap; + struct imap_cmd_simple *cmdp = (struct imap_cmd_simple *)cmd; - va_start( ap, fmt ); - cmdp = v_submit_imap_cmd( ctx, cmdp, fmt, ap ); - va_end( ap ); - if (!cmdp) - return DRV_STORE_BAD; + transform_msg_response( &response ); + cmdp->callback( response, cmdp->callback_aux ); +} - switch (get_cmd_result( ctx, cmdp )) { - case RESP_BAD: return DRV_STORE_BAD; - case RESP_NO: return DRV_MSG_BAD; - default: return DRV_OK; - } +static void +imap_refcounted_done( struct imap_cmd_refcounted_state *sts ) +{ + sts->callback( sts->ret_val, sts->callback_aux ); + free( sts ); } /* @@ -628,12 +719,16 @@ drain_imap_replies( imap_store_t *ctx ) } */ +/* call with a ref on ctx! */ static void process_imap_replies( imap_store_t *ctx ) { while (ctx->num_in_progress > max_in_progress || - socket_pending( &ctx->buf.sock )) + socket_pending( &ctx->buf.sock )) { get_cmd_result( ctx, 0 ); + if (ctx->store_canceled) + break; + } } static int @@ -861,7 +956,7 @@ parse_fetch( imap_store_t *ctx, char *cmd ) /* move this down */ free_list( list ); return -1; gotuid: - msgdata = (msg_data_t *)cmdp->param.aux; + msgdata = ((struct imap_cmd_fetch_msg *)cmdp)->msg_data; msgdata->data = body; msgdata->len = size; if (status & M_FLAGS) @@ -929,10 +1024,11 @@ parse_response_code( imap_store_t *ctx, struct imap_cmd *cmd, char *s ) */ for (; isspace( (unsigned char)*p ); p++); error( "*** IMAP ALERT *** %s\n", p ); - } else if (cmd && cmd->param.aux && !strcmp( "APPENDUID", arg )) { + } else if (cmd && !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 ))) + !(arg = next_arg( &s )) || + !(((struct imap_cmd_out_uid *)cmd)->out_uid = atoi( arg ))) { error( "IMAP error: malformed APPENDUID status\n" ); return RESP_BAD; @@ -964,7 +1060,7 @@ parse_search( imap_store_t *ctx, char *cmd ) */ for (cmdp = ctx->in_progress; cmdp; cmdp = cmdp->next) if (cmdp->param.uid == -1) { - *(int *)cmdp->param.aux = uid; + ((struct imap_cmd_out_uid *)cmdp)->out_uid = uid; return; } error( "IMAP error: unexpected SEARCH response (UID %u)\n", uid ); @@ -996,23 +1092,27 @@ parse_list_rsp( imap_store_t *ctx, char *cmd ) add_string_list( &ctx->gen.boxes, arg ); } -static int +struct imap_cmd_trycreate { + struct imap_cmd gen; + struct imap_cmd *orig_cmd; +}; + +static void get_cmd_result_p2( imap_store_t *, struct imap_cmd *, int ); + +static void 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 )) - return RESP_BAD; - + while (!buffer_gets( &ctx->buf, &cmd )) { arg = next_arg( &cmd ); if (*arg == '*') { arg = next_arg( &cmd ); if (!arg) { error( "IMAP error: unable to parse untagged response\n" ); - return RESP_BAD; + break; } if (!strcmp( "NAMESPACE", arg )) { @@ -1035,15 +1135,15 @@ get_cmd_result( imap_store_t *ctx, struct imap_cmd *tcmd ) ctx->gen.recent = atoi( arg ); else if(!strcmp ( "FETCH", arg1 )) { if (parse_fetch( ctx, cmd )) - return RESP_BAD; + break; /* stream is likely to be useless now */ } } else { - error( "IMAP error: unable to parse untagged response\n" ); - return RESP_BAD; + 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 : "" ); - return RESP_BAD; + 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. */ @@ -1056,27 +1156,27 @@ get_cmd_result( imap_store_t *ctx, struct imap_cmd *tcmd ) free( cmdp->param.data ); cmdp->param.data = 0; if (n != (int)cmdp->param.data_len) - return RESP_BAD; + break; } else if (cmdp->param.cont) { if (cmdp->param.cont( ctx, cmdp, cmd )) - return RESP_BAD; + break; } else { error( "IMAP error: unexpected command continuation request\n" ); - return RESP_BAD; + break; } if (socket_write( &ctx->buf.sock, "\r\n", 2 ) != 2) - return RESP_BAD; + break; if (!cmdp->param.cont) ctx->literal_pending = 0; if (!tcmd) - return DRV_OK; + return; } 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 ); - return RESP_BAD; + break; gottag: if (!(*pcmdp = cmdp->next)) ctx->in_progress_append = pcmdp; @@ -1087,24 +1187,25 @@ get_cmd_result( imap_store_t *ctx, struct imap_cmd *tcmd ) if (!strcmp( "OK", arg )) { if (cmdp->param.to_trash) ctx->trashnc = 0; /* Can't get NO [TRYCREATE] any more. */ - resp = DRV_OK; + 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 */ + if (cmdp->param.create && + (cmdp->param.trycreate || + (cmd && !memcmp( cmd, "[TRYCREATE]", 11 )))) + { /* SELECT, APPEND or UID COPY */ + struct imap_cmd_trycreate *cmd2 = + (struct imap_cmd_trycreate *)new_imap_cmd( sizeof(*cmd2) ); + cmd2->orig_cmd = cmdp; + cmd2->gen.param.done = get_cmd_result_p2; + ctx->ref_count++; p = strchr( cmdp->cmd, '"' ); - if (!submit_imap_cmd( ctx, 0, "CREATE %.*s", strchr( p + 1, '"' ) - p + 1, p )) { - resp = RESP_BAD; - 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_BAD; - goto normal; - } + submit_imap_cmd( ctx, &cmd2->gen, "CREATE %.*s", strchr( p + 1, '"' ) - p + 1, p ); + if (ctx->store_canceled) + tcmd = 0; + deref_store( ctx ); if (!tcmd) - return 0; /* ignored */ + return; continue; } resp = RESP_NO; @@ -1116,17 +1217,52 @@ get_cmd_result( imap_store_t *ctx, struct imap_cmd *tcmd ) } if ((resp2 = parse_response_code( ctx, cmdp, cmd )) > resp) resp = resp2; - normal: - if (cmdp->param.done) - cmdp->param.done( ctx, cmdp, resp ); + cmdp->param.done( ctx, cmdp, resp ); free( cmdp->param.data ); free( cmdp->cmd ); free( cmdp ); + if (ctx->store_canceled) + tcmd = 0; + deref_store( ctx ); if (!tcmd || tcmd == cmdp) - return resp; + return; } } - /* not reached */ + /* Piggy-back the stream error on the next pending command. */ + if ((cmdp = ctx->in_progress)) { + ctx->in_progress = cmdp->next; /* We're dead, so don't update in_progress_append */ + ctx->num_in_progress--; + cmdp->param.done( ctx, cmdp, RESP_BAD ); + free( cmdp->param.data ); + free( cmdp->cmd ); + free( cmdp ); + deref_store( ctx ); + return; + } + /* Made no callback, so let the next command submission fail. */ + if (ctx->buf.sock.fd != -1) { + close( ctx->buf.sock.fd ); + ctx->buf.sock.fd = -1; + } +} + +static void +get_cmd_result_p2( imap_store_t *ctx, struct imap_cmd *cmd, int response ) +{ + struct imap_cmd_trycreate *cmdp = (struct imap_cmd_trycreate *)cmd; + struct imap_cmd *ocmd = cmdp->orig_cmd; + + if (response != RESP_OK) { + ocmd->param.done( ctx, ocmd, response ); + free( ocmd->param.data ); + free( ocmd->cmd ); + free( ocmd ); + deref_store( ctx ); + } else { + ctx->uidnext = 0; + ocmd->param.create = 0; + submit_imap_cmd( ctx, ocmd, 0 ); + } } static void @@ -1151,7 +1287,8 @@ imap_cancel_store( store_t *gctx ) free_list( ctx->ns_personal ); free_list( ctx->ns_other ); free_list( ctx->ns_shared ); - free( ctx ); + ctx->store_canceled = 1; + deref_store( ctx ); } static store_t *unowned; @@ -1178,6 +1315,8 @@ imap_own_store( store_conf_t *conf ) return 0; } +static void imap_cleanup_p2( imap_store_t *, struct imap_cmd *, int ); + static void imap_cleanup( void ) { @@ -1185,11 +1324,17 @@ imap_cleanup( void ) for (ctx = unowned; ctx; ctx = nctx) { nctx = ctx->next; - imap_exec( (imap_store_t *)ctx, 0, "LOGOUT" ); - imap_cancel_store( ctx ); + imap_exec( (imap_store_t *)ctx, 0, imap_cleanup_p2, "LOGOUT" ); } } +static void +imap_cleanup_p2( imap_store_t *ctx, + struct imap_cmd *cmd ATTR_UNUSED, int response ATTR_UNUSED ) +{ + imap_cancel_store( &ctx->gen ); +} + #ifdef HAVE_LIBSSL static int start_tls( imap_store_t *ctx ) @@ -1298,6 +1443,19 @@ do_cram_auth( imap_store_t *ctx, struct imap_cmd *cmdp, const char *prompt ) } #endif +static void imap_open_store_p2( imap_store_t *, struct imap_cmd *, int ); +static void imap_open_store_authenticate( imap_store_t * ); +static void imap_open_store_authenticate_p2( imap_store_t *, struct imap_cmd *, int ); +static void imap_open_store_authenticate_p3( imap_store_t *, struct imap_cmd *, int ); +static void imap_open_store_authenticate2( imap_store_t * ); +static void imap_open_store_authenticate2_p2( imap_store_t *, struct imap_cmd *, int ); +static void imap_open_store_namespace( imap_store_t * ); +static void imap_open_store_namespace_p2( imap_store_t *, struct imap_cmd *, int ); +static void imap_open_store_namespace2( imap_store_t * ); +static void imap_open_store_finalize( imap_store_t * ); +static void imap_open_store_ssl_bail( imap_store_t * ); +static void imap_open_store_bail( imap_store_t * ); + static void imap_open_store( store_conf_t *conf, void (*cb)( store_t *srv, void *aux ), void *aux ) @@ -1309,7 +1467,7 @@ imap_open_store( store_conf_t *conf, char *arg, *rsp; struct hostent *he; struct sockaddr_in addr; - int s, a[2], preauth; + int s, a[2]; for (ctxp = &unowned; (ctx = (imap_store_t *)*ctxp); ctxp = &ctx->gen.next) if (((imap_store_conf_t *)ctx->gen.conf)->server == srvc) { @@ -1320,12 +1478,18 @@ imap_open_store( store_conf_t *conf, ctx->gen.boxes = 0; ctx->gen.listed = 0; ctx->gen.conf = conf; - goto final; + ctx->callbacks.imap_open = cb; + ctx->callback_aux = aux; + imap_open_store_namespace( ctx ); + return; } ctx = nfcalloc( sizeof(*ctx) ); + ctx->ref_count = 1; ctx->gen.conf = conf; ctx->buf.sock.fd = -1; + ctx->callbacks.imap_open = cb; + ctx->callback_aux = aux; ctx->in_progress_append = &ctx->in_progress; /* open connection to IMAP server */ @@ -1389,8 +1553,10 @@ imap_open_store( store_conf_t *conf, #if HAVE_LIBSSL if (srvc->use_imaps) { - if (start_tls( ctx )) - goto ssl_bail; + if (start_tls( ctx )) { + imap_open_store_ssl_bail( ctx ); + return; + } } #endif @@ -1402,121 +1568,217 @@ imap_open_store( store_conf_t *conf, error( "IMAP error: invalid greeting response\n" ); goto bail; } - preauth = 0; if (!strcmp( "PREAUTH", arg )) - preauth = 1; + ctx->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 (!ctx->caps) + imap_exec( ctx, 0, imap_open_store_p2, "CAPABILITY" ); + else + imap_open_store_authenticate( ctx ); + return; + + bail: + imap_open_store_bail( ctx ); +} - if (!preauth) { +static void +imap_open_store_p2( imap_store_t *ctx, struct imap_cmd *cmd ATTR_UNUSED, int response ) +{ + if (response != RESP_OK) + imap_open_store_bail( ctx ); + else + imap_open_store_authenticate( ctx ); +} + +static void +imap_open_store_authenticate( imap_store_t *ctx ) +{ + if (!ctx->preauth) { #if HAVE_LIBSSL + imap_store_conf_t *cfg = (imap_store_conf_t *)ctx->gen.conf; + imap_server_conf_t *srvc = cfg->server; + 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; + imap_exec( ctx, 0, imap_open_store_authenticate_p2, "STARTTLS" ); + return; } else { if (srvc->require_ssl) { error( "IMAP error: SSL support not available\n" ); - goto bail; - } else + imap_open_store_bail( ctx ); + return; + } else { warn( "IMAP warning: SSL support not available\n" ); + } } } #endif + imap_open_store_authenticate2( ctx ); + } else { + imap_open_store_namespace( ctx ); + } +} - info ("Logging in...\n"); - if (!srvc->user) { - error( "Skipping account %s, no user\n", srvc->name ); - goto bail; +#if HAVE_LIBSSL +static void +imap_open_store_authenticate_p2( imap_store_t *ctx, struct imap_cmd *cmd ATTR_UNUSED, int response ) +{ + if (response != RESP_OK) + imap_open_store_bail( ctx ); + else if (start_tls( ctx )) + imap_open_store_ssl_bail( ctx ); + else + imap_exec( ctx, 0, imap_open_store_authenticate_p3, "CAPABILITY" ); +} + +static void +imap_open_store_authenticate_p3( imap_store_t *ctx, struct imap_cmd *cmd ATTR_UNUSED, int response ) +{ + if (response != RESP_OK) + imap_open_store_bail( ctx ); + else + imap_open_store_authenticate2( ctx ); +} +#endif + +static void +imap_open_store_authenticate2( imap_store_t *ctx ) +{ + imap_store_conf_t *cfg = (imap_store_conf_t *)ctx->gen.conf; + imap_server_conf_t *srvc = cfg->server; + char *arg; + + 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 (!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 ); + 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 ); + } #if HAVE_LIBSSL - if (CAP(CRAM)) { - struct imap_cmd *cmd = new_imap_cmd(); + if (CAP(CRAM)) { + struct imap_cmd *cmd = new_imap_cmd( sizeof(*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 + info( "Authenticating with CRAM-MD5\n" ); + cmd->param.cont = do_cram_auth; + imap_exec( ctx, cmd, imap_open_store_authenticate2_p2, "AUTHENTICATE CRAM-MD5" ); + return; + } + if (srvc->require_cram) { + error( "IMAP error: CRAM-MD5 authentication is not supported by server\n" ); + goto bail; + } #endif - { - if (CAP(NOLOGIN)) { - error( "Skipping account %s, server forbids LOGIN\n", srvc->name ); - goto bail; - } + if (CAP(NOLOGIN)) { + error( "Skipping account %s, server forbids LOGIN\n", srvc->name ); + goto bail; + } #if HAVE_LIBSSL - if (!ctx->buf.sock.use_ssl) + if (!ctx->buf.sock.use_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 */ + warn( "*** IMAP Warning *** Password is being sent in the clear\n" ); + imap_exec( ctx, 0, imap_open_store_authenticate2_p2, + "LOGIN \"%s\" \"%s\"", srvc->user, srvc->pass ); + return; + + bail: + imap_open_store_bail( ctx ); +} + +static void +imap_open_store_authenticate2_p2( imap_store_t *ctx, struct imap_cmd *cmd ATTR_UNUSED, int response ) +{ + if (response != RESP_OK) + imap_open_store_bail( ctx ); + else + imap_open_store_namespace( ctx ); +} + +static void +imap_open_store_namespace( imap_store_t *ctx ) +{ + imap_store_conf_t *cfg = (imap_store_conf_t *)ctx->gen.conf; - final: ctx->prefix = ""; - if (*conf->path) - ctx->prefix = conf->path; + if (*cfg->gen.path) + ctx->prefix = cfg->gen.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; + if (!ctx->got_namespace) + imap_exec( ctx, 0, imap_open_store_namespace_p2, "NAMESPACE" ); + else + imap_open_store_namespace2( ctx ); + return; } + imap_open_store_finalize( ctx ); +} + +static void +imap_open_store_namespace_p2( imap_store_t *ctx, struct imap_cmd *cmd ATTR_UNUSED, int response ) +{ + if (response != RESP_OK) { + imap_open_store_bail( ctx ); + } else { + ctx->got_namespace = 1; + imap_open_store_namespace2( ctx ); + } +} + +static void +imap_open_store_namespace2( imap_store_t *ctx ) +{ + /* 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; + imap_open_store_finalize( ctx ); +} + +static void +imap_open_store_finalize( imap_store_t *ctx ) +{ ctx->trashnc = 1; - cb( &ctx->gen, aux ); - return; + ctx->callbacks.imap_open( &ctx->gen, ctx->callback_aux ); +} #if HAVE_LIBSSL - ssl_bail: +static void +imap_open_store_ssl_bail( imap_store_t *ctx ) +{ /* This avoids that we try to send LOGOUT to an unusable socket. */ close( ctx->buf.sock.fd ); ctx->buf.sock.fd = -1; + imap_open_store_bail( ctx ); +} #endif - bail: + +static void +imap_open_store_bail( imap_store_t *ctx ) +{ + void (*cb)( store_t *srv, void *aux ) = ctx->callbacks.imap_open; + void *aux = ctx->callback_aux; imap_cancel_store( &ctx->gen ); cb( 0, aux ); } @@ -1534,16 +1796,23 @@ imap_prepare_opts( store_t *gctx, int opts ) gctx->opts = opts; } +struct imap_cmd_select { + struct imap_cmd_simple gen; + int minuid, maxuid, *excs, nexcs; +}; + +static void imap_select_p2( imap_store_t *, struct imap_cmd *, int ); +static void imap_submit_select2( imap_store_t *, const char *, struct imap_cmd_refcounted_state *, + struct imap_cmd_refcounted ** ); +static void imap_select2_p2( imap_store_t *, struct imap_cmd *, int ); + static void imap_select( store_t *gctx, int minuid, int maxuid, int *excs, int nexcs, void (*cb)( int sts, void *aux ), void *aux ) { imap_store_t *ctx = (imap_store_t *)gctx; - struct imap_cmd *cmd = new_imap_cmd(); + struct imap_cmd_select *cmd; const char *prefix; - int ret, i, j, bl; - char buf[1000]; - if (!strcmp( gctx->name, "INBOX" )) { prefix = ""; @@ -1553,56 +1822,130 @@ imap_select( store_t *gctx, int minuid, int maxuid, int *excs, int nexcs, 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; + INIT_IMAP_CMD_X(imap_cmd_select, cmd, cb, aux) + cmd->gen.gen.param.create = (gctx->opts & OPEN_CREATE) != 0; + cmd->gen.gen.param.trycreate = 1; + cmd->minuid = minuid; + cmd->maxuid = maxuid; + cmd->excs = excs; + cmd->nexcs = nexcs; + imap_exec( ctx, &cmd->gen.gen, imap_select_p2, + "SELECT \"%s%s\"", prefix, gctx->name ); +} - if (gctx->count) { - ctx->msgapp = &gctx->msgs; - sort_ints( excs, nexcs ); - for (i = 0; i < nexcs; ) { - for (bl = 0; i < nexcs && bl < 960; i++) { +static void +imap_select_p2( imap_store_t *ctx, struct imap_cmd *cmd, int response ) +{ + struct imap_cmd_select *cmdp = (struct imap_cmd_select *)cmd; + int i, j, bl; + char buf[1000]; + + transform_box_response( &response ); + if (response != DRV_OK || !ctx->gen.count) { + free( cmdp->excs ); + cmdp->gen.callback( response, cmdp->gen.callback_aux ); + } else { + struct imap_cmd_refcounted *cmd2 = 0; + struct imap_cmd_refcounted_state *sts = nfmalloc( sizeof(*sts) ); + sts->callback = cmdp->gen.callback; + sts->callback_aux = cmdp->gen.callback_aux; + sts->ref_count = 1; /* so forced sync does not cause an early exit */ + sts->ret_val = DRV_OK; + ctx->ref_count++; + + ctx->msgapp = &ctx->gen.msgs; + sort_ints( cmdp->excs, cmdp->nexcs ); + for (i = 0; i < cmdp->nexcs; ) { + for (bl = 0; i < cmdp->nexcs && bl < 960; i++) { if (bl) buf[bl++] = ','; - bl += sprintf( buf + bl, "%d", excs[i] ); + bl += sprintf( buf + bl, "%d", cmdp->excs[i] ); j = i; - for (; i + 1 < nexcs && excs[i + 1] == excs[i] + 1; i++); + for (; i + 1 < cmdp->nexcs && cmdp->excs[i + 1] == cmdp->excs[i] + 1; i++); if (i != j) - bl += sprintf( buf + bl, ":%d", excs[i] ); + bl += sprintf( buf + bl, ":%d", cmdp->excs[i] ); + } + imap_submit_select2( ctx, buf, sts, &cmd2 ); + if (ctx->store_canceled) { + deref_store( ctx ); + free( cmdp->excs ); + return; } - 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; + if (cmdp->maxuid == INT_MAX) + cmdp->maxuid = ctx->uidnext >= 0 ? ctx->uidnext - 1 : 1000000000; + if (cmdp->maxuid >= cmdp->minuid) { + sprintf( buf, "%d:%d", cmdp->minuid, cmdp->maxuid ); + imap_submit_select2( ctx, buf, sts, &cmd2 ); + if (ctx->store_canceled) { + deref_store( ctx ); + free( cmdp->excs ); + return; + } + } + deref_store( ctx ); + free( cmdp->excs ); + if (!--sts->ref_count) + imap_refcounted_done( sts ); + else + get_cmd_result( ctx, &cmd2->gen ); } +} + +static void +imap_submit_select2( imap_store_t *ctx, const char *buf, struct imap_cmd_refcounted_state *sts, + struct imap_cmd_refcounted **cmdp ) +{ + struct imap_cmd_refcounted *cmd = (struct imap_cmd_refcounted *)new_imap_cmd( sizeof(*cmd) ); + cmd->gen.param.done = imap_select2_p2; + cmd->state = sts; + sts->ref_count++; + *cmdp = cmd; + submit_imap_cmd( ctx, &cmd->gen, + "UID FETCH %s (UID%s%s)", buf, + (ctx->gen.opts & OPEN_FLAGS) ? " FLAGS" : "", + (ctx->gen.opts & OPEN_SIZE) ? " RFC822.SIZE" : "" ); +} - ret = DRV_OK; +static void +imap_select2_p2( imap_store_t *ctx ATTR_UNUSED, struct imap_cmd *cmd, int response ) +{ + struct imap_cmd_refcounted_state *sts = ((struct imap_cmd_refcounted *)cmd)->state; - bail: - free( excs ); - cb( ret, aux ); + switch (response) { + case RESP_CANCEL: + /* sts was already free()d by the BAD callback. We can do that, because we know + * that our fetches are the only commands in flight, so nothing else can cause a + * cancelation. */ + return; + case RESP_BAD: + sts->ret_val = DRV_STORE_BAD; + imap_refcounted_done( sts ); + return; + case RESP_NO: + sts->ret_val = DRV_BOX_BAD; + break; + } + if (!--sts->ref_count) + imap_refcounted_done( sts ); } static void imap_fetch_msg( store_t *ctx, message_t *msg, msg_data_t *data, void (*cb)( int sts, void *aux ), void *aux ) { - struct imap_cmd *cmd = new_imap_cmd(); - cmd->param.uid = msg->uid; - cmd->param.aux = data; - cb( imap_exec_m( (imap_store_t *)ctx, cmd, "UID FETCH %d (%sBODY.PEEK[])", - msg->uid, (msg->status & M_FLAGS) ? "" : "FLAGS " ), aux ); + struct imap_cmd_fetch_msg *cmd; + + INIT_IMAP_CMD_X(imap_cmd_fetch_msg, cmd, cb, aux) + cmd->gen.gen.param.uid = msg->uid; + cmd->msg_data = data; + imap_exec( (imap_store_t *)ctx, &cmd->gen.gen, imap_done_simple_msg, + "UID FETCH %d (%sBODY.PEEK[])", + msg->uid, (msg->status & M_FLAGS) ? "" : "FLAGS " ); } +static void imap_set_flags_p2( imap_store_t *, struct imap_cmd *, int ); + static int imap_make_flags( int flags, char *buf ) { @@ -1622,15 +1965,18 @@ imap_make_flags( int flags, char *buf ) } static int -imap_flags_helper( imap_store_t *ctx, int uid, char what, int flags) +imap_flags_helper( imap_store_t *ctx, int uid, char what, int flags, + struct imap_cmd_refcounted_state *sts ) { char buf[256]; + struct imap_cmd_refcounted *cmd = (struct imap_cmd_refcounted *)new_imap_cmd( sizeof(*cmd) ); + cmd->gen.param.done = imap_set_flags_p2; + cmd->state = sts; + sts->ref_count++; buf[imap_make_flags( flags, buf )] = 0; - if (!submit_imap_cmd( ctx, 0, "UID STORE %d %cFLAGS.SILENT %s", uid, what, buf )) - return DRV_STORE_BAD; - process_imap_replies( ctx ); - return DRV_OK; + submit_imap_cmd( ctx, &cmd->gen, "UID STORE %d %cFLAGS.SILENT %s", uid, what, buf ); + return ctx->store_canceled; } static void @@ -1638,7 +1984,6 @@ imap_set_flags( store_t *gctx, message_t *msg, int uid, int add, int del, void (*cb)( int sts, void *aux ), void *aux ) { imap_store_t *ctx = (imap_store_t *)gctx; - int ret; if (msg) { uid = msg->uid; @@ -1647,17 +1992,66 @@ imap_set_flags( store_t *gctx, message_t *msg, int uid, int add, int del, 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; - cb( ret, aux ); + + if (add || del) { + struct imap_cmd_refcounted_state *sts = nfmalloc( sizeof(*sts) ); + sts->callback = cb; + sts->callback_aux = aux; + sts->ref_count = 1; /* so forced sync does not cause an early exit */ + sts->ret_val = DRV_OK; + ctx->ref_count++; + if ((add && imap_flags_helper( ctx, uid, '+', add, sts )) || + (del && imap_flags_helper( ctx, uid, '-', del, sts ))) + { + deref_store( ctx ); + if (!--sts->ref_count) + free( sts ); + return; + } + if (!--sts->ref_count) + imap_refcounted_done( sts ); + else + process_imap_replies( ctx ); + deref_store( ctx ); + } else { + cb( DRV_OK, aux ); + } +} + +static void +imap_set_flags_p2( imap_store_t *ctx ATTR_UNUSED, struct imap_cmd *cmd, int response ) +{ + struct imap_cmd_refcounted_state *sts = ((struct imap_cmd_refcounted *)cmd)->state; + switch (response) { + case RESP_CANCEL: + if (sts->ret_val == DRV_STORE_BAD) + goto badout; /* Our own command's bad-store callback caused cancelation. */ + sts->ret_val = DRV_CANCELED; /* Unrelated command caused cancelation. */ + break; + case RESP_BAD: + sts->ret_val = DRV_STORE_BAD; + sts->callback( sts->ret_val, sts->callback_aux ); + badout: + if (!--sts->ref_count) + free( sts ); + return; + case RESP_NO: + if (sts->ret_val == DRV_OK) /* Don't override cancelation. */ + sts->ret_val = DRV_MSG_BAD; + break; + } + if (!--sts->ref_count) + imap_refcounted_done( sts ); } static void imap_close( store_t *ctx, void (*cb)( int sts, void *aux ), void *aux ) { - cb( imap_exec_b( (imap_store_t *)ctx, 0, "CLOSE" ), aux ); + struct imap_cmd_simple *cmd; + + INIT_IMAP_CMD(imap_cmd_simple, cmd, cb, aux) + imap_exec( (imap_store_t *)ctx, &cmd->gen, imap_done_simple_box, "CLOSE" ); } static void @@ -1665,21 +2059,26 @@ imap_trash_msg( store_t *gctx, message_t *msg, void (*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; - cb( imap_exec_m( ctx, cmd, "UID COPY %d \"%s%s\"", - msg->uid, ctx->prefix, gctx->conf->trash ), aux ); + struct imap_cmd_simple *cmd; + + INIT_IMAP_CMD(imap_cmd_simple, cmd, cb, aux) + cmd->gen.param.create = 1; + cmd->gen.param.to_trash = 1; + imap_exec( ctx, &cmd->gen, imap_done_simple_msg, + "UID COPY %d \"%s%s\"", + msg->uid, ctx->prefix, gctx->conf->trash ); } +static void imap_store_msg_p2( imap_store_t *, struct imap_cmd *, int ); + static void imap_store_msg( store_t *gctx, msg_data_t *data, int to_trash, void (*cb)( int sts, int uid, void *aux ), void *aux ) { imap_store_t *ctx = (imap_store_t *)gctx; - struct imap_cmd *cmd = new_imap_cmd(); + struct imap_cmd_out_uid *cmd; const char *prefix, *box; - int ret, d, uid; + int d; char flagstr[128]; d = 0; @@ -1689,53 +2088,91 @@ imap_store_msg( store_t *gctx, msg_data_t *data, int to_trash, } flagstr[d] = 0; - cmd->param.data_len = data->len; - cmd->param.data = data->data; - cmd->param.aux = &uid; - uid = -2; + INIT_IMAP_CMD(imap_cmd_out_uid, cmd, cb, aux) + cmd->gen.param.data_len = data->len; + cmd->gen.param.data = data->data; + cmd->out_uid = -1; if (to_trash) { box = gctx->conf->trash; prefix = ctx->prefix; - cmd->param.create = 1; - cmd->param.to_trash = 1; + cmd->gen.param.create = 1; + cmd->gen.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) - uid = -1; - cb( ret, uid, aux ); + imap_exec( ctx, &cmd->gen, imap_store_msg_p2, + "APPEND \"%s%s\" %s", prefix, box, flagstr ); +} + +static void +imap_store_msg_p2( imap_store_t *ctx ATTR_UNUSED, struct imap_cmd *cmd, int response ) +{ + struct imap_cmd_out_uid *cmdp = (struct imap_cmd_out_uid *)cmd; + + transform_msg_response( &response ); + cmdp->callback( response, cmdp->out_uid, cmdp->callback_aux ); } +static void imap_find_msg_p2( imap_store_t *, struct imap_cmd *, int ); + static void imap_find_msg( store_t *gctx, const char *tuid, void (*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) - cb( ret, -1, aux ); + struct imap_cmd_out_uid *cmd; + + INIT_IMAP_CMD(imap_cmd_out_uid, cmd, cb, aux) + cmd->gen.param.uid = -1; /* we're looking for a UID */ + cmd->out_uid = -1; /* in case we get no SEARCH response at all */ + imap_exec( ctx, &cmd->gen, imap_find_msg_p2, + "UID SEARCH HEADER X-TUID %." stringify(TUIDL) "s", tuid ); +} + +static void +imap_find_msg_p2( imap_store_t *ctx ATTR_UNUSED, struct imap_cmd *cmd, int response ) +{ + struct imap_cmd_out_uid *cmdp = (struct imap_cmd_out_uid *)cmd; + + transform_msg_response( &response ); + if (response != DRV_OK) + cmdp->callback( response, -1, cmdp->callback_aux ); else - cb( uid <= 0 ? DRV_MSG_BAD : DRV_OK, uid, aux ); + cmdp->callback( cmdp->out_uid <= 0 ? DRV_MSG_BAD : DRV_OK, + cmdp->out_uid, cmdp->callback_aux ); } +struct imap_cmd_list { + struct imap_cmd gen; + void (*callback)( int sts, void *aux ); + void *callback_aux; +}; + +static void imap_list_p2( imap_store_t *, struct imap_cmd *, int ); + static void imap_list( store_t *gctx, void (*cb)( int sts, void *aux ), void *aux ) { imap_store_t *ctx = (imap_store_t *)gctx; - int ret; + struct imap_cmd_list *cmd; + + INIT_IMAP_CMD(imap_cmd_list, cmd, cb, aux) + imap_exec( ctx, &cmd->gen, imap_list_p2, + "LIST \"\" \"%s%%\"", ctx->prefix ); +} + +static void +imap_list_p2( imap_store_t *ctx, struct imap_cmd *cmd, int response ) +{ + struct imap_cmd_list *cmdp = (struct imap_cmd_list *)cmd; - if ((ret = imap_exec_b( ctx, 0, "LIST \"\" \"%s%%\"", ctx->prefix )) == DRV_OK) - gctx->listed = 1; - cb( ret, aux ); + transform_box_response( &response ); + if (response == DRV_OK) + ctx->gen.listed = 1; + cmdp->callback( response, cmdp->callback_aux ); } static void ------------------------------------------------------------------------------ Colocation vs. Managed Hosting A question and answer guide to determining the best fit for your organization - today and in the future. http://p.sf.net/sfu/internap-sfd2d _______________________________________________ isync-devel mailing list isync-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/isync-devel