diff --git a/Makefile.am b/Makefile.am index 68452fcd..319ac905 100644 --- a/Makefile.am +++ b/Makefile.am @@ -27,11 +27,15 @@ lib_srcs_posix = tun.c lib_srcs_gssapi = gssapi.c lib_srcs_iconv = iconv.c lib_srcs_oath = oath.c +lib_srcs_stoken = stoken.c POTFILES = $(openconnect_SOURCES) $(lib_srcs_openssl) $(lib_srcs_gnutls) \ $(library_srcs) $(lib_srcs_win32) $(lib_srcs_posix) $(lib_srcs_gssapi) \ - $(lib_srcs_iconv) $(lib_srcs_oath) openconnect-internal.h + $(lib_srcs_iconv) $(lib_srcs_oath) $(lib_srcs_stoken) openconnect-internal.h +if OPENCONNECT_STOKEN +library_srcs += $(lib_srcs_stoken) +endif if OPENCONNECT_OATH library_srcs += $(lib_srcs_oath) endif diff --git a/auth.c b/auth.c index a2d267c7..85cf578e 100644 --- a/auth.c +++ b/auth.c @@ -27,10 +27,6 @@ #include #include -#ifdef HAVE_LIBSTOKEN -#include -#endif - #ifdef HAVE_LIBOATH #include #endif @@ -677,7 +673,7 @@ int parse_xml_response(struct openconnect_info *vpninfo, char *response, struct return ret; } -static void nuke_opt_values(struct oc_form_opt *opt) +void nuke_opt_values(struct oc_form_opt *opt) { for (; opt; opt = opt->next) { if (opt->type == OC_FORM_OPT_TEXT || @@ -1024,297 +1020,6 @@ static int xmlpost_append_form_opts(struct openconnect_info *vpninfo, return -ENOMEM; } -#ifdef HAVE_LIBSTOKEN - -#ifndef STOKEN_CHECK_VER -#define STOKEN_CHECK_VER(x,y) 0 -#endif - -/* - * A SecurID token can be encrypted with a device ID, a password, both, - * or neither. Gather the required information, decrypt the token, and - * check the hash to make sure it is sane. - * - * Return value: - * < 0, on error - * = 0, on success - * = 1, if the user cancelled the form submission - * = 2, if the user left the entire form blank and clicked OK - */ -static int decrypt_stoken(struct openconnect_info *vpninfo) -{ - struct oc_auth_form form; - struct oc_form_opt opts[2], *opt = opts; - char **devid = NULL, **pass = NULL; - int ret = 0; - - memset(&form, 0, sizeof(form)); - memset(&opts, 0, sizeof(opts)); - - form.opts = opts; - form.message = _("Enter credentials to unlock software token."); - - if (stoken_devid_required(vpninfo->stoken_ctx)) { - opt->type = OC_FORM_OPT_TEXT; - opt->name = (char *)"devid"; - opt->label = _("Device ID:"); - devid = &opt->value; - opt++; - } - if (stoken_pass_required(vpninfo->stoken_ctx)) { - opt->type = OC_FORM_OPT_PASSWORD; - opt->name = (char *)"password"; - opt->label = _("Password:"); - pass = &opt->value; - opt++; - } - - opts[0].next = opts[1].type ? &opts[1] : NULL; - - while (1) { - nuke_opt_values(opts); - - if (!opts[0].type) { - /* don't bug the user if there's nothing to enter */ - ret = 0; - } else { - int some_empty = 0, all_empty = 1; - - /* < 0 for error; 1 if cancelled */ - ret = process_auth_form(vpninfo, &form); - if (ret) - break; - - for (opt = opts; opt; opt = opt->next) { - if (!opt->value || !strlen(opt->value)) - some_empty = 1; - else - all_empty = 0; - } - if (all_empty) { - vpn_progress(vpninfo, PRG_INFO, - _("User bypassed soft token.\n")); - ret = 2; - break; - } - if (some_empty) { - vpn_progress(vpninfo, PRG_INFO, - _("All fields are required; try again.\n")); - continue; - } - } - - ret = stoken_decrypt_seed(vpninfo->stoken_ctx, - pass ? *pass : NULL, - devid ? *devid : NULL); - if (ret == -EIO || (ret && !devid && !pass)) { - vpn_progress(vpninfo, PRG_ERR, - _("General failure in libstoken.\n")); - break; - } else if (ret != 0) { - vpn_progress(vpninfo, PRG_INFO, - _("Incorrect device ID or password; try again.\n")); - continue; - } - - vpn_progress(vpninfo, PRG_DEBUG, _("Soft token init was successful.\n")); - ret = 0; - break; - } - - nuke_opt_values(opts); - return ret; -} - -static void get_stoken_details(struct openconnect_info *vpninfo) -{ -#if STOKEN_CHECK_VER(1,3) - struct stoken_info *info = stoken_get_info(vpninfo->stoken_ctx); - - if (info) { - vpninfo->stoken_concat_pin = !info->uses_pin; - vpninfo->stoken_interval = info->interval; - return; - } -#endif - vpninfo->stoken_concat_pin = 0; - vpninfo->stoken_interval = 60; -} - -/* - * Return value: - * < 0, on error - * = 0, on success - * = 1, if the user cancelled the form submission - */ -static int request_stoken_pin(struct openconnect_info *vpninfo) -{ - struct oc_auth_form form; - struct oc_form_opt opts[1], *opt = opts; - int ret = 0; - - if (!vpninfo->stoken_concat_pin && !stoken_pin_required(vpninfo->stoken_ctx)) - return 0; - - memset(&form, 0, sizeof(form)); - memset(&opts, 0, sizeof(opts)); - - form.opts = opts; - form.message = _("Enter software token PIN."); - - opt->type = OC_FORM_OPT_PASSWORD; - opt->name = (char *)"password"; - opt->label = _("PIN:"); - opt->flags = OC_FORM_OPT_NUMERIC; - - while (1) { - char *pin; - - nuke_opt_values(opts); - - /* < 0 for error; 1 if cancelled */ - ret = process_auth_form(vpninfo, &form); - if (ret) - break; - - pin = opt->value; - if (!pin || !strlen(pin)) { - /* in some cases there really is no PIN */ - if (vpninfo->stoken_concat_pin) - return 0; - - vpn_progress(vpninfo, PRG_INFO, - _("All fields are required; try again.\n")); - continue; - } - - if (!vpninfo->stoken_concat_pin && - stoken_check_pin(vpninfo->stoken_ctx, pin) != 0) { - vpn_progress(vpninfo, PRG_INFO, - _("Invalid PIN format; try again.\n")); - continue; - } - - free(vpninfo->stoken_pin); - vpninfo->stoken_pin = strdup(pin); - if (!vpninfo->stoken_pin) - ret = -ENOMEM; - break; - } - - nuke_opt_values(opts); - return ret; -} - -/* - * If the user clicks OK on the devid/password prompt without entering - * any data, we will continue connecting but bypass soft token generation - * for the duration of this "obtain_cookie" session. (They might not even - * have the credentials that we're prompting for.) - * - * If the user clicks Cancel, we will abort the connection. - * - * Return value: - * < 0, on error - * = 0, on success (or if the user bypassed soft token init) - * = 1, if the user cancelled the form submission - */ -int prepare_stoken(struct openconnect_info *vpninfo) -{ - int ret; - - vpninfo->token_tries = 0; - vpninfo->token_bypassed = 0; - - ret = decrypt_stoken(vpninfo); - if (ret == 2) { - vpninfo->token_bypassed = 1; - return 0; - } else if (ret != 0) - return ret; - - get_stoken_details(vpninfo); - return request_stoken_pin(vpninfo); -} - -/* Return value: - * < 0, if unable to generate a tokencode - * = 0, on success - */ -static int can_gen_stoken_code(struct openconnect_info *vpninfo, - struct oc_auth_form *form, - struct oc_form_opt *opt) -{ - if ((strcmp(opt->name, "password") && strcmp(opt->name, "answer")) || - vpninfo->token_bypassed) - return -EINVAL; - if (vpninfo->token_tries == 0) { - vpn_progress(vpninfo, PRG_DEBUG, - _("OK to generate INITIAL tokencode\n")); - vpninfo->token_time = 0; - } else if (vpninfo->token_tries == 1 && form->message && - strcasestr(form->message, "next tokencode")) { - vpn_progress(vpninfo, PRG_DEBUG, - _("OK to generate NEXT tokencode\n")); - vpninfo->token_time += vpninfo->stoken_interval; - } 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")); - return -ENOENT; - } - return 0; -} - -static int do_gen_stoken_code(struct openconnect_info *vpninfo, - struct oc_auth_form *form, - struct oc_form_opt *opt) -{ - char tokencode[STOKEN_MAX_TOKENCODE + 1]; - - if (!vpninfo->token_time) - vpninfo->token_time = time(NULL); - vpn_progress(vpninfo, PRG_INFO, _("Generating RSA token code\n")); - - /* This doesn't normally fail */ - if (stoken_compute_tokencode(vpninfo->stoken_ctx, vpninfo->token_time, - vpninfo->stoken_pin, tokencode) < 0) { - vpn_progress(vpninfo, PRG_ERR, _("General failure in libstoken.\n")); - return -EIO; - } - - vpninfo->token_tries++; - - if (asprintf(&opt->value, "%s%s", - (vpninfo->stoken_concat_pin && vpninfo->stoken_pin) ? vpninfo->stoken_pin : "", - tokencode) < 0) - return -ENOMEM; - return 0; -} - -#else - -int prepare_stoken(struct openconnect_info *vpninfo) -{ - return -EOPNOTSUPP; -} - -static int can_gen_stoken_code(struct openconnect_info *vpninfo, - struct oc_auth_form *form, - struct oc_form_opt *opt) -{ - return -EOPNOTSUPP; -} - -static int do_gen_stoken_code(struct openconnect_info *vpninfo, - struct oc_auth_form *form, - struct oc_form_opt *opt) -{ - return 0; -} - -#endif - /* Return value: * < 0, if unable to generate a tokencode * = 0, on success @@ -1324,9 +1029,10 @@ static int can_gen_tokencode(struct openconnect_info *vpninfo, struct oc_form_opt *opt) { switch (vpninfo->token_mode) { +#ifdef HAVE_LIBSTOKEN case OC_TOKEN_MODE_STOKEN: return can_gen_stoken_code(vpninfo, form, opt); - +#endif #ifdef HAVE_LIBOATH case OC_TOKEN_MODE_TOTP: return can_gen_totp_code(vpninfo, form, opt); @@ -1357,9 +1063,10 @@ static int do_gen_tokencode(struct openconnect_info *vpninfo, } switch (vpninfo->token_mode) { +#ifdef HAVE_LIBSTOKEN case OC_TOKEN_MODE_STOKEN: return do_gen_stoken_code(vpninfo, form, opt); - +#endif #ifdef HAVE_LIBOATH case OC_TOKEN_MODE_TOTP: return do_gen_totp_code(vpninfo, form, opt); diff --git a/configure.ac b/configure.ac index b7800ee8..102fb83b 100644 --- a/configure.ac +++ b/configure.ac @@ -612,6 +612,7 @@ AS_IF([test "x$with_stoken" != "xno"], [ libstoken_pkg=yes], libstoken_pkg=no) ], [libstoken_pkg=disabled]) +AM_CONDITIONAL(OPENCONNECT_STOKEN, [test "$libstoken_pkg" = "yes"]) AC_ARG_WITH([liboath], AS_HELP_STRING([--without-liboath], diff --git a/http.c b/http.c index 7600c7a8..910a135b 100644 --- a/http.c +++ b/http.c @@ -1190,12 +1190,14 @@ int openconnect_obtain_cookie(struct openconnect_info *vpninfo) int orig_port = 0; int cert_rq, cert_sent = !vpninfo->cert; +#ifdef HAVE_LIBSTOKEN /* Step 1: Unlock software token (if applicable) */ if (vpninfo->token_mode == OC_TOKEN_MODE_STOKEN) { result = prepare_stoken(vpninfo); if (result) return result; } +#endif if (!vpninfo->xmlpost) goto no_xmlpost; diff --git a/library.c b/library.c index f9384f42..0e77c3be 100644 --- a/library.c +++ b/library.c @@ -506,31 +506,6 @@ int openconnect_has_oath_support(void) #endif } -static int set_libstoken_mode(struct openconnect_info *vpninfo, - const char *token_str) -{ -#ifdef HAVE_LIBSTOKEN - int ret; - - if (!vpninfo->stoken_ctx) { - vpninfo->stoken_ctx = stoken_new(); - if (!vpninfo->stoken_ctx) - return -EIO; - } - - ret = token_str ? - stoken_import_string(vpninfo->stoken_ctx, token_str) : - stoken_import_rcfile(vpninfo->stoken_ctx, NULL); - if (ret) - return ret; - - vpninfo->token_mode = OC_TOKEN_MODE_STOKEN; - return 0; -#else - return -EOPNOTSUPP; -#endif -} - int openconnect_set_token_callbacks(struct openconnect_info *vpninfo, void *tokdata, openconnect_lock_token_vfn lock, @@ -571,9 +546,10 @@ int openconnect_set_token_mode(struct openconnect_info *vpninfo, case OC_TOKEN_MODE_NONE: return 0; +#ifdef HAVE_LIBSTOKEN case OC_TOKEN_MODE_STOKEN: return set_libstoken_mode(vpninfo, token_str); - +#endif #ifdef HAVE_LIBOATH case OC_TOKEN_MODE_TOTP: return set_totp_mode(vpninfo, token_str); diff --git a/openconnect-internal.h b/openconnect-internal.h index 3062a29d..7160ae70 100644 --- a/openconnect-internal.h +++ b/openconnect-internal.h @@ -661,7 +661,18 @@ int do_gen_hotp_code(struct openconnect_info *vpninfo, struct oc_auth_form *form, struct oc_form_opt *opt); +/* stoken.c */ +int prepare_stoken(struct openconnect_info *vpninfo); +int set_libstoken_mode(struct openconnect_info *vpninfo, const char *token_str); +int can_gen_stoken_code(struct openconnect_info *vpninfo, + struct oc_auth_form *form, + struct oc_form_opt *opt); +int do_gen_stoken_code(struct openconnect_info *vpninfo, + struct oc_auth_form *form, + struct oc_form_opt *opt); + /* auth.c */ +void nuke_opt_values(struct oc_form_opt *opt); int parse_xml_response(struct openconnect_info *vpninfo, char *response, struct oc_auth_form **form, int *cert_rq); int process_auth_form(struct openconnect_info *vpninfo, @@ -673,7 +684,6 @@ int handle_auth_form(struct openconnect_info *vpninfo, void free_auth_form(struct oc_auth_form *form); int xmlpost_initial_req(struct openconnect_info *vpninfo, struct oc_text_buf *request_body, int cert_fail); -int prepare_stoken(struct openconnect_info *vpninfo); /* http.c */ struct oc_text_buf *buf_alloc(void); diff --git a/stoken.c b/stoken.c new file mode 100644 index 00000000..706819eb --- /dev/null +++ b/stoken.c @@ -0,0 +1,316 @@ +/* + * OpenConnect (SSL + DTLS) VPN client + * + * Copyright © 2008-2014 Intel Corporation. + * Copyright © 2012-2014 Kevin Cernekee + * + * Author: David Woodhouse + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * version 2.1, as published by the Free Software Foundation. + * + * 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 + * Lesser General Public License for more details. + */ + +#include + +#include +#include +#include +#include + +#include + +#include "openconnect-internal.h" + +#ifndef STOKEN_CHECK_VER +#define STOKEN_CHECK_VER(x,y) 0 +#endif + +int set_libstoken_mode(struct openconnect_info *vpninfo, const char *token_str) +{ + int ret; + + if (!vpninfo->stoken_ctx) { + vpninfo->stoken_ctx = stoken_new(); + if (!vpninfo->stoken_ctx) + return -EIO; + } + + ret = token_str ? + stoken_import_string(vpninfo->stoken_ctx, token_str) : + stoken_import_rcfile(vpninfo->stoken_ctx, NULL); + if (ret) + return ret; + + vpninfo->token_mode = OC_TOKEN_MODE_STOKEN; + return 0; +} + + +/* + * A SecurID token can be encrypted with a device ID, a password, both, + * or neither. Gather the required information, decrypt the token, and + * check the hash to make sure it is sane. + * + * Return value: + * < 0, on error + * = 0, on success + * = 1, if the user cancelled the form submission + * = 2, if the user left the entire form blank and clicked OK + */ +static int decrypt_stoken(struct openconnect_info *vpninfo) +{ + struct oc_auth_form form; + struct oc_form_opt opts[2], *opt = opts; + char **devid = NULL, **pass = NULL; + int ret = 0; + + memset(&form, 0, sizeof(form)); + memset(&opts, 0, sizeof(opts)); + + form.opts = opts; + form.message = _("Enter credentials to unlock software token."); + + if (stoken_devid_required(vpninfo->stoken_ctx)) { + opt->type = OC_FORM_OPT_TEXT; + opt->name = (char *)"devid"; + opt->label = _("Device ID:"); + devid = &opt->value; + opt++; + } + if (stoken_pass_required(vpninfo->stoken_ctx)) { + opt->type = OC_FORM_OPT_PASSWORD; + opt->name = (char *)"password"; + opt->label = _("Password:"); + pass = &opt->value; + opt++; + } + + opts[0].next = opts[1].type ? &opts[1] : NULL; + + while (1) { + nuke_opt_values(opts); + + if (!opts[0].type) { + /* don't bug the user if there's nothing to enter */ + ret = 0; + } else { + int some_empty = 0, all_empty = 1; + + /* < 0 for error; 1 if cancelled */ + ret = process_auth_form(vpninfo, &form); + if (ret) + break; + + for (opt = opts; opt; opt = opt->next) { + if (!opt->value || !strlen(opt->value)) + some_empty = 1; + else + all_empty = 0; + } + if (all_empty) { + vpn_progress(vpninfo, PRG_INFO, + _("User bypassed soft token.\n")); + ret = 2; + break; + } + if (some_empty) { + vpn_progress(vpninfo, PRG_INFO, + _("All fields are required; try again.\n")); + continue; + } + } + + ret = stoken_decrypt_seed(vpninfo->stoken_ctx, + pass ? *pass : NULL, + devid ? *devid : NULL); + if (ret == -EIO || (ret && !devid && !pass)) { + vpn_progress(vpninfo, PRG_ERR, + _("General failure in libstoken.\n")); + break; + } else if (ret != 0) { + vpn_progress(vpninfo, PRG_INFO, + _("Incorrect device ID or password; try again.\n")); + continue; + } + + vpn_progress(vpninfo, PRG_DEBUG, _("Soft token init was successful.\n")); + ret = 0; + break; + } + + nuke_opt_values(opts); + return ret; +} + +static void get_stoken_details(struct openconnect_info *vpninfo) +{ +#if STOKEN_CHECK_VER(1,3) + struct stoken_info *info = stoken_get_info(vpninfo->stoken_ctx); + + if (info) { + vpninfo->stoken_concat_pin = !info->uses_pin; + vpninfo->stoken_interval = info->interval; + return; + } +#endif + vpninfo->stoken_concat_pin = 0; + vpninfo->stoken_interval = 60; +} + +/* + * Return value: + * < 0, on error + * = 0, on success + * = 1, if the user cancelled the form submission + */ +static int request_stoken_pin(struct openconnect_info *vpninfo) +{ + struct oc_auth_form form; + struct oc_form_opt opts[1], *opt = opts; + int ret = 0; + + if (!vpninfo->stoken_concat_pin && !stoken_pin_required(vpninfo->stoken_ctx)) + return 0; + + memset(&form, 0, sizeof(form)); + memset(&opts, 0, sizeof(opts)); + + form.opts = opts; + form.message = _("Enter software token PIN."); + + opt->type = OC_FORM_OPT_PASSWORD; + opt->name = (char *)"password"; + opt->label = _("PIN:"); + opt->flags = OC_FORM_OPT_NUMERIC; + + while (1) { + char *pin; + + nuke_opt_values(opts); + + /* < 0 for error; 1 if cancelled */ + ret = process_auth_form(vpninfo, &form); + if (ret) + break; + + pin = opt->value; + if (!pin || !strlen(pin)) { + /* in some cases there really is no PIN */ + if (vpninfo->stoken_concat_pin) + return 0; + + vpn_progress(vpninfo, PRG_INFO, + _("All fields are required; try again.\n")); + continue; + } + + if (!vpninfo->stoken_concat_pin && + stoken_check_pin(vpninfo->stoken_ctx, pin) != 0) { + vpn_progress(vpninfo, PRG_INFO, + _("Invalid PIN format; try again.\n")); + continue; + } + + free(vpninfo->stoken_pin); + vpninfo->stoken_pin = strdup(pin); + if (!vpninfo->stoken_pin) + ret = -ENOMEM; + break; + } + + nuke_opt_values(opts); + return ret; +} + +/* + * If the user clicks OK on the devid/password prompt without entering + * any data, we will continue connecting but bypass soft token generation + * for the duration of this "obtain_cookie" session. (They might not even + * have the credentials that we're prompting for.) + * + * If the user clicks Cancel, we will abort the connection. + * + * Return value: + * < 0, on error + * = 0, on success (or if the user bypassed soft token init) + * = 1, if the user cancelled the form submission + */ +int prepare_stoken(struct openconnect_info *vpninfo) +{ + int ret; + + vpninfo->token_tries = 0; + vpninfo->token_bypassed = 0; + + ret = decrypt_stoken(vpninfo); + if (ret == 2) { + vpninfo->token_bypassed = 1; + return 0; + } else if (ret != 0) + return ret; + + get_stoken_details(vpninfo); + return request_stoken_pin(vpninfo); +} + +/* Return value: + * < 0, if unable to generate a tokencode + * = 0, on success + */ +int can_gen_stoken_code(struct openconnect_info *vpninfo, + struct oc_auth_form *form, + struct oc_form_opt *opt) +{ + if ((strcmp(opt->name, "password") && strcmp(opt->name, "answer")) || + vpninfo->token_bypassed) + return -EINVAL; + if (vpninfo->token_tries == 0) { + vpn_progress(vpninfo, PRG_DEBUG, + _("OK to generate INITIAL tokencode\n")); + vpninfo->token_time = 0; + } else if (vpninfo->token_tries == 1 && form->message && + strcasestr(form->message, "next tokencode")) { + vpn_progress(vpninfo, PRG_DEBUG, + _("OK to generate NEXT tokencode\n")); + vpninfo->token_time += vpninfo->stoken_interval; + } 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")); + return -ENOENT; + } + return 0; +} + +int do_gen_stoken_code(struct openconnect_info *vpninfo, + struct oc_auth_form *form, + struct oc_form_opt *opt) +{ + char tokencode[STOKEN_MAX_TOKENCODE + 1]; + + if (!vpninfo->token_time) + vpninfo->token_time = time(NULL); + vpn_progress(vpninfo, PRG_INFO, _("Generating RSA token code\n")); + + /* This doesn't normally fail */ + if (stoken_compute_tokencode(vpninfo->stoken_ctx, vpninfo->token_time, + vpninfo->stoken_pin, tokencode) < 0) { + vpn_progress(vpninfo, PRG_ERR, _("General failure in libstoken.\n")); + return -EIO; + } + + vpninfo->token_tries++; + + if (asprintf(&opt->value, "%s%s", + (vpninfo->stoken_concat_pin && vpninfo->stoken_pin) ? vpninfo->stoken_pin : "", + tokencode) < 0) + return -ENOMEM; + return 0; +} +