From 9a0bb5ada335017c5da35cb41333632ae5577a93 Mon Sep 17 00:00:00 2001 From: Greg Hudson Date: Tue, 15 Jan 2013 11:11:27 -0500 Subject: [PATCH 1/4] Add internal KDC_DIR macro Define KDC_DIR in osconf.hin and use it for paths within the KDC directory. --- src/include/osconf.hin | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/include/osconf.hin b/src/include/osconf.hin index c3a33c2..90ab86d 100644 --- a/src/include/osconf.hin +++ b/src/include/osconf.hin @@ -58,14 +58,15 @@ #define DEFAULT_PLUGIN_BASE_DIR "@LIBDIR/krb5/plugins" #define PLUGIN_EXT "@DYNOBJEXT" -#define DEFAULT_KDB_FILE "@LOCALSTATEDIR/krb5kdc/principal" -#define DEFAULT_KEYFILE_STUB "@LOCALSTATEDIR/krb5kdc/.k5." -#define KRB5_DEFAULT_ADMIN_ACL "@LOCALSTATEDIR/krb5kdc/krb5_adm.acl" +#define KDC_DIR "@LOCALSTATEDIR/krb5kdc" +#define DEFAULT_KDB_FILE KDC_DIR "/principal" +#define DEFAULT_KEYFILE_STUB KDC_DIR "/.k5." +#define KRB5_DEFAULT_ADMIN_ACL KDC_DIR "/krb5_adm.acl" /* Used by old admin server */ -#define DEFAULT_ADMIN_ACL "@LOCALSTATEDIR/krb5kdc/kadm_old.acl" +#define DEFAULT_ADMIN_ACL KDC_DIR "/kadm_old.acl" /* Location of KDC profile */ -#define DEFAULT_KDC_PROFILE "@LOCALSTATEDIR/krb5kdc/kdc.conf" +#define DEFAULT_KDC_PROFILE KDC_DIR "/kdc.conf" #define KDC_PROFILE_ENV "KRB5_KDC_PROFILE" #if TARGET_OS_MAC @@ -93,8 +94,8 @@ /* * Defaults for the KADM5 admin system. */ -#define DEFAULT_KADM5_KEYTAB "@LOCALSTATEDIR/krb5kdc/kadm5.keytab" -#define DEFAULT_KADM5_ACL_FILE "@LOCALSTATEDIR/krb5kdc/kadm5.acl" +#define DEFAULT_KADM5_KEYTAB KDC_DIR "/kadm5.keytab" +#define DEFAULT_KADM5_ACL_FILE KDC_DIR "/kadm5.acl" #define DEFAULT_KADM5_PORT 749 /* assigned by IANA */ #define KRB5_DEFAULT_SUPPORTED_ENCTYPES \ @@ -116,12 +117,12 @@ * krb5 slave support follows */ -#define KPROP_DEFAULT_FILE "@LOCALSTATEDIR/krb5kdc/slave_datatrans" -#define KPROPD_DEFAULT_FILE "@LOCALSTATEDIR/krb5kdc/from_master" +#define KPROP_DEFAULT_FILE KDC_DIR "/slave_datatrans" +#define KPROPD_DEFAULT_FILE KDC_DIR "/from_master" #define KPROPD_DEFAULT_KDB5_UTIL "@SBINDIR/kdb5_util" #define KPROPD_DEFAULT_KPROP "@SBINDIR/kprop" #define KPROPD_DEFAULT_KRB_DB DEFAULT_KDB_FILE -#define KPROPD_ACL_FILE "@LOCALSTATEDIR/krb5kdc/kpropd.acl" +#define KPROPD_ACL_FILE KDC_DIR "/kpropd.acl" /* * GSS mechglue -- 1.8.2.1 From 4ba748915908a45564d2e8a098cf107e39de3315 Mon Sep 17 00:00:00 2001 From: Nathaniel McCallum Date: Tue, 9 Apr 2013 11:17:04 -0400 Subject: [PATCH 2/4] add k5memdup() --- src/include/k5-int.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/include/k5-int.h b/src/include/k5-int.h index 75e6783..7b5ab2c 100644 --- a/src/include/k5-int.h +++ b/src/include/k5-int.h @@ -2600,6 +2600,17 @@ k5alloc(size_t len, krb5_error_code *code) return ptr; } +/* Return a copy of the len bytes of memory at in; set *code to 0 or ENOMEM. */ +static inline void * +k5memdup(const void *in, size_t len, krb5_error_code *code) +{ + void *ptr = k5alloc(len, code); + + if (ptr != NULL) + memcpy(ptr, in, len); + return ptr; +} + krb5_error_code KRB5_CALLCONV krb5_get_credentials_for_user(krb5_context context, krb5_flags options, krb5_ccache ccache, -- 1.8.2.1 From f98e3f2569996d84c968852b50f76173203e568f Mon Sep 17 00:00:00 2001 From: Nathaniel McCallum Date: Thu, 4 Apr 2013 13:39:21 -0400 Subject: [PATCH 3/4] add libkrad --- src/configure.in | 2 +- src/include/Makefile.in | 1 + src/include/krad.h | 267 ++++++++++++++++++++++ src/lib/Makefile.in | 2 +- src/lib/krad/Makefile.in | 74 ++++++ src/lib/krad/attr.c | 317 ++++++++++++++++++++++++++ src/lib/krad/attrset.c | 244 ++++++++++++++++++++ src/lib/krad/client.c | 335 ++++++++++++++++++++++++++++ src/lib/krad/code.c | 111 +++++++++ src/lib/krad/deps | 156 +++++++++++++ src/lib/krad/internal.h | 155 +++++++++++++ src/lib/krad/libkrad.exports | 23 ++ src/lib/krad/packet.c | 470 +++++++++++++++++++++++++++++++++++++++ src/lib/krad/remote.c | 519 +++++++++++++++++++++++++++++++++++++++++++ src/lib/krad/t_attr.c | 89 ++++++++ src/lib/krad/t_attrset.c | 98 ++++++++ src/lib/krad/t_client.c | 126 +++++++++++ src/lib/krad/t_code.c | 54 +++++ src/lib/krad/t_daemon.h | 92 ++++++++ src/lib/krad/t_daemon.py | 76 +++++++ src/lib/krad/t_packet.c | 194 ++++++++++++++++ src/lib/krad/t_remote.c | 170 ++++++++++++++ src/lib/krad/t_test.c | 50 +++++ src/lib/krad/t_test.h | 60 +++++ 24 files changed, 3683 insertions(+), 2 deletions(-) create mode 100644 src/include/krad.h create mode 100644 src/lib/krad/Makefile.in create mode 100644 src/lib/krad/attr.c create mode 100644 src/lib/krad/attrset.c create mode 100644 src/lib/krad/client.c create mode 100644 src/lib/krad/code.c create mode 100644 src/lib/krad/deps create mode 100644 src/lib/krad/internal.h create mode 100644 src/lib/krad/libkrad.exports create mode 100644 src/lib/krad/packet.c create mode 100644 src/lib/krad/remote.c create mode 100644 src/lib/krad/t_attr.c create mode 100644 src/lib/krad/t_attrset.c create mode 100644 src/lib/krad/t_client.c create mode 100644 src/lib/krad/t_code.c create mode 100644 src/lib/krad/t_daemon.h create mode 100644 src/lib/krad/t_daemon.py create mode 100644 src/lib/krad/t_packet.c create mode 100644 src/lib/krad/t_remote.c create mode 100644 src/lib/krad/t_test.c create mode 100644 src/lib/krad/t_test.h diff --git a/src/configure.in b/src/configure.in index faf93a1..d8676e5 100644 --- a/src/configure.in +++ b/src/configure.in @@ -1318,7 +1318,7 @@ dnl lib/krb5/ccache/ccapi lib/rpc lib/rpc/unit-test lib/kadm5 lib/kadm5/clnt lib/kadm5/srv lib/kadm5/unit-test - + lib/krad lib/apputils dnl ccapi ccapi/lib ccapi/lib/unix ccapi/server ccapi/server/unix ccapi/test diff --git a/src/include/Makefile.in b/src/include/Makefile.in index c69b809..869b04b 100644 --- a/src/include/Makefile.in +++ b/src/include/Makefile.in @@ -145,5 +145,6 @@ install-headers-unix install:: krb5/krb5.h profile.h $(INSTALL_DATA) $(srcdir)/krb5/kadm5_hook_plugin.h $(DESTDIR)$(KRB5_INCDIR)$(S)krb5$(S)kadm5_hook_plugin.h $(INSTALL_DATA) profile.h $(DESTDIR)$(KRB5_INCDIR)$(S)profile.h $(INSTALL_DATA) $(srcdir)/gssapi.h $(DESTDIR)$(KRB5_INCDIR)$(S)gssapi.h + $(INSTALL_DATA) $(srcdir)/krad.h $(DESTDIR)$(KRB5_INCDIR)/krad.h depend:: krb5/krb5.h $(BUILT_HEADERS) diff --git a/src/include/krad.h b/src/include/krad.h new file mode 100644 index 0000000..e6d5766 --- /dev/null +++ b/src/include/krad.h @@ -0,0 +1,267 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + * Copyright 2013 Red Hat, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This API is not considered as stable as the main krb5 API. + * + * - We may make arbitrary incompatible changes between feature releases + * (e.g. from 1.12 to 1.13). + * - We will make some effort to avoid making incompatible changes for + * bugfix releases, but will make them if necessary. + */ + +#ifndef KRAD_H_ +#define KRAD_H_ + +#include +#include +#include +#include + +#define KRAD_PACKET_SIZE_MAX 4096 + +#define KRAD_SERVICE_TYPE_LOGIN 1 +#define KRAD_SERVICE_TYPE_FRAMED 2 +#define KRAD_SERVICE_TYPE_CALLBACK_LOGIN 3 +#define KRAD_SERVICE_TYPE_CALLBACK_FRAMED 4 +#define KRAD_SERVICE_TYPE_OUTBOUND 5 +#define KRAD_SERVICE_TYPE_ADMINISTRATIVE 6 +#define KRAD_SERVICE_TYPE_NAS_PROMPT 7 +#define KRAD_SERVICE_TYPE_AUTHENTICATE_ONLY 8 +#define KRAD_SERVICE_TYPE_CALLBACK_NAS_PROMPT 9 +#define KRAD_SERVICE_TYPE_CALL_CHECK 10 +#define KRAD_SERVICE_TYPE_CALLBACK_ADMINISTRATIVE 11 + +typedef struct krad_attrset_st krad_attrset; +typedef struct krad_packet_st krad_packet; +typedef struct krad_client_st krad_client; +typedef unsigned char krad_code; +typedef unsigned char krad_attr; + +/* Called when a response is received or the request times out. */ +typedef void +(*krad_cb)(krb5_error_code retval, const krad_packet *request, + const krad_packet *response, void *data); + +/* + * Called to iterate over a set of requests. Either the callback will be + * called until it returns NULL, or it will be called with cancel = TRUE to + * terminate in the middle of an iteration. + */ +typedef const krad_packet * +(*krad_packet_iter_cb)(void *data, krb5_boolean cancel); + +/* + * Code + */ + +/* Convert a code name to its number. Only works for codes defined + * by RFC 2875 or 2882. Returns 0 if the name was not found. */ +krad_code +krad_code_name2num(const char *name); + +/* Convert a code number to its name. Only works for attributes defined + * by RFC 2865 or 2882. Returns NULL if the name was not found. */ +const char * +krad_code_num2name(krad_code code); + +/* + * Attribute + */ + +/* Convert an attribute name to its number. Only works for attributes defined + * by RFC 2865. Returns 0 if the name was not found. */ +krad_attr +krad_attr_name2num(const char *name); + +/* Convert an attribute number to its name. Only works for attributes defined + * by RFC 2865. Returns NULL if the name was not found. */ +const char * +krad_attr_num2name(krad_attr type); + +/* + * Attribute set + */ + +/* Create a new attribute set. */ +krb5_error_code +krad_attrset_new(krb5_context ctx, krad_attrset **set); + +/* Create a deep copy of an attribute set. */ +krb5_error_code +krad_attrset_copy(const krad_attrset *set, krad_attrset **copy); + +/* Free an attribute set. */ +void +krad_attrset_free(krad_attrset *set); + +/* Add an attribute to a set. */ +krb5_error_code +krad_attrset_add(krad_attrset *set, krad_attr type, const krb5_data *data); + +/* Add a four-octet unsigned number attribute to the given set. */ +krb5_error_code +krad_attrset_add_number(krad_attrset *set, krad_attr type, krb5_ui_4 num); + +/* Delete the specified attribute. */ +void +krad_attrset_del(krad_attrset *set, krad_attr type, size_t indx); + +/* Get the specified attribute. */ +const krb5_data * +krad_attrset_get(const krad_attrset *set, krad_attr type, size_t indx); + +/* + * Packet + */ + +/* Determine the bytes needed from the socket to get the whole packet. Don't + * cache the return value as it can change! Returns -1 on EBADMSG. */ +ssize_t +krad_packet_bytes_needed(const krb5_data *buffer); + +/* Free a packet. */ +void +krad_packet_free(krad_packet *pkt); + +/* + * Create a new request packet. + * + * This function takes the attributes specified in set and converts them into a + * radius packet. The packet will have a randomized id. If cb is not NULL, it + * will be called passing data as the argument to iterate over a set of + * outstanding requests. In this case, the id will be both random and unique + * across the set of requests. + */ +krb5_error_code +krad_packet_new_request(krb5_context ctx, const char *secret, krad_code code, + const krad_attrset *set, krad_packet_iter_cb cb, + void *data, krad_packet **request); + +/* + * Create a new response packet. + * + * This function is similar to krad_packet_new_requst() except that it crafts a + * packet in response to a request packet. This new packet will borrow values + * from the request such as the id and the authenticator. + */ +krb5_error_code +krad_packet_new_response(krb5_context ctx, const char *secret, krad_code code, + const krad_attrset *set, const krad_packet *request, + krad_packet **response); + +/* + * Decode a request radius packet from krb5_data. + * + * The resulting decoded packet will be a request packet stored in *reqpkt. + * + * If cb is NULL, *duppkt will always be NULL. + * + * If cb is not NULL, it will be called (with the data argument) to iterate + * over a set of requests currently being processed. In this case, if the + * packet is a duplicate of an already received request, the original request + * will be set in *duppkt. + */ +krb5_error_code +krad_packet_decode_request(krb5_context ctx, const char *secret, + const krb5_data *buffer, krad_packet_iter_cb cb, + void *data, const krad_packet **duppkt, + krad_packet **reqpkt); + +/* + * Decode a response radius packet from krb5_data. + * + * The resulting decoded packet will be a response packet stored in *rsppkt. + * + * If cb is NULL, *reqpkt will always be NULL. + * + * If cb is not NULL, it will be called (with the data argument) to iterate + * over a set of requests awaiting responses. In this case, if the response + * packet matches one of these requests, the original request will be set in + * *reqpkt. + */ +krb5_error_code +krad_packet_decode_response(krb5_context ctx, const char *secret, + const krb5_data *buffer, krad_packet_iter_cb cb, + void *data, const krad_packet **reqpkt, + krad_packet **rsppkt); + +/* Encode packet. */ +const krb5_data * +krad_packet_encode(const krad_packet *pkt); + +/* Get the code for the given packet. */ +krad_code +krad_packet_get_code(const krad_packet *pkt); + +/* Get the specified attribute. */ +const krb5_data * +krad_packet_get_attr(const krad_packet *pkt, krad_attr type, size_t indx); + +/* + * Client + */ + +/* Create a new client. */ +krb5_error_code +krad_client_new(krb5_context kctx, verto_ctx *vctx, krad_client **client); + +/* Free the client. */ +void +krad_client_free(krad_client *client); + +/* + * Send a request to a radius server. + * + * The remote host may be specified by one of the following formats: + * - /path/to/unix.socket + * - IPv4 + * - IPv4:port + * - IPv4:service + * - [IPv6] + * - [IPv6]:port + * - [IPv6]:service + * - hostname + * - hostname:port + * - hostname:service + * + * The timeout parameter (milliseconds) is the total timeout across all remote + * hosts (when DNS returns multiple entries) and all retries. + * + * The cb function will be called with the data argument when either a response + * is received or the request times out on all possible remote hosts. + * + * If the remote host is a unix domain socket, retries is ignored due to + * guaranteed delivery. + */ +krb5_error_code +krad_client_send(krad_client *rc, krad_code code, const krad_attrset *attrs, + const char *remote, const char *secret, int timeout, + size_t retries, krad_cb cb, void *data); + +#endif /* KRAD_H_ */ diff --git a/src/lib/Makefile.in b/src/lib/Makefile.in index 485db40..4dde514 100644 --- a/src/lib/Makefile.in +++ b/src/lib/Makefile.in @@ -1,5 +1,5 @@ mydir=lib -SUBDIRS=crypto krb5 gssapi rpc kdb kadm5 apputils +SUBDIRS=crypto krb5 gssapi rpc kdb kadm5 apputils krad WINSUBDIRS=crypto krb5 gssapi BUILDTOP=$(REL).. diff --git a/src/lib/krad/Makefile.in b/src/lib/krad/Makefile.in new file mode 100644 index 0000000..75431a0 --- /dev/null +++ b/src/lib/krad/Makefile.in @@ -0,0 +1,74 @@ +mydir=lib$(S)krad +BUILDTOP=$(REL)..$(S).. +RELDIR=krad + +RUN_SETUP=@KRB5_RUN_ENV@ +PROG_LIBPATH=-L$(TOPLIBD) +PROG_RPATH=$(KRB5_LIBDIR) + +SHLIB_EXPLIBS=$(KRB5_BASE_LIBS) $(VERTO_LIBS) +SHLIB_EXPDEPLIBS=$(KRB5_BASE_DEPLIBS) $(VERTO_DEPLIB) +SHLIB_DIRS=-L$(TOPLIBD) +SHLIB_RDIRS=$(KRB5_LIBDIR) + +LIBBASE=krad +LIBMAJOR=0 +LIBMINOR=0 + +STLIBOBJS=attr.o attrset.o client.o code.o packet.o remote.o +LIBOBJS=$(OUTPRE)attr.$(OBJEXT) \ + $(OUTPRE)attrset.$(OBJEXT) \ + $(OUTPRE)client.$(OBJEXT) \ + $(OUTPRE)code.$(OBJEXT) \ + $(OUTPRE)packet.$(OBJEXT) \ + $(OUTPRE)remote.$(OBJEXT) +SRCS=attr.c attrset.c client.c code.c packet.c remote.c \ + t_attr.c t_attrset.c t_client.c t_code.c t_packet.c t_remote.c t_test.c + +STOBJLISTS=OBJS.ST + +all-unix:: all-liblinks +install-unix:: install-libs + +clean-unix:: clean-liblinks clean-libs clean-libobjs + +check-unix:: t_attr t_attrset t_code t_packet t_remote t_client + $(RUN_SETUP) $(VALGRIND) ./t_attr + $(RUN_SETUP) $(VALGRIND) ./t_attrset + $(RUN_SETUP) $(VALGRIND) ./t_code + $(RUN_SETUP) $(VALGRIND) ./t_packet $(PYTHON) $(srcdir)/t_daemon.py + $(RUN_SETUP) $(VALGRIND) ./t_remote $(PYTHON) $(srcdir)/t_daemon.py + $(RUN_SETUP) $(VALGRIND) ./t_client $(PYTHON) $(srcdir)/t_daemon.py + +TESTDEPS=t_test.o $(KRB5_BASE_DEPLIBS) +TESTLIBS=t_test.o $(KRB5_BASE_LIBS) + +T_ATTR_OBJS=attr.o t_attr.o +t_attr: $(T_ATTR_OBJS) $(TESTDEPS) + $(CC_LINK) -o $@ $(T_ATTR_OBJS) $(TESTLIBS) + +T_ATTRSET_OBJS=attr.o attrset.o t_attrset.o +t_attrset: $(T_ATTRSET_OBJS) $(TESTDEPS) + $(CC_LINK) -o $@ $(T_ATTRSET_OBJS) $(TESTLIBS) + +T_CODE_OBJS=code.o t_code.o +t_code: $(T_CODE_OBJS) $(TESTDEPS) + $(CC_LINK) -o $@ $(T_CODE_OBJS) $(TESTLIBS) + +T_PACKET_OBJS=attr.o attrset.o code.o packet.o t_packet.o +t_packet: $(T_PACKET_OBJS) $(TESTDEPS) + $(CC_LINK) -o $@ $(T_PACKET_OBJS) $(TESTLIBS) + +T_REMOTE_OBJS=attr.o attrset.o code.o packet.o remote.o t_remote.o +t_remote: $(T_REMOTE_OBJS) $(TESTDEPS) $(VERTO_DEPLIB) + $(CC_LINK) -o $@ $(T_REMOTE_OBJS) $(TESTLIBS) $(VERTO_LIBS) + +T_CLIENT_OBJS=attr.o attrset.o code.o packet.o remote.o client.o t_client.o +t_client: $(T_CLIENT_OBJS) $(TESTDEPS) $(VERTO_DEPLIB) + $(CC_LINK) -o $@ $(T_CLIENT_OBJS) $(TESTLIBS) $(VERTO_LIBS) + +clean-unix:: clean-libobjs + $(RM) *.o t_attr t_attrset t_code t_packet t_remote t_client + +@lib_frag@ +@libobj_frag@ diff --git a/src/lib/krad/attr.c b/src/lib/krad/attr.c new file mode 100644 index 0000000..9c13d9d --- /dev/null +++ b/src/lib/krad/attr.c @@ -0,0 +1,317 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* lib/krad/attr.c - RADIUS attribute functions for libkrad */ +/* + * Copyright 2013 Red Hat, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include "internal.h" + +#include + +/* RFC 2865 */ +#define BLOCKSIZE 16 + +typedef krb5_error_code +(*attribute_transform_fn)(krb5_context ctx, const char *secret, + const unsigned char *auth, const krb5_data *in, + unsigned char outbuf[MAX_ATTRSIZE], size_t *outlen); + +typedef struct { + const char *name; + unsigned char minval; + unsigned char maxval; + attribute_transform_fn encode; + attribute_transform_fn decode; +} attribute_record; + +static krb5_error_code +user_password_encode(krb5_context ctx, const char *secret, + const unsigned char *auth, const krb5_data *in, + unsigned char outbuf[MAX_ATTRSIZE], size_t *outlen); + +static krb5_error_code +user_password_decode(krb5_context ctx, const char *secret, + const unsigned char *auth, const krb5_data *in, + unsigned char outbuf[MAX_ATTRSIZE], size_t *outlen); + +static const attribute_record attributes[UCHAR_MAX] = { + {"User-Name", 1, MAX_ATTRSIZE, NULL, NULL}, + {"User-Password", 1, 128, user_password_encode, user_password_decode}, + {"CHAP-Password", 17, 17, NULL, NULL}, + {"NAS-IP-Address", 4, 4, NULL, NULL}, + {"NAS-Port", 4, 4, NULL, NULL}, + {"Service-Type", 4, 4, NULL, NULL}, + {"Framed-Protocol", 4, 4, NULL, NULL}, + {"Framed-IP-Address", 4, 4, NULL, NULL}, + {"Framed-IP-Netmask", 4, 4, NULL, NULL}, + {"Framed-Routing", 4, 4, NULL, NULL}, + {"Filter-Id", 1, MAX_ATTRSIZE, NULL, NULL}, + {"Framed-MTU", 4, 4, NULL, NULL}, + {"Framed-Compression", 4, 4, NULL, NULL}, + {"Login-IP-Host", 4, 4, NULL, NULL}, + {"Login-Service", 4, 4, NULL, NULL}, + {"Login-TCP-Port", 4, 4, NULL, NULL}, + {NULL, 0, 0, NULL, NULL}, /* Unassigned */ + {"Reply-Message", 1, MAX_ATTRSIZE, NULL, NULL}, + {"Callback-Number", 1, MAX_ATTRSIZE, NULL, NULL}, + {"Callback-Id", 1, MAX_ATTRSIZE, NULL, NULL}, + {NULL, 0, 0, NULL, NULL}, /* Unassigned */ + {"Framed-Route", 1, MAX_ATTRSIZE, NULL, NULL}, + {"Framed-IPX-Network", 4, 4, NULL, NULL}, + {"State", 1, MAX_ATTRSIZE, NULL, NULL}, + {"Class", 1, MAX_ATTRSIZE, NULL, NULL}, + {"Vendor-Specific", 5, MAX_ATTRSIZE, NULL, NULL}, + {"Session-Timeout", 4, 4, NULL, NULL}, + {"Idle-Timeout", 4, 4, NULL, NULL}, + {"Termination-Action", 4, 4, NULL, NULL}, + {"Called-Station-Id", 1, MAX_ATTRSIZE, NULL, NULL}, + {"Calling-Station-Id", 1, MAX_ATTRSIZE, NULL, NULL}, + {"NAS-Identifier", 1, MAX_ATTRSIZE, NULL, NULL}, + {"Proxy-State", 1, MAX_ATTRSIZE, NULL, NULL}, + {"Login-LAT-Service", 1, MAX_ATTRSIZE, NULL, NULL}, + {"Login-LAT-Node", 1, MAX_ATTRSIZE, NULL, NULL}, + {"Login-LAT-Group", 32, 32, NULL, NULL}, + {"Framed-AppleTalk-Link", 4, 4, NULL, NULL}, + {"Framed-AppleTalk-Network", 4, 4, NULL, NULL}, + {"Framed-AppleTalk-Zone", 1, MAX_ATTRSIZE, NULL, NULL}, + {NULL, 0, 0, NULL, NULL}, /* Reserved for accounting */ + {NULL, 0, 0, NULL, NULL}, /* Reserved for accounting */ + {NULL, 0, 0, NULL, NULL}, /* Reserved for accounting */ + {NULL, 0, 0, NULL, NULL}, /* Reserved for accounting */ + {NULL, 0, 0, NULL, NULL}, /* Reserved for accounting */ + {NULL, 0, 0, NULL, NULL}, /* Reserved for accounting */ + {NULL, 0, 0, NULL, NULL}, /* Reserved for accounting */ + {NULL, 0, 0, NULL, NULL}, /* Reserved for accounting */ + {NULL, 0, 0, NULL, NULL}, /* Reserved for accounting */ + {NULL, 0, 0, NULL, NULL}, /* Reserved for accounting */ + {NULL, 0, 0, NULL, NULL}, /* Reserved for accounting */ + {NULL, 0, 0, NULL, NULL}, /* Reserved for accounting */ + {NULL, 0, 0, NULL, NULL}, /* Reserved for accounting */ + {NULL, 0, 0, NULL, NULL}, /* Reserved for accounting */ + {NULL, 0, 0, NULL, NULL}, /* Reserved for accounting */ + {NULL, 0, 0, NULL, NULL}, /* Reserved for accounting */ + {NULL, 0, 0, NULL, NULL}, /* Reserved for accounting */ + {NULL, 0, 0, NULL, NULL}, /* Reserved for accounting */ + {NULL, 0, 0, NULL, NULL}, /* Reserved for accounting */ + {NULL, 0, 0, NULL, NULL}, /* Reserved for accounting */ + {"CHAP-Challenge", 5, MAX_ATTRSIZE, NULL, NULL}, + {"NAS-Port-Type", 4, 4, NULL, NULL}, + {"Port-Limit", 4, 4, NULL, NULL}, + {"Login-LAT-Port", 1, MAX_ATTRSIZE, NULL, NULL}, +}; + +/* Encode User-Password attribute. */ +static krb5_error_code +user_password_encode(krb5_context ctx, const char *secret, + const unsigned char *auth, const krb5_data *in, + unsigned char outbuf[MAX_ATTRSIZE], size_t *outlen) +{ + const unsigned char *indx; + krb5_error_code retval; + unsigned int seclen; + krb5_checksum sum; + size_t blck, len, i; + krb5_data tmp; + + /* Copy the input buffer to the (zero-padded) output buffer. */ + len = (in->length + BLOCKSIZE - 1) / BLOCKSIZE * BLOCKSIZE; + if (len > MAX_ATTRSIZE) + return ENOBUFS; + memset(outbuf, 0, len); + memcpy(outbuf, in->data, in->length); + + /* Create our temporary space for processing each block. */ + seclen = strlen(secret); + retval = alloc_data(&tmp, seclen + BLOCKSIZE); + if (retval != 0) + return retval; + + memcpy(tmp.data, secret, seclen); + for (blck = 0, indx = auth; blck * BLOCKSIZE < len; blck++) { + memcpy(tmp.data + seclen, indx, BLOCKSIZE); + + retval = krb5_c_make_checksum(ctx, CKSUMTYPE_RSA_MD5, NULL, 0, &tmp, + &sum); + if (retval != 0) { + zap(tmp.data, tmp.length); + zap(outbuf, len); + krb5_free_data_contents(ctx, &tmp); + return retval; + } + + for (i = 0; i < BLOCKSIZE; i++) + outbuf[blck * BLOCKSIZE + i] ^= sum.contents[i]; + krb5_free_checksum_contents(ctx, &sum); + + indx = &outbuf[blck * BLOCKSIZE]; + } + + zap(tmp.data, tmp.length); + krb5_free_data_contents(ctx, &tmp); + *outlen = len; + return 0; +} + +/* Decode User-Password attribute. */ +static krb5_error_code +user_password_decode(krb5_context ctx, const char *secret, + const unsigned char *auth, const krb5_data *in, + unsigned char outbuf[MAX_ATTRSIZE], size_t *outlen) +{ + const unsigned char *indx; + krb5_error_code retval; + unsigned int seclen; + krb5_checksum sum; + ssize_t blck, i; + krb5_data tmp; + + if (in->length % BLOCKSIZE != 0) + return EINVAL; + if (in->length > MAX_ATTRSIZE) + return ENOBUFS; + + /* Create our temporary space for processing each block. */ + seclen = strlen(secret); + retval = alloc_data(&tmp, seclen + BLOCKSIZE); + if (retval != 0) + return retval; + + memcpy(tmp.data, secret, seclen); + for (blck = 0, indx = auth; blck * BLOCKSIZE < in->length; blck++) { + memcpy(tmp.data + seclen, indx, BLOCKSIZE); + + retval = krb5_c_make_checksum(ctx, CKSUMTYPE_RSA_MD5, NULL, 0, + &tmp, &sum); + if (retval != 0) { + zap(tmp.data, tmp.length); + zap(outbuf, in->length); + krb5_free_data_contents(ctx, &tmp); + return retval; + } + + for (i = 0; i < BLOCKSIZE; i++) { + outbuf[blck * BLOCKSIZE + i] = in->data[blck * BLOCKSIZE + i] ^ + sum.contents[i]; + } + krb5_free_checksum_contents(ctx, &sum); + + indx = (const unsigned char *)&in->data[blck * BLOCKSIZE]; + } + + /* Strip off trailing NULL bytes. */ + *outlen = in->length; + while (*outlen > 0 && outbuf[*outlen - 1] == '\0') + (*outlen)--; + + krb5_free_data_contents(ctx, &tmp); + return 0; +} + +krb5_error_code +kr_attr_valid(krad_attr type, const krb5_data *data) +{ + const attribute_record *ar; + + if (type == 0) + return EINVAL; + + ar = &attributes[type - 1]; + return (data->length >= ar->minval && data->length <= ar->maxval) ? 0 : + EMSGSIZE; +} + +krb5_error_code +kr_attr_encode(krb5_context ctx, const char *secret, + const unsigned char *auth, krad_attr type, + const krb5_data *in, unsigned char outbuf[MAX_ATTRSIZE], + size_t *outlen) +{ + krb5_error_code retval; + + retval = kr_attr_valid(type, in); + if (retval != 0) + return retval; + + if (attributes[type - 1].encode == NULL) { + if (in->length > MAX_ATTRSIZE) + return ENOBUFS; + + *outlen = in->length; + memcpy(outbuf, in->data, in->length); + return 0; + } + + return attributes[type - 1].encode(ctx, secret, auth, in, outbuf, outlen); +} + +krb5_error_code +kr_attr_decode(krb5_context ctx, const char *secret, const unsigned char *auth, + krad_attr type, const krb5_data *in, + unsigned char outbuf[MAX_ATTRSIZE], size_t *outlen) +{ + krb5_error_code retval; + + retval = kr_attr_valid(type, in); + if (retval != 0) + return retval; + + if (attributes[type - 1].encode == NULL) { + if (in->length > MAX_ATTRSIZE) + return ENOBUFS; + + *outlen = in->length; + memcpy(outbuf, in->data, in->length); + return 0; + } + + return attributes[type - 1].decode(ctx, secret, auth, in, outbuf, outlen); +} + +krad_attr +krad_attr_name2num(const char *name) +{ + unsigned char i; + + for (i = 0; i < UCHAR_MAX; i++) { + if (attributes[i].name == NULL) + continue; + + if (strcmp(attributes[i].name, name) == 0) + return i + 1; + } + + return 0; +} + +const char * +krad_attr_num2name(krad_attr type) +{ + if (type == 0) + return NULL; + + return attributes[type - 1].name; +} diff --git a/src/lib/krad/attrset.c b/src/lib/krad/attrset.c new file mode 100644 index 0000000..fbd0621 --- /dev/null +++ b/src/lib/krad/attrset.c @@ -0,0 +1,244 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* lib/krad/attrset.c - RADIUS attribute set functions for libkrad */ +/* + * Copyright 2013 Red Hat, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include "internal.h" + +#include + +TAILQ_HEAD(attr_head, attr_st); + +typedef struct attr_st attr; +struct attr_st { + TAILQ_ENTRY(attr_st) list; + krad_attr type; + krb5_data attr; + char buffer[MAX_ATTRSIZE]; +}; + +struct krad_attrset_st { + krb5_context ctx; + struct attr_head list; +}; + +krb5_error_code +krad_attrset_new(krb5_context ctx, krad_attrset **set) +{ + krad_attrset *tmp; + + tmp = calloc(1, sizeof(krad_attrset)); + if (tmp == NULL) + return ENOMEM; + tmp->ctx = ctx; + TAILQ_INIT(&tmp->list); + + *set = tmp; + return 0; +} + +void +krad_attrset_free(krad_attrset *set) +{ + attr *a; + + if (set == NULL) + return; + + while (!TAILQ_EMPTY(&set->list)) { + a = TAILQ_FIRST(&set->list); + TAILQ_REMOVE(&set->list, a, list); + zap(a->buffer, sizeof(a->buffer)); + free(a); + } + + free(set); +} + +krb5_error_code +krad_attrset_add(krad_attrset *set, krad_attr type, const krb5_data *data) +{ + krb5_error_code retval; + attr *tmp; + + retval = kr_attr_valid(type, data); + if (retval != 0) + return retval; + + tmp = calloc(1, sizeof(attr)); + if (tmp == NULL) + return ENOMEM; + + tmp->type = type; + tmp->attr = make_data(tmp->buffer, data->length); + memcpy(tmp->attr.data, data->data, data->length); + + TAILQ_INSERT_TAIL(&set->list, tmp, list); + return 0; +} + +krb5_error_code +krad_attrset_add_number(krad_attrset *set, krad_attr type, krb5_ui_4 num) +{ + krb5_data data; + + num = htonl(num); + data = make_data(&num, sizeof(num)); + return krad_attrset_add(set, type, &data); +} + +void +krad_attrset_del(krad_attrset *set, krad_attr type, size_t indx) +{ + attr *a; + + TAILQ_FOREACH(a, &set->list, list) { + if (a->type == type && indx-- == 0) { + TAILQ_REMOVE(&set->list, a, list); + zap(a->buffer, sizeof(a->buffer)); + free(a); + return; + } + } +} + +const krb5_data * +krad_attrset_get(const krad_attrset *set, krad_attr type, size_t indx) +{ + attr *a; + + TAILQ_FOREACH(a, &set->list, list) { + if (a->type == type && indx-- == 0) + return &a->attr; + } + + return NULL; +} + +krb5_error_code +krad_attrset_copy(const krad_attrset *set, krad_attrset **copy) +{ + krb5_error_code retval; + krad_attrset *tmp; + attr *a; + + retval = krad_attrset_new(set->ctx, &tmp); + if (retval != 0) + return retval; + + TAILQ_FOREACH(a, &set->list, list) { + retval = krad_attrset_add(tmp, a->type, &a->attr); + if (retval != 0) { + krad_attrset_free(tmp); + return retval; + } + } + + *copy = tmp; + return 0; +} + +krb5_error_code +kr_attrset_encode(const krad_attrset *set, const char *secret, + const unsigned char *auth, + unsigned char outbuf[MAX_ATTRSETSIZE], size_t *outlen) +{ + unsigned char buffer[MAX_ATTRSIZE]; + krb5_error_code retval; + size_t i = 0, attrlen; + attr *a; + + if (set == NULL) { + *outlen = 0; + return 0; + } + + TAILQ_FOREACH(a, &set->list, list) { + retval = kr_attr_encode(set->ctx, secret, auth, a->type, &a->attr, + buffer, &attrlen); + if (retval != 0) + return retval; + + if (i + attrlen + 2 > MAX_ATTRSETSIZE) + return EMSGSIZE; + + outbuf[i++] = a->type; + outbuf[i++] = attrlen + 2; + memcpy(&outbuf[i], buffer, attrlen); + i += attrlen; + } + + *outlen = i; + return 0; +} + +krb5_error_code +kr_attrset_decode(krb5_context ctx, const krb5_data *in, const char *secret, + const unsigned char *auth, krad_attrset **set_out) +{ + unsigned char buffer[MAX_ATTRSIZE]; + krb5_data tmp; + krb5_error_code retval; + krad_attr type; + krad_attrset *set; + size_t i, len; + + *set_out = NULL; + + retval = krad_attrset_new(ctx, &set); + if (retval != 0) + return retval; + + for (i = 0; i + 2 < in->length; ) { + type = in->data[i++]; + tmp = make_data(&in->data[i + 1], in->data[i] - 2); + i += tmp.length + 1; + + retval = (in->length < i) ? EBADMSG : 0; + if (retval != 0) + goto cleanup; + + retval = kr_attr_decode(ctx, secret, auth, type, &tmp, buffer, &len); + if (retval != 0) + goto cleanup; + + tmp = make_data(buffer, len); + retval = krad_attrset_add(set, type, &tmp); + if (retval != 0) + goto cleanup; + } + + *set_out = set; + set = NULL; + +cleanup: + zap(buffer, sizeof(buffer)); + krad_attrset_free(set); + return retval; +} diff --git a/src/lib/krad/client.c b/src/lib/krad/client.c new file mode 100644 index 0000000..0c37680 --- /dev/null +++ b/src/lib/krad/client.c @@ -0,0 +1,335 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* lib/krad/client.c - Client request code for libkrad */ +/* + * Copyright 2013 Red Hat, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include "internal.h" + +#include +#include +#include +#include +#include + +LIST_HEAD(server_head, server_st); + +typedef struct remote_state_st remote_state; +typedef struct request_st request; +typedef struct server_st server; + +struct remote_state_st { + const krad_packet *packet; + krad_remote *remote; +}; + +struct request_st { + krad_client *rc; + + krad_code code; + krad_attrset *attrs; + int timeout; + size_t retries; + krad_cb cb; + void *data; + + remote_state *remotes; + ssize_t current; + ssize_t count; +}; + +struct server_st { + krad_remote *serv; + time_t last; + LIST_ENTRY(server_st) list; +}; + +struct krad_client_st { + krb5_context kctx; + verto_ctx *vctx; + struct server_head servers; +}; + +/* Return either a pre-existing server that matches the address info and the + * secret, or create a new one. */ +static krb5_error_code +get_server(krad_client *rc, const struct addrinfo *ai, const char *secret, + krad_remote **out) +{ + krb5_error_code retval; + time_t currtime; + server *srv; + + if (time(&currtime) == (time_t)-1) + return errno; + + LIST_FOREACH(srv, &rc->servers, list) { + if (kr_remote_equals(srv->serv, ai, secret)) { + srv->last = currtime; + *out = srv->serv; + return 0; + } + } + + srv = calloc(1, sizeof(server)); + if (srv == NULL) + return ENOMEM; + srv->last = currtime; + + retval = kr_remote_new(rc->kctx, rc->vctx, ai, secret, &srv->serv); + if (retval != 0) { + free(srv); + return retval; + } + + LIST_INSERT_HEAD(&rc->servers, srv, list); + *out = srv->serv; + return 0; +} + +/* Free a request. */ +static void +request_free(request *req) +{ + krad_attrset_free(req->attrs); + free(req->remotes); + free(req); +} + +/* Create a request. */ +static krb5_error_code +request_new(krad_client *rc, krad_code code, const krad_attrset *attrs, + const struct addrinfo *ai, const char *secret, int timeout, + size_t retries, krad_cb cb, void *data, request **req) +{ + const struct addrinfo *tmp; + krb5_error_code retval; + request *rqst; + size_t i; + + if (ai == NULL) + return EINVAL; + + rqst = calloc(1, sizeof(request)); + if (rqst == NULL) + return ENOMEM; + + for (tmp = ai; tmp != NULL; tmp = tmp->ai_next) + rqst->count++; + + rqst->rc = rc; + rqst->code = code; + rqst->cb = cb; + rqst->data = data; + rqst->timeout = timeout / rqst->count; + rqst->retries = retries; + + retval = krad_attrset_copy(attrs, &rqst->attrs); + if (retval != 0) { + request_free(rqst); + return retval; + } + + rqst->remotes = calloc(rqst->count + 1, sizeof(remote_state)); + if (rqst->remotes == NULL) { + request_free(rqst); + return ENOMEM; + } + + i = 0; + for (tmp = ai; tmp != NULL; tmp = tmp->ai_next) { + retval = get_server(rc, tmp, secret, &rqst->remotes[i++].remote); + if (retval != 0) { + request_free(rqst); + return retval; + } + } + + *req = rqst; + return 0; +} + +/* Close remotes that haven't been used in a while. */ +static void +age(struct server_head *head, time_t currtime) +{ + server *srv, *tmp; + + LIST_FOREACH_SAFE(srv, head, list, tmp) { + if (currtime == (time_t)-1 || currtime - srv->last > 60 * 60) { + LIST_REMOVE(srv, list); + kr_remote_free(srv->serv); + free(srv); + } + } +} + +/* Handle a response from a server (or related errors). */ +static void +on_response(krb5_error_code retval, const krad_packet *reqp, + const krad_packet *rspp, void *data) +{ + request *req = data; + time_t currtime; + size_t i; + + /* Do nothing if we are already completed. */ + if (req->count < 0) + return; + + /* If we have timed out and have more remotes to try, do so. */ + if (retval == ETIMEDOUT && req->remotes[++req->current].remote != NULL) { + retval = kr_remote_send(req->remotes[req->current].remote, req->code, + req->attrs, on_response, req, req->timeout, + req->retries, + &req->remotes[req->current].packet); + if (retval == 0) + return; + } + + /* Mark the request as complete. */ + req->count = -1; + + /* Inform the callback. */ + req->cb(retval, reqp, rspp, req->data); + + /* Cancel the outstanding packets. */ + for (i = 0; req->remotes[i].remote != NULL; i++) + kr_remote_cancel(req->remotes[i].remote, req->remotes[i].packet); + + /* Age out servers that haven't been used in a while. */ + if (time(&currtime) != (time_t)-1) + age(&req->rc->servers, currtime); + + request_free(req); +} + +krb5_error_code +krad_client_new(krb5_context kctx, verto_ctx *vctx, krad_client **out) +{ + krad_client *tmp; + + tmp = calloc(1, sizeof(krad_client)); + if (tmp == NULL) + return ENOMEM; + + tmp->kctx = kctx; + tmp->vctx = vctx; + + *out = tmp; + return 0; +} + +void +krad_client_free(krad_client *rc) +{ + if (rc == NULL) + return; + + age(&rc->servers, -1); + free(rc); +} + +static krb5_error_code +resolve_remote(const char *remote, struct addrinfo **ai) +{ + const char *svc = "radius"; + krb5_error_code retval; + struct addrinfo hints; + char *sep, *srv; + + /* Isolate the port number if it exists. */ + srv = strdup(remote); + if (srv == NULL) + return ENOMEM; + + if (srv[0] == '[') { + /* IPv6 */ + sep = strrchr(srv, ']'); + if (sep != NULL && sep[1] == ':') { + sep[1] = '\0'; + svc = &sep[2]; + } + } else { + /* IPv4 or DNS */ + sep = strrchr(srv, ':'); + if (sep != NULL && sep[1] != '\0') { + sep[0] = '\0'; + svc = &sep[1]; + } + } + + /* Perform the lookup. */ + memset(&hints, 0, sizeof(hints)); + hints.ai_socktype = SOCK_DGRAM; + retval = gai_error_code(getaddrinfo(srv, svc, &hints, ai)); + free(srv); + return retval; +} + +krb5_error_code +krad_client_send(krad_client *rc, krad_code code, const krad_attrset *attrs, + const char *remote, const char *secret, int timeout, + size_t retries, krad_cb cb, void *data) +{ + struct addrinfo usock, *ai = NULL; + krb5_error_code retval; + struct sockaddr_un ua; + request *req; + + if (remote[0] == '/') { + ua.sun_family = AF_UNIX; + snprintf(ua.sun_path, sizeof(ua.sun_path), "%s", remote); + memset(&usock, 0, sizeof(usock)); + usock.ai_family = AF_UNIX; + usock.ai_socktype = SOCK_STREAM; + usock.ai_addr = (struct sockaddr *)&ua; + usock.ai_addrlen = sizeof(ua); + + retval = request_new(rc, code, attrs, &usock, secret, timeout, retries, + cb, data, &req); + } else { + retval = resolve_remote(remote, &ai); + if (retval == 0) { + retval = request_new(rc, code, attrs, ai, secret, timeout, retries, + cb, data, &req); + freeaddrinfo(ai); + } + } + if (retval != 0) + return retval; + + retval = kr_remote_send(req->remotes[req->current].remote, req->code, + req->attrs, on_response, req, req->timeout, + req->retries, &req->remotes[req->current].packet); + if (retval != 0) { + request_free(req); + return retval; + } + + return 0; +} diff --git a/src/lib/krad/code.c b/src/lib/krad/code.c new file mode 100644 index 0000000..16871bb --- /dev/null +++ b/src/lib/krad/code.c @@ -0,0 +1,111 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* lib/krad/code.c - RADIUS code name table for libkrad */ +/* + * Copyright 2013 Red Hat, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "internal.h" + +#include + +static const char *codes[UCHAR_MAX] = { + "Access-Request", + "Access-Accept", + "Access-Reject", + "Accounting-Request", + "Accounting-Response", + "Accounting-Status", + "Password-Request", + "Password-Ack", + "Password-Reject", + "Accounting-Message", + "Access-Challenge", + "Status-Server", + "Status-Client", + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + "Resource-Free-Request", + "Resource-Free-Response", + "Resource-Query-Request", + "Resource-Query-Response", + "Alternate-Resource-Reclaim-Request", + "NAS-Reboot-Request", + "NAS-Reboot-Response", + NULL, + "Next-Passcode", + "New-Pin", + "Terminate-Session", + "Password-Expired", + "Event-Request", + "Event-Response", + NULL, + NULL, + NULL, + NULL, + NULL, + "Disconnect-Request", + "Disconnect-Ack", + "Disconnect-Nak", + "Change-Filters-Request", + "Change-Filters-Ack", + "Change-Filters-Nak", + NULL, + NULL, + NULL, + NULL, + "IP-Address-Allocate", + "IP-Address-Release", +}; + +krad_code +krad_code_name2num(const char *name) +{ + unsigned char i; + + for (i = 0; i < UCHAR_MAX; i++) { + if (codes[i] == NULL) + continue; + + if (strcmp(codes[i], name) == 0) + return ++i; + } + + return 0; +} + +const char * +krad_code_num2name(krad_code code) +{ + if (code == 0) + return NULL; + + return codes[code - 1]; +} diff --git a/src/lib/krad/deps b/src/lib/krad/deps new file mode 100644 index 0000000..8171f94 --- /dev/null +++ b/src/lib/krad/deps @@ -0,0 +1,156 @@ +# +# Generated makefile dependencies follow. +# +attr.so attr.po $(OUTPRE)attr.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \ + $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \ + $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(VERTO_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-platform.h \ + $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-thread.h \ + $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/krad.h \ + $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \ + $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \ + $(top_srcdir)/include/socket-utils.h attr.c internal.h +attrset.so attrset.po $(OUTPRE)attrset.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \ + $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \ + $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(VERTO_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-platform.h \ + $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-queue.h \ + $(top_srcdir)/include/k5-thread.h $(top_srcdir)/include/k5-trace.h \ + $(top_srcdir)/include/krad.h $(top_srcdir)/include/krb5.h \ + $(top_srcdir)/include/krb5/authdata_plugin.h $(top_srcdir)/include/krb5/plugin.h \ + $(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h \ + attrset.c internal.h +client.so client.po $(OUTPRE)client.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \ + $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \ + $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(VERTO_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-platform.h \ + $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-queue.h \ + $(top_srcdir)/include/k5-thread.h $(top_srcdir)/include/k5-trace.h \ + $(top_srcdir)/include/krad.h $(top_srcdir)/include/krb5.h \ + $(top_srcdir)/include/krb5/authdata_plugin.h $(top_srcdir)/include/krb5/plugin.h \ + $(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h \ + client.c internal.h +code.so code.po $(OUTPRE)code.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \ + $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \ + $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(VERTO_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-platform.h \ + $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-thread.h \ + $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/krad.h \ + $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \ + $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \ + $(top_srcdir)/include/socket-utils.h code.c internal.h +packet.so packet.po $(OUTPRE)packet.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \ + $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \ + $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(VERTO_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-platform.h \ + $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-thread.h \ + $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/krad.h \ + $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \ + $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \ + $(top_srcdir)/include/socket-utils.h internal.h packet.c +remote.so remote.po $(OUTPRE)remote.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \ + $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \ + $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(VERTO_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-platform.h \ + $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-queue.h \ + $(top_srcdir)/include/k5-thread.h $(top_srcdir)/include/k5-trace.h \ + $(top_srcdir)/include/krad.h $(top_srcdir)/include/krb5.h \ + $(top_srcdir)/include/krb5/authdata_plugin.h $(top_srcdir)/include/krb5/plugin.h \ + $(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h \ + internal.h remote.c +t_attr.so t_attr.po $(OUTPRE)t_attr.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \ + $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \ + $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(VERTO_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-platform.h \ + $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-thread.h \ + $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/krad.h \ + $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \ + $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \ + $(top_srcdir)/include/socket-utils.h internal.h t_attr.c \ + t_test.h +t_attrset.so t_attrset.po $(OUTPRE)t_attrset.$(OBJEXT): \ + $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \ + $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \ + $(COM_ERR_DEPS) $(VERTO_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-platform.h $(top_srcdir)/include/k5-plugin.h \ + $(top_srcdir)/include/k5-thread.h $(top_srcdir)/include/k5-trace.h \ + $(top_srcdir)/include/krad.h $(top_srcdir)/include/krb5.h \ + $(top_srcdir)/include/krb5/authdata_plugin.h $(top_srcdir)/include/krb5/plugin.h \ + $(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h \ + internal.h t_attrset.c t_test.h +t_client.so t_client.po $(OUTPRE)t_client.$(OBJEXT): \ + $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \ + $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \ + $(COM_ERR_DEPS) $(VERTO_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-platform.h $(top_srcdir)/include/k5-plugin.h \ + $(top_srcdir)/include/k5-thread.h $(top_srcdir)/include/k5-trace.h \ + $(top_srcdir)/include/krad.h $(top_srcdir)/include/krb5.h \ + $(top_srcdir)/include/krb5/authdata_plugin.h $(top_srcdir)/include/krb5/plugin.h \ + $(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h \ + internal.h t_client.c t_daemon.h t_test.h +t_code.so t_code.po $(OUTPRE)t_code.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \ + $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \ + $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(VERTO_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-platform.h \ + $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-thread.h \ + $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/krad.h \ + $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \ + $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \ + $(top_srcdir)/include/socket-utils.h internal.h t_code.c \ + t_test.h +t_packet.so t_packet.po $(OUTPRE)t_packet.$(OBJEXT): \ + $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \ + $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \ + $(COM_ERR_DEPS) $(VERTO_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-platform.h $(top_srcdir)/include/k5-plugin.h \ + $(top_srcdir)/include/k5-thread.h $(top_srcdir)/include/k5-trace.h \ + $(top_srcdir)/include/krad.h $(top_srcdir)/include/krb5.h \ + $(top_srcdir)/include/krb5/authdata_plugin.h $(top_srcdir)/include/krb5/plugin.h \ + $(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h \ + internal.h t_daemon.h t_packet.c t_test.h +t_remote.so t_remote.po $(OUTPRE)t_remote.$(OBJEXT): \ + $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \ + $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \ + $(COM_ERR_DEPS) $(VERTO_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-platform.h $(top_srcdir)/include/k5-plugin.h \ + $(top_srcdir)/include/k5-thread.h $(top_srcdir)/include/k5-trace.h \ + $(top_srcdir)/include/krad.h $(top_srcdir)/include/krb5.h \ + $(top_srcdir)/include/krb5/authdata_plugin.h $(top_srcdir)/include/krb5/plugin.h \ + $(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h \ + internal.h t_daemon.h t_remote.c t_test.h +t_test.so t_test.po $(OUTPRE)t_test.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \ + $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \ + $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(VERTO_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-platform.h \ + $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-thread.h \ + $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/krad.h \ + $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \ + $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \ + $(top_srcdir)/include/socket-utils.h internal.h t_test.c \ + t_test.h diff --git a/src/lib/krad/internal.h b/src/lib/krad/internal.h new file mode 100644 index 0000000..996a893 --- /dev/null +++ b/src/lib/krad/internal.h @@ -0,0 +1,155 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* lib/krad/internal.h - Internal declarations for libkrad */ +/* + * Copyright 2013 Red Hat, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef INTERNAL_H_ +#define INTERNAL_H_ + +#include +#include "krad.h" + +#include + +#include +#include +#include + +#ifndef UCHAR_MAX +#define UCHAR_MAX 255 +#endif + +/* RFC 2865 */ +#define MAX_ATTRSIZE (UCHAR_MAX - 2) +#define MAX_ATTRSETSIZE (KRAD_PACKET_SIZE_MAX - 20) + +typedef struct krad_remote_st krad_remote; + +/* Validate constraints of an attribute. */ +krb5_error_code +kr_attr_valid(krad_attr type, const krb5_data *data); + +/* Encode an attribute. */ +krb5_error_code +kr_attr_encode(krb5_context ctx, const char *secret, const unsigned char *auth, + krad_attr type, const krb5_data *in, + unsigned char outbuf[MAX_ATTRSIZE], size_t *outlen); + +/* Decode an attribute. */ +krb5_error_code +kr_attr_decode(krb5_context ctx, const char *secret, const unsigned char *auth, + krad_attr type, const krb5_data *in, + unsigned char outbuf[MAX_ATTRSIZE], size_t *outlen); + +/* Encode the attributes into the buffer. */ +krb5_error_code +kr_attrset_encode(const krad_attrset *set, const char *secret, + const unsigned char *auth, + unsigned char outbuf[MAX_ATTRSETSIZE], size_t *outlen); + +/* Decode attributes from a buffer. */ +krb5_error_code +kr_attrset_decode(krb5_context ctx, const krb5_data *in, const char *secret, + const unsigned char *auth, krad_attrset **set); + +/* Create a new remote object which manages a socket and the state of + * outstanding requests. */ +krb5_error_code +kr_remote_new(krb5_context kctx, verto_ctx *vctx, const struct addrinfo *info, + const char *secret, krad_remote **rr); + +/* Free a remote object. */ +void +kr_remote_free(krad_remote *rr); + +/* + * Send the packet to the remote. The cb will be called when a response is + * received, the request times out, the request is canceled or an error occurs. + * + * The timeout parameter is the total timeout across all retries in + * milliseconds. + * + * If the cb is called with a retval of ETIMEDOUT it indicates that the alloted + * time has elapsed. However, in the case of a timeout, we continue to listen + * for the packet until krad_remote_cancel() is called or a response is + * received. This means that cb will always be called twice in the event of a + * timeout. This permits you to pursue other remotes while still listening for + * a response from the first one. + */ +krb5_error_code +kr_remote_send(krad_remote *rr, krad_code code, krad_attrset *attrs, + krad_cb cb, void *data, int timeout, size_t retries, + const krad_packet **pkt); + +/* Remove packet from the queue of requests awaiting responses. */ +void +kr_remote_cancel(krad_remote *rr, const krad_packet *pkt); + +/* Determine if this remote object refers to the remote resource identified + * by the addrinfo struct and the secret. */ +krb5_boolean +kr_remote_equals(const krad_remote *rr, const struct addrinfo *info, + const char *secret); + +/* Adapted from lib/krb5/os/sendto_kdc.c. */ +static inline krb5_error_code +gai_error_code(int err) +{ + switch (err) { + case 0: + return 0; + case EAI_BADFLAGS: + case EAI_FAMILY: + case EAI_SOCKTYPE: + case EAI_SERVICE: +#ifdef EAI_ADDRFAMILY + case EAI_ADDRFAMILY: +#endif + return EINVAL; + case EAI_AGAIN: + return EAGAIN; + case EAI_MEMORY: + return ENOMEM; +#if defined(EAI_NODATA) && EAI_NODATA != EAI_NONAME + case EAI_NODATA: +#endif + case EAI_NONAME: + return EADDRNOTAVAIL; +#ifdef EAI_OVERFLOW + case EAI_OVERFLOW: + return EOVERFLOW; +#endif +#ifdef EAI_SYSTEM + case EAI_SYSTEM: + return errno; +#endif + default: + return EINVAL; + } +} + +#endif /* INTERNAL_H_ */ diff --git a/src/lib/krad/libkrad.exports b/src/lib/krad/libkrad.exports new file mode 100644 index 0000000..fe3f159 --- /dev/null +++ b/src/lib/krad/libkrad.exports @@ -0,0 +1,23 @@ +krad_code_name2num +krad_code_num2name +krad_attr_name2num +krad_attr_num2name +krad_attrset_new +krad_attrset_copy +krad_attrset_free +krad_attrset_add +krad_attrset_add_number +krad_attrset_del +krad_attrset_get +krad_packet_bytes_needed +krad_packet_free +krad_packet_new_request +krad_packet_new_response +krad_packet_decode_request +krad_packet_decode_response +krad_packet_encode +krad_packet_get_code +krad_packet_get_attr +krad_client_new +krad_client_free +krad_client_send diff --git a/src/lib/krad/packet.c b/src/lib/krad/packet.c new file mode 100644 index 0000000..6e6b27e --- /dev/null +++ b/src/lib/krad/packet.c @@ -0,0 +1,470 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* lib/krad/packet.c - Packet functions for libkrad */ +/* + * Copyright 2013 Red Hat, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "internal.h" + +#include + +#include + +typedef unsigned char uchar; + +/* RFC 2865 */ +#define OFFSET_CODE 0 +#define OFFSET_ID 1 +#define OFFSET_LENGTH 2 +#define OFFSET_AUTH 4 +#define OFFSET_ATTR 20 +#define AUTH_FIELD_SIZE (OFFSET_ATTR - OFFSET_AUTH) + +#define offset(d, o) (&(d)->data[o]) +#define pkt_code_get(p) (*(krad_code *)offset(&(p)->pkt, OFFSET_CODE)) +#define pkt_code_set(p, v) (*(krad_code *)offset(&(p)->pkt, OFFSET_CODE)) = v +#define pkt_id_get(p) (*(uchar *)offset(&(p)->pkt, OFFSET_ID)) +#define pkt_id_set(p, v) (*(uchar *)offset(&(p)->pkt, OFFSET_ID)) = v +#define pkt_len_get(p) load_16_be(offset(&(p)->pkt, OFFSET_LENGTH)) +#define pkt_len_set(p, v) store_16_be(v, offset(&(p)->pkt, OFFSET_LENGTH)) +#define pkt_auth(p) ((uchar *)offset(&(p)->pkt, OFFSET_AUTH)) +#define pkt_attr(p) ((unsigned char *)offset(&(p)->pkt, OFFSET_ATTR)) + +struct krad_packet_st { + char buffer[KRAD_PACKET_SIZE_MAX]; + krad_attrset *attrset; + krb5_data pkt; +}; + +typedef struct { + uchar x[(UCHAR_MAX + 1) / 8]; +} idmap; + +/* Ensure the map is empty. */ +static inline void +idmap_init(idmap *map) +{ + memset(map, 0, sizeof(*map)); +} + +/* Set an id as already allocated. */ +static inline void +idmap_set(idmap *map, uchar id) +{ + map->x[id / 8] |= 1 << (id % 8); +} + +/* Determine whether or not an id is used. */ +static inline krb5_boolean +idmap_isset(const idmap *map, uchar id) +{ + return (map->x[id / 8] & (1 << (id % 8))) != 0; +} + +/* Find an unused id starting the search at the value specified in id. + * NOTE: For optimal security, the initial value of id should be random. */ +static inline krb5_error_code +idmap_find(const idmap *map, uchar *id) +{ + krb5_int16 i; + + for (i = *id; i >= 0 && i <= UCHAR_MAX; *id % 2 == 0 ? i++ : i--) { + if (!idmap_isset(map, i)) + goto success; + } + + for (i = *id; i >= 0 && i <= UCHAR_MAX; *id % 2 == 1 ? i++ : i--) { + if (!idmap_isset(map, i)) + goto success; + } + + return ERANGE; + +success: + *id = i; + return 0; +} + +/* Generate size bytes of random data into the buffer. */ +static inline krb5_error_code +randomize(krb5_context ctx, void *buffer, unsigned int size) +{ + krb5_data rdata = make_data(buffer, size); + return krb5_c_random_make_octets(ctx, &rdata); +} + +/* Generate a radius packet id. */ +static krb5_error_code +id_generate(krb5_context ctx, krad_packet_iter_cb cb, void *data, uchar *id) +{ + krb5_error_code retval; + const krad_packet *tmp; + idmap used; + uchar i; + + retval = randomize(ctx, &i, sizeof(i)); + if (retval != 0) { + if (cb != NULL) + (*cb)(data, TRUE); + return retval; + } + + if (cb != NULL) { + idmap_init(&used); + for (tmp = (*cb)(data, FALSE); tmp != NULL; tmp = (*cb)(data, FALSE)) + idmap_set(&used, tmp->pkt.data[1]); + + retval = idmap_find(&used, &i); + if (retval != 0) + return retval; + } + + *id = i; + return 0; +} + +/* Generate a random authenticator field. */ +static krb5_error_code +auth_generate_random(krb5_context ctx, uchar *rauth) +{ + krb5_ui_4 trunctime; + time_t currtime; + + /* Get the least-significant four bytes of the current time. */ + currtime = time(NULL); + if (currtime == (time_t)-1) + return errno; + trunctime = (krb5_ui_4)currtime; + memcpy(rauth, &trunctime, sizeof(trunctime)); + + /* Randomize the rest of the buffer. */ + return randomize(ctx, rauth + sizeof(trunctime), + AUTH_FIELD_SIZE - sizeof(trunctime)); +} + +/* Generate a response authenticator field. */ +static krb5_error_code +auth_generate_response(krb5_context ctx, const char *secret, + const krad_packet *response, const uchar *auth, + uchar *rauth) +{ + krb5_error_code retval; + krb5_checksum hash; + krb5_data data; + + /* Allocate the temporary buffer. */ + retval = alloc_data(&data, response->pkt.length + strlen(secret)); + if (retval != 0) + return retval; + + /* Encoded RADIUS packet with the request's + * authenticator and the secret at the end. */ + memcpy(data.data, response->pkt.data, response->pkt.length); + memcpy(data.data + OFFSET_AUTH, auth, AUTH_FIELD_SIZE); + memcpy(data.data + response->pkt.length, secret, strlen(secret)); + + /* Hash it. */ + retval = krb5_c_make_checksum(ctx, CKSUMTYPE_RSA_MD5, NULL, 0, &data, + &hash); + free(data.data); + if (retval != 0) + return retval; + + memcpy(rauth, hash.contents, AUTH_FIELD_SIZE); + krb5_free_checksum_contents(ctx, &hash); + return 0; +} + +/* Create a new packet. */ +static krad_packet * +packet_new() +{ + krad_packet *pkt; + + pkt = calloc(1, sizeof(krad_packet)); + if (pkt == NULL) + return NULL; + pkt->pkt = make_data(pkt->buffer, sizeof(pkt->buffer)); + + return pkt; +} + +/* Set the attrset object by decoding the packet. */ +static krb5_error_code +packet_set_attrset(krb5_context ctx, const char *secret, krad_packet *pkt) +{ + krb5_data tmp; + + tmp = make_data(pkt_attr(pkt), pkt->pkt.length - OFFSET_ATTR); + return kr_attrset_decode(ctx, &tmp, secret, pkt_auth(pkt), &pkt->attrset); +} + +ssize_t +krad_packet_bytes_needed(const krb5_data *buffer) +{ + size_t len; + + if (buffer->length < OFFSET_AUTH) + return OFFSET_AUTH - buffer->length; + + len = load_16_be(offset(buffer, OFFSET_LENGTH)); + if (len > KRAD_PACKET_SIZE_MAX) + return -1; + + return buffer->length > len ? 0 : len - buffer->length; +} + +void +krad_packet_free(krad_packet *pkt) +{ + if (pkt) + krad_attrset_free(pkt->attrset); + free(pkt); +} + +/* Create a new request packet. */ +krb5_error_code +krad_packet_new_request(krb5_context ctx, const char *secret, krad_code code, + const krad_attrset *set, krad_packet_iter_cb cb, + void *data, krad_packet **request) +{ + krb5_error_code retval; + krad_packet *pkt; + uchar id; + size_t attrset_len; + + pkt = packet_new(); + if (pkt == NULL) { + if (cb != NULL) + (*cb)(data, TRUE); + return ENOMEM; + } + + /* Generate the ID. */ + retval = id_generate(ctx, cb, data, &id); + if (retval != 0) + goto error; + pkt_id_set(pkt, id); + + /* Generate the authenticator. */ + retval = auth_generate_random(ctx, pkt_auth(pkt)); + if (retval != 0) + goto error; + + /* Encode the attributes. */ + retval = kr_attrset_encode(set, secret, pkt_auth(pkt), pkt_attr(pkt), + &attrset_len); + if (retval != 0) + goto error; + + /* Set the code, ID and length. */ + pkt->pkt.length = attrset_len + OFFSET_ATTR; + pkt_code_set(pkt, code); + pkt_len_set(pkt, pkt->pkt.length); + + /* Copy the attrset for future use. */ + retval = packet_set_attrset(ctx, secret, pkt); + if (retval != 0) + goto error; + + *request = pkt; + return 0; + +error: + free(pkt); + return retval; +} + +/* Create a new request packet. */ +krb5_error_code +krad_packet_new_response(krb5_context ctx, const char *secret, krad_code code, + const krad_attrset *set, const krad_packet *request, + krad_packet **response) +{ + krb5_error_code retval; + krad_packet *pkt; + size_t attrset_len; + + pkt = packet_new(); + if (pkt == NULL) + return ENOMEM; + + /* Encode the attributes. */ + retval = kr_attrset_encode(set, secret, pkt_auth(request), pkt_attr(pkt), + &attrset_len); + if (retval != 0) + goto error; + + /* Set the code, ID and length. */ + pkt->pkt.length = attrset_len + OFFSET_ATTR; + pkt_code_set(pkt, code); + pkt_id_set(pkt, pkt_id_get(request)); + pkt_len_set(pkt, pkt->pkt.length); + + /* Generate the authenticator. */ + retval = auth_generate_response(ctx, secret, pkt, pkt_auth(request), + pkt_auth(pkt)); + if (retval != 0) + goto error; + + /* Copy the attrset for future use. */ + retval = packet_set_attrset(ctx, secret, pkt); + if (retval != 0) + goto error; + + *response = pkt; + return 0; + +error: + free(pkt); + return retval; +} + +/* Decode a packet. */ +static krb5_error_code +decode_packet(krb5_context ctx, const char *secret, const krb5_data *buffer, + krad_packet **pkt) +{ + krb5_error_code retval; + krad_packet *tmp; + krb5_ui_2 len; + + tmp = packet_new(); + if (tmp == NULL) { + retval = ENOMEM; + goto error; + } + + /* Ensure a proper message length. */ + retval = buffer->length < OFFSET_ATTR ? EMSGSIZE : 0; + if (retval != 0) + goto error; + len = load_16_be(offset(buffer, OFFSET_LENGTH)); + retval = len < OFFSET_ATTR ? EBADMSG : 0; + if (retval != 0) + goto error; + retval = (len > buffer->length || len > tmp->pkt.length) ? EBADMSG : 0; + if (retval != 0) + goto error; + + /* Copy over the buffer. */ + tmp->pkt.length = len; + memcpy(tmp->pkt.data, buffer->data, len); + + /* Parse the packet to ensure it is well-formed. */ + retval = packet_set_attrset(ctx, secret, tmp); + if (retval != 0) + goto error; + + *pkt = tmp; + return 0; + +error: + krad_packet_free(tmp); + return retval; +} + +krb5_error_code +krad_packet_decode_request(krb5_context ctx, const char *secret, + const krb5_data *buffer, krad_packet_iter_cb cb, + void *data, const krad_packet **duppkt, + krad_packet **reqpkt) +{ + const krad_packet *tmp = NULL; + krb5_error_code retval; + + retval = decode_packet(ctx, secret, buffer, reqpkt); + if (cb != NULL && retval == 0) { + for (tmp = (*cb)(data, FALSE); tmp != NULL; tmp = (*cb)(data, FALSE)) { + if (pkt_id_get(*reqpkt) == pkt_id_get(tmp)) + break; + } + } + + if (cb != NULL && (retval != 0 || tmp != NULL)) + (*cb)(data, TRUE); + + *duppkt = tmp; + return retval; +} + +krb5_error_code +krad_packet_decode_response(krb5_context ctx, const char *secret, + const krb5_data *buffer, krad_packet_iter_cb cb, + void *data, const krad_packet **reqpkt, + krad_packet **rsppkt) +{ + uchar auth[AUTH_FIELD_SIZE]; + const krad_packet *tmp = NULL; + krb5_error_code retval; + + retval = decode_packet(ctx, secret, buffer, rsppkt); + if (cb != NULL && retval == 0) { + for (tmp = (*cb)(data, FALSE); tmp != NULL; tmp = (*cb)(data, FALSE)) { + if (pkt_id_get(*rsppkt) != pkt_id_get(tmp)) + continue; + + /* Response */ + retval = auth_generate_response(ctx, secret, *rsppkt, + pkt_auth(tmp), auth); + if (retval != 0) { + krad_packet_free(*rsppkt); + break; + } + + /* If the authenticator matches, then the response is valid. */ + if (memcmp(pkt_auth(*rsppkt), auth, sizeof(auth)) == 0) + break; + } + } + + if (cb != NULL && (retval != 0 || tmp != NULL)) + (*cb)(data, TRUE); + + *reqpkt = tmp; + return retval; +} + +const krb5_data * +krad_packet_encode(const krad_packet *pkt) +{ + return &pkt->pkt; +} + +krad_code +krad_packet_get_code(const krad_packet *pkt) +{ + if (pkt == NULL) + return 0; + + return pkt_code_get(pkt); +} + +const krb5_data * +krad_packet_get_attr(const krad_packet *pkt, krad_attr type, size_t indx) +{ + return krad_attrset_get(pkt->attrset, type, indx); +} diff --git a/src/lib/krad/remote.c b/src/lib/krad/remote.c new file mode 100644 index 0000000..74d9865 --- /dev/null +++ b/src/lib/krad/remote.c @@ -0,0 +1,519 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* lib/krad/remote.c - Protocol code for libkrad */ +/* + * Copyright 2013 Red Hat, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include "internal.h" + +#include +#include + +#include + +#define FLAGS_READ (VERTO_EV_FLAG_PERSIST | VERTO_EV_FLAG_IO_CLOSE_FD | \ + VERTO_EV_FLAG_IO_ERROR | VERTO_EV_FLAG_IO_READ) +#define FLAGS_WRITE (FLAGS_READ | VERTO_EV_FLAG_IO_WRITE) + +TAILQ_HEAD(request_head, request_st); + +typedef struct request_st request; +struct request_st { + TAILQ_ENTRY(request_st) list; + krad_remote *rr; + krad_packet *request; + krad_cb cb; + void *data; + verto_ev *timer; + int timeout; + size_t retries; + size_t sent; +}; + +struct krad_remote_st { + krb5_context kctx; + verto_ctx *vctx; + verto_ev *io; + char *secret; + struct addrinfo *info; + struct request_head list; + char buffer_[KRAD_PACKET_SIZE_MAX]; + krb5_data buffer; +}; + +static void +on_io(verto_ctx *ctx, verto_ev *ev); + +/* Iterate over the set of outstanding packets. */ +static const krad_packet * +iterator(request **out) +{ + request *tmp = *out; + + if (tmp == NULL) + return NULL; + + *out = TAILQ_NEXT(tmp, list); + return tmp->request; +} + +/* Create a new request. */ +static krb5_error_code +request_new(krad_remote *rr, krad_packet *rqst, int timeout, size_t retries, + krad_cb cb, void *data, request **out) +{ + request *tmp; + + tmp = calloc(1, sizeof(request)); + if (tmp == NULL) + return ENOMEM; + + tmp->rr = rr; + tmp->request = rqst; + tmp->cb = cb; + tmp->data = data; + tmp->timeout = timeout; + tmp->retries = retries; + + *out = tmp; + return 0; +} + +/* Finish a request, calling the callback and freeing it. */ +static inline void +request_finish(request *req, krb5_error_code retval, + const krad_packet *response) +{ + if (retval != ETIMEDOUT) + TAILQ_REMOVE(&req->rr->list, req, list); + + req->cb(retval, req->request, response, req->data); + + if (retval != ETIMEDOUT) { + krad_packet_free(req->request); + verto_del(req->timer); + free(req); + } +} + +/* Handle when packets receive no response within their alloted time. */ +static void +on_timeout(verto_ctx *ctx, verto_ev *ev) +{ + request *req = verto_get_private(ev); + + req->timer = NULL; /* Void the timer event. */ + + /* If we have more retries to perform, resend the packet. */ + if (req->retries-- > 1) { + req->sent = 0; + verto_set_flags(req->rr->io, FLAGS_WRITE); + return; + } + + request_finish(req, ETIMEDOUT, NULL); +} + +/* Connect to the remote host. */ +static krb5_error_code +remote_connect(krad_remote *rr) +{ + int i, sock = -1; + verto_ev *ev; + + sock = socket(rr->info->ai_family, rr->info->ai_socktype, + rr->info->ai_protocol); + if (sock < 0) + return errno; + + i = connect(sock, rr->info->ai_addr, rr->info->ai_addrlen); + if (i < 0) { + i = errno; + close(sock); + return i; + } + + ev = verto_add_io(rr->vctx, FLAGS_READ, on_io, sock); + if (ev == NULL) { + close(sock); + return ENOMEM; + } + + rr->io = ev; + verto_set_private(rr->io, rr, NULL); + return 0; +} + +/* Disconnect and reconnect to the remote host. */ +static krb5_error_code +remote_reconnect(krad_remote *rr, int errnum) +{ + krb5_error_code retval; + const krb5_data *tmp; + request *r; + + verto_del(rr->io); + rr->io = NULL; + retval = remote_connect(rr); + if (retval != 0) + return retval; + + TAILQ_FOREACH(r, &rr->list, list) { + tmp = krad_packet_encode(r->request); + + if (r->sent == tmp->length) { + /* Error out sent requests. */ + request_finish(r, errnum, NULL); + } else { + /* Reset partially sent requests. */ + r->sent = 0; + } + } + + return 0; +} + +/* Close the connection and call the callbacks of all oustanding requests. */ +static void +remote_shutdown(krad_remote *rr, int errnum) +{ + verto_del(rr->io); + rr->io = NULL; + while (!TAILQ_EMPTY(&rr->list)) + request_finish(TAILQ_FIRST(&rr->list), errnum, NULL); +} + +/* Write data to the socket. */ +static void +on_io_write(krad_remote *rr) +{ + const krb5_data *tmp; + request *r; + int i; + + TAILQ_FOREACH(r, &rr->list, list) { + tmp = krad_packet_encode(r->request); + + /* If the packet has already been sent, do nothing. */ + if (r->sent == tmp->length) + continue; + + /* Send the packet. */ + i = sendto(verto_get_fd(rr->io), tmp->data + r->sent, + tmp->length - r->sent, 0, NULL, 0); + if (i < 0) { + /* Should we try again? */ + if (errno == EWOULDBLOCK || errno == EAGAIN || errno == ENOBUFS || + errno == EINTR) + return; + + /* In this case, we need to re-connect. */ + i = remote_reconnect(rr, errno); + if (i == 0) + return; + + /* Do a full reset. */ + remote_shutdown(rr, i); + return; + } + + /* SOCK_STREAM permits partial writes. */ + if (rr->info->ai_socktype == SOCK_STREAM) + r->sent += i; + else if (i == (int)tmp->length) + r->sent = i; + + /* If the packet was completely sent, set a timeout. */ + if (r->sent == tmp->length) { + verto_del(r->timer); + r->timer = verto_add_timeout(rr->vctx, VERTO_EV_FLAG_NONE, + on_timeout, r->timeout); + if (r->timer == NULL) + request_finish(r, ENOMEM, NULL); + else + verto_set_private(r->timer, r, NULL); + } + + return; + } + + verto_set_flags(rr->io, FLAGS_READ); +} + +/* Read data from the socket. */ +static void +on_io_read(krad_remote *rr) +{ + const krad_packet *req = NULL; + krad_packet *rsp = NULL; + krb5_error_code retval; + ssize_t pktlen; + request *tmp, *r; + int i; + + pktlen = sizeof(rr->buffer_); + if (rr->info->ai_socktype == SOCK_STREAM) { + pktlen = krad_packet_bytes_needed(&rr->buffer); + if (pktlen < 0) { + retval = remote_reconnect(rr, EBADMSG); + if (retval != 0) + remote_shutdown(rr, retval); + return; + } + } + + /* Read the packet. */ + i = recv(verto_get_fd(rr->io), rr->buffer.data + rr->buffer.length, + pktlen - rr->buffer.length, 0); + if (i < 0) { + /* Should we try again? */ + if (errno == EWOULDBLOCK || errno == EAGAIN || errno == EINTR) + return; + + if (errno == ECONNREFUSED || errno == ECONNRESET || + errno == ENOTCONN) { + /* + * When doing UDP against a local socket, the kernel will notify + * when the daemon closes. But not against remote sockets. We want + * to treat them both the same. Returning here will cause an + * eventual timeout. + */ + if (rr->info->ai_socktype != SOCK_STREAM) + return; + } + + /* In this case, we need to re-connect. */ + i = remote_reconnect(rr, errno); + if (i == 0) + return; + + /* Do a full reset. */ + remote_shutdown(rr, i); + return; + } + + /* If we have a partial read or just the header, try again. */ + rr->buffer.length += i; + pktlen = krad_packet_bytes_needed(&rr->buffer); + if (rr->info->ai_socktype == SOCK_STREAM && pktlen > 0) + return; + + /* Decode the packet. */ + tmp = TAILQ_FIRST(&rr->list); + retval = krad_packet_decode_response(rr->kctx, rr->secret, &rr->buffer, + (krad_packet_iter_cb)iterator, &tmp, + &req, &rsp); + rr->buffer.length = 0; + if (retval != 0) + return; + + /* Match the response with an outstanding request. */ + if (req != NULL) { + TAILQ_FOREACH(r, &rr->list, list) { + if (r->request == req && + r->sent == krad_packet_encode(req)->length) { + request_finish(r, 0, rsp); + break; + } + } + } + + krad_packet_free(rsp); +} + +/* Handle when IO is ready on the socket. */ +static void +on_io(verto_ctx *ctx, verto_ev *ev) +{ + krad_remote *rr; + + rr = verto_get_private(ev); + + if (verto_get_fd_state(ev) & VERTO_EV_FLAG_IO_WRITE) + on_io_write(rr); + else + on_io_read(rr); +} + +krb5_error_code +kr_remote_new(krb5_context kctx, verto_ctx *vctx, const struct addrinfo *info, + const char *secret, krad_remote **rr) +{ + krb5_error_code retval = ENOMEM; + krad_remote *tmp = NULL; + + tmp = calloc(1, sizeof(krad_remote)); + if (tmp == NULL) + goto error; + tmp->kctx = kctx; + tmp->vctx = vctx; + tmp->buffer = make_data(tmp->buffer_, 0); + TAILQ_INIT(&tmp->list); + + tmp->secret = strdup(secret); + if (tmp->secret == NULL) + goto error; + + tmp->info = k5memdup(info, sizeof(*info), &retval); + if (tmp->info == NULL) + goto error; + + tmp->info->ai_addr = k5memdup(info->ai_addr, info->ai_addrlen, &retval); + if (tmp->info == NULL) + goto error; + tmp->info->ai_next = NULL; + tmp->info->ai_canonname = NULL; + + retval = remote_connect(tmp); + if (retval != 0) + goto error; + + *rr = tmp; + return 0; + +error: + kr_remote_free(tmp); + return retval; +} + +void +kr_remote_free(krad_remote *rr) +{ + if (rr == NULL) + return; + + while (!TAILQ_EMPTY(&rr->list)) + request_finish(TAILQ_FIRST(&rr->list), ECANCELED, NULL); + + free(rr->secret); + if (rr->info != NULL) + free(rr->info->ai_addr); + free(rr->info); + verto_del(rr->io); + free(rr); +} + +krb5_error_code +kr_remote_send(krad_remote *rr, krad_code code, krad_attrset *attrs, + krad_cb cb, void *data, int timeout, size_t retries, + const krad_packet **pkt) +{ + krad_packet *tmp = NULL; + krb5_error_code retval; + request *r; + + r = TAILQ_FIRST(&rr->list); + retval = krad_packet_new_request(rr->kctx, rr->secret, code, attrs, + (krad_packet_iter_cb)iterator, &r, &tmp); + if (retval != 0) + goto error; + + TAILQ_FOREACH(r, &rr->list, list) { + if (r->request == tmp) { + retval = EALREADY; + goto error; + } + } + + if (rr->io == NULL) { + retval = remote_connect(rr); + if (retval != 0) + goto error; + } + + if (rr->info->ai_socktype == SOCK_STREAM) + retries = 0; + timeout = timeout / (retries + 1); + retval = request_new(rr, tmp, timeout, retries, cb, data, &r); + if (retval != 0) + goto error; + + if ((verto_get_flags(rr->io) & VERTO_EV_FLAG_IO_WRITE) == 0) + verto_set_flags(rr->io, FLAGS_WRITE); + + TAILQ_INSERT_TAIL(&rr->list, r, list); + if (pkt != NULL) + *pkt = tmp; + return 0; + +error: + krad_packet_free(tmp); + return retval; +} + +void +kr_remote_cancel(krad_remote *rr, const krad_packet *pkt) +{ + request *r; + + TAILQ_FOREACH(r, &rr->list, list) { + if (r->request == pkt) { + request_finish(r, ECANCELED, NULL); + return; + } + } +} + +krb5_boolean +kr_remote_equals(const krad_remote *rr, const struct addrinfo *info, + const char *secret) +{ + struct sockaddr_un *a, *b; + + if (strcmp(rr->secret, secret) != 0) + return FALSE; + + if (info->ai_addrlen != rr->info->ai_addrlen) + return FALSE; + + if (info->ai_family != rr->info->ai_family) + return FALSE; + + if (info->ai_socktype != rr->info->ai_socktype) + return FALSE; + + if (info->ai_protocol != rr->info->ai_protocol) + return FALSE; + + if (info->ai_flags != rr->info->ai_flags) + return FALSE; + + if (memcmp(rr->info->ai_addr, info->ai_addr, info->ai_addrlen) != 0) { + /* AF_UNIX fails the memcmp() test due to uninitialized bytes after the + * socket name. */ + if (info->ai_family != AF_UNIX) + return FALSE; + + a = (struct sockaddr_un *)info->ai_addr; + b = (struct sockaddr_un *)rr->info->ai_addr; + if (strncmp(a->sun_path, b->sun_path, sizeof(a->sun_path)) != 0) + return FALSE; + } + + return TRUE; +} diff --git a/src/lib/krad/t_attr.c b/src/lib/krad/t_attr.c new file mode 100644 index 0000000..e80d77b --- /dev/null +++ b/src/lib/krad/t_attr.c @@ -0,0 +1,89 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* lib/krad/t_attr.c - Attribute test program */ +/* + * Copyright 2013 Red Hat, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "t_test.h" + +const static char encoded[] = { + 0xba, 0xfc, 0xed, 0x50, 0xe1, 0xeb, 0xa6, 0xc3, + 0xc1, 0x75, 0x20, 0xe9, 0x10, 0xce, 0xc2, 0xcb +}; + +const static unsigned char auth[] = { + 0xac, 0x9d, 0xc1, 0x62, 0x08, 0xc4, 0xc7, 0x8b, + 0xa1, 0x2f, 0x25, 0x0a, 0xc4, 0x1d, 0x36, 0x41 +}; + +int +main() +{ + unsigned char outbuf[MAX_ATTRSETSIZE]; + const char *decoded = "accept"; + const char *secret = "foo"; + krb5_error_code retval; + krb5_context ctx; + const char *tmp; + krb5_data in; + size_t len; + + noerror(krb5_init_context(&ctx)); + + /* Make sure User-Name is 1. */ + insist(krad_attr_name2num("User-Name") == 1); + + /* Make sure 2 is User-Password. */ + tmp = krad_attr_num2name(2); + insist(tmp != NULL); + insist(strcmp(tmp, "User-Password") == 0); + + /* Test decoding. */ + in = make_data((void *)encoded, sizeof(encoded)); + noerror(kr_attr_decode(ctx, secret, auth, + krad_attr_name2num("User-Password"), + &in, outbuf, &len)); + insist(len == strlen(decoded)); + insist(memcmp(outbuf, decoded, len) == 0); + + /* Test encoding. */ + in = string2data((char *)decoded); + retval = kr_attr_encode(ctx, secret, auth, + krad_attr_name2num("User-Password"), + &in, outbuf, &len); + insist(retval == 0); + insist(len == sizeof(encoded)); + insist(memcmp(outbuf, encoded, len) == 0); + + /* Test constraint. */ + in.length = 100; + insist(kr_attr_valid(krad_attr_name2num("User-Password"), &in) == 0); + in.length = 200; + insist(kr_attr_valid(krad_attr_name2num("User-Password"), &in) != 0); + + krb5_free_context(ctx); + return 0; +} diff --git a/src/lib/krad/t_attrset.c b/src/lib/krad/t_attrset.c new file mode 100644 index 0000000..afae5e4 --- /dev/null +++ b/src/lib/krad/t_attrset.c @@ -0,0 +1,98 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* lib/krad/t_attrset.c - Attribute set test program */ +/* + * Copyright 2013 Red Hat, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "t_test.h" + +const static unsigned char auth[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +const static char encpass[] = { + 0x58, 0x8d, 0xff, 0xda, 0x37, 0xf9, 0xe4, 0xca, + 0x19, 0xae, 0x49, 0xb7, 0x16, 0x6d, 0x58, 0x27 +}; + +int +main() +{ + unsigned char buffer[KRAD_PACKET_SIZE_MAX], encoded[MAX_ATTRSETSIZE]; + const char *username = "testUser", *password = "accept"; + const krb5_data *tmpp; + krad_attrset *set; + krb5_context ctx; + size_t len = 0, encode_len; + krb5_data tmp; + + noerror(krb5_init_context(&ctx)); + noerror(krad_attrset_new(ctx, &set)); + + /* Add username. */ + tmp = string2data((char *)username); + noerror(krad_attrset_add(set, krad_attr_name2num("User-Name"), &tmp)); + + /* Add password. */ + tmp = string2data((char *)password); + noerror(krad_attrset_add(set, krad_attr_name2num("User-Password"), &tmp)); + + /* Encode attrset. */ + noerror(kr_attrset_encode(set, "foo", auth, buffer, &encode_len)); + krad_attrset_free(set); + + /* Manually encode User-Name. */ + encoded[len + 0] = krad_attr_name2num("User-Name"); + encoded[len + 1] = strlen(username) + 2; + memcpy(encoded + len + 2, username, strlen(username)); + len += encoded[len + 1]; + + /* Manually encode User-Password. */ + encoded[len + 0] = krad_attr_name2num("User-Password"); + encoded[len + 1] = sizeof(encpass) + 2; + memcpy(encoded + len + 2, encpass, sizeof(encpass)); + len += encoded[len + 1]; + + /* Compare output. */ + insist(len == encode_len); + insist(memcmp(encoded, buffer, len) == 0); + + /* Decode output. */ + tmp = make_data(buffer, len); + noerror(kr_attrset_decode(ctx, &tmp, "foo", auth, &set)); + + /* Test getting an attribute. */ + tmp = string2data((char *)username); + tmpp = krad_attrset_get(set, krad_attr_name2num("User-Name"), 0); + insist(tmpp != NULL); + insist(tmpp->length == tmp.length); + insist(strncmp(tmpp->data, tmp.data, tmp.length) == 0); + + krad_attrset_free(set); + krb5_free_context(ctx); + return 0; +} diff --git a/src/lib/krad/t_client.c b/src/lib/krad/t_client.c new file mode 100644 index 0000000..3d0fda9 --- /dev/null +++ b/src/lib/krad/t_client.c @@ -0,0 +1,126 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* lib/krad/t_client.c - Client request test program */ +/* + * Copyright 2013 Red Hat, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "t_daemon.h" + +#define EVENT_COUNT 4 + +static struct +{ + int count; + struct event events[EVENT_COUNT]; +} record; + +static verto_ctx *vctx; + +static void +callback(krb5_error_code retval, const krad_packet *request, + const krad_packet *response, void *data) +{ + struct event *evt; + + evt = &record.events[record.count++]; + evt->error = retval != 0; + if (evt->error) + evt->result.retval = retval; + else + evt->result.code = krad_packet_get_code(response); + verto_break(vctx); +} + +int +main(int argc, const char **argv) +{ + krad_attrset *attrs; + krad_client *rc; + krb5_context kctx; + krb5_data tmp; + + if (!daemon_start(argc, argv)) { + fprintf(stderr, "Unable to start pyrad daemon, skipping test...\n"); + return 0; + } + + noerror(krb5_init_context(&kctx)); + vctx = verto_new(NULL, VERTO_EV_TYPE_IO | VERTO_EV_TYPE_TIMEOUT); + insist(vctx != NULL); + noerror(krad_client_new(kctx, vctx, &rc)); + + tmp = string2data("testUser"); + noerror(krad_attrset_new(kctx, &attrs)); + noerror(krad_attrset_add(attrs, krad_attr_name2num("User-Name"), &tmp)); + + /* Test accept. */ + tmp = string2data("accept"); + noerror(krad_attrset_add(attrs, krad_attr_name2num("User-Password"), + &tmp)); + noerror(krad_client_send(rc, krad_code_name2num("Access-Request"), attrs, + "localhost", "foo", 1000, 3, callback, NULL)); + verto_run(vctx); + + /* Test reject. */ + tmp = string2data("reject"); + krad_attrset_del(attrs, krad_attr_name2num("User-Password"), 0); + noerror(krad_attrset_add(attrs, krad_attr_name2num("User-Password"), + &tmp)); + noerror(krad_client_send(rc, krad_code_name2num("Access-Request"), attrs, + "localhost", "foo", 1000, 3, callback, NULL)); + verto_run(vctx); + + /* Test timeout. */ + daemon_stop(); + noerror(krad_client_send(rc, krad_code_name2num("Access-Request"), attrs, + "localhost", "foo", 1000, 3, callback, NULL)); + verto_run(vctx); + + /* Test outstanding packet freeing. */ + noerror(krad_client_send(rc, krad_code_name2num("Access-Request"), attrs, + "localhost", "foo", 1000, 3, callback, NULL)); + krad_client_free(rc); + rc = NULL; + + /* Verify the results. */ + insist(record.count == EVENT_COUNT); + insist(record.events[0].error == FALSE); + insist(record.events[0].result.code == + krad_code_name2num("Access-Accept")); + insist(record.events[1].error == FALSE); + insist(record.events[1].result.code == + krad_code_name2num("Access-Reject")); + insist(record.events[2].error == TRUE); + insist(record.events[2].result.retval == ETIMEDOUT); + insist(record.events[3].error == TRUE); + insist(record.events[3].result.retval == ECANCELED); + + krad_attrset_free(attrs); + krad_client_free(rc); + verto_free(vctx); + krb5_free_context(kctx); + return 0; +} diff --git a/src/lib/krad/t_code.c b/src/lib/krad/t_code.c new file mode 100644 index 0000000..b245a7e --- /dev/null +++ b/src/lib/krad/t_code.c @@ -0,0 +1,54 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* lib/krad/t_code.c - RADIUS code table test program */ +/* + * Copyright 2013 Red Hat, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "t_test.h" + +int +main() +{ + const char *tmp; + + insist(krad_code_name2num("Access-Request") == 1); + insist(krad_code_name2num("Access-Accept") == 2); + insist(krad_code_name2num("Access-Reject") == 3); + + tmp = krad_code_num2name(1); + insist(tmp != NULL); + insist(strcmp(tmp, "Access-Request") == 0); + + tmp = krad_code_num2name(2); + insist(tmp != NULL); + insist(strcmp(tmp, "Access-Accept") == 0); + + tmp = krad_code_num2name(3); + insist(tmp != NULL); + insist(strcmp(tmp, "Access-Reject") == 0); + + return 0; +} diff --git a/src/lib/krad/t_daemon.h b/src/lib/krad/t_daemon.h new file mode 100644 index 0000000..7c345a6 --- /dev/null +++ b/src/lib/krad/t_daemon.h @@ -0,0 +1,92 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* lib/krad/t_daemon.h - Daemonization helper for RADIUS test programs */ +/* + * Copyright 2013 Red Hat, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef T_DAEMON_H_ +#define T_DAEMON_H_ + +#include "t_test.h" +#include +#include +#include +#include + +static pid_t daemon_pid; + +static void +daemon_stop(void) +{ + if (daemon_pid == 0) + return; + kill(daemon_pid, SIGTERM); + waitpid(daemon_pid, NULL, 0); + daemon_pid = 0; +} + +static krb5_boolean +daemon_start(int argc, const char **argv) +{ + sigset_t set; + int sig; + + if (argc != 3 || argv == NULL) + return FALSE; + + if (daemon_pid != 0) + return TRUE; + + if (sigemptyset(&set) != 0) + return FALSE; + + if (sigaddset(&set, SIGUSR1) != 0) + return FALSE; + + if (sigaddset(&set, SIGCHLD) != 0) + return FALSE; + + if (sigprocmask(SIG_BLOCK, &set, NULL) != 0) + return FALSE; + + daemon_pid = fork(); + if (daemon_pid == 0) { + close(STDOUT_FILENO); + open("/dev/null", O_WRONLY); + exit(execlp(argv[1], argv[1], argv[2], NULL)); + } + + if (sigwait(&set, &sig) != 0 || sig == SIGCHLD) { + daemon_stop(); + daemon_pid = 0; + return FALSE; + } + + atexit(daemon_stop); + return TRUE; +} + +#endif /* T_DAEMON_H_ */ diff --git a/src/lib/krad/t_daemon.py b/src/lib/krad/t_daemon.py new file mode 100644 index 0000000..d62bc29 --- /dev/null +++ b/src/lib/krad/t_daemon.py @@ -0,0 +1,76 @@ +#!/usr/bin/python +# +# Copyright 2013 Red Hat, Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +# OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import StringIO +import os +import sys +import signal + +try: + from pyrad import dictionary, packet, server +except ImportError: + sys.stdout.write("pyrad not found!\n") + sys.exit(0) + +# We could use a dictionary file, but since we need +# such few attributes, we'll just include them here +DICTIONARY = """ +ATTRIBUTE\tUser-Name\t1\tstring +ATTRIBUTE\tUser-Password\t2\toctets +ATTRIBUTE\tNAS-Identifier\t32\tstring +""" + +class TestServer(server.Server): + def _HandleAuthPacket(self, pkt): + server.Server._HandleAuthPacket(self, pkt) + + passwd = [] + + print "Request: " + for key in pkt.keys(): + if key == "User-Password": + passwd = map(pkt.PwDecrypt, pkt[key]) + print "\t%s\t%s" % (key, passwd) + else: + print "\t%s\t%s" % (key, pkt[key]) + + reply = self.CreateReplyPacket(pkt) + if passwd == ['accept']: + reply.code = packet.AccessAccept + print "Response: %s" % "Access-Accept" + else: + reply.code = packet.AccessReject + print "Response: %s" % "Access-Reject" + print + self.SendReplyPacket(pkt.fd, reply) + +srv = TestServer(addresses=["localhost"], + hosts={"127.0.0.1": + server.RemoteHost("127.0.0.1", "foo", "localhost")}, + dict=dictionary.Dictionary(StringIO.StringIO(DICTIONARY))) +os.kill(os.getppid(), signal.SIGUSR1) +srv.Run() diff --git a/src/lib/krad/t_packet.c b/src/lib/krad/t_packet.c new file mode 100644 index 0000000..0a92e9c --- /dev/null +++ b/src/lib/krad/t_packet.c @@ -0,0 +1,194 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* lib/krad/t_packet.c - RADIUS packet test program */ +/* + * Copyright 2013 Red Hat, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "t_daemon.h" + +#define ACCEPT_PACKET 0 +#define REJECT_PACKET 1 + +static krad_packet *packets[3]; + +static const krad_packet * +iterator(void *data, krb5_boolean cancel) +{ + krad_packet *tmp; + int *i = data; + + if (cancel || packets[*i] == NULL) + return NULL; + + tmp = packets[*i]; + *i += 1; + return tmp; +} + +static krb5_error_code +make_packet(krb5_context ctx, const krb5_data *username, + const krb5_data *password, krad_packet **pkt) +{ + krad_attrset *set = NULL; + krad_packet *tmp = NULL; + krb5_error_code retval; + const krb5_data *data; + int i = 0; + + retval = krad_attrset_new(ctx, &set); + if (retval != 0) + goto out; + + retval = krad_attrset_add(set, krad_attr_name2num("User-Name"), username); + if (retval != 0) + goto out; + + retval = krad_attrset_add(set, krad_attr_name2num("User-Password"), + password); + if (retval != 0) + goto out; + + retval = krad_packet_new_request(ctx, "foo", + krad_code_name2num("Access-Request"), + set, iterator, &i, &tmp); + if (retval != 0) + goto out; + + data = krad_packet_get_attr(tmp, krad_attr_name2num("User-Name"), 0); + if (data == NULL) { + retval = ENOENT; + goto out; + } + + if (data->length != username->length || + memcmp(data->data, username->data, data->length) != 0) { + retval = EINVAL; + goto out; + } + + *pkt = tmp; + tmp = NULL; + +out: + krad_attrset_free(set); + krad_packet_free(tmp); + return retval; +} + +static krb5_error_code +do_auth(krb5_context ctx, struct addrinfo *ai, const char *secret, + const krad_packet *rqst, krb5_boolean *auth) +{ + const krad_packet *req = NULL; + char tmp[KRAD_PACKET_SIZE_MAX]; + const krb5_data *request; + krad_packet *rsp = NULL; + krb5_error_code retval; + krb5_data response; + int sock = -1, i; + + response = make_data(tmp, sizeof(tmp)); + + sock = socket(ai->ai_family, ai->ai_socktype, 0); + if (sock < 0) { + retval = errno; + goto out; + } + + request = krad_packet_encode(rqst); + if (sendto(sock, request->data, request->length, 0, ai->ai_addr, + ai->ai_addrlen) < 0) { + retval = errno; + goto out; + } + + i = recv(sock, response.data, sizeof(tmp), 0); + if (i < 0) { + retval = errno; + goto out; + } + response.length = i; + + i = 0; + retval = krad_packet_decode_response(ctx, secret, &response, iterator, &i, + &req, &rsp); + if (retval != 0) + goto out; + + if (req != rqst) { + retval = EBADMSG; + goto out; + } + + *auth = krad_packet_get_code(rsp) == krad_code_name2num("Access-Accept"); + +out: + krad_packet_free(rsp); + if (sock >= 0) + close(sock); + return retval; +} + +int +main(int argc, const char **argv) +{ + struct addrinfo *ai = NULL, hints; + krb5_data username, password; + krb5_boolean auth = FALSE; + krb5_context ctx; + + username = string2data("testUser"); + + if (!daemon_start(argc, argv)) { + fprintf(stderr, "Unable to start pyrad daemon, skipping test...\n"); + return 0; + } + + noerror(krb5_init_context(&ctx)); + + password = string2data("accept"); + noerror(make_packet(ctx, &username, &password, &packets[ACCEPT_PACKET])); + + password = string2data("reject"); + noerror(make_packet(ctx, &username, &password, &packets[REJECT_PACKET])); + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_DGRAM; + noerror(gai_error_code(getaddrinfo("127.0.0.1", "radius", &hints, &ai))); + + noerror(do_auth(ctx, ai, "foo", packets[ACCEPT_PACKET], &auth)); + insist(auth == TRUE); + + noerror(do_auth(ctx, ai, "foo", packets[REJECT_PACKET], &auth)); + insist(auth == FALSE); + + krad_packet_free(packets[ACCEPT_PACKET]); + krad_packet_free(packets[REJECT_PACKET]); + krb5_free_context(ctx); + freeaddrinfo(ai); + return 0; +} diff --git a/src/lib/krad/t_remote.c b/src/lib/krad/t_remote.c new file mode 100644 index 0000000..a521ecb --- /dev/null +++ b/src/lib/krad/t_remote.c @@ -0,0 +1,170 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* lib/krad/t_remote.c - Protocol test program */ +/* + * Copyright 2013 Red Hat, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "t_daemon.h" + +#define EVENT_COUNT 6 + +static struct +{ + int count; + struct event events[EVENT_COUNT]; +} record; + +static krad_attrset *set; +static krad_remote *rr; +static verto_ctx *vctx; + +static void +callback(krb5_error_code retval, const krad_packet *request, + const krad_packet *response, void *data) +{ + struct event *evt; + + evt = &record.events[record.count++]; + evt->error = retval != 0; + if (evt->error) + evt->result.retval = retval; + else + evt->result.code = krad_packet_get_code(response); + verto_break(vctx); +} + +static void +remote_new(krb5_context kctx, krad_remote **remote) +{ + struct addrinfo *ai = NULL, hints; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_DGRAM; + noerror(gai_error_code(getaddrinfo("127.0.0.1", "radius", &hints, &ai))); + + noerror(kr_remote_new(kctx, vctx, ai, "foo", remote)); + insist(kr_remote_equals(*remote, ai, "foo")); + freeaddrinfo(ai); +} + +static krb5_error_code +do_auth(const char *password, const krad_packet **pkt) +{ + const krad_packet *tmppkt; + krb5_error_code retval; + krb5_data tmp = string2data((char *)password); + + retval = krad_attrset_add(set, krad_attr_name2num("User-Password"), &tmp); + if (retval != 0) + return retval; + + retval = kr_remote_send(rr, krad_code_name2num("Access-Request"), set, + callback, NULL, 1000, 3, &tmppkt); + krad_attrset_del(set, krad_attr_name2num("User-Password"), 0); + if (retval != 0) + return retval; + + if (pkt != NULL) + *pkt = tmppkt; + return 0; +} + +static void +test_timeout(verto_ctx *ctx, verto_ev *ev) +{ + static const krad_packet *pkt; + + noerror(do_auth("accept", &pkt)); + kr_remote_cancel(rr, pkt); +} + +int +main(int argc, const char **argv) +{ + krb5_context kctx = NULL; + krb5_data tmp; + + if (!daemon_start(argc, argv)) { + fprintf(stderr, "Unable to start pyrad daemon, skipping test...\n"); + return 0; + } + + /* Initialize. */ + noerror(krb5_init_context(&kctx)); + vctx = verto_new(NULL, VERTO_EV_TYPE_IO | VERTO_EV_TYPE_TIMEOUT); + insist(vctx != NULL); + remote_new(kctx, &rr); + + /* Create attribute set. */ + noerror(krad_attrset_new(kctx, &set)); + tmp = string2data("testUser"); + noerror(krad_attrset_add(set, krad_attr_name2num("User-Name"), &tmp)); + + /* Send accept packet. */ + noerror(do_auth("accept", NULL)); + verto_run(vctx); + + /* Send reject packet. */ + noerror(do_auth("reject", NULL)); + verto_run(vctx); + + /* Send canceled packet. */ + insist(verto_add_timeout(vctx, VERTO_EV_FLAG_NONE, test_timeout, 0) != + NULL); + verto_run(vctx); + + /* Test timeout. */ + daemon_stop(); + noerror(do_auth("accept", NULL)); + verto_run(vctx); + + /* Test outstanding packet freeing. */ + noerror(do_auth("accept", NULL)); + kr_remote_free(rr); + krad_attrset_free(set); + + /* Verify the results. */ + insist(record.count == EVENT_COUNT); + insist(record.events[0].error == FALSE); + insist(record.events[0].result.code == + krad_code_name2num("Access-Accept")); + insist(record.events[1].error == FALSE); + insist(record.events[1].result.code == + krad_code_name2num("Access-Reject")); + insist(record.events[2].error == TRUE); + insist(record.events[2].result.retval == ECANCELED); + insist(record.events[3].error == TRUE); + insist(record.events[3].result.retval == ETIMEDOUT); + insist(record.events[4].error == TRUE); + insist(record.events[4].result.retval == ECANCELED); + insist(record.events[5].error == TRUE); + insist(record.events[5].result.retval == ECANCELED); + + verto_free(vctx); + krb5_free_context(kctx); + return 0; +} diff --git a/src/lib/krad/t_test.c b/src/lib/krad/t_test.c new file mode 100644 index 0000000..152bc77 --- /dev/null +++ b/src/lib/krad/t_test.c @@ -0,0 +1,50 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* lib/krad/t_test.c - Utility functions for libkrad tests */ +/* + * Copyright 2013 Red Hat, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "t_test.h" + +void +noerror_impl(const char *file, int line, const char *cmd, int retval) +{ + if (retval == 0) + return; + + fprintf(stderr, "%s:%d: %s:\n\t%s\n", file, line, strerror(retval), cmd); + exit(1); +} + +void +insist_impl(const char *file, int line, const char *cmd, krb5_boolean result) +{ + if (result) + return; + + fprintf(stderr, "%s:%d: insist failed:\n\t%s\n", file, line, cmd); + exit(1); +} diff --git a/src/lib/krad/t_test.h b/src/lib/krad/t_test.h new file mode 100644 index 0000000..f44742f --- /dev/null +++ b/src/lib/krad/t_test.h @@ -0,0 +1,60 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* lib/krad/t_test.h - Shared declarations for libkrad test programs */ +/* + * Copyright 2013 Red Hat, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef T_TEST_H_ +#define T_TEST_H_ + +#include "internal.h" + +#include +#include +#include +#include +#include +#include + +#define insist(x) insist_impl(__FILE__, __LINE__, #x, x) +#define noerror(x) noerror_impl(__FILE__, __LINE__, #x, x) + +struct event { + krb5_boolean error; + union + { + krb5_error_code retval; + krad_code code; + } result; +}; + +void +noerror_impl(const char *file, int line, const char *cmd, int retval); + +void +insist_impl(const char *file, int line, const char *cmd, krb5_boolean result); + +#endif /* T_TEST_H_ */ -- 1.8.2.1 From 01c7bae6c9a962ffd38f9d5f70fdde7b9e6eb929 Mon Sep 17 00:00:00 2001 From: Nathaniel McCallum Date: Tue, 9 Apr 2013 12:24:47 -0400 Subject: [PATCH 4/4] add otp plugin --- src/Makefile.in | 1 + src/configure.in | 1 + src/kdc/kdc_preauth.c | 2 + src/plugins/preauth/otp/Makefile.in | 39 +++ src/plugins/preauth/otp/deps | 26 ++ src/plugins/preauth/otp/main.c | 379 ++++++++++++++++++++++++ src/plugins/preauth/otp/otp.exports | 1 + src/plugins/preauth/otp/otp_state.c | 560 ++++++++++++++++++++++++++++++++++++ src/plugins/preauth/otp/otp_state.h | 59 ++++ 9 files changed, 1068 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 d8676e5..520e0c8 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..43bd2ba --- /dev/null +++ b/src/plugins/preauth/otp/Makefile.in @@ -0,0 +1,39 @@ +mydir=plugins$(S)preauth$(S)otp +BUILDTOP=$(REL)..$(S)..$(S).. +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@ + +LIBBASE=otp +LIBMAJOR=0 +LIBMINOR=0 +SO_EXT=.so +RELDIR=../plugins/preauth/otp + +SHLIB_EXPDEPS = $(VERTO_DEPLIBS) $(KRB5_BASE_DEPLIBS) \ + $(TOPLIBD)/libkrad$(SHLIBEXT) + +SHLIB_EXPLIBS= -lkrad $(VERTO_LIBS) $(KRB5_BASE_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:: all-liblinks +install-unix:: install-libs +clean-unix:: clean-liblinks clean-libs clean-libobjs + +clean:: + $(RM) lib$(LIBBASE)$(SO_EXT) + +@libnover_frag@ +@libobj_frag@ diff --git a/src/plugins/preauth/otp/deps b/src/plugins/preauth/otp/deps new file mode 100644 index 0000000..3352126 --- /dev/null +++ b/src/plugins/preauth/otp/deps @@ -0,0 +1,26 @@ +# +# Generated makefile dependencies follow. +# +otp_state.so otp_state.po $(OUTPRE)otp_state.$(OBJEXT): \ + $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \ + $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \ + $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-err.h \ + $(top_srcdir)/include/k5-gmt_mktime.h $(top_srcdir)/include/k5-int-pkinit.h \ + $(top_srcdir)/include/k5-int.h $(top_srcdir)/include/k5-json.h \ + $(top_srcdir)/include/k5-platform.h $(top_srcdir)/include/k5-plugin.h \ + $(top_srcdir)/include/k5-thread.h $(top_srcdir)/include/k5-trace.h \ + $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \ + $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/krb5/preauth_plugin.h \ + $(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h \ + otp_state.c otp_state.h +main.so main.po $(OUTPRE)main.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \ + $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \ + $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h \ + $(top_srcdir)/include/k5-err.h $(top_srcdir)/include/k5-gmt_mktime.h \ + $(top_srcdir)/include/k5-int-pkinit.h $(top_srcdir)/include/k5-int.h \ + $(top_srcdir)/include/k5-json.h $(top_srcdir)/include/k5-platform.h \ + $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-thread.h \ + $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/krb5.h \ + $(top_srcdir)/include/krb5/authdata_plugin.h $(top_srcdir)/include/krb5/plugin.h \ + $(top_srcdir)/include/krb5/preauth_plugin.h $(top_srcdir)/include/port-sockets.h \ + $(top_srcdir)/include/socket-utils.h main.c otp_state.h diff --git a/src/plugins/preauth/otp/main.c b/src/plugins/preauth/otp/main.c new file mode 100644 index 0000000..2f7470e --- /dev/null +++ b/src/plugins/preauth/otp/main.c @@ -0,0 +1,379 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* plugins/preauth/otp/main.c - OTP kdcpreauth module definition */ +/* + * Copyright 2011 NORDUnet A/S. All rights reserved. + * Copyright 2013 Red Hat, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "k5-int.h" +#include "k5-json.h" +#include +#include "otp_state.h" + +#include +#include + +static krb5_preauthtype otp_pa_type_list[] = + { KRB5_PADATA_OTP_REQUEST, 0 }; + +struct request_state { + krb5_kdcpreauth_verify_respond_fn respond; + void *arg; +}; + +static krb5_error_code +decrypt_encdata(krb5_context context, krb5_keyblock *armor_key, + krb5_pa_otp_req *req, krb5_data *out) +{ + krb5_error_code retval; + krb5_data plaintext; + + if (req == NULL) + return EINVAL; + + retval = alloc_data(&plaintext, req->enc_data.ciphertext.length); + if (retval) + return retval; + + retval = krb5_c_decrypt(context, armor_key, KRB5_KEYUSAGE_PA_OTP_REQUEST, + NULL, &req->enc_data, &plaintext); + if (retval != 0) { + com_err("otp", retval, "Unable to decrypt encData in PA-OTP-REQUEST"); + free(plaintext.data); + return retval; + } + + *out = plaintext; + return 0; +} + +static krb5_error_code +nonce_verify(krb5_context ctx, krb5_keyblock *armor_key, + const krb5_data *nonce) +{ + krb5_error_code retval; + krb5_timestamp ts; + krb5_data *er = NULL; + + if (armor_key == NULL || nonce->data == NULL) { + retval = EINVAL; + goto out; + } + + /* Decode the PA-OTP-ENC-REQUEST structure. */ + retval = decode_krb5_pa_otp_enc_req(nonce, &er); + if (retval != 0) + goto out; + + /* Make sure the nonce is exactly the same size as the one generated. */ + if (er->length != armor_key->length + sizeof(krb5_timestamp)) + goto out; + + /* Check to make sure the timestamp at the beginning is still valid. */ + ts = load_32_be(er->data); + retval = krb5_check_clockskew(ctx, ts); + +out: + krb5_free_data(ctx, er); + return retval; +} + +static krb5_error_code +timestamp_verify(krb5_context ctx, const krb5_data *nonce) +{ + krb5_error_code retval = EINVAL; + krb5_pa_enc_ts *et = NULL; + + if (nonce->data == NULL) + goto out; + + /* Decode the PA-ENC-TS-ENC structure. */ + retval = decode_krb5_pa_enc_ts(nonce, &et); + if (retval != 0) + goto out; + + /* Check the clockskew. */ + retval = krb5_check_clockskew(ctx, et->patimestamp); + +out: + krb5_free_pa_enc_ts(ctx, et); + return retval; +} + +static krb5_error_code +nonce_generate(krb5_context ctx, unsigned int length, krb5_data *nonce_out) +{ + krb5_data nonce; + krb5_error_code retval; + krb5_timestamp now; + + retval = krb5_timeofday(ctx, &now); + if (retval != 0) + return retval; + + retval = alloc_data(&nonce, sizeof(now) + length); + if (retval != 0) + return retval; + + retval = krb5_c_random_make_octets(ctx, &nonce); + if (retval != 0) { + free(nonce.data); + return retval; + } + + store_32_be(now, nonce.data); + *nonce_out = nonce; + return 0; +} + +static void +on_response(void *data, krb5_error_code retval, otp_response response) +{ + struct request_state rs = *(struct request_state *)data; + + free(data); + + if (retval == 0 && response != otp_response_success) + retval = KRB5_PREAUTH_FAILED; + + rs.respond(rs.arg, retval, NULL, NULL, NULL); +} + +static krb5_error_code +otp_init(krb5_context context, krb5_kdcpreauth_moddata *moddata_out, + const char **realmnames) +{ + krb5_error_code retval; + otp_state *state; + + retval = otp_state_new(context, &state); + if (retval) + return retval; + *moddata_out = (krb5_kdcpreauth_moddata)state; + return 0; +} + +static void +otp_fini(krb5_context context, krb5_kdcpreauth_moddata moddata) +{ + otp_state_free((otp_state *)moddata); +} + +static int +otp_flags(krb5_context context, krb5_preauthtype pa_type) +{ + return PA_REPLACES_KEY; +} + +static void +otp_edata(krb5_context context, krb5_kdc_req *request, + krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock, + krb5_kdcpreauth_moddata moddata, krb5_preauthtype pa_type, + krb5_kdcpreauth_edata_respond_fn respond, void *arg) +{ + krb5_otp_tokeninfo ti, *tis[2] = { &ti, NULL }; + krb5_keyblock *armor_key = NULL; + krb5_pa_otp_challenge chl; + krb5_pa_data *pa = NULL; + krb5_error_code retval; + krb5_data *encoding; + char *config; + + /* Determine if otp is enabled for the user. */ + retval = cb->get_string(context, rock, "otp", &config); + if (retval != 0 || config == NULL) + goto out; + cb->free_string(context, rock, config); + + /* Get the armor key. This indicates the length of random data to use in + * the nonce. */ + armor_key = cb->fast_armor(context, rock); + if (armor_key == NULL) { + retval = ENOENT; + goto out; + } + + /* Build the (mostly empty) challenge. */ + memset(&ti, 0, sizeof(ti)); + memset(&chl, 0, sizeof(chl)); + chl.tokeninfo = tis; + ti.format = -1; + ti.length = -1; + ti.iteration_count = -1; + + /* Generate the nonce. */ + retval = nonce_generate(context, armor_key->length, &chl.nonce); + if (retval != 0) + goto out; + + /* Build the output pa-data. */ + retval = encode_krb5_pa_otp_challenge(&chl, &encoding); + if (retval != 0) + goto out; + pa = k5alloc(sizeof(krb5_pa_data), &retval); + if (pa == NULL) { + krb5_free_data(context, encoding); + goto out; + } + pa->pa_type = KRB5_PADATA_OTP_CHALLENGE; + pa->contents = (krb5_octet *)encoding->data; + pa->length = encoding->length; + free(encoding); + +out: + (*respond)(arg, retval, pa); +} + +static void +otp_verify(krb5_context context, krb5_data *req_pkt, krb5_kdc_req *request, + krb5_enc_tkt_part *enc_tkt_reply, krb5_pa_data *pa, + krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock, + krb5_kdcpreauth_moddata moddata, + krb5_kdcpreauth_verify_respond_fn respond, void *arg) +{ + krb5_keyblock *armor_key = NULL; + krb5_pa_otp_req *req = NULL; + struct request_state *rs; + krb5_error_code retval; + krb5_data d, plaintext; + char *config; + + enc_tkt_reply->flags |= TKT_FLG_PRE_AUTH; + + /* Get the FAST armor key. */ + armor_key = cb->fast_armor(context, rock); + if (armor_key == NULL) { + retval = KRB5KDC_ERR_PREAUTH_FAILED; + com_err("otp", retval, "No armor key found when verifying padata"); + goto error; + } + + /* Decode the request. */ + d = make_data(pa->contents, pa->length); + retval = decode_krb5_pa_otp_req(&d, &req); + if (retval != 0) { + com_err("otp", retval, "Unable to decode OTP request"); + goto error; + } + + /* Decrypt the nonce from the request. */ + retval = decrypt_encdata(context, armor_key, req, &plaintext); + if (retval != 0) { + com_err("otp", retval, "Unable to decrypt nonce"); + goto error; + } + + /* Verify the nonce or timestamp. */ + retval = nonce_verify(context, armor_key, &plaintext); + if (retval != 0) + retval = timestamp_verify(context, &plaintext); + krb5_free_data_contents(context, &plaintext); + if (retval != 0) { + com_err("otp", retval, "Unable to verify nonce or timestamp"); + goto error; + } + + /* Create the request state. */ + rs = k5alloc(sizeof(struct request_state), &retval); + if (rs == NULL) + goto error; + rs->arg = arg; + rs->respond = respond; + + /* Get the principal's OTP configuration string. */ + retval = cb->get_string(context, rock, "otp", &config); + if (config == NULL) + retval = KRB5_PREAUTH_FAILED; + if (retval != 0) { + free(rs); + goto error; + } + + /* Send the request. */ + otp_state_verify((otp_state *)moddata, cb->event_context(context, rock), + request->client, config, req, on_response, rs); + cb->free_string(context, rock, config); + + k5_free_pa_otp_req(context, req); + return; + +error: + k5_free_pa_otp_req(context, req); + (*respond)(arg, retval, NULL, NULL, NULL); +} + +static krb5_error_code +otp_return_padata(krb5_context context, krb5_pa_data *padata, + krb5_data *req_pkt, krb5_kdc_req *request, + krb5_kdc_rep *reply, krb5_keyblock *encrypting_key, + krb5_pa_data **send_pa_out, krb5_kdcpreauth_callbacks cb, + krb5_kdcpreauth_rock rock, krb5_kdcpreauth_moddata moddata, + krb5_kdcpreauth_modreq modreq) +{ + krb5_keyblock *armor_key = NULL; + + if (padata->length == 0) + return 0; + + /* Get the armor key. */ + armor_key = cb->fast_armor(context, rock); + if (!armor_key) { + com_err("otp", ENOENT, "No armor key found when returning padata"); + return ENOENT; + } + + /* Replace the reply key with the FAST armor key. */ + krb5_free_keyblock_contents(context, encrypting_key); + return krb5_copy_keyblock_contents(context, armor_key, encrypting_key); +} + +krb5_error_code +kdcpreauth_otp_initvt(krb5_context context, int maj_ver, int min_ver, + krb5_plugin_vtable vtable); + +krb5_error_code +kdcpreauth_otp_initvt(krb5_context context, int maj_ver, int min_ver, + krb5_plugin_vtable vtable) +{ + krb5_kdcpreauth_vtable vt; + + if (maj_ver != 1) + return KRB5_PLUGIN_VER_NOTSUPP; + + vt = (krb5_kdcpreauth_vtable)vtable; + vt->name = "otp"; + vt->pa_type_list = otp_pa_type_list; + vt->init = otp_init; + vt->fini = otp_fini; + vt->flags = otp_flags; + vt->edata = otp_edata; + vt->verify = otp_verify; + vt->return_padata = otp_return_padata; + + com_err("otp", 0, "Loaded"); + + return 0; +} diff --git a/src/plugins/preauth/otp/otp.exports b/src/plugins/preauth/otp/otp.exports new file mode 100644 index 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..95f88e0 --- /dev/null +++ b/src/plugins/preauth/otp/otp_state.c @@ -0,0 +1,560 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* plugins/preauth/otp/otp_state.c - Verify OTP token values using RADIUS */ +/* + * Copyright 2013 Red Hat, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "otp_state.h" + +#include +#include + +#include + +#ifndef HOST_NAME_MAX +/* SUSv2 */ +#define HOST_NAME_MAX 255 +#endif + +#define DEFAULT_TYPE_NAME "DEFAULT" +#define DEFAULT_SOCKET_FMT KDC_DIR "/%s.socket" +#define DEFAULT_TIMEOUT 5 +#define DEFAULT_RETRIES 3 + +typedef struct token_type_st { + char *name; + char *server; + char *secret; + int timeout; + size_t retries; + krb5_boolean strip_realm; +} token_type; + +typedef struct token_st { + const token_type *type; + krb5_data username; +} token; + +typedef struct request_st { + otp_state *state; + token *tokens; + ssize_t index; + otp_cb cb; + void *data; + krad_attrset *attrs; +} request; + +struct otp_state_st { + krb5_context ctx; + token_type *types; + krad_client *radius; + krad_attrset *attrs; +}; + +static void request_send(request *req); + +/* Free the contents of a single token type. */ +static void +token_type_free(token_type *type) +{ + if (type == NULL) + return; + + free(type->name); + free(type->server); + free(type->secret); +} + +/* Construct the internal default token type. */ +static krb5_error_code +token_type_default(token_type *out) +{ + char *name = NULL, *server = NULL, *secret = NULL; + + memset(out, 0, sizeof(*out)); + + name = strdup(DEFAULT_TYPE_NAME); + if (name == NULL) + goto oom; + if (asprintf(&server, DEFAULT_SOCKET_FMT, name) < 0) + goto oom; + secret = strdup(""); + if (secret == NULL) + goto oom; + + out->name = name; + out->server = server; + out->secret = secret; + out->timeout = DEFAULT_TIMEOUT * 1000; + out->retries = DEFAULT_RETRIES; + out->strip_realm = FALSE; + return 0; + +oom: + free(name); + free(server); + free(secret); + return ENOMEM; +} + +/* Decode a single token type from the profile. */ +static krb5_error_code +token_type_decode(profile_t profile, const char *name, token_type *out) +{ + krb5_error_code retval; + char *server = NULL, *name_copy = NULL, *secret = NULL; + const char *default_secret; + int strip_realm, timeout, retries; + + memset(out, 0, sizeof(*out)); + + /* Set the name. */ + name_copy = strdup(name); + if (name_copy == NULL) + return ENOMEM; + + /* Set strip_realm. */ + retval = profile_get_boolean(profile, "otp", name, "strip_realm", TRUE, + &strip_realm); + if (retval != 0) + goto cleanup; + + /* Set the server. */ + retval = profile_get_string(profile, "otp", name, "server", NULL, &server); + if (retval != 0) + goto cleanup; + if (server == NULL && asprintf(&server, DEFAULT_SOCKET_FMT, name) < 0) { + retval = ENOMEM; + goto cleanup; + } + + /* Get the secret (optional for Unix-domain sockets). */ + default_secret = (*server == '/') ? "" : NULL; + retval = profile_get_string(profile, "otp", name, "secret", default_secret, + &secret); + if (retval != 0) + goto cleanup; + if (secret == NULL) { + com_err("otp", EINVAL, "Secret not specified in token type '%s'", + name); + retval = EINVAL; + goto cleanup; + } + + /* Get the timeout (profile value in seconds, result in milliseconds). */ + retval = profile_get_integer(profile, "otp", name, "timeout", + DEFAULT_TIMEOUT, &timeout); + if (retval != 0) + goto cleanup; + timeout *= 1000; + + /* Get the number of retries. */ + retval = profile_get_integer(profile, "otp", name, "retries", + DEFAULT_RETRIES, &retries); + if (retval != 0) + goto cleanup; + + out->name = name_copy; + out->server = server; + out->secret = secret; + out->timeout = timeout; + out->retries = retries; + out->strip_realm = strip_realm; + name_copy = server = secret = NULL; + +cleanup: + free(name_copy); + free(server); + free(secret); + return retval; +} + +/* Free an array of token types. */ +static void +token_types_free(token_type *types) +{ + size_t i; + + if (types == NULL) + return; + + for (i = 0; types[i].server != NULL; i++) + token_type_free(&types[i]); + + free(types); +} + +/* Decode an array of token types from the profile. */ +static krb5_error_code +token_types_decode(profile_t profile, token_type **out) +{ + const char *hier[2] = { "otp", NULL }; + token_type *types = NULL; + char **names = NULL; + krb5_error_code retval; + size_t i, pos; + krb5_boolean have_default = FALSE; + + retval = profile_get_subsection_names(profile, hier, &names); + if (retval != 0) + return retval; + + /* Check if any of the profile subsections overrides the default. */ + for (i = 0; names[i] != NULL; i++) { + if (strcmp(names[i], DEFAULT_TYPE_NAME) == 0) + have_default = TRUE; + } + + /* Leave space for the default (possibly) and the terminator. */ + types = k5alloc((i + 2) * sizeof(token_type), &retval); + if (types == NULL) + goto cleanup; + + /* If no default has been specified, use our internal default. */ + pos = 0; + if (!have_default) { + retval = token_type_default(&types[pos++]); + if (retval != 0) + goto cleanup; + } + + /* Decode each profile section into a token type element. */ + for (i = 0; names[i] != NULL; i++) { + retval = token_type_decode(profile, names[i], &types[pos++]); + if (retval != 0) + goto cleanup; + } + + *out = types; + types = NULL; + +cleanup: + profile_free_list(names); + token_types_free(types); + return retval; +} + +/* Free the contents of a single token. */ +static void +token_free_contents(token *t) +{ + if (t != NULL) + free(t->username.data); +} + +/* Decode a single token from a JSON token object. */ +static krb5_error_code +token_decode(krb5_context ctx, krb5_const_principal princ, + const token_type *types, k5_json_object obj, token *out) +{ + const char *typename = DEFAULT_TYPE_NAME; + const token_type *type = NULL; + char *username = NULL; + krb5_error_code retval; + k5_json_value val; + size_t i; + int flags; + + memset(out, 0, sizeof(*out)); + + /* Find the token type. */ + val = k5_json_object_get(obj, "type"); + if (val != NULL && k5_json_get_tid(val) == K5_JSON_TID_STRING) + typename = k5_json_string_utf8(val); + for (i = 0; types[i].server != NULL; i++) { + if (strcmp(typename, types[i].name) == 0) + type = &types[i]; + } + if (type == NULL) + return EINVAL; + + /* Get the username, either from obj or from unparsing the principal. */ + val = k5_json_object_get(obj, "username"); + if (val != NULL && k5_json_get_tid(val) == K5_JSON_TID_STRING) { + username = strdup(k5_json_string_utf8(val)); + if (username == NULL) + return ENOMEM; + } else { + flags = type->strip_realm ? KRB5_PRINCIPAL_UNPARSE_NO_REALM : 0; + retval = krb5_unparse_name_flags(ctx, princ, flags, &username); + if (retval != 0) + return retval; + } + + out->type = type; + out->username = string2data(username); + return 0; +} + +/* Free an array of tokens. */ +static void +tokens_free(token *tokens) +{ + size_t i; + + if (tokens == NULL) + return; + + for (i = 0; tokens[i].type != NULL; i++) + token_free_contents(&tokens[i]); + + free(tokens); +} + +/* Decode 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_contents(&tokens[j]); + free(tokens); + return retval; + } + } + + k5_json_release(arr); + *out = tokens; + return 0; +} + +static void +request_free(request *req) +{ + if (req == NULL) + return; + + krad_attrset_free(req->attrs); + tokens_free(req->tokens); + free(req); +} + +krb5_error_code +otp_state_new(krb5_context ctx, otp_state **out) +{ + char hostname[HOST_NAME_MAX + 1]; + krb5_error_code retval; + profile_t profile; + krb5_data hndata; + otp_state *self; + + retval = gethostname(hostname, sizeof(hostname)); + if (retval != 0) + return retval; + + self = calloc(1, sizeof(otp_state)); + if (self == NULL) + return ENOMEM; + + retval = krb5_get_profile(ctx, &profile); + if (retval != 0) + goto error; + + retval = token_types_decode(profile, &self->types); + profile_abandon(profile); + if (retval != 0) + goto error; + + retval = krad_attrset_new(ctx, &self->attrs); + if (retval != 0) + goto error; + + hndata = make_data(hostname, strlen(hostname)); + retval = krad_attrset_add(self->attrs, + krad_attr_name2num("NAS-Identifier"), &hndata); + if (retval != 0) + goto error; + + retval = krad_attrset_add_number(self->attrs, + krad_attr_name2num("Service-Type"), + KRAD_SERVICE_TYPE_AUTHENTICATE_ONLY); + if (retval != 0) + goto error; + + self->ctx = ctx; + *out = self; + return 0; + +error: + otp_state_free(self); + return retval; +} + +void +otp_state_free(otp_state *self) +{ + if (self == NULL) + return; + + krad_attrset_free(self->attrs); + token_types_free(self->types); + free(self); +} + +static void +callback(krb5_error_code retval, const krad_packet *rqst, + const krad_packet *resp, void *data) +{ + request *req = data; + + req->index++; + + if (retval != 0) + goto error; + + /* If we received an accept packet, success! */ + if (krad_packet_get_code(resp) == + krad_code_name2num("Access-Accept")) { + req->cb(req->data, retval, otp_response_success); + request_free(req); + return; + } + + /* If we have no more tokens to try, failure! */ + if (req->tokens[req->index].type == NULL) + goto error; + + /* Try the next token. */ + request_send(req); + +error: + req->cb(req->data, retval, otp_response_fail); + request_free(req); +} + +static void +request_send(request *req) +{ + krb5_error_code retval; + token *tok = &req->tokens[req->index]; + const token_type *t = tok->type; + + retval = krad_attrset_add(req->attrs, krad_attr_name2num("User-Name"), + &tok->username); + if (retval != 0) + goto error; + + retval = krad_client_send(req->state->radius, + krad_code_name2num("Access-Request"), req->attrs, + t->server, t->secret, t->timeout, t->retries, + callback, req); + krad_attrset_del(req->attrs, krad_attr_name2num("User-Name"), 0); + if (retval != 0) + goto error; + + return; + +error: + req->cb(req->data, retval, otp_response_fail); + request_free(req); +} + +void +otp_state_verify(otp_state *state, verto_ctx *ctx, krb5_const_principal princ, + const char *config, const krb5_pa_otp_req *req, + otp_cb cb, void *data) +{ + krb5_error_code retval; + request *rqst = NULL; + + if (state->radius == NULL) { + retval = krad_client_new(state->ctx, ctx, &state->radius); + if (retval != 0) + goto error; + } + + rqst = calloc(1, sizeof(request)); + if (rqst == NULL) { + (*cb)(data, ENOMEM, otp_response_fail); + return; + } + rqst->state = state; + rqst->data = data; + rqst->cb = cb; + + retval = krad_attrset_copy(state->attrs, &rqst->attrs); + if (retval != 0) + goto error; + + retval = krad_attrset_add(rqst->attrs, krad_attr_name2num("User-Password"), + &req->otp_value); + if (retval != 0) + goto error; + + retval = tokens_decode(state->ctx, princ, state->types, config, + &rqst->tokens); + if (retval != 0) + goto error; + + request_send(rqst); + return; + +error: + (*cb)(data, retval, otp_response_fail); + request_free(rqst); +} diff --git a/src/plugins/preauth/otp/otp_state.h b/src/plugins/preauth/otp/otp_state.h new file mode 100644 index 0000000..4247d0b --- /dev/null +++ b/src/plugins/preauth/otp/otp_state.h @@ -0,0 +1,59 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* plugins/preauth/otp/otp_state.h - Internal declarations for OTP module */ +/* + * Copyright 2013 Red Hat, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef OTP_H_ +#define OTP_H_ + +#include +#include + +#include + +typedef enum otp_response { + otp_response_fail = 0, + otp_response_success + /* Other values reserved for responses like next token or new pin. */ +} otp_response; + +typedef struct otp_state_st otp_state; +typedef void +(*otp_cb)(void *data, krb5_error_code retval, otp_response response); + +krb5_error_code +otp_state_new(krb5_context ctx, otp_state **self); + +void +otp_state_free(otp_state *self); + +void +otp_state_verify(otp_state *state, verto_ctx *ctx, krb5_const_principal princ, + const char *config, const krb5_pa_otp_req *request, + otp_cb cb, void *data); + +#endif /* OTP_H_ */ -- 1.8.2.1