diff options
author | Nathaniel McCallum <npmccallum@redhat.com> | 2013-04-04 13:39:21 -0400 |
---|---|---|
committer | Greg Hudson <ghudson@mit.edu> | 2013-07-11 14:14:32 -0400 |
commit | 8b8f031c6e64360a26c484b548d2158944e09087 (patch) | |
tree | 5099280d7aab2f9e5a6be01defd9ce568ff6d8b5 /src/lib/krad/client.c | |
parent | 13880cfe3ed4f2a8c6dc37a093ddc68165afd276 (diff) | |
download | krb5-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.c | 335 |
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; +} |