summaryrefslogtreecommitdiffstats
path: root/src/lib/krad/remote.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/krad/remote.c')
-rw-r--r--src/lib/krad/remote.c532
1 files changed, 532 insertions, 0 deletions
diff --git a/src/lib/krad/remote.c b/src/lib/krad/remote.c
new file mode 100644
index 000000000..bb7c06124
--- /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;
+}