diff --git a/Makefile.am b/Makefile.am index 463df520..06aae423 100644 --- a/Makefile.am +++ b/Makefile.am @@ -14,8 +14,8 @@ man8_MANS = openconnect.8 AM_CPPFLAGS = -DLOCALEDIR="\"$(localedir)\"" openconnect_SOURCES = xml.c main.c dtls.c cstp.c mainloop.c tun.c -openconnect_CFLAGS = $(SSL_CFLAGS) $(DTLS_SSL_CFLAGS) $(LIBXML2_CFLAGS) $(LIBPROXY_CFLAGS) $(ZLIB_CFLAGS) $(LIBSTOKEN_CFLAGS) -openconnect_LDADD = libopenconnect.la $(SSL_LIBS) $(DTLS_SSL_LIBS) $(LIBXML2_LIBS) $(LIBPROXY_LIBS) $(ZLIB_LIBS) $(LIBINTL) $(LIBSTOKEN_LIBS) +openconnect_CFLAGS = $(SSL_CFLAGS) $(DTLS_SSL_CFLAGS) $(LIBXML2_CFLAGS) $(LIBPROXY_CFLAGS) $(ZLIB_CFLAGS) $(LIBSTOKEN_CFLAGS) $(LIBOATH_CFLAGS) +openconnect_LDADD = libopenconnect.la $(SSL_LIBS) $(DTLS_SSL_LIBS) $(LIBXML2_LIBS) $(LIBPROXY_LIBS) $(ZLIB_LIBS) $(LIBINTL) $(LIBSTOKEN_LIBS) $(LIBOATH_LIBS) library_srcs = ssl.c http.c auth.c library.c compat.c lib_srcs_gnutls = gnutls.c gnutls_pkcs12.c gnutls_tpm.c @@ -30,8 +30,8 @@ if OPENCONNECT_OPENSSL library_srcs += $(lib_srcs_openssl) endif libopenconnect_la_SOURCES = version.c $(library_srcs) -libopenconnect_la_CFLAGS = $(SSL_CFLAGS) $(LIBXML2_CFLAGS) $(LIBPROXY_CFLAGS) $(P11KIT_CFLAGS) $(TSS_CFLAGS) $(LIBSTOKEN_CFLAGS) -libopenconnect_la_LIBADD = $(SSL_LIBS) $(LIBXML2_LIBS) $(LIBPROXY_LIBS) $(LIBINTL) $(P11KIT_LIBS) $(TSS_LIBS) $(LIBSTOKEN_LIBS) +libopenconnect_la_CFLAGS = $(SSL_CFLAGS) $(LIBXML2_CFLAGS) $(LIBPROXY_CFLAGS) $(P11KIT_CFLAGS) $(TSS_CFLAGS) $(LIBSTOKEN_CFLAGS) $(LIBOATH_CFLAGS) +libopenconnect_la_LIBADD = $(SSL_LIBS) $(LIBXML2_LIBS) $(LIBPROXY_LIBS) $(LIBINTL) $(P11KIT_LIBS) $(TSS_LIBS) $(LIBSTOKEN_LIBS) $(LIBOATH_LIBS) if OPENBSD_LIBTOOL # OpenBSD's libtool doesn't have -version-number, but its -version-info arg # does what GNU libtool's -version-number does. Which arguably is what the diff --git a/auth.c b/auth.c index d5f64a4e..e3f80da0 100644 --- a/auth.c +++ b/auth.c @@ -3,6 +3,7 @@ * * Copyright © 2008-2011 Intel Corporation. * Copyright © 2008 Nick Andrew + * Copyright © 2013 John Morrissey * * Author: David Woodhouse * @@ -36,6 +37,10 @@ #include LIBSTOKEN_HDR #endif +#ifdef LIBOATH_HDR +#include LIBOATH_HDR +#endif + #include #include @@ -233,13 +238,15 @@ static int parse_form(struct openconnect_info *vpninfo, struct oc_auth_form *for if (!strcmp(input_type, "hidden")) { opt->type = OC_FORM_OPT_HIDDEN; opt->value = (char *)xmlGetProp(xml_node, (unsigned char *)"value"); - } else if (!strcmp(input_type, "text")) + } else if (!strcmp(input_type, "text")) { opt->type = OC_FORM_OPT_TEXT; - else if (!strcmp(input_type, "password")) { - if (vpninfo->use_stoken && !can_gen_tokencode(vpninfo, form, opt)) - opt->type = OC_FORM_OPT_STOKEN; - else + } else if (!strcmp(input_type, "password")) { + if (vpninfo->token_mode != OC_TOKEN_MODE_NONE && + (can_gen_tokencode(vpninfo, form, opt) == 0)) { + opt->type = OC_FORM_OPT_TOKEN; + } else { opt->type = OC_FORM_OPT_PASSWORD; + } } else { vpn_progress(vpninfo, PRG_INFO, _("Unknown input type %s in form\n"), @@ -637,7 +644,7 @@ void free_auth_form(struct oc_auth_form *form) if (form->opts->type == OC_FORM_OPT_TEXT || form->opts->type == OC_FORM_OPT_PASSWORD || form->opts->type == OC_FORM_OPT_HIDDEN || - form->opts->type == OC_FORM_OPT_STOKEN) + form->opts->type == OC_FORM_OPT_TOKEN) free(form->opts->value); else if (form->opts->type == OC_FORM_OPT_SELECT) { struct oc_form_opt_select *sel = (void *)form->opts; @@ -878,8 +885,8 @@ int prepare_stoken(struct openconnect_info *vpninfo) form.opts = opts; form.message = _("Enter credentials to unlock software token."); - vpninfo->stoken_tries = 0; - vpninfo->stoken_bypassed = 0; + vpninfo->token_tries = 0; + vpninfo->token_bypassed = 0; if (stoken_devid_required(vpninfo->stoken_ctx)) { opt->type = OC_FORM_OPT_TEXT; @@ -929,7 +936,7 @@ int prepare_stoken(struct openconnect_info *vpninfo) if (all_empty) { vpn_progress(vpninfo, PRG_INFO, _("User bypassed soft token.\n")); - vpninfo->stoken_bypassed = 1; + vpninfo->token_bypassed = 1; ret = 0; break; } @@ -987,22 +994,23 @@ int prepare_stoken(struct openconnect_info *vpninfo) * < 0, if unable to generate a tokencode * = 0, on success */ -static int can_gen_tokencode(struct openconnect_info *vpninfo, struct oc_auth_form *form, - struct oc_form_opt *opt) +static int can_gen_stoken_code(struct openconnect_info *vpninfo, + struct oc_auth_form *form, + struct oc_form_opt *opt) { #ifdef LIBSTOKEN_HDR if ((strcmp(opt->name, "password") && strcmp(opt->name, "answer")) || - vpninfo->stoken_bypassed) + vpninfo->token_bypassed) return -EINVAL; - if (vpninfo->stoken_tries == 0) { + if (vpninfo->token_tries == 0) { vpn_progress(vpninfo, PRG_DEBUG, _("OK to generate INITIAL tokencode\n")); - vpninfo->stoken_time = 0; - } else if (vpninfo->stoken_tries == 1 && form->message && + 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->stoken_time += 60; + vpninfo->token_time += 60; } else { /* limit the number of retries, to avoid account lockouts */ vpn_progress(vpninfo, PRG_INFO, @@ -1019,35 +1027,139 @@ static int can_gen_tokencode(struct openconnect_info *vpninfo, struct oc_auth_fo * < 0, if unable to generate a tokencode * = 0, on success */ -static int do_gen_tokencode(struct openconnect_info *vpninfo, struct oc_auth_form *form) +static int can_gen_totp_code(struct openconnect_info *vpninfo, + struct oc_auth_form *form, + struct oc_form_opt *opt) { -#ifdef LIBSTOKEN_HDR - char tokencode[STOKEN_MAX_TOKENCODE + 1]; - struct oc_form_opt *opt; +#if defined(LIBOATH_HDR) + if ((strcmp(opt->name, "secondary_password") != 0) || + 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) { + vpn_progress(vpninfo, PRG_DEBUG, + _("OK to generate NEXT tokencode\n")); + vpninfo->token_time += OATH_TOTP_DEFAULT_TIME_STEP_SIZE; + } 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; +#else + return -EOPNOTSUPP; +#endif +} - for (opt = form->opts; ; opt = opt->next) { - /* this form might not have anything for us to do */ - if (!opt) - return 0; - if (opt->type == OC_FORM_OPT_STOKEN) - break; +/* Return value: + * < 0, if unable to generate a tokencode + * = 0, on success + */ +static int can_gen_tokencode(struct openconnect_info *vpninfo, + struct oc_auth_form *form, + struct oc_form_opt *opt) +{ + switch (vpninfo->token_mode) { + case OC_TOKEN_MODE_STOKEN: + return can_gen_stoken_code(vpninfo, form, opt); + + case OC_TOKEN_MODE_TOTP: + return can_gen_totp_code(vpninfo, form, opt); + + default: + return -EINVAL; } +} + +static int do_gen_stoken_code(struct openconnect_info *vpninfo, + struct oc_auth_form *form, + struct oc_form_opt *opt) +{ +#ifdef LIBSTOKEN_HDR + char tokencode[STOKEN_MAX_TOKENCODE + 1]; - if (!vpninfo->stoken_time) - vpninfo->stoken_time = time(NULL); - vpn_progress(vpninfo, PRG_INFO, _("Generating tokencode\n")); + 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->stoken_time, + 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->stoken_tries++; + vpninfo->token_tries++; opt->value = strdup(tokencode); return opt->value ? 0 : -ENOMEM; #else return 0; #endif } + +static int do_gen_totp_code(struct openconnect_info *vpninfo, + struct oc_auth_form *form, + struct oc_form_opt *opt) +{ +#if defined(LIBOATH_HDR) + int oath_err; + char tokencode[7]; + + if (!vpninfo->token_time) + vpninfo->token_time = time(NULL); + + vpn_progress(vpninfo, PRG_INFO, _("Generating OATH TOTP token code\n")); + + oath_err = oath_totp_generate(vpninfo->oath_secret, + vpninfo->oath_secret_len, + vpninfo->token_time, + OATH_TOTP_DEFAULT_TIME_STEP_SIZE, + OATH_TOTP_DEFAULT_START_TIME, + 6, tokencode); + if (oath_err != OATH_OK) { + vpn_progress(vpninfo, PRG_ERR, + _("Unable to generate OATH TOTP token code: %s\n"), + oath_strerror(oath_err)); + return -EIO; + } + + vpninfo->token_tries++; + opt->value = strdup(tokencode); + return opt->value ? 0 : -ENOMEM; +#else + return 0; +#endif +} + +/* Return value: + * < 0, if unable to generate a tokencode + * = 0, on success + */ +static int do_gen_tokencode(struct openconnect_info *vpninfo, + struct oc_auth_form *form) +{ + struct oc_form_opt *opt; + + for (opt = form->opts; ; opt = opt->next) { + /* this form might not have anything for us to do */ + if (!opt) + return 0; + if (opt->type == OC_FORM_OPT_TOKEN) + break; + } + + switch (vpninfo->token_mode) { + case OC_TOKEN_MODE_STOKEN: + return do_gen_stoken_code(vpninfo, form, opt); + + case OC_TOKEN_MODE_TOTP: + return do_gen_totp_code(vpninfo, form, opt); + + default: + return -EINVAL; + } +} diff --git a/configure.ac b/configure.ac index 2f0f702c..e66b79d5 100644 --- a/configure.ac +++ b/configure.ac @@ -504,6 +504,17 @@ AS_IF([test "x$with_stoken" != "xno"], [ libstoken_pkg=no) ], [libstoken_pkg=disabled]) +AC_ARG_WITH([liboath], + AS_HELP_STRING([--without-liboath], + [Build without liboath library (default: test)])) +AS_IF([test "x$with_liboath" != "xno"], [ + PKG_CHECK_MODULES(LIBOATH, liboath, + [AC_SUBST(LIBOATH_PC, liboath) + AC_DEFINE([LIBOATH_HDR], ["liboath/oath.h"]) + liboath_pkg=yes], + liboath_pkg=no) +]) + AC_CHECK_HEADER([if_tun.h], [AC_DEFINE([IF_TUN_HDR], ["if_tun.h"])], [AC_CHECK_HEADER([linux/if_tun.h], diff --git a/http.c b/http.c index 7939bba2..98693543 100644 --- a/http.c +++ b/http.c @@ -948,7 +948,7 @@ int openconnect_obtain_cookie(struct openconnect_info *vpninfo) int xmlpost = 1; /* Step 1: Unlock software token (if applicable) */ - if (vpninfo->use_stoken) { + if (vpninfo->token_mode == OC_TOKEN_MODE_STOKEN) { result = prepare_stoken(vpninfo); if (result) return result; diff --git a/libopenconnect.map.in b/libopenconnect.map.in index eae3e706..16d33802 100644 --- a/libopenconnect.map.in +++ b/libopenconnect.map.in @@ -37,6 +37,12 @@ OPENCONNECT_2.1 { openconnect_set_reported_os; } OPENCONNECT_2.0; +OPENCONNECT_2.2 { + global: + openconnect_has_oath_support; + openconnect_set_token_mode; +} OPENCONNECT_2.1; + OPENCONNECT_PRIVATE { global: @SYMVER_TIME@ @SYMVER_ASPRINTF@ @SYMVER_GETLINE@ @SYMVER_PRINT_ERR@ openconnect_SSL_gets; diff --git a/library.c b/library.c index 9a8f133b..c0db569b 100644 --- a/library.c +++ b/library.c @@ -2,6 +2,7 @@ * OpenConnect (SSL + DTLS) VPN client * * Copyright © 2008-2012 Intel Corporation. + * Copyright © 2013 John Morrissey * * Authors: David Woodhouse * @@ -30,6 +31,10 @@ #include LIBSTOKEN_HDR #endif +#ifdef LIBOATH_HDR +#include LIBOATH_HDR +#endif + #include #include "openconnect-internal.h" @@ -144,6 +149,10 @@ void openconnect_vpninfo_free(struct openconnect_info *vpninfo) free(vpninfo->stoken_pin); if (vpninfo->stoken_ctx) stoken_destroy(vpninfo->stoken_ctx); +#endif +#ifdef LIBOATH_HDR + if (vpninfo->oath_secret) + oath_done(); #endif /* No need to free deflate streams; they weren't initialised */ free(vpninfo); @@ -321,29 +330,21 @@ int openconnect_has_stoken_support(void) #endif } -/* - * Enable software token generation if use_stoken == 1. - * - * If token_str is not NULL, try to parse the string. Otherwise, try to read - * the token data from ~/.stokenrc - * - * Return value: - * = -EOPNOTSUPP, if libstoken is not available - * = -EINVAL, if the token string is invalid (token_str was provided) - * = -ENOENT, if ~/.stokenrc is missing (token_str was NULL) - * = -EIO, for other libstoken failures - * = 0, on success - */ -int openconnect_set_stoken_mode(struct openconnect_info *vpninfo, - int use_stoken, const char *token_str) +int openconnect_has_oath_support(void) +{ +#ifdef LIBOATH_HDR + return 1; +#else + return 0; +#endif +} + +static int set_libstoken_mode(struct openconnect_info *vpninfo, + const char *token_str) { #ifdef LIBSTOKEN_HDR int ret; - vpninfo->use_stoken = 0; - if (!use_stoken) - return 0; - if (!vpninfo->stoken_ctx) { vpninfo->stoken_ctx = stoken_new(); if (!vpninfo->stoken_ctx) @@ -356,9 +357,100 @@ int openconnect_set_stoken_mode(struct openconnect_info *vpninfo, if (ret) return ret; - vpninfo->use_stoken = 1; + vpninfo->token_mode = OC_TOKEN_MODE_STOKEN; + return 0; +#else + return -EOPNOTSUPP; +#endif +} + +static int set_oath_mode(struct openconnect_info *vpninfo, + const char *token_str) +{ +#ifdef LIBOATH_HDR + int ret; + + ret = oath_init(); + if (ret != OATH_OK) + return -EIO; + + if (strncasecmp(token_str, "base32:", strlen("base32:")) == 0) { + ret = oath_base32_decode(token_str + strlen("base32:"), + strlen(token_str) - strlen("base32:"), + &vpninfo->oath_secret, + &vpninfo->oath_secret_len); + if (ret != OATH_OK) + return -EINVAL; + } else { + vpninfo->oath_secret = strdup(token_str); + vpninfo->oath_secret_len = strlen(token_str); + } + + vpninfo->token_mode = OC_TOKEN_MODE_TOTP; return 0; #else return -EOPNOTSUPP; #endif } + +/* + * Enable software token generation. + * + * If token_mode is OC_TOKEN_MODE_STOKEN and token_str is NULL, + * read the token data from ~/.stokenrc. + * + * Return value: + * = -EOPNOTSUPP, if the underlying library (libstoken, liboath) is not + * available or an invalid token_mode was provided + * = -EINVAL, if the token string is invalid (token_str was provided) + * = -ENOENT, if token_mode is OC_TOKEN_MODE_STOKEN and ~/.stokenrc is + * missing (token_str was NULL) + * = -EIO, for other failures in the underlying library (libstoken, liboath) + * = 0, on success + */ +int openconnect_set_token_mode(struct openconnect_info *vpninfo, + oc_token_mode_t token_mode, + const char *token_str) +{ + vpninfo->token_mode = OC_TOKEN_MODE_NONE; + + switch (token_mode) { + case OC_TOKEN_MODE_NONE: + return 0; + + case OC_TOKEN_MODE_STOKEN: + return set_libstoken_mode(vpninfo, token_str); + + case OC_TOKEN_MODE_TOTP: + return set_oath_mode(vpninfo, token_str); + + default: + return -EOPNOTSUPP; + } +} + +/* + * Enable libstoken token generation if use_stoken == 1. + * + * If token_str is not NULL, try to parse the string. Otherwise, try to read + * the token data from ~/.stokenrc + * + * DEPRECATED: use openconnect_set_stoken_mode() instead. + * + * Return value: + * = -EOPNOTSUPP, if libstoken is not available + * = -EINVAL, if the token string is invalid (token_str was provided) + * = -ENOENT, if ~/.stokenrc is missing (token_str was NULL) + * = -EIO, for other libstoken failures + * = 0, on success + */ +int openconnect_set_stoken_mode(struct openconnect_info *vpninfo, + int use_stoken, const char *token_str) +{ + oc_token_mode_t token_mode = OC_TOKEN_MODE_NONE; + + if (use_stoken) + token_mode = OC_TOKEN_MODE_STOKEN; + + return openconnect_set_token_mode(vpninfo, token_mode, token_str); +} diff --git a/main.c b/main.c index c5ebd57a..8cfbbb34 100644 --- a/main.c +++ b/main.c @@ -3,6 +3,7 @@ * * Copyright © 2008-2012 Intel Corporation. * Copyright © 2008 Nick Andrew + * Copyright © 2013 John Morrissey * * Author: David Woodhouse * @@ -66,8 +67,8 @@ 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(struct openconnect_info *vpninfo, - const char *token_str); +static void init_token(struct openconnect_info *vpninfo, + oc_token_mode_t token_mode, const char *token_str); /* A sanity check that the openconnect executable is running against a library of the same version */ @@ -110,7 +111,8 @@ enum { OPT_USERAGENT, OPT_NON_INTER, OPT_DTLS_LOCAL_PORT, - OPT_STOKEN, + OPT_TOKEN_MODE, + OPT_TOKEN_SECRET, OPT_OS, }; @@ -175,7 +177,10 @@ 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("token-mode", 1, OPT_TOKEN_MODE), + /* Alias --stoken to --token-secret for backwards compatibility. */ + OPTION("stoken", 2, OPT_TOKEN_SECRET), + OPTION("token-secret", 2, OPT_TOKEN_SECRET), OPTION("os", 1, OPT_OS), OPTION(NULL, 0, 0) }; @@ -211,7 +216,11 @@ static void print_build_opts(void) sep = comma; } if (openconnect_has_stoken_support()) { - printf("%sSoftware token", sep); + printf("%sRSA software token", sep); + sep = comma; + } + if (openconnect_has_oath_support()) { + printf("%sTOTP software token", sep); sep = comma; } @@ -281,9 +290,14 @@ 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")); + printf(" --token-mode=MODE %s\n", _("Software token type: stoken (default) or totp")); + printf(" --token-secret[=STRING] %s\n", _("Software token secret (can be empty for stoken mode")); + printf(" %s\n", _(" to read from ~/.stokenrc)")); #ifndef LIBSTOKEN_HDR - printf(" %s\n", _("(NOTE: libstoken disabled in this build)")); + printf(" %s\n", _("(NOTE: libstoken (RSA SecurID) disabled in this build)")); +#endif +#ifndef LIBOATH_HDR + printf(" %s\n", _("(NOTE: liboath (TOTP) 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")); @@ -448,8 +462,8 @@ int main(int argc, char **argv) char *pidfile = NULL; FILE *fp = NULL; char *config_arg; - int use_stoken = 0; char *token_str = NULL; + oc_token_mode_t token_mode = OC_TOKEN_MODE_NONE; #ifdef ENABLE_NLS bindtextdomain("openconnect", LOCALEDIR); @@ -711,8 +725,18 @@ 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; + case OPT_TOKEN_MODE: + if (strcasecmp(config_arg, "stoken") == 0) { + token_mode = OC_TOKEN_MODE_STOKEN; + } else if (strcasecmp(config_arg, "totp") == 0) { + token_mode = OC_TOKEN_MODE_TOTP; + } else { + fprintf(stderr, _("Invalid software token mode \"%s\"\n"), + config_arg); + exit(1); + } + break; + case OPT_TOKEN_SECRET: token_str = keep_config_arg(); break; case OPT_OS: @@ -749,8 +773,8 @@ int main(int argc, char **argv) #endif } - if (use_stoken) - init_stoken(vpninfo, token_str); + if (token_mode != OC_TOKEN_MODE_NONE) + init_token(vpninfo, token_mode, token_str); if (proxy && openconnect_set_http_proxy(vpninfo, strdup(proxy))) exit(1); @@ -1224,25 +1248,55 @@ static int process_auth_form(void *_vpninfo, return -EINVAL; } -static void init_stoken(struct openconnect_info *vpninfo, - const char *token_str) +static void init_token(struct openconnect_info *vpninfo, + oc_token_mode_t token_mode, const char *token_str) { - int ret = openconnect_set_stoken_mode(vpninfo, 1, token_str); + int ret; - switch (ret) { - case 0: - return; - case -EINVAL: - fprintf(stderr, _("Soft token string is invalid\n")); - exit(1); - case -ENOENT: - fprintf(stderr, _("Can't open ~/.stokenrc file\n")); - exit(1); - case -EOPNOTSUPP: - fprintf(stderr, _("OpenConnect was not built with soft token support\n")); - exit(1); - default: - fprintf(stderr, _("General failure in libstoken\n")); - exit(1); + ret = openconnect_set_token_mode(vpninfo, token_mode, token_str); + + switch (token_mode) { + case OC_TOKEN_MODE_STOKEN: + switch (ret) { + case 0: + return; + case -EINVAL: + fprintf(stderr, _("Soft token string is invalid\n")); + exit(1); + case -ENOENT: + fprintf(stderr, _("Can't open ~/.stokenrc file\n")); + exit(1); + case -EOPNOTSUPP: + fprintf(stderr, _("OpenConnect was not built with libstoken support\n")); + exit(1); + default: + fprintf(stderr, _("General failure in libstoken\n")); + exit(1); + } + + break; + + case OC_TOKEN_MODE_TOTP: + switch (ret) { + case 0: + return; + case -EINVAL: + fprintf(stderr, _("Soft token string is invalid\n")); + exit(1); + case -EOPNOTSUPP: + fprintf(stderr, _("OpenConnect was not built with liboath support\n")); + exit(1); + default: + fprintf(stderr, _("General failure in liboath\n")); + exit(1); + } + + break; + + case OC_TOKEN_MODE_NONE: + /* No-op */ + break; + + /* Option parsing already checked for invalid modes. */ } } diff --git a/openconnect-internal.h b/openconnect-internal.h index 8ec2dc52..1ca93f6f 100644 --- a/openconnect-internal.h +++ b/openconnect-internal.h @@ -3,6 +3,7 @@ * * Copyright © 2008-2012 Intel Corporation. * Copyright © 2008 Nick Andrew + * Copyright © 2013 John Morrissey * * Author: David Woodhouse * @@ -180,14 +181,18 @@ struct openconnect_info { int uid_csd_given; int no_http_keepalive; + int token_mode; + int token_bypassed; + int token_tries; + time_t token_time; #ifdef LIBSTOKEN_HDR struct stoken_ctx *stoken_ctx; -#endif - int use_stoken; - int stoken_bypassed; - int stoken_tries; - time_t stoken_time; char *stoken_pin; +#endif +#ifdef LIBOATH_HDR + char *oath_secret; + size_t oath_secret_len; +#endif OPENCONNECT_X509 *peer_cert; diff --git a/openconnect.8.in b/openconnect.8.in index b9411447..88f2bbb7 100644 --- a/openconnect.8.in +++ b/openconnect.8.in @@ -49,7 +49,8 @@ openconnect \- Connect to Cisco AnyConnect VPN .OP \-\-no\-passwd .OP \-\-non\-inter .OP \-\-passwd\-on\-stdin -.OP \-\-stoken[=\fItoken-string\fP] +.OP \-\-token-mode=\fIstoken|totp\fP +.OP \-\-token-secret=\fIsecret\fP .OP \-\-reconnect\-timeout .OP \-\-servercert sha1 .OP \-\-useragent string @@ -324,11 +325,17 @@ Do not expect user input; exit if it is required. .B \-\-passwd\-on\-stdin Read password from standard input .TP -.B \-\-stoken[=\fItoken-string\fP] -Use libstoken to generate one-time passwords compatible with the RSA SecurID -system (when built with libstoken support). If \fItoken-string\fP is omitted, -libstoken will try to use the software token seed stored in \fI~/.stokenrc\fP, -if this file exists. +.B \-\-token\-mode=\fIstoken|totp\fP +Select the algorithm to use to generate one-time passwords/verification +codes. \fIstoken\fP for RSA SecurID requires libstoken, and \fItotp\fP +for RFC 6238 requires liboath. +.TP +.B \-\-token\-secret[=\fIsecret\fP] +The secret to use when generating one-time passwords/verification codes. +If \fIsecret\fP is omitted and \-\-token-mode is \fIstoken\fP, libstoken +will try to use the software token seed stored in \fI~/.stokenrc\fP, if this +file exists. Base 32-encoded TOTP secrets can be specified by specifying +"base32:" at the beginning of the secret. .TP .B \-\-reconnect\-timeout Keep reconnect attempts until so much seconds are elapsed. The default diff --git a/openconnect.h b/openconnect.h index 2533e2c5..793d9d5d 100644 --- a/openconnect.h +++ b/openconnect.h @@ -3,6 +3,7 @@ * * Copyright © 2008-2012 Intel Corporation. * Copyright © 2008 Nick Andrew + * Copyright © 2013 John Morrissey * * Author: David Woodhouse * @@ -31,7 +32,7 @@ #include #define OPENCONNECT_API_VERSION_MAJOR 2 -#define OPENCONNECT_API_VERSION_MINOR 1 +#define OPENCONNECT_API_VERSION_MINOR 2 /* * API version 2.1: @@ -86,7 +87,7 @@ #define OC_FORM_OPT_PASSWORD 2 #define OC_FORM_OPT_SELECT 3 #define OC_FORM_OPT_HIDDEN 4 -#define OC_FORM_OPT_STOKEN 5 +#define OC_FORM_OPT_TOKEN 5 /* char * fields are static (owned by XML parser) and don't need to be freed by the form handling code -- except for value, which for TEXT @@ -137,6 +138,12 @@ struct openconnect_info; #define OPENCONNECT_X509 void +typedef enum { + OC_TOKEN_MODE_NONE, + OC_TOKEN_MODE_STOKEN, + OC_TOKEN_MODE_TOTP, +} oc_token_mode_t; + /* Unless otherwise specified, all functions which set strings will take ownership of those strings and the library will free them later in openconnect_vpninfo_free() */ @@ -163,10 +170,12 @@ void openconnect_set_hostname(struct openconnect_info *, char *); char *openconnect_get_urlpath(struct openconnect_info *); void openconnect_set_urlpath(struct openconnect_info *, char *); -/* This function does *not* take ownership of the string; it is parsed +/* These functions do *not* take ownership of the string; it is parsed and then discarded. */ -int openconnect_set_stoken_mode(struct openconnect_info *, - int use_stoken, const char *token_str); +int openconnect_set_token_mode(struct openconnect_info *, + oc_token_mode_t, const char *token_str); +/* Legacy stoken-only function; do not use */ +int openconnect_set_stoken_mode(struct openconnect_info *, int, const char *); /* This function does *not* take ownership of the string; it's copied into a static buffer in the vpninfo. The size must be 41 bytes, @@ -262,5 +271,6 @@ int openconnect_has_tss_blob_support(void); /* Software token capabilities. */ int openconnect_has_stoken_support(void); +int openconnect_has_oath_support(void); #endif /* __OPENCONNECT_H__ */ diff --git a/www/building.xml b/www/building.xml index 07f36895..ac066942 100644 --- a/www/building.xml +++ b/www/building.xml @@ -33,6 +33,7 @@ And optionally also:
  • libproxy
  • trousers (for TPM support if using GnuTLS)
  • libstoken (for SecurID software token support)
  • +
  • liboath (for RFC6238 TOTP support)
  • OpenConnect supports the use of HTTP and SOCKS proxies to connect to the AnyConnect service, even without using libproxy. You may wish to use libproxy diff --git a/www/changelog.xml b/www/changelog.xml index be07c95c..13eb07fe 100644 --- a/www/changelog.xml +++ b/www/changelog.xml @@ -22,6 +22,7 @@

  • Fix compatibility issues with XML POST authentication.
  • Fix memory leaks on realloc() failure.
  • Fix certificate validation problem caused by hostname canonicalisation.
  • +
  • Add RFC6238 TOTP token support using liboath.

  • OpenConnect v4.99 diff --git a/www/features.xml b/www/features.xml index 0f8eeec7..4060c4a4 100644 --- a/www/features.xml +++ b/www/features.xml @@ -18,6 +18,7 @@
  • Authentication via HTTP forms.
  • Authentication using SSL certificates — from local file, Trusted Platform Module and (when built with GnuTLS) PKCS#11 smartcards.
  • Authentication using SecurID software tokens (when built with libstoken)
  • +
  • Authentication using RFC6238 TOTP software tokens (when built with liboath)
  • UserGroup support for selecting between multiple configurations on a single VPN server.
  • Data transport over TCP (HTTPS) or UDP (DTLS).
  • Keepalive and Dead Peer Detection on both HTTPS and DTLS.