summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorNathaniel McCallum <npmccallum@redhat.com>2013-04-04 13:39:21 -0400
committerGreg Hudson <ghudson@mit.edu>2013-07-11 14:14:32 -0400
commit8b8f031c6e64360a26c484b548d2158944e09087 (patch)
tree5099280d7aab2f9e5a6be01defd9ce568ff6d8b5 /src
parent13880cfe3ed4f2a8c6dc37a093ddc68165afd276 (diff)
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)
Diffstat (limited to 'src')
-rw-r--r--src/configure.in2
-rw-r--r--src/include/Makefile.in1
-rw-r--r--src/include/krad.h264
-rw-r--r--src/lib/Makefile.in2
-rw-r--r--src/lib/krad/Makefile.in74
-rw-r--r--src/lib/krad/attr.c317
-rw-r--r--src/lib/krad/attrset.c244
-rw-r--r--src/lib/krad/client.c335
-rw-r--r--src/lib/krad/code.c111
-rw-r--r--src/lib/krad/deps156
-rw-r--r--src/lib/krad/internal.h155
-rw-r--r--src/lib/krad/libkrad.exports23
-rw-r--r--src/lib/krad/packet.c470
-rw-r--r--src/lib/krad/remote.c532
-rw-r--r--src/lib/krad/t_attr.c89
-rw-r--r--src/lib/krad/t_attrset.c98
-rw-r--r--src/lib/krad/t_client.c126
-rw-r--r--src/lib/krad/t_code.c54
-rw-r--r--src/lib/krad/t_daemon.h92
-rw-r--r--src/lib/krad/t_daemon.py76
-rw-r--r--src/lib/krad/t_packet.c194
-rw-r--r--src/lib/krad/t_remote.c170
-rw-r--r--src/lib/krad/t_test.c50
-rw-r--r--src/lib/krad/t_test.h60
24 files changed, 3693 insertions, 2 deletions
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 <krb5.h>
+#include <verto.h>
+#include <stddef.h>
+#include <stdio.h>
+
+#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 <k5-int.h>
+#include "internal.h"
+
+#include <string.h>
+
+/* 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 <k5-int.h>
+#include <k5-queue.h>
+#include "internal.h"
+
+#include <string.h>
+
+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 <k5-queue.h>
+#include "internal.h"
+
+#include <string.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <limits.h>
+
+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 <string.h>
+
+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 <k5-int.h>
+#include "krad.h"
+
+#include <errno.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+
+#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 <string.h>
+
+#include <arpa/inet.h>
+
+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 <k5-int.h>
+#include <k5-queue.h>
+#include "internal.h"
+
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/un.h>
+
+#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 <libgen.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+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 <sys/wait.h>
+#include <signal.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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_ */