diff --git a/NEWS b/NEWS index 761337e..41934a5 100644 --- a/NEWS +++ b/NEWS @@ -11,6 +11,8 @@ Support for configuring a TLS cipher string was added. IMAP mailbox subscriptions are supported now. +The IMAP user query can be scripted now. + [1.3.0] Network timeout handling has been added. diff --git a/src/drv_imap.c b/src/drv_imap.c index 92008b7..3980044 100644 --- a/src/drv_imap.c +++ b/src/drv_imap.c @@ -50,6 +50,7 @@ typedef struct imap_server_conf { char *name; server_conf_t sconf; char *user; + char *user_cmd; char *pass; char *pass_cmd; int max_in_progress; @@ -1936,12 +1937,50 @@ imap_open_store_authenticate_p3( imap_store_t *ctx, imap_cmd_t *cmd ATTR_UNUSED, } #endif +static char * +cred_from_cmd( const char *cred, const char *cmd, const char *srv_name ) +{ + FILE *fp; + int ret; + char buffer[8192]; // Hopefully more than enough room for XOAUTH2, etc. tokens + + if (*cmd == '+') { + flushn(); + cmd++; + } + if (!(fp = popen( cmd, "r" ))) { + pipeerr: + sys_error( "Skipping account %s, %s failed", srv_name, cred ); + return NULL; + } + if (!fgets( buffer, sizeof(buffer), fp )) + buffer[0] = 0; + if ((ret = pclose( fp )) < 0) + goto pipeerr; + if (ret) { + if (WIFSIGNALED( ret )) + error( "Skipping account %s, %s crashed\n", srv_name, cred ); + else + error( "Skipping account %s, %s exited with status %d\n", srv_name, cred, WEXITSTATUS( ret ) ); + return NULL; + } + if (!buffer[0]) { + error( "Skipping account %s, %s produced no output\n", srv_name, cred ); + return NULL; + } + buffer[strcspn( buffer, "\n" )] = 0; /* Strip trailing newline */ + return nfstrdup( buffer ); +} + static const char * ensure_user( imap_server_conf_t *srvc ) { if (!srvc->user) { - error( "Skipping account %s, no user\n", srvc->name ); - return NULL; + if (srvc->user_cmd) { + srvc->user = cred_from_cmd( "UserCmd", srvc->user_cmd, srvc->name ); + } else { + error( "Skipping account %s, no user\n", srvc->name ); + } } return srvc->user; } @@ -1949,56 +1988,25 @@ ensure_user( imap_server_conf_t *srvc ) static const char * ensure_password( imap_server_conf_t *srvc ) { - char *cmd = srvc->pass_cmd; - - if (cmd) { - FILE *fp; - int ret; - char buffer[8192]; // Hopefully more than enough room for XOAUTH2, etc. tokens - - if (*cmd == '+') { + if (!srvc->pass) { + if (srvc->pass_cmd) { + srvc->pass = cred_from_cmd( "PassCmd", srvc->pass_cmd, srvc->name ); + } else { flushn(); - cmd++; + char prompt[80]; + sprintf( prompt, "Password (%s): ", srvc->name ); + char *pass = getpass( prompt ); + if (!pass) { + perror( "getpass" ); + exit( 1 ); + } + if (!*pass) { + error( "Skipping account %s, no password\n", srvc->name ); + return NULL; + } + /* getpass() returns a pointer to a static buffer. Make a copy for long term storage. */ + srvc->pass = nfstrdup( pass ); } - if (!(fp = popen( cmd, "r" ))) { - pipeerr: - sys_error( "Skipping account %s, password command failed", srvc->name ); - return NULL; - } - if (!fgets( buffer, sizeof(buffer), fp )) - buffer[0] = 0; - if ((ret = pclose( fp )) < 0) - goto pipeerr; - if (ret) { - if (WIFSIGNALED( ret )) - error( "Skipping account %s, password command crashed\n", srvc->name ); - else - error( "Skipping account %s, password command exited with status %d\n", srvc->name, WEXITSTATUS( ret ) ); - return NULL; - } - if (!buffer[0]) { - error( "Skipping account %s, password command produced no output\n", srvc->name ); - return NULL; - } - buffer[strcspn( buffer, "\n" )] = 0; /* Strip trailing newline */ - free( srvc->pass ); /* From previous runs */ - srvc->pass = nfstrdup( buffer ); - } else if (!srvc->pass) { - char *pass, prompt[80]; - - flushn(); - sprintf( prompt, "Password (%s): ", srvc->name ); - pass = getpass( prompt ); - if (!pass) { - perror( "getpass" ); - exit( 1 ); - } - if (!*pass) { - error( "Skipping account %s, no password\n", srvc->name ); - return NULL; - } - /* getpass() returns a pointer to a static buffer. Make a copy for long term storage. */ - srvc->pass = nfstrdup( pass ); } return srvc->pass; } @@ -2182,6 +2190,17 @@ imap_open_store_authenticate2( imap_store_t *ctx ) char saslmechs[1024], *saslend = saslmechs; #endif + // Ensure that there are no leftovers from previous runs. This is needed in case + // the credentials have a timing dependency or otherwise lose validity after use. + if (srvc->user_cmd) { + free( srvc->user ); + srvc->user = NULL; + } + if (srvc->pass_cmd) { + free( srvc->pass ); + srvc->pass = NULL; + } + info( "Logging in...\n" ); for (mech = srvc->auth_mechs; mech; mech = mech->next) { int any = !strcmp( mech->string, "*" ); @@ -3268,6 +3287,8 @@ imap_parse_store( conffile_t *cfg, store_conf_t **storep ) } else if (!strcasecmp( "User", cfg->cmd )) server->user = nfstrdup( cfg->val ); + else if (!strcasecmp( "UserCmd", cfg->cmd )) + server->user_cmd = nfstrdup( cfg->val ); else if (!strcasecmp( "Pass", cfg->cmd )) server->pass = nfstrdup( cfg->val ); else if (!strcasecmp( "PassCmd", cfg->cmd )) @@ -3431,6 +3452,11 @@ imap_parse_store( conffile_t *cfg, store_conf_t **storep ) cfg->err = 1; return 1; } + if (server->user && server->user_cmd) { + error( "%s '%s' has both User and UserCmd\n", type, name ); + cfg->err = 1; + return 1; + } if (server->pass && server->pass_cmd) { error( "%s '%s' has both Pass and PassCmd\n", type, name ); cfg->err = 1; diff --git a/src/mbsync.1 b/src/mbsync.1 index 7f9a0f9..3a98d50 100644 --- a/src/mbsync.1 +++ b/src/mbsync.1 @@ -315,6 +315,15 @@ Zero means unlimited. Specify the login name on the IMAP server. . .TP +\fBUserCmd\fR [\fB+\fR]\fIcommand\fR +Specify a shell command to obtain a user rather than specifying a +user directly. This allows you to script retrieving user names. +The command must produce exactly one line on stdout; the trailing newline +is optional. +Prepend \fB+\fR to the command to indicate that it produces TTY output +(e.g., a prompt); failure to do so will merely produce messier output. +. +.TP \fBPass\fR \fIpassword\fR Specify the password for \fIusername\fR on the IMAP server. Note that this option is \fInot\fR required.