summaryrefslogtreecommitdiffstats
path: root/0003-add-otp-plugin.patch
diff options
context:
space:
mode:
Diffstat (limited to '0003-add-otp-plugin.patch')
-rw-r--r--0003-add-otp-plugin.patch1174
1 files changed, 1174 insertions, 0 deletions
diff --git a/0003-add-otp-plugin.patch b/0003-add-otp-plugin.patch
new file mode 100644
index 0000000..f957b9c
--- /dev/null
+++ b/0003-add-otp-plugin.patch
@@ -0,0 +1,1174 @@
+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
+