]> www.infradead.org Git - users/dwmw2/openconnect.git/commitdiff
Add TOTP (RFC6238) one-time password support
authorJohn Morrissey <jwm@horde.net>
Tue, 19 Mar 2013 03:53:09 +0000 (23:53 -0400)
committerDavid Woodhouse <David.Woodhouse@intel.com>
Sat, 23 Mar 2013 22:52:22 +0000 (22:52 +0000)
Signed-off-by: John Morrissey <jwm@horde.net>
Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>
13 files changed:
Makefile.am
auth.c
configure.ac
http.c
libopenconnect.map.in
library.c
main.c
openconnect-internal.h
openconnect.8.in
openconnect.h
www/building.xml
www/changelog.xml
www/features.xml

index 463df5202aaaad407e94cc93d5619d3005649402..06aae4230d693882cd88db2e2acaba3b85c60bd9 100644 (file)
@@ -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 d5f64a4e4d486154f0b8b6e285dd89de7123653f..e3f80da035357f7559c9a186af699d728658ca41 100644 (file)
--- a/auth.c
+++ b/auth.c
@@ -3,6 +3,7 @@
  *
  * Copyright © 2008-2011 Intel Corporation.
  * Copyright © 2008 Nick Andrew <nick@nick-andrew.net>
+ * Copyright © 2013 John Morrissey <jwm@horde.net>
  *
  * Author: David Woodhouse <dwmw2@infradead.org>
  *
 #include LIBSTOKEN_HDR
 #endif
 
+#ifdef LIBOATH_HDR
+#include LIBOATH_HDR
+#endif
+
 #include <libxml/parser.h>
 #include <libxml/tree.h>
 
@@ -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;
+       }
+}
index 2f0f702ca12de08f9f1c7e165bb95f25d014e40d..e66b79d55e2e01ac962d3574178b77ceea211b05 100644 (file)
@@ -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 7939bba2e5b45d3249d481a87cf1aed93b6b22de..98693543f394cc929a0780724515dd33dd84d96b 100644 (file)
--- 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;
index eae3e70677ba3772cc7ea867ce9ee6a5142c660a..16d338027b00283e1bdceeb30ad32319f7ae2877 100644 (file)
@@ -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;
index 9a8f133b76ea109f678baa90b5511c30c421c5d8..c0db569b8c730bc0427a67fbb89cd6bac4046fce 100644 (file)
--- a/library.c
+++ b/library.c
@@ -2,6 +2,7 @@
  * OpenConnect (SSL + DTLS) VPN client
  *
  * Copyright © 2008-2012 Intel Corporation.
+ * Copyright © 2013 John Morrissey <jwm@horde.net>
  *
  * Authors: David Woodhouse <dwmw2@infradead.org>
  *
 #include LIBSTOKEN_HDR
 #endif
 
+#ifdef LIBOATH_HDR
+#include LIBOATH_HDR
+#endif
+
 #include <libxml/tree.h>
 
 #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 c5ebd57aa9b38ce119e08f59d4b184551139d6ae..8cfbbb34f7798e97382edbc31d1494dbf7971a5a 100644 (file)
--- a/main.c
+++ b/main.c
@@ -3,6 +3,7 @@
  *
  * Copyright © 2008-2012 Intel Corporation.
  * Copyright © 2008 Nick Andrew <nick@nick-andrew.net>
+ * Copyright © 2013 John Morrissey <jwm@horde.net>
  *
  * Author: David Woodhouse <dwmw2@infradead.org>
  *
@@ -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. */
        }
 }
index 8ec2dc5252d62b9fd6452ed15c24902da148a683..1ca93f6fa65304726bc79a9e9d242f9cf30b01e2 100644 (file)
@@ -3,6 +3,7 @@
  *
  * Copyright © 2008-2012 Intel Corporation.
  * Copyright © 2008 Nick Andrew <nick@nick-andrew.net>
+ * Copyright © 2013 John Morrissey <jwm@horde.net>
  *
  * Author: David Woodhouse <dwmw2@infradead.org>
  *
@@ -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;
 
index b9411447da91b839035f65c723c13f97609dacb6..88f2bbb79b5f407b9a7b58d192a3fc343b6bf7bb 100644 (file)
@@ -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
index 2533e2c546f11c45fefa7d3c2ee200ace96cdd48..793d9d5d48af1e52f071f8a825f57cee266a5c90 100644 (file)
@@ -3,6 +3,7 @@
  *
  * Copyright © 2008-2012 Intel Corporation.
  * Copyright © 2008 Nick Andrew <nick@nick-andrew.net>
+ * Copyright © 2013 John Morrissey <jwm@horde.net>
  *
  * Author: David Woodhouse <dwmw2@infradead.org>
  *
@@ -31,7 +32,7 @@
 #include <unistd.h>
 
 #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__ */
index 07f36895fb3b1b1eb633780c39ddd0762cc03753..ac06694225286ea62f786946138c95b45fed5c66 100644 (file)
@@ -33,6 +33,7 @@ And <em>optionally</em> also:
   <li><b><tt><a href="http://code.google.com/p/libproxy/">libproxy</a></tt></b></li>
   <li><b><tt><a href="http://trousers.sourceforge.net/">trousers</a></tt></b> <i>(for TPM support if using GnuTLS)</i></li>
   <li><b><tt><a href="http://stoken.sourceforge.net/">libstoken</a></tt></b> <i>(for SecurID software token support)</i></li>
+  <li><b><tt><a href="http://www.nongnu.org/oath-toolkit/">liboath</a></tt></b> <i>(for RFC6238 TOTP support)</i></li>
 </ul>
 <p>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
index be07c95c02273a4251bde45f68f418ad6b3be75e..13eb07febed04aff791dfe427a88c9a3f45e94f8 100644 (file)
@@ -22,6 +22,7 @@
        <li>Fix compatibility issues with XML POST authentication.</li>
        <li>Fix memory leaks on <tt>realloc()</tt> failure.</li>
        <li>Fix certificate validation problem caused by hostname canonicalisation.</li>
+       <li>Add RFC6238 TOTP token support using <a href="http://www.nongnu.org/oath-toolkit/">liboath</a>.</li>
      </ul><br/>
   </li>
   <li><b><a href="ftp://ftp.infradead.org/pub/openconnect/openconnect-4.99.tar.gz">OpenConnect v4.99</a></b>
index 0f8eeec7cad2f08b599809f0dcdc27629da176cb..4060c4a41016607904f9761f55638f246772ebaa 100644 (file)
@@ -18,6 +18,7 @@
   <li>Authentication via HTTP forms.</li>
   <li>Authentication using SSL certificates &#8212; from local file, <a href="http://en.wikipedia.org/wiki/Trusted_Platform_Module">Trusted Platform Module</a> and <i>(when built with GnuTLS)</i> PKCS#11 smartcards.</li>
   <li>Authentication using SecurID software tokens <i>(when built with libstoken)</i></li>
+  <li>Authentication using RFC6238 TOTP software tokens <i>(when built with liboath)</i></li>
   <li><i>UserGroup</i> support for selecting between multiple configurations on a single VPN server.</li>
   <li>Data transport over TCP <i>(HTTPS)</i> or UDP <i>(DTLS)</i>.</li>
   <li>Keepalive and Dead Peer Detection on both HTTPS and DTLS.</li>