summaryrefslogtreecommitdiffstats
path: root/src/plugins/preauth
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/preauth')
-rw-r--r--src/plugins/preauth/otp/Makefile.in31
-rw-r--r--src/plugins/preauth/otp/deps26
-rw-r--r--src/plugins/preauth/otp/main.c379
-rw-r--r--src/plugins/preauth/otp/otp.exports1
-rw-r--r--src/plugins/preauth/otp/otp_state.c649
-rw-r--r--src/plugins/preauth/otp/otp_state.h59
6 files changed, 1145 insertions, 0 deletions
diff --git a/src/plugins/preauth/otp/Makefile.in b/src/plugins/preauth/otp/Makefile.in
new file mode 100644
index 000000000..b512c8725
--- /dev/null
+++ b/src/plugins/preauth/otp/Makefile.in
@@ -0,0 +1,31 @@
+mydir=plugins$(S)preauth$(S)otp
+BUILDTOP=$(REL)..$(S)..$(S)..
+MODULE_INSTALL_DIR = $(KRB5_PA_MODULE_DIR)
+
+LIBBASE=otp
+LIBMAJOR=0
+LIBMINOR=0
+RELDIR=../plugins/preauth/otp
+
+SHLIB_EXPDEPS = $(VERTO_DEPLIBS) $(KRB5_BASE_DEPLIBS) \
+ $(TOPLIBD)/libkrad$(SHLIBEXT)
+
+SHLIB_EXPLIBS= -lkrad $(VERTO_LIBS) $(KRB5_BASE_LIBS)
+
+STLIBOBJS = \
+ otp_state.o \
+ main.o
+
+SRCS = \
+ $(srcdir)/otp_state.c \
+ $(srcdir)/main.c
+
+all-unix:: all-liblinks
+install-unix:: install-libs
+clean-unix:: clean-liblinks clean-libs clean-libobjs
+
+clean::
+ $(RM) lib$(LIBBASE)$(SO_EXT)
+
+@libnover_frag@
+@libobj_frag@
diff --git a/src/plugins/preauth/otp/deps b/src/plugins/preauth/otp/deps
new file mode 100644
index 000000000..68a3b258f
--- /dev/null
+++ b/src/plugins/preauth/otp/deps
@@ -0,0 +1,26 @@
+#
+# Generated makefile dependencies follow.
+#
+otp_state.so otp_state.po $(OUTPRE)otp_state.$(OBJEXT): \
+ $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \
+ $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \
+ $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-err.h \
+ $(top_srcdir)/include/k5-gmt_mktime.h $(top_srcdir)/include/k5-int-pkinit.h \
+ $(top_srcdir)/include/k5-int.h $(top_srcdir)/include/k5-json.h \
+ $(top_srcdir)/include/k5-platform.h $(top_srcdir)/include/k5-plugin.h \
+ $(top_srcdir)/include/k5-thread.h $(top_srcdir)/include/k5-trace.h \
+ $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \
+ $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/krb5/preauth_plugin.h \
+ $(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h \
+ otp_state.c otp_state.h
+main.so main.po $(OUTPRE)main.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \
+ $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \
+ $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h \
+ $(top_srcdir)/include/k5-err.h $(top_srcdir)/include/k5-gmt_mktime.h \
+ $(top_srcdir)/include/k5-int-pkinit.h $(top_srcdir)/include/k5-int.h \
+ $(top_srcdir)/include/k5-json.h $(top_srcdir)/include/k5-platform.h \
+ $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-thread.h \
+ $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/krb5.h \
+ $(top_srcdir)/include/krb5/authdata_plugin.h $(top_srcdir)/include/krb5/plugin.h \
+ $(top_srcdir)/include/krb5/preauth_plugin.h $(top_srcdir)/include/port-sockets.h \
+ $(top_srcdir)/include/socket-utils.h main.c otp_state.h
diff --git a/src/plugins/preauth/otp/main.c b/src/plugins/preauth/otp/main.c
new file mode 100644
index 000000000..2f7470e11
--- /dev/null
+++ b/src/plugins/preauth/otp/main.c
@@ -0,0 +1,379 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* plugins/preauth/otp/main.c - OTP kdcpreauth module definition */
+/*
+ * Copyright 2011 NORDUnet A/S. All rights reserved.
+ * Copyright 2013 Red Hat, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+ * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "k5-int.h"
+#include "k5-json.h"
+#include <krb5/preauth_plugin.h>
+#include "otp_state.h"
+
+#include <errno.h>
+#include <ctype.h>
+
+static krb5_preauthtype otp_pa_type_list[] =
+ { KRB5_PADATA_OTP_REQUEST, 0 };
+
+struct request_state {
+ krb5_kdcpreauth_verify_respond_fn respond;
+ void *arg;
+};
+
+static krb5_error_code
+decrypt_encdata(krb5_context context, krb5_keyblock *armor_key,
+ krb5_pa_otp_req *req, krb5_data *out)
+{
+ krb5_error_code retval;
+ krb5_data plaintext;
+
+ if (req == NULL)
+ return EINVAL;
+
+ retval = alloc_data(&plaintext, req->enc_data.ciphertext.length);
+ if (retval)
+ return retval;
+
+ retval = krb5_c_decrypt(context, armor_key, KRB5_KEYUSAGE_PA_OTP_REQUEST,
+ NULL, &req->enc_data, &plaintext);
+ if (retval != 0) {
+ com_err("otp", retval, "Unable to decrypt encData in PA-OTP-REQUEST");
+ free(plaintext.data);
+ return retval;
+ }
+
+ *out = plaintext;
+ return 0;
+}
+
+static krb5_error_code
+nonce_verify(krb5_context ctx, krb5_keyblock *armor_key,
+ const krb5_data *nonce)
+{
+ krb5_error_code retval;
+ krb5_timestamp ts;
+ krb5_data *er = NULL;
+
+ if (armor_key == NULL || nonce->data == NULL) {
+ retval = EINVAL;
+ goto out;
+ }
+
+ /* Decode the PA-OTP-ENC-REQUEST structure. */
+ retval = decode_krb5_pa_otp_enc_req(nonce, &er);
+ if (retval != 0)
+ goto out;
+
+ /* Make sure the nonce is exactly the same size as the one generated. */
+ if (er->length != armor_key->length + sizeof(krb5_timestamp))
+ goto out;
+
+ /* Check to make sure the timestamp at the beginning is still valid. */
+ ts = load_32_be(er->data);
+ retval = krb5_check_clockskew(ctx, ts);
+
+out:
+ krb5_free_data(ctx, er);
+ return retval;
+}
+
+static krb5_error_code
+timestamp_verify(krb5_context ctx, const krb5_data *nonce)
+{
+ krb5_error_code retval = EINVAL;
+ krb5_pa_enc_ts *et = NULL;
+
+ if (nonce->data == NULL)
+ goto out;
+
+ /* Decode the PA-ENC-TS-ENC structure. */
+ retval = decode_krb5_pa_enc_ts(nonce, &et);
+ if (retval != 0)
+ goto out;
+
+ /* Check the clockskew. */
+ retval = krb5_check_clockskew(ctx, et->patimestamp);
+
+out:
+ krb5_free_pa_enc_ts(ctx, et);
+ return retval;
+}
+
+static krb5_error_code
+nonce_generate(krb5_context ctx, unsigned int length, krb5_data *nonce_out)
+{
+ krb5_data nonce;
+ krb5_error_code retval;
+ krb5_timestamp now;
+
+ retval = krb5_timeofday(ctx, &now);
+ if (retval != 0)
+ return retval;
+
+ retval = alloc_data(&nonce, sizeof(now) + length);
+ if (retval != 0)
+ return retval;
+
+ retval = krb5_c_random_make_octets(ctx, &nonce);
+ if (retval != 0) {
+ free(nonce.data);
+ return retval;
+ }
+
+ store_32_be(now, nonce.data);
+ *nonce_out = nonce;
+ return 0;
+}
+
+static void
+on_response(void *data, krb5_error_code retval, otp_response response)
+{
+ struct request_state rs = *(struct request_state *)data;
+
+ free(data);
+
+ if (retval == 0 && response != otp_response_success)
+ retval = KRB5_PREAUTH_FAILED;
+
+ rs.respond(rs.arg, retval, NULL, NULL, NULL);
+}
+
+static krb5_error_code
+otp_init(krb5_context context, krb5_kdcpreauth_moddata *moddata_out,
+ const char **realmnames)
+{
+ krb5_error_code retval;
+ otp_state *state;
+
+ retval = otp_state_new(context, &state);
+ if (retval)
+ return retval;
+ *moddata_out = (krb5_kdcpreauth_moddata)state;
+ return 0;
+}
+
+static void
+otp_fini(krb5_context context, krb5_kdcpreauth_moddata moddata)
+{
+ otp_state_free((otp_state *)moddata);
+}
+
+static int
+otp_flags(krb5_context context, krb5_preauthtype pa_type)
+{
+ return PA_REPLACES_KEY;
+}
+
+static void
+otp_edata(krb5_context context, krb5_kdc_req *request,
+ krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock,
+ krb5_kdcpreauth_moddata moddata, krb5_preauthtype pa_type,
+ krb5_kdcpreauth_edata_respond_fn respond, void *arg)
+{
+ krb5_otp_tokeninfo ti, *tis[2] = { &ti, NULL };
+ krb5_keyblock *armor_key = NULL;
+ krb5_pa_otp_challenge chl;
+ krb5_pa_data *pa = NULL;
+ krb5_error_code retval;
+ krb5_data *encoding;
+ char *config;
+
+ /* Determine if otp is enabled for the user. */
+ retval = cb->get_string(context, rock, "otp", &config);
+ if (retval != 0 || config == NULL)
+ goto out;
+ cb->free_string(context, rock, config);
+
+ /* Get the armor key. This indicates the length of random data to use in
+ * the nonce. */
+ armor_key = cb->fast_armor(context, rock);
+ if (armor_key == NULL) {
+ retval = ENOENT;
+ goto out;
+ }
+
+ /* Build the (mostly empty) challenge. */
+ memset(&ti, 0, sizeof(ti));
+ memset(&chl, 0, sizeof(chl));
+ chl.tokeninfo = tis;
+ ti.format = -1;
+ ti.length = -1;
+ ti.iteration_count = -1;
+
+ /* Generate the nonce. */
+ retval = nonce_generate(context, armor_key->length, &chl.nonce);
+ if (retval != 0)
+ goto out;
+
+ /* Build the output pa-data. */
+ retval = encode_krb5_pa_otp_challenge(&chl, &encoding);
+ if (retval != 0)
+ goto out;
+ pa = k5alloc(sizeof(krb5_pa_data), &retval);
+ if (pa == NULL) {
+ krb5_free_data(context, encoding);
+ goto out;
+ }
+ pa->pa_type = KRB5_PADATA_OTP_CHALLENGE;
+ pa->contents = (krb5_octet *)encoding->data;
+ pa->length = encoding->length;
+ free(encoding);
+
+out:
+ (*respond)(arg, retval, pa);
+}
+
+static void
+otp_verify(krb5_context context, krb5_data *req_pkt, krb5_kdc_req *request,
+ krb5_enc_tkt_part *enc_tkt_reply, krb5_pa_data *pa,
+ krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock,
+ krb5_kdcpreauth_moddata moddata,
+ krb5_kdcpreauth_verify_respond_fn respond, void *arg)
+{
+ krb5_keyblock *armor_key = NULL;
+ krb5_pa_otp_req *req = NULL;
+ struct request_state *rs;
+ krb5_error_code retval;
+ krb5_data d, plaintext;
+ char *config;
+
+ enc_tkt_reply->flags |= TKT_FLG_PRE_AUTH;
+
+ /* Get the FAST armor key. */
+ armor_key = cb->fast_armor(context, rock);
+ if (armor_key == NULL) {
+ retval = KRB5KDC_ERR_PREAUTH_FAILED;
+ com_err("otp", retval, "No armor key found when verifying padata");
+ goto error;
+ }
+
+ /* Decode the request. */
+ d = make_data(pa->contents, pa->length);
+ retval = decode_krb5_pa_otp_req(&d, &req);
+ if (retval != 0) {
+ com_err("otp", retval, "Unable to decode OTP request");
+ goto error;
+ }
+
+ /* Decrypt the nonce from the request. */
+ retval = decrypt_encdata(context, armor_key, req, &plaintext);
+ if (retval != 0) {
+ com_err("otp", retval, "Unable to decrypt nonce");
+ goto error;
+ }
+
+ /* Verify the nonce or timestamp. */
+ retval = nonce_verify(context, armor_key, &plaintext);
+ if (retval != 0)
+ retval = timestamp_verify(context, &plaintext);
+ krb5_free_data_contents(context, &plaintext);
+ if (retval != 0) {
+ com_err("otp", retval, "Unable to verify nonce or timestamp");
+ goto error;
+ }
+
+ /* Create the request state. */
+ rs = k5alloc(sizeof(struct request_state), &retval);
+ if (rs == NULL)
+ goto error;
+ rs->arg = arg;
+ rs->respond = respond;
+
+ /* Get the principal's OTP configuration string. */
+ retval = cb->get_string(context, rock, "otp", &config);
+ if (config == NULL)
+ retval = KRB5_PREAUTH_FAILED;
+ if (retval != 0) {
+ free(rs);
+ goto error;
+ }
+
+ /* Send the request. */
+ otp_state_verify((otp_state *)moddata, cb->event_context(context, rock),
+ request->client, config, req, on_response, rs);
+ cb->free_string(context, rock, config);
+
+ k5_free_pa_otp_req(context, req);
+ return;
+
+error:
+ k5_free_pa_otp_req(context, req);
+ (*respond)(arg, retval, NULL, NULL, NULL);
+}
+
+static krb5_error_code
+otp_return_padata(krb5_context context, krb5_pa_data *padata,
+ krb5_data *req_pkt, krb5_kdc_req *request,
+ krb5_kdc_rep *reply, krb5_keyblock *encrypting_key,
+ krb5_pa_data **send_pa_out, krb5_kdcpreauth_callbacks cb,
+ krb5_kdcpreauth_rock rock, krb5_kdcpreauth_moddata moddata,
+ krb5_kdcpreauth_modreq modreq)
+{
+ krb5_keyblock *armor_key = NULL;
+
+ if (padata->length == 0)
+ return 0;
+
+ /* Get the armor key. */
+ armor_key = cb->fast_armor(context, rock);
+ if (!armor_key) {
+ com_err("otp", ENOENT, "No armor key found when returning padata");
+ return ENOENT;
+ }
+
+ /* Replace the reply key with the FAST armor key. */
+ krb5_free_keyblock_contents(context, encrypting_key);
+ return krb5_copy_keyblock_contents(context, armor_key, encrypting_key);
+}
+
+krb5_error_code
+kdcpreauth_otp_initvt(krb5_context context, int maj_ver, int min_ver,
+ krb5_plugin_vtable vtable);
+
+krb5_error_code
+kdcpreauth_otp_initvt(krb5_context context, int maj_ver, int min_ver,
+ krb5_plugin_vtable vtable)
+{
+ krb5_kdcpreauth_vtable vt;
+
+ if (maj_ver != 1)
+ return KRB5_PLUGIN_VER_NOTSUPP;
+
+ vt = (krb5_kdcpreauth_vtable)vtable;
+ vt->name = "otp";
+ vt->pa_type_list = otp_pa_type_list;
+ vt->init = otp_init;
+ vt->fini = otp_fini;
+ vt->flags = otp_flags;
+ vt->edata = otp_edata;
+ vt->verify = otp_verify;
+ vt->return_padata = otp_return_padata;
+
+ com_err("otp", 0, "Loaded");
+
+ return 0;
+}
diff --git a/src/plugins/preauth/otp/otp.exports b/src/plugins/preauth/otp/otp.exports
new file mode 100644
index 000000000..26aa19d47
--- /dev/null
+++ b/src/plugins/preauth/otp/otp.exports
@@ -0,0 +1 @@
+kdcpreauth_otp_initvt
diff --git a/src/plugins/preauth/otp/otp_state.c b/src/plugins/preauth/otp/otp_state.c
new file mode 100644
index 000000000..f2a64a404
--- /dev/null
+++ b/src/plugins/preauth/otp/otp_state.c
@@ -0,0 +1,649 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* plugins/preauth/otp/otp_state.c - Verify OTP token values using RADIUS */
+/*
+ * Copyright 2013 Red Hat, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+ * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "otp_state.h"
+
+#include <krad.h>
+#include <k5-json.h>
+
+#include <ctype.h>
+
+#ifndef HOST_NAME_MAX
+/* SUSv2 */
+#define HOST_NAME_MAX 255
+#endif
+
+#define DEFAULT_TYPE_NAME "DEFAULT"
+#define DEFAULT_SOCKET_FMT KDC_DIR "/%s.socket"
+#define DEFAULT_TIMEOUT 5
+#define DEFAULT_RETRIES 3
+#define MAX_SECRET_LEN 1024
+
+typedef struct token_type_st {
+ char *name;
+ char *server;
+ char *secret;
+ int timeout;
+ size_t retries;
+ krb5_boolean strip_realm;
+} token_type;
+
+typedef struct token_st {
+ const token_type *type;
+ krb5_data username;
+} token;
+
+typedef struct request_st {
+ otp_state *state;
+ token *tokens;
+ ssize_t index;
+ otp_cb cb;
+ void *data;
+ krad_attrset *attrs;
+} request;
+
+struct otp_state_st {
+ krb5_context ctx;
+ token_type *types;
+ krad_client *radius;
+ krad_attrset *attrs;
+};
+
+static void request_send(request *req);
+
+static krb5_error_code
+read_secret_file(const char *secret_file, char **secret)
+{
+ char buf[MAX_SECRET_LEN];
+ krb5_error_code retval;
+ char *filename;
+ FILE *file;
+ int i, j;
+
+ *secret = NULL;
+
+ retval = k5_path_join(KDC_DIR, secret_file, &filename);
+ if (retval != 0) {
+ com_err("otp", retval, "Unable to resolve secret file '%s'", filename);
+ return retval;
+ }
+
+ file = fopen(filename, "r");
+ if (file == NULL) {
+ retval = errno;
+ com_err("otp", retval, "Unable to open secret file '%s'", filename);
+ return retval;
+ }
+
+ if (fgets(buf, sizeof(buf), file) == NULL)
+ retval = EIO;
+ fclose(file);
+ if (retval != 0) {
+ com_err("otp", retval, "Unable to read secret file '%s'", filename);
+ return retval;
+ }
+
+ /* Strip whitespace. */
+ for (i = 0; buf[i] != '\0'; i++) {
+ if (!isspace(buf[i]))
+ break;
+ }
+ for (j = strlen(buf) - i; j > 0; j--) {
+ if (!isspace(buf[j - 1]))
+ break;
+ }
+
+ *secret = k5memdup0(&buf[i], j - i, &retval);
+ return retval;
+}
+
+/* Free the contents of a single token type. */
+static void
+token_type_free(token_type *type)
+{
+ if (type == NULL)
+ return;
+
+ free(type->name);
+ free(type->server);
+ free(type->secret);
+}
+
+/* Construct the internal default token type. */
+static krb5_error_code
+token_type_default(token_type *out)
+{
+ char *name = NULL, *server = NULL, *secret = NULL;
+
+ memset(out, 0, sizeof(*out));
+
+ name = strdup(DEFAULT_TYPE_NAME);
+ if (name == NULL)
+ goto oom;
+ if (asprintf(&server, DEFAULT_SOCKET_FMT, name) < 0)
+ goto oom;
+ secret = strdup("");
+ if (secret == NULL)
+ goto oom;
+
+ out->name = name;
+ out->server = server;
+ out->secret = secret;
+ out->timeout = DEFAULT_TIMEOUT * 1000;
+ out->retries = DEFAULT_RETRIES;
+ out->strip_realm = FALSE;
+ return 0;
+
+oom:
+ free(name);
+ free(server);
+ free(secret);
+ return ENOMEM;
+}
+
+/* Decode a single token type from the profile. */
+static krb5_error_code
+token_type_decode(profile_t profile, const char *name, token_type *out)
+{
+ char *server = NULL, *name_copy = NULL, *secret = NULL, *pstr = NULL;
+ int strip_realm, timeout, retries;
+ krb5_error_code retval;
+
+ memset(out, 0, sizeof(*out));
+
+ /* Set the name. */
+ name_copy = strdup(name);
+ if (name_copy == NULL)
+ return ENOMEM;
+
+ /* Set strip_realm. */
+ retval = profile_get_boolean(profile, "otp", name, "strip_realm", TRUE,
+ &strip_realm);
+ if (retval != 0)
+ goto cleanup;
+
+ /* Set the server. */
+ retval = profile_get_string(profile, "otp", name, "server", NULL, &pstr);
+ if (retval != 0)
+ goto cleanup;
+ if (pstr != NULL) {
+ server = strdup(pstr);
+ profile_release_string(pstr);
+ if (server == NULL) {
+ retval = ENOMEM;
+ goto cleanup;
+ }
+ } else if (asprintf(&server, DEFAULT_SOCKET_FMT, name) < 0) {
+ retval = ENOMEM;
+ goto cleanup;
+ }
+
+ /* Get the secret (optional for Unix-domain sockets). */
+ retval = profile_get_string(profile, "otp", name, "secret", NULL, &pstr);
+ if (retval != 0)
+ goto cleanup;
+ if (pstr != NULL) {
+ retval = read_secret_file(pstr, &secret);
+ profile_release_string(pstr);
+ if (retval != 0)
+ goto cleanup;
+ } else {
+ if (server[0] != '/') {
+ com_err("otp", EINVAL, "Secret missing (token type '%s')", name);
+ retval = EINVAL;
+ goto cleanup;
+ }
+
+ /* Use the default empty secret for UNIX domain stream sockets. */
+ secret = strdup("");
+ if (secret == NULL) {
+ retval = ENOMEM;
+ goto cleanup;
+ }
+ }
+
+ /* Get the timeout (profile value in seconds, result in milliseconds). */
+ retval = profile_get_integer(profile, "otp", name, "timeout",
+ DEFAULT_TIMEOUT, &timeout);
+ if (retval != 0)
+ goto cleanup;
+ timeout *= 1000;
+
+ /* Get the number of retries. */
+ retval = profile_get_integer(profile, "otp", name, "retries",
+ DEFAULT_RETRIES, &retries);
+ if (retval != 0)
+ goto cleanup;
+
+ out->name = name_copy;
+ out->server = server;
+ out->secret = secret;
+ out->timeout = timeout;
+ out->retries = retries;
+ out->strip_realm = strip_realm;
+ name_copy = server = secret = NULL;
+
+cleanup:
+ free(name_copy);
+ free(server);
+ free(secret);
+ return retval;
+}
+
+/* Free an array of token types. */
+static void
+token_types_free(token_type *types)
+{
+ size_t i;
+
+ if (types == NULL)
+ return;
+
+ for (i = 0; types[i].server != NULL; i++)
+ token_type_free(&types[i]);
+
+ free(types);
+}
+
+/* Decode an array of token types from the profile. */
+static krb5_error_code
+token_types_decode(profile_t profile, token_type **out)
+{
+ const char *hier[2] = { "otp", NULL };
+ token_type *types = NULL;
+ char **names = NULL;
+ krb5_error_code retval;
+ size_t i, pos;
+ krb5_boolean have_default = FALSE;
+
+ retval = profile_get_subsection_names(profile, hier, &names);
+ if (retval != 0)
+ return retval;
+
+ /* Check if any of the profile subsections overrides the default. */
+ for (i = 0; names[i] != NULL; i++) {
+ if (strcmp(names[i], DEFAULT_TYPE_NAME) == 0)
+ have_default = TRUE;
+ }
+
+ /* Leave space for the default (possibly) and the terminator. */
+ types = k5alloc((i + 2) * sizeof(token_type), &retval);
+ if (types == NULL)
+ goto cleanup;
+
+ /* If no default has been specified, use our internal default. */
+ pos = 0;
+ if (!have_default) {
+ retval = token_type_default(&types[pos++]);
+ if (retval != 0)
+ goto cleanup;
+ }
+
+ /* Decode each profile section into a token type element. */
+ for (i = 0; names[i] != NULL; i++) {
+ retval = token_type_decode(profile, names[i], &types[pos++]);
+ if (retval != 0)
+ goto cleanup;
+ }
+
+ *out = types;
+ types = NULL;
+
+cleanup:
+ profile_free_list(names);
+ token_types_free(types);
+ return retval;
+}
+
+/* Free the contents of a single token. */
+static void
+token_free_contents(token *t)
+{
+ if (t != NULL)
+ free(t->username.data);
+}
+
+/* Decode a single token from a JSON token object. */
+static krb5_error_code
+token_decode(krb5_context ctx, krb5_const_principal princ,
+ const token_type *types, k5_json_object obj, token *out)
+{
+ const char *typename = DEFAULT_TYPE_NAME;
+ const token_type *type = NULL;
+ char *username = NULL;
+ krb5_error_code retval;
+ k5_json_value val;
+ size_t i;
+ int flags;
+
+ memset(out, 0, sizeof(*out));
+
+ /* Find the token type. */
+ val = k5_json_object_get(obj, "type");
+ if (val != NULL && k5_json_get_tid(val) == K5_JSON_TID_STRING)
+ typename = k5_json_string_utf8(val);
+ for (i = 0; types[i].server != NULL; i++) {
+ if (strcmp(typename, types[i].name) == 0)
+ type = &types[i];
+ }
+ if (type == NULL)
+ return EINVAL;
+
+ /* Get the username, either from obj or from unparsing the principal. */
+ val = k5_json_object_get(obj, "username");
+ if (val != NULL && k5_json_get_tid(val) == K5_JSON_TID_STRING) {
+ username = strdup(k5_json_string_utf8(val));
+ if (username == NULL)
+ return ENOMEM;
+ } else {
+ flags = type->strip_realm ? KRB5_PRINCIPAL_UNPARSE_NO_REALM : 0;
+ retval = krb5_unparse_name_flags(ctx, princ, flags, &username);
+ if (retval != 0)
+ return retval;
+ }
+
+ out->type = type;
+ out->username = string2data(username);
+ return 0;
+}
+
+/* Free an array of tokens. */
+static void
+tokens_free(token *tokens)
+{
+ size_t i;
+
+ if (tokens == NULL)
+ return;
+
+ for (i = 0; tokens[i].type != NULL; i++)
+ token_free_contents(&tokens[i]);
+
+ free(tokens);
+}
+
+/* Decode a principal config string into a JSON array. Treat an empty string
+ * or array as if it were "[{}]" which uses the default token type. */
+static krb5_error_code
+decode_config_json(const char *config, k5_json_array *out)
+{
+ krb5_error_code retval;
+ k5_json_value val;
+ k5_json_object obj;
+
+ *out = NULL;
+
+ /* Decode the config string and make sure it's an array. */
+ retval = k5_json_decode((config != NULL) ? config : "[{}]", &val);
+ if (k5_json_get_tid(val) != K5_JSON_TID_ARRAY) {
+ retval = EINVAL;
+ goto error;
+ }
+
+ /* If the array is empty, add in an empty object. */
+ if (k5_json_array_length(val) == 0) {
+ retval = k5_json_object_create(&obj);
+ if (retval != 0)
+ goto error;
+ retval = k5_json_array_add(val, obj);
+ k5_json_release(obj);
+ if (retval != 0)
+ goto error;
+ }
+
+ *out = val;
+ return 0;
+
+error:
+ k5_json_release(val);
+ return retval;
+}
+
+/* Decode an array of tokens from the configuration string. */
+static krb5_error_code
+tokens_decode(krb5_context ctx, krb5_const_principal princ,
+ const token_type *types, const char *config, token **out)
+{
+ krb5_error_code retval;
+ k5_json_array arr = NULL;
+ k5_json_value obj;
+ token *tokens = NULL;
+ size_t len, i;
+
+ retval = decode_config_json(config, &arr);
+ if (retval != 0)
+ return retval;
+ len = k5_json_array_length(arr);
+
+ tokens = k5alloc((len + 1) * sizeof(token), &retval);
+ if (tokens == NULL)
+ goto cleanup;
+
+ for (i = 0; i < len; i++) {
+ obj = k5_json_array_get(arr, i);
+ if (k5_json_get_tid(obj) != K5_JSON_TID_OBJECT) {
+ retval = EINVAL;
+ goto cleanup;
+ }
+ retval = token_decode(ctx, princ, types, obj, &tokens[i]);
+ if (retval != 0)
+ goto cleanup;
+ }
+
+ *out = tokens;
+ tokens = NULL;
+
+cleanup:
+ k5_json_release(arr);
+ tokens_free(tokens);
+ return retval;
+}
+
+static void
+request_free(request *req)
+{
+ if (req == NULL)
+ return;
+
+ krad_attrset_free(req->attrs);
+ tokens_free(req->tokens);
+ free(req);
+}
+
+krb5_error_code
+otp_state_new(krb5_context ctx, otp_state **out)
+{
+ char hostname[HOST_NAME_MAX + 1];
+ krb5_error_code retval;
+ profile_t profile;
+ krb5_data hndata;
+ otp_state *self;
+
+ retval = gethostname(hostname, sizeof(hostname));
+ if (retval != 0)
+ return retval;
+
+ self = calloc(1, sizeof(otp_state));
+ if (self == NULL)
+ return ENOMEM;
+
+ retval = krb5_get_profile(ctx, &profile);
+ if (retval != 0)
+ goto error;
+
+ retval = token_types_decode(profile, &self->types);
+ profile_abandon(profile);
+ if (retval != 0)
+ goto error;
+
+ retval = krad_attrset_new(ctx, &self->attrs);
+ if (retval != 0)
+ goto error;
+
+ hndata = make_data(hostname, strlen(hostname));
+ retval = krad_attrset_add(self->attrs,
+ krad_attr_name2num("NAS-Identifier"), &hndata);
+ if (retval != 0)
+ goto error;
+
+ retval = krad_attrset_add_number(self->attrs,
+ krad_attr_name2num("Service-Type"),
+ KRAD_SERVICE_TYPE_AUTHENTICATE_ONLY);
+ if (retval != 0)
+ goto error;
+
+ self->ctx = ctx;
+ *out = self;
+ return 0;
+
+error:
+ otp_state_free(self);
+ return retval;
+}
+
+void
+otp_state_free(otp_state *self)
+{
+ if (self == NULL)
+ return;
+
+ krad_attrset_free(self->attrs);
+ token_types_free(self->types);
+ free(self);
+}
+
+static void
+callback(krb5_error_code retval, const krad_packet *rqst,
+ const krad_packet *resp, void *data)
+{
+ request *req = data;
+
+ req->index++;
+
+ if (retval != 0)
+ goto error;
+
+ /* If we received an accept packet, success! */
+ if (krad_packet_get_code(resp) ==
+ krad_code_name2num("Access-Accept")) {
+ req->cb(req->data, retval, otp_response_success);
+ request_free(req);
+ return;
+ }
+
+ /* If we have no more tokens to try, failure! */
+ if (req->tokens[req->index].type == NULL)
+ goto error;
+
+ /* Try the next token. */
+ request_send(req);
+
+error:
+ req->cb(req->data, retval, otp_response_fail);
+ request_free(req);
+}
+
+static void
+request_send(request *req)
+{
+ krb5_error_code retval;
+ token *tok = &req->tokens[req->index];
+ const token_type *t = tok->type;
+
+ retval = krad_attrset_add(req->attrs, krad_attr_name2num("User-Name"),
+ &tok->username);
+ if (retval != 0)
+ goto error;
+
+ retval = krad_client_send(req->state->radius,
+ krad_code_name2num("Access-Request"), req->attrs,
+ t->server, t->secret, t->timeout, t->retries,
+ callback, req);
+ krad_attrset_del(req->attrs, krad_attr_name2num("User-Name"), 0);
+ if (retval != 0)
+ goto error;
+
+ return;
+
+error:
+ req->cb(req->data, retval, otp_response_fail);
+ request_free(req);
+}
+
+void
+otp_state_verify(otp_state *state, verto_ctx *ctx, krb5_const_principal princ,
+ const char *config, const krb5_pa_otp_req *req,
+ otp_cb cb, void *data)
+{
+ krb5_error_code retval;
+ request *rqst = NULL;
+ char *name;
+
+ if (state->radius == NULL) {
+ retval = krad_client_new(state->ctx, ctx, &state->radius);
+ if (retval != 0)
+ goto error;
+ }
+
+ rqst = calloc(1, sizeof(request));
+ if (rqst == NULL) {
+ (*cb)(data, ENOMEM, otp_response_fail);
+ return;
+ }
+ rqst->state = state;
+ rqst->data = data;
+ rqst->cb = cb;
+
+ retval = krad_attrset_copy(state->attrs, &rqst->attrs);
+ if (retval != 0)
+ goto error;
+
+ retval = krad_attrset_add(rqst->attrs, krad_attr_name2num("User-Password"),
+ &req->otp_value);
+ if (retval != 0)
+ goto error;
+
+ retval = tokens_decode(state->ctx, princ, state->types, config,
+ &rqst->tokens);
+ if (retval != 0) {
+ if (krb5_unparse_name(state->ctx, princ, &name) == 0) {
+ com_err("otp", retval,
+ "Can't decode otp config string for principal '%s'", name);
+ krb5_free_unparsed_name(state->ctx, name);
+ }
+ goto error;
+ }
+
+ request_send(rqst);
+ return;
+
+error:
+ (*cb)(data, retval, otp_response_fail);
+ request_free(rqst);
+}
diff --git a/src/plugins/preauth/otp/otp_state.h b/src/plugins/preauth/otp/otp_state.h
new file mode 100644
index 000000000..4247d0b0d
--- /dev/null
+++ b/src/plugins/preauth/otp/otp_state.h
@@ -0,0 +1,59 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* plugins/preauth/otp/otp_state.h - Internal declarations for OTP module */
+/*
+ * Copyright 2013 Red Hat, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+ * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef OTP_H_
+#define OTP_H_
+
+#include <k5-int.h>
+#include <verto.h>
+
+#include <com_err.h>
+
+typedef enum otp_response {
+ otp_response_fail = 0,
+ otp_response_success
+ /* Other values reserved for responses like next token or new pin. */
+} otp_response;
+
+typedef struct otp_state_st otp_state;
+typedef void
+(*otp_cb)(void *data, krb5_error_code retval, otp_response response);
+
+krb5_error_code
+otp_state_new(krb5_context ctx, otp_state **self);
+
+void
+otp_state_free(otp_state *self);
+
+void
+otp_state_verify(otp_state *state, verto_ctx *ctx, krb5_const_principal princ,
+ const char *config, const krb5_pa_otp_req *request,
+ otp_cb cb, void *data);
+
+#endif /* OTP_H_ */