Signed-off-by: Kevin Cernekee <cernekee at gmail.com> --- main.c | 159 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 159 insertions(+), 0 deletions(-) diff --git a/main.c b/main.c index 0506481..da8ba04 100644 --- a/main.c +++ b/main.c @@ -51,6 +51,9 @@ #ifdef LIBPROXY_HDR #include LIBPROXY_HDR #endif +#ifdef LIBSTOKEN_HDR +#include LIBSTOKEN_HDR +#endif #include <getopt.h> #include "openconnect-internal.h" @@ -66,6 +69,9 @@ static int validate_peer_cert(void *_vpninfo, const char *reason); static int process_auth_form(void *_vpninfo, struct oc_auth_form *form); +static void init_stoken(char *token_str); +static int prompt_user(const char *prompt, char *buf, int max_len, + int hide_chars); /* A sanity check that the openconnect executable is running against a library of the same version */ @@ -82,6 +88,13 @@ int do_passphrase_from_fsid; int nocertcheck; int non_inter; int cookieonly; +int use_stoken; + +#ifdef LIBSTOKEN_HDR +struct stoken_ctx *stoken_ctx; +char stoken_pin[USER_BUFLEN]; +int stoken_tries; +#endif enum { OPT_AUTHENTICATE = 0x100, @@ -111,6 +124,7 @@ enum { OPT_USERAGENT, OPT_NON_INTER, OPT_DTLS_LOCAL_PORT, + OPT_STOKEN, }; #ifdef __sun__ @@ -174,6 +188,7 @@ static struct option long_options[] = { OPTION("force-dpd", 1, OPT_FORCE_DPD), OPTION("non-inter", 0, OPT_NON_INTER), OPTION("dtls-local-port", 1, OPT_DTLS_LOCAL_PORT), + OPTION("stoken", 2, OPT_STOKEN), OPTION(NULL, 0, 0) }; @@ -274,6 +289,10 @@ static void usage(void) printf(" --no-cert-check %s\n", _("Do not require server SSL cert to be valid")); printf(" --non-inter %s\n", _("Do not expect user input; exit if it is required")); printf(" --passwd-on-stdin %s\n", _("Read password from standard input")); + printf(" --stoken[=TOKENSTRING] %s\n", _("Use software token to generate password")); +#ifndef LIBSTOKEN_HDR + printf(" %s\n", _("(NOTE: libstoken disabled in this build)")); +#endif printf(" --reconnect-timeout %s\n", _("Connection retry timeout in seconds")); printf(" --servercert=FINGERPRINT %s\n", _("Server's certificate SHA1 fingerprint")); printf(" --useragent=STRING %s\n", _("HTTP header User-Agent: field")); @@ -436,6 +455,7 @@ int main(int argc, char **argv) char *pidfile = NULL; FILE *fp = NULL; char *config_arg; + char *token_str = NULL; #ifdef ENABLE_NLS bindtextdomain("openconnect", LOCALEDIR); @@ -702,6 +722,10 @@ int main(int argc, char **argv) case OPT_DTLS_LOCAL_PORT: vpninfo->dtls_local_port = atoi(config_arg); break; + case OPT_STOKEN: + use_stoken = 1; + token_str = keep_config_arg(); + break; default: usage(); } @@ -729,6 +753,9 @@ int main(int argc, char **argv) #endif } + if (use_stoken) + init_stoken(token_str); + if (proxy && openconnect_set_http_proxy(vpninfo, strdup(proxy))) exit(1); @@ -1017,6 +1044,129 @@ static int validate_peer_cert(void *_vpninfo, OPENCONNECT_X509 *peer_cert, } } +static void init_stoken(char *token_str) +{ +#ifdef LIBSTOKEN_HDR + int ret; + char devid[USER_BUFLEN], pass[USER_BUFLEN]; + + stoken_ctx = stoken_new(); + if (!stoken_ctx) { + fprintf(stderr, _("Error initializing stoken library\n")); + exit(1); + } + + if (token_str) { + if (stoken_import_string(stoken_ctx, token_str) < 0) { + fprintf(stderr, _("Token string is invalid\n")); + exit(1); + } + } else { + if (stoken_import_rcfile(stoken_ctx, NULL) < 0) { + fprintf(stderr, _("Unable to read token configuration\n")); + exit(1); + } + } + + while (1) { + int n_prompts = 0; + + if (stoken_devid_required(stoken_ctx)) { + n_prompts++; + if (prompt_user(_("Device ID (for software token): "), + devid, USER_BUFLEN, 0) <= 0) + goto skip; + } + if (stoken_pass_required(stoken_ctx)) { + n_prompts++; + if (prompt_user(_("Password (for software token): "), + pass, USER_BUFLEN, 1) <= 0) + goto skip; + } + ret = stoken_decrypt_seed(stoken_ctx, pass, devid); + if (!ret) + break; + + if (ret == -EIO || !n_prompts) { + fprintf(stderr, _("Fatal error decrypting software token\n")); + exit(1); + } + fprintf(stderr, _("Invalid ID/password; try again\n")); + } + + while (stoken_pin_required(stoken_ctx)) { + if (prompt_user(_("PIN (for software token): "), stoken_pin, + USER_BUFLEN, 1) <= 0) + goto skip; + if (stoken_check_pin(stoken_ctx, stoken_pin) == 0) + break; + fprintf(stderr, _("Invalid PIN format; try again\n")); + } + + return; + +skip: + fprintf(stderr, _("Skipped response; disabling software token\n")); + use_stoken = 0; +#else + fprintf(stderr, _("This version of openconnect was built without libstoken support\n")); + exit(1); +#endif +} + +/* Return value: + * < 0, on error + * = 0, new tokencode is in opt->value + */ +static int try_stoken(struct openconnect_info *vpninfo, struct oc_form_opt *opt) +{ +#ifdef LIBSTOKEN_HDR + char tokencode[STOKEN_MAX_TOKENCODE + 1], + lastcode[STOKEN_MAX_TOKENCODE + 1]; + + if (stoken_compute_tokencode(stoken_ctx, time(NULL), stoken_pin, + tokencode) < 0) { + vpn_progress(vpninfo, PRG_ERR, + _("Error generating tokencode\n")); + return -1; + } + + if (stoken_tries == 0) { + /* generate tokencode immediately */ + } else if (stoken_tries == 1 && strcasestr(opt->label, "next")) { + /* + * The server has asked for the NEXT tokencode/passcode, so + * wait. We "could" compute it immediately, but the point is + * to sync up the local + remote clocks. + */ + vpn_progress(vpninfo, PRG_INFO, _("Waiting for next tokencode...\n")); + + strcpy(lastcode, tokencode); + do { + sleep(1); + if (stoken_compute_tokencode(stoken_ctx, time(NULL), stoken_pin, + tokencode) < 0) { + vpn_progress(vpninfo, PRG_ERR, + _("Error generating tokencode\n")); + return -1; + } + } while (!strcmp(tokencode, lastcode)); + } else { + /* limit the number of retries, to avoid account lockouts */ + vpn_progress(vpninfo, PRG_INFO, + _("Server is rejecting the soft token; switching to manual entry\n")); + use_stoken = 0; + return -1; + } + + stoken_tries++; + opt->value = strdup(tokencode); + return opt->value ? 0 : -1; +#else + return -1; +#endif +} + /* Return value: * < 0, on error * >=0, otherwise, indicating the length of the response @@ -1026,6 +1176,11 @@ static int prompt_user(const char *prompt, char *buf, int max_len, int hide_char struct termios t; char *p; + if (non_inter) { + fprintf(stderr, _("User input required in non-interactive mode\n")); + exit(1); + } + fprintf(stderr, "%s", prompt); fflush(stderr); @@ -1195,6 +1350,10 @@ static int process_auth_form(void *_vpninfo, vpninfo->password = NULL; if (!opt->value) goto err; + } else if (use_stoken && + !strcmp(opt->name, "password") && + try_stoken(vpninfo, opt) == 0) { + /* success; but if not, fall back to manual entry */ } else if (prompt_opt(vpninfo, opt, 1) < 0) goto err; } -- 1.7.5.4