From 8b8f031c6e64360a26c484b548d2158944e09087 Mon Sep 17 00:00:00 2001 From: Nathaniel McCallum Date: Thu, 4 Apr 2013 13:39:21 -0400 Subject: Add libkrad The new library libkrad provides code for the parsing of RADIUS packets as well as client implementation based around libverto. This library should be considered unstable. ticket: 7678 (new) --- src/configure.in | 2 +- src/include/Makefile.in | 1 + src/include/krad.h | 264 +++++++++++++++++++++ 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 | 532 +++++++++++++++++++++++++++++++++++++++++++ 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, 3693 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 02955bb9a5..2569092a91 100644 --- a/src/configure.in +++ b/src/configure.in @@ -1350,7 +1350,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 03f511c32d..b8ea640b42 100644 --- a/src/include/Makefile.in +++ b/src/include/Makefile.in @@ -149,5 +149,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 0000000000..913464c804 --- /dev/null +++ b/src/include/krad.h @@ -0,0 +1,264 @@ +/* -*- 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. + */ +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 485db40489..4dde514312 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 0000000000..75431a00bd --- /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 0000000000..9c13d9d755 --- /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 0000000000..fbd0621a08 --- /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 0000000000..0c37680b2b --- /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 0000000000..16871bb098 --- /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 0000000000..8171f944d2 --- /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 0000000000..996a89372d --- /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 0000000000..fe3f1590cf --- /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 0000000000..c597174b65 --- /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 0000000000..bb7c061242 --- /dev/null +++ b/src/lib/krad/remote.c @@ -0,0 +1,532 @@ +/* -*- 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_NONE VERTO_EV_FLAG_NONE +#define FLAGS_READ VERTO_EV_FLAG_IO_READ +#define FLAGS_WRITE VERTO_EV_FLAG_IO_WRITE +#define FLAGS_BASE VERTO_EV_FLAG_PERSIST | VERTO_EV_FLAG_IO_ERROR + +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; + int fd; + 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); + +static void +on_timeout(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); + } +} + +/* Start the timeout timer for the request. */ +static krb5_error_code +request_start_timer(request *r, verto_ctx *vctx) +{ + verto_del(r->timer); + + r->timer = verto_add_timeout(vctx, VERTO_EV_FLAG_NONE, on_timeout, + r->timeout); + if (r->timer != NULL) + verto_set_private(r->timer, r, NULL); + + return (r->timer == NULL) ? ENOMEM : 0; +} + +/* Disconnect from the remote host. */ +static void +remote_disconnect(krad_remote *rr) +{ + close(rr->fd); + verto_del(rr->io); + rr->fd = -1; + rr->io = NULL; +} + +/* Add the specified flags to the remote. This automatically manages the + * lifecyle of the underlying event. Also connects if disconnected. */ +static krb5_error_code +remote_add_flags(krad_remote *remote, verto_ev_flag flags) +{ + verto_ev_flag curflags = VERTO_EV_FLAG_NONE; + int i; + + flags &= (FLAGS_READ | FLAGS_WRITE); + if (remote == NULL || flags == FLAGS_NONE) + return EINVAL; + + /* If there is no connection, connect. */ + if (remote->fd < 0) { + verto_del(remote->io); + remote->io = NULL; + + remote->fd = socket(remote->info->ai_family, remote->info->ai_socktype, + remote->info->ai_protocol); + if (remote->fd < 0) + return errno; + + i = connect(remote->fd, remote->info->ai_addr, + remote->info->ai_addrlen); + if (i < 0) { + i = errno; + remote_disconnect(remote); + return i; + } + } + + if (remote->io == NULL) { + remote->io = verto_add_io(remote->vctx, FLAGS_BASE | flags, + on_io, remote->fd); + if (remote->io == NULL) + return ENOMEM; + verto_set_private(remote->io, remote, NULL); + } + + curflags = verto_get_flags(remote->io); + if ((curflags & flags) != flags) + verto_set_flags(remote->io, FLAGS_BASE | curflags | flags); + + return 0; +} + +/* Remove the specified flags to the remote. This automatically manages the + * lifecyle of the underlying event. */ +static void +remote_del_flags(krad_remote *remote, verto_ev_flag flags) +{ + if (remote == NULL || remote->io == NULL) + return; + + flags = verto_get_flags(remote->io) & (FLAGS_READ | FLAGS_WRITE) & ~flags; + if (flags == FLAGS_NONE) { + verto_del(remote->io); + remote->io = NULL; + return; + } + + verto_set_flags(remote->io, FLAGS_BASE | flags); +} + +/* Close the connection and start the timers of all outstanding requests. */ +static void +remote_shutdown(krad_remote *rr) +{ + krb5_error_code retval; + request *r; + + remote_disconnect(rr); + + /* Start timers for all unsent packets. */ + TAILQ_FOREACH(r, &rr->list, list) { + if (r->timer == NULL) { + retval = request_start_timer(r, rr->vctx); + if (retval != 0) + request_finish(r, retval, NULL); + } + } +} + +/* 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); + krb5_error_code retval = ETIMEDOUT; + + req->timer = NULL; /* Void the timer event. */ + + /* If we have more retries to perform, resend the packet. */ + if (req->retries-- > 1) { + req->sent = 0; + retval = remote_add_flags(req->rr, FLAGS_WRITE); + if (retval == 0) + return; + } + + request_finish(req, retval, NULL); +} + +/* Write data to the socket. */ +static void +on_io_write(krad_remote *rr) +{ + const krb5_data *tmp; + ssize_t written; + request *r; + + 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. */ + written = sendto(verto_get_fd(rr->io), tmp->data + r->sent, + tmp->length - r->sent, 0, NULL, 0); + if (written < 0) { + /* Should we try again? */ + if (errno == EWOULDBLOCK || errno == EAGAIN || errno == ENOBUFS || + errno == EINTR) + return; + + /* This error can't be worked around. */ + remote_shutdown(rr); + return; + } + + /* If the packet was completely sent, set a timeout. */ + r->sent += written; + if (r->sent == tmp->length) { + if (request_start_timer(r, rr->vctx) != 0) { + request_finish(r, ENOMEM, NULL); + return; + } + + if (remote_add_flags(rr, FLAGS_READ) != 0) { + remote_shutdown(rr); + return; + } + } + + return; + } + + remote_del_flags(rr, FLAGS_WRITE); + return; +} + +/* 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) { + /* If we received a malformed packet on a stream socket, + * assume the socket to be unrecoverable. */ + remote_shutdown(rr); + 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; + + /* The socket is unrecoverable. */ + remote_shutdown(rr); + return; + } else if (i == 0) { + remote_del_flags(rr, FLAGS_READ); + } + + /* 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->fd = -1; + + 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; + + *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); + remote_disconnect(rr); + 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; + } + } + + timeout = timeout / (retries + 1); + retval = request_new(rr, tmp, timeout, retries, cb, data, &r); + if (retval != 0) + goto error; + + retval = remote_add_flags(rr, FLAGS_WRITE); + if (retval != 0) + goto error; + + 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 0000000000..e80d77b23b --- /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 0000000000..afae5e4f0f --- /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 0000000000..3d0fda93e9 --- /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 0000000000..b245a7efc0 --- /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 0000000000..7c345a629f --- /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 0000000000..71e70dde22 --- /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 0000000000..0a92e9cc2b --- /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 0000000000..a521ecb7cd --- /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 0000000000..152bc77cbb --- /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 0000000000..f44742f55b --- /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_ */ -- cgit