summaryrefslogtreecommitdiffstats
path: root/src/lib/krad/client.c
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/lib/krad/client.c
parent13880cfe3ed4f2a8c6dc37a093ddc68165afd276 (diff)
downloadkrb5-8b8f031c6e64360a26c484b548d2158944e09087.tar.gz
krb5-8b8f031c6e64360a26c484b548d2158944e09087.tar.xz
krb5-8b8f031c6e64360a26c484b548d2158944e09087.zip
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/lib/krad/client.c')
-rw-r--r--src/lib/krad/client.c335
1 files changed, 335 insertions, 0 deletions
diff --git a/src/lib/krad/client.c b/src/lib/krad/client.c
new file mode 100644
index 000000000..0c37680b2
--- /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;
+}