diff options
Diffstat (limited to '0003-add-otp-plugin.patch')
-rw-r--r-- | 0003-add-otp-plugin.patch | 1174 |
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 + |