Commit 465198ce authored by David Woodhouse's avatar David Woodhouse

Move stoken code to stoken.c

Signed-off-by: default avatarDavid Woodhouse <David.Woodhouse@intel.com>
parent 6f0ee16c
......@@ -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
......
......@@ -27,10 +27,6 @@
#include <ctype.h>
#include <errno.h>
#ifdef HAVE_LIBSTOKEN
#include <stoken.h>
#endif
#ifdef HAVE_LIBOATH
#include <liboath/oath.h>
#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 @@ bad:
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);
......
......@@ -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],
......
......@@ -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;
......
......@@ -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);
......
......@@ -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);
......
/*
* OpenConnect (SSL + DTLS) VPN client
*
* Copyright © 2008-2014 Intel Corporation.
* Copyright © 2012-2014 Kevin Cernekee <cernekee@gmail.com>
*
* Author: David Woodhouse <dwmw2@infradead.org>
*
* 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 <config.h>
#include <ctype.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stoken.h>
#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;
}