krb5/0003-add-otp-plugin.patch
2013-03-11 16:26:50 -04:00

1175 lines
34 KiB
Diff

From 9c67d6fd21692d8bbfbe880511cbcbc5d9e6a2e5 Mon Sep 17 00:00:00 2001
From: Nathaniel McCallum <npmccallum@redhat.com>
Date: Fri, 8 Mar 2013 10:22:03 -0500
Subject: [PATCH 3/3] add otp plugin
---
src/Makefile.in | 1 +
src/configure.in | 1 +
src/kdc/kdc_preauth.c | 2 +
src/plugins/preauth/otp/Makefile.in | 45 +++
src/plugins/preauth/otp/deps | 26 ++
src/plugins/preauth/otp/main.c | 374 +++++++++++++++++++++++
src/plugins/preauth/otp/otp.exports | 1 +
src/plugins/preauth/otp/otp_state.c | 571 ++++++++++++++++++++++++++++++++++++
src/plugins/preauth/otp/otp_state.h | 58 ++++
9 files changed, 1079 insertions(+)
create mode 100644 src/plugins/preauth/otp/Makefile.in
create mode 100644 src/plugins/preauth/otp/deps
create mode 100644 src/plugins/preauth/otp/main.c
create mode 100644 src/plugins/preauth/otp/otp.exports
create mode 100644 src/plugins/preauth/otp/otp_state.c
create mode 100644 src/plugins/preauth/otp/otp_state.h
diff --git a/src/Makefile.in b/src/Makefile.in
index 2c65831..0b9d355 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -12,6 +12,7 @@ SUBDIRS=util include lib \
plugins/kadm5_hook/test \
plugins/kdb/db2 \
@ldap_plugin_dir@ \
+ plugins/preauth/otp \
plugins/preauth/pkinit \
kdc kadmin slave clients appl tests \
config-files man doc @po@
diff --git a/src/configure.in b/src/configure.in
index 6a9757f..053e7b4 100644
--- a/src/configure.in
+++ b/src/configure.in
@@ -1337,6 +1337,7 @@ dnl ccapi ccapi/lib ccapi/lib/unix ccapi/server ccapi/server/unix ccapi/test
plugins/kdb/db2/libdb2/test
plugins/kdb/hdb
plugins/preauth/cksum_body
+ plugins/preauth/otp
plugins/preauth/securid_sam2
plugins/preauth/wpse
plugins/authdata/greet
diff --git a/src/kdc/kdc_preauth.c b/src/kdc/kdc_preauth.c
index 42a37a8..afbf1f6 100644
--- a/src/kdc/kdc_preauth.c
+++ b/src/kdc/kdc_preauth.c
@@ -238,6 +238,8 @@ get_plugin_vtables(krb5_context context,
/* Auto-register encrypted challenge and (if possible) pkinit. */
k5_plugin_register_dyn(context, PLUGIN_INTERFACE_KDCPREAUTH, "pkinit",
"preauth");
+ k5_plugin_register_dyn(context, PLUGIN_INTERFACE_KDCPREAUTH, "otp",
+ "preauth");
k5_plugin_register(context, PLUGIN_INTERFACE_KDCPREAUTH,
"encrypted_challenge",
kdcpreauth_encrypted_challenge_initvt);
diff --git a/src/plugins/preauth/otp/Makefile.in b/src/plugins/preauth/otp/Makefile.in
new file mode 100644
index 0000000..c610be9
--- /dev/null
+++ b/src/plugins/preauth/otp/Makefile.in
@@ -0,0 +1,45 @@
+mydir=plugins$(S)preauth$(S)otp
+BUILDTOP=$(REL)..$(S)..$(S)..
+KRB5_RUN_ENV = @KRB5_RUN_ENV@
+KRB5_CONFIG_SETUP = KRB5_CONFIG=$(top_srcdir)/config-files/krb5.conf ; export KRB5_CONFIG ;
+PROG_LIBPATH=-L$(TOPLIBD)
+PROG_RPATH=$(KRB5_LIBDIR)
+MODULE_INSTALL_DIR = $(KRB5_PA_MODULE_DIR)
+DEFS=@DEFS@
+
+LOCALINCLUDES = -I../../../include/krb5 -I../../../include/
+
+LIBBASE=otp
+LIBMAJOR=0
+LIBMINOR=0
+SO_EXT=.so
+RELDIR=../plugins/preauth/otp
+# Depends on libk5crypto and libkrb5
+SHLIB_EXPDEPS = \
+ $(TOPLIBD)/libk5crypto$(SHLIBEXT) \
+ $(TOPLIBD)/libkrb5$(SHLIBEXT) \
+ $(TOPLIBD)/radius/libk5radius$(SHLIBEXT)
+
+SHLIB_EXPLIBS= -lverto -lk5radius $(KRB5_LIB) $(K5CRYPTO_LIB) $(COM_ERR_LIB) $(SUPPORT_LIB) $(LIBS)
+
+SHLIB_DIRS=-L$(TOPLIBD)
+SHLIB_RDIRS=$(KRB5_LIBDIR)
+STOBJLISTS=OBJS.ST
+STLIBOBJS = \
+ otp_state.o \
+ main.o
+
+SRCS = \
+ $(srcdir)/otp_state.c \
+ $(srcdir)/main.c
+
+all-unix:: $(LIBBASE)$(SO_EXT)
+install-unix:: install-libs
+clean-unix:: 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 0000000..cf5f19f
--- /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/k5radius.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 0000000..e980666
--- /dev/null
+++ b/src/plugins/preauth/otp/main.c
@@ -0,0 +1,374 @@
+/*
+ * Copyright 2011 NORDUnet A/S. All rights reserved.
+ * Copyright 2011 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 tmp;
+
+ if (!req)
+ return EINVAL;
+
+ tmp.length = req->enc_data.ciphertext.length;
+ tmp.data = calloc(tmp.length, sizeof(char));
+ if (!tmp.data)
+ return ENOMEM;
+
+ retval = krb5_c_decrypt(context, armor_key, KRB5_KEYUSAGE_PA_OTP_REQUEST,
+ NULL, &req->enc_data, &tmp);
+ if (retval != 0) {
+ DEBUGMSG(retval, "Unable to decrypt encData in PA-OTP-REQUEST.");
+ free(tmp.data);
+ return retval;
+ }
+
+ *out = tmp;
+ return 0;
+}
+
+static krb5_error_code
+nonce_verify(krb5_context ctx, krb5_keyblock *armor_key,
+ const krb5_data *nonce)
+{
+ krb5_error_code retval = EINVAL;
+ krb5_timestamp ts;
+ krb5_data *er = NULL;
+
+ if (armor_key == NULL || nonce->data == NULL)
+ 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 = ntohl(((krb5_timestamp *)er->data)[0]);
+ 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)
+{
+ krb5_data tmp;
+ krb5_error_code retval;
+ krb5_timestamp time;
+
+ retval = krb5_timeofday(ctx, &time);
+ if (retval != 0)
+ return retval;
+
+ tmp.length = length + sizeof(time);
+ tmp.data = (char *)malloc(tmp.length);
+ if (!tmp.data)
+ return ENOMEM;
+
+ retval = krb5_c_random_make_octets(ctx, &tmp);
+ if (retval != 0) {
+ free(tmp.data);
+ return retval;
+ }
+
+ *((krb5_timestamp *)tmp.data) = htonl(time);
+ *nonce = tmp;
+ return 0;
+}
+
+static void
+on_response(krb5_error_code retval, otp_response response, void *data)
+{
+ 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)
+{
+ return otp_state_new(context, (otp_state **)moddata_out);
+}
+
+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 *tmp = NULL;
+ 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 = EINVAL;
+ 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. */
+ pa = calloc(1, sizeof(krb5_pa_data));
+ if (pa) {
+ retval = encode_krb5_pa_otp_challenge(&chl, &tmp);
+ if (retval != 0) {
+ DEBUGMSG(ENOMEM, "Unable to encode challenge.");
+ free(pa);
+ pa = NULL;
+ }
+
+ pa->pa_type = KRB5_PADATA_OTP_CHALLENGE;
+ pa->contents = (krb5_octet *)tmp->data;
+ pa->length = tmp->length;
+ free(tmp); /* Is there a better way to steal the data contents? */
+ } else {
+ retval = ENOMEM;
+ }
+
+out:
+ (*respond)(arg, retval, pa);
+ return;
+}
+
+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 *data,
+ 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 tmp;
+ 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;
+ DEBUGMSG(retval, "No armor key found when verifying padata.");
+ goto error;
+ }
+
+ /* Decode the request */
+ tmp = make_data(data->contents, data->length);
+ retval = decode_krb5_pa_otp_req(&tmp, &req);
+ if (retval != 0) {
+ DEBUGMSG(retval, "Unable to decode OTP request.");
+ goto error;
+ }
+
+ /* Decrypt the nonce from the request */
+ retval = decrypt_encdata(context, armor_key, req, &tmp);
+ if (retval != 0) {
+ DEBUGMSG(retval, "Unable to decrypt encData.");
+ goto error;
+ }
+
+ /* Verify the nonce or timestamp */
+ retval = nonce_verify(context, armor_key, &tmp);
+ if (retval != 0)
+ retval = timestamp_verify(context, &tmp);
+ krb5_free_data_contents(context, &tmp);
+ if (retval != 0) {
+ DEBUGMSG(retval, "Unable to verify nonce or timestamp.");
+ goto error;
+ }
+
+ /* Create the request state. */
+ rs = malloc(sizeof(struct request_state));
+ if (rs == NULL) {
+ retval = ENOMEM;
+ goto error;
+ }
+ rs->arg = arg;
+ rs->respond = respond;
+
+ /* Get the configuration string. */
+ retval = cb->get_string(context, rock, "otp", &config);
+ if (retval != 0 || config == NULL) {
+ if (config == NULL)
+ retval = KRB5_PREAUTH_FAILED;
+ 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 || padata->length == 0)
+ return 0;
+
+ /* Get the armor key. */
+ armor_key = cb->fast_armor(context, rock);
+ if (!armor_key) {
+ DEBUGMSG(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_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 0000000..26aa19d
--- /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 0000000..a42141c
--- /dev/null
+++ b/src/plugins/preauth/otp/otp_state.c
@@ -0,0 +1,571 @@
+/*
+ * Copyright 2012 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 <k5radius.h>
+#include <k5-json.h>
+
+#include <ctype.h>
+
+#ifndef HOST_NAME_MAX
+/* SUSv2 */
+#define HOST_NAME_MAX 255
+#endif
+
+typedef struct token_type_ {
+ char *name;
+ char *server;
+ char *secret;
+ time_t timeout;
+ size_t retries;
+ krb5_boolean strip_realm;
+} token_type;
+
+typedef struct token_ {
+ const token_type *type;
+ krb5_data username;
+} token;
+
+typedef struct request_ {
+ otp_state *state;
+ token *tokens;
+ ssize_t index;
+ otp_cb *cb;
+ void *data;
+ k5_radius_attrset *attrs;
+} request;
+
+struct otp_state_ {
+ krb5_context ctx;
+ token_type *types;
+ k5_radius_client *radius;
+ k5_radius_attrset *attrs;
+};
+
+static inline krb5_data
+string2data_copy(const char *s)
+{
+ char *tmp;
+
+ tmp = strdup(s);
+ return make_data(NULL, tmp == NULL ? 0 : strlen(tmp));
+}
+
+/* Free a NULL-terminated array of strings. */
+static void
+stringv_free(char **strv)
+{
+ size_t i;
+
+ if (strv == NULL)
+ return;
+
+ for (i = 0; strv[i] != NULL; i++)
+ free(strv[i]);
+
+ free(strv);
+}
+
+/* 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);
+}
+
+/* Decode a single token type from the profile. */
+static krb5_error_code
+token_type_decode(profile_t profile, const char *name, token_type *out)
+{
+ krb5_error_code retval;
+ char *defsrv = NULL;
+ token_type tt;
+ int tmp;
+
+ memset(&tt, 0, sizeof(tt));
+
+ /* Set the name. */
+ tt.name = strdup(name == NULL ? "DEFAULT" : name);
+ if (tt.name == NULL) {
+ retval = ENOMEM;
+ goto error;
+ }
+
+ /* Set defaults. */
+ tt.timeout = 5000;
+ tt.retries = 3;
+ if (asprintf(&defsrv, "%s/%s.socket", KDC_DIR, tt.name) < 0) {
+ retval = ENOMEM;
+ goto error;
+ }
+
+ /* Set the internal default. */
+ if (name == NULL) {
+ retval = ENOMEM;
+
+ tt.secret = strdup("");
+ if (tt.secret == NULL)
+ goto error;
+
+ tt.server = defsrv;
+ tt.strip_realm = FALSE;
+
+ *out = tt;
+ return 0;
+ }
+
+ /* Set strip_realm. */
+ retval = profile_get_boolean(profile, "otp", name, "strip_realm", TRUE,
+ &tmp);
+ if (retval != 0)
+ goto error;
+ tt.strip_realm = tmp == 0 ? FALSE : TRUE;
+
+ /* Set the server. */
+ retval = profile_get_string(profile, "otp", name, "server",
+ defsrv, &tt.server);
+ if (retval != 0)
+ goto error;
+
+ /* Set the secret. */
+ retval = profile_get_string(profile, "otp", name, "secret",
+ tt.server[0] == '/' ? "" : NULL,
+ &tt.server);
+ if (retval != 0) {
+ goto error;
+ } else if (tt.secret == NULL) {
+ DEBUGMSG(EINVAL, "Secret not specified in token type '%s'.", name);
+ retval = EINVAL;
+ goto error;
+ }
+
+ /* Set the timeout. */
+ retval = profile_get_integer(profile, "otp", name, "timeout",
+ tt.timeout / 1000, &tmp);
+ if (retval != 0)
+ goto error;
+ tt.timeout = tmp * 1000; /* Convert to milliseconds. */
+
+ /* Set the retries. */
+ retval = profile_get_integer(profile, "otp", name, "retries",
+ tt.retries, &tmp);
+ if (retval != 0)
+ goto error;
+ tt.retries = tmp;
+
+ *out = tt;
+ free(defsrv);
+ return 0;
+
+error:
+ token_type_free(&tt);
+ free(defsrv);
+ 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 *tmp[2] = { "otp", NULL };
+ token_type *types = NULL;
+ char **names = NULL;
+ errcode_t retval;
+ ssize_t i, j;
+
+ retval = profile_get_subsection_names(profile, tmp, &names);
+ if (retval != 0)
+ return retval;
+
+ for (i = 0, j = 0; names[i] != NULL; i++) {
+ if (strcmp(names[i], "DEFAULT") == 0)
+ j = 1;
+ }
+
+ types = calloc(i - j + 2, sizeof(token_type));
+ if (types == NULL) {
+ retval = ENOMEM;
+ goto error;
+ }
+
+ /* If no default has been specified, use our internal default. */
+ if (j == 0) {
+ retval = token_type_decode(profile, NULL, &types[j++]);
+ if (retval != 0)
+ goto error;
+ } else {
+ j = 0;
+ }
+
+ for (i = 0; names[i] != NULL; i++) {
+ retval = token_type_decode(profile, names[i], &types[j++]);
+ if (retval != 0)
+ goto error;
+ }
+
+ stringv_free(names);
+ *out = types;
+ return 0;
+
+error:
+ token_types_free(types);
+ stringv_free(names);
+ return retval;
+}
+
+/* Free the contents of a single token. */
+static void
+token_free(token *t)
+{
+ if (t == NULL)
+ return;
+
+ 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 *type = NULL;
+ krb5_error_code retval;
+ k5_json_value tmp;
+ size_t i;
+ token t;
+
+ memset(&t, 0, sizeof(t));
+
+ tmp = k5_json_object_get(obj, "username");
+ if (tmp != NULL && k5_json_get_tid(tmp) == K5_JSON_TID_STRING) {
+ t.username = string2data_copy(k5_json_string_utf8(tmp));
+ if (t.username.data == NULL)
+ return ENOMEM;
+ }
+
+ tmp = k5_json_object_get(obj, "type");
+ if (tmp != NULL && k5_json_get_tid(tmp) == K5_JSON_TID_STRING)
+ type = k5_json_string_utf8(tmp);
+
+ for (i = 0; types[i].server != NULL; i++) {
+ if (strcmp(type == NULL ? "DEFAULT" : type, types[i].name) == 0)
+ t.type = &types[i];
+ }
+
+ if (t.username.data == NULL) {
+ retval = krb5_unparse_name_flags(ctx, princ,
+ t.type->strip_realm
+ ? KRB5_PRINCIPAL_UNPARSE_NO_REALM
+ : 0,
+ &t.username.data);
+ if (retval != 0)
+ return retval;
+ t.username.length = strlen(t.username.data);
+ }
+
+ *out = t;
+ 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(&tokens[i]);
+
+ free(tokens);
+}
+
+/* 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_value arr, obj;
+ token *tokens;
+ ssize_t len, i, j;
+
+ if (config == NULL)
+ config = "[{}]";
+
+ arr = k5_json_decode(config);
+ if (arr == NULL)
+ return ENOMEM;
+
+ if (k5_json_get_tid(arr) != K5_JSON_TID_ARRAY ||
+ (len = k5_json_array_length(arr)) == 0) {
+ k5_json_release(arr);
+
+ arr = k5_json_decode("[{}]");
+ if (arr == NULL)
+ return ENOMEM;
+
+ if (k5_json_get_tid(arr) != K5_JSON_TID_ARRAY) {
+ k5_json_release(arr);
+ return ENOMEM;
+ }
+
+ len = k5_json_array_length(arr);
+ }
+
+ tokens = calloc(len + 1, sizeof(token));
+ if (tokens == NULL) {
+ k5_json_release(arr);
+ return ENOMEM;
+ }
+
+ for (i = 0, j = 0; i < len; i++) {
+ obj = k5_json_array_get(arr, i);
+ if (k5_json_get_tid(obj) != K5_JSON_TID_OBJECT)
+ continue;
+
+ retval = token_decode(ctx, princ, types, obj, &tokens[j++]);
+ if (retval != 0) {
+ k5_json_release(arr);
+ while (--j > 0)
+ token_free(&tokens[j]);
+ free(tokens);
+ return retval;
+ }
+ }
+
+ k5_json_release(arr);
+ *out = tokens;
+ return 0;
+}
+
+static void
+request_free(request *req)
+{
+ if (req == NULL)
+ return;
+
+ k5_radius_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 = k5_radius_attrset_new(ctx, &self->attrs);
+ if (retval != 0)
+ goto error;
+
+ hndata = make_data(hostname, strlen(hostname));
+ retval = k5_radius_attrset_add(self->attrs,
+ k5_radius_attr_name2num("NAS-Identifier"),
+ &hndata);
+ if (retval != 0)
+ goto error;
+
+ retval = k5_radius_attrset_add_number(
+ self->attrs, k5_radius_attr_name2num("Service-Type"),
+ K5_RADIUS_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;
+
+ k5_radius_attrset_free(self->attrs);
+ token_types_free(self->types);
+ free(self);
+}
+
+static void
+request_send(request *req);
+
+static void
+callback(krb5_error_code retval, const k5_radius_packet *rqst,
+ const k5_radius_packet *resp, void *data)
+{
+ request *req = data;
+
+ req->index++;
+
+ if (retval != 0)
+ goto error;
+
+ /* If we received an accept packet, success! */
+ if (k5_radius_packet_get_code(resp) ==
+ k5_radius_code_name2num("Access-Accept")) {
+ (*req->cb)(retval, otp_response_success, req->data);
+ 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)(retval, otp_response_fail, req->data);
+ request_free(req);
+}
+
+static void
+request_send(request *req)
+{
+ krb5_error_code retval;
+
+ retval = k5_radius_attrset_add(req->attrs,
+ k5_radius_attr_name2num("User-Name"),
+ &req->tokens[req->index].username);
+ if (retval != 0)
+ goto error;
+
+ retval = k5_radius_client_send(req->state->radius,
+ k5_radius_code_name2num("Access-Request"),
+ req->attrs,
+ req->tokens[req->index].type->server,
+ req->tokens[req->index].type->secret,
+ req->tokens[req->index].type->timeout,
+ req->tokens[req->index].type->retries,
+ callback, req);
+ k5_radius_attrset_del(req->attrs, k5_radius_attr_name2num("User-Name"), 0);
+ if (retval != 0)
+ goto error;
+
+ return;
+
+error:
+ (*req->cb)(retval, otp_response_fail, req->data);
+ 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;
+
+ if (state->radius == NULL) {
+ retval = k5_radius_client_new(state->ctx, ctx, &state->radius);
+ if (retval != 0)
+ goto error;
+ }
+
+ rqst = calloc(1, sizeof(request));
+ if (rqst == NULL) {
+ (*cb)(ENOMEM, otp_response_fail, data);
+ return;
+ }
+ rqst->state = state;
+ rqst->data = data;
+ rqst->cb = cb;
+
+ retval = k5_radius_attrset_copy(state->attrs, &rqst->attrs);
+ if (retval != 0)
+ goto error;
+
+ retval = k5_radius_attrset_add(rqst->attrs,
+ k5_radius_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)
+ goto error;
+
+ request_send(rqst);
+ return;
+
+error:
+ (*cb)(retval, otp_response_fail, data);
+ 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 0000000..89a164a
--- /dev/null
+++ b/src/plugins/preauth/otp/otp_state.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2012 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>
+#define DEBUGMSG(code, ...) com_err("otp", code, __VA_ARGS__)
+
+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_ otp_state;
+typedef void
+(otp_cb)(krb5_error_code retval, otp_response response, void *data);
+
+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_ */
--
1.8.1.4