Path: blob/main/crypto/krb5/src/lib/krad/remote.c
106109 views
/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */1/* lib/krad/remote.c - Protocol code for libkrad */2/*3* Copyright 2013 Red Hat, Inc. All rights reserved.4*5* Redistribution and use in source and binary forms, with or without6* modification, are permitted provided that the following conditions are met:7*8* 1. Redistributions of source code must retain the above copyright9* notice, this list of conditions and the following disclaimer.10*11* 2. Redistributions in binary form must reproduce the above copyright12* notice, this list of conditions and the following disclaimer in13* the documentation and/or other materials provided with the14* distribution.15*16* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS17* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED18* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A19* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER20* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,21* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,22* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR23* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF24* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING25* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS26* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.27*/2829#include <k5-int.h>30#include <k5-queue.h>31#include "internal.h"3233#include <string.h>34#include <unistd.h>3536#include <sys/un.h>3738#define FLAGS_NONE VERTO_EV_FLAG_NONE39#define FLAGS_READ VERTO_EV_FLAG_IO_READ40#define FLAGS_WRITE VERTO_EV_FLAG_IO_WRITE41#define FLAGS_BASE VERTO_EV_FLAG_PERSIST | VERTO_EV_FLAG_IO_ERROR4243K5_TAILQ_HEAD(request_head, request_st);4445typedef struct request_st request;46struct request_st {47K5_TAILQ_ENTRY(request_st) list;48krad_remote *rr;49krad_packet *request;50krad_cb cb;51void *data;52verto_ev *timer;53int timeout;54size_t retries;55size_t sent;56};5758struct krad_remote_st {59krb5_context kctx;60verto_ctx *vctx;61int fd;62verto_ev *io;63char *secret;64struct addrinfo *info;65struct request_head list;66char buffer_[KRAD_PACKET_SIZE_MAX];67krb5_data buffer;68};6970static void71on_io(verto_ctx *ctx, verto_ev *ev);7273static void74on_timeout(verto_ctx *ctx, verto_ev *ev);7576/* Iterate over the set of outstanding packets. */77static const krad_packet *78iterator(void *data, krb5_boolean cancel)79{80request **rptr = data, *req = *rptr;8182if (cancel || req == NULL)83return NULL;8485*rptr = K5_TAILQ_NEXT(req, list);86return req->request;87}8889/* Create a new request. */90static krb5_error_code91request_new(krad_remote *rr, krad_packet *rqst, int timeout, size_t retries,92krad_cb cb, void *data, request **out)93{94request *tmp;9596tmp = calloc(1, sizeof(request));97if (tmp == NULL)98return ENOMEM;99100tmp->rr = rr;101tmp->request = rqst;102tmp->cb = cb;103tmp->data = data;104tmp->timeout = timeout;105tmp->retries = retries;106107*out = tmp;108return 0;109}110111/* Finish a request, calling the callback and freeing it. */112static inline void113request_finish(request *req, krb5_error_code retval,114const krad_packet *response)115{116if (retval != ETIMEDOUT)117K5_TAILQ_REMOVE(&req->rr->list, req, list);118119req->cb(retval, req->request, response, req->data);120121if (retval != ETIMEDOUT) {122krad_packet_free(req->request);123verto_del(req->timer);124free(req);125}126}127128/* Start the timeout timer for the request. */129static krb5_error_code130request_start_timer(request *r, verto_ctx *vctx)131{132verto_del(r->timer);133134r->timer = verto_add_timeout(vctx, VERTO_EV_FLAG_NONE, on_timeout,135r->timeout);136if (r->timer != NULL)137verto_set_private(r->timer, r, NULL);138139return (r->timer == NULL) ? ENOMEM : 0;140}141142/* Disconnect from the remote host. */143static void144remote_disconnect(krad_remote *rr)145{146if (rr->fd >= 0)147close(rr->fd);148verto_del(rr->io);149rr->fd = -1;150rr->io = NULL;151}152153/* Add the specified flags to the remote. This automatically manages the154* lifecycle of the underlying event. Also connects if disconnected. */155static krb5_error_code156remote_add_flags(krad_remote *remote, verto_ev_flag flags)157{158verto_ev_flag curflags = VERTO_EV_FLAG_NONE;159int i;160161flags &= (FLAGS_READ | FLAGS_WRITE);162if (remote == NULL || flags == FLAGS_NONE)163return EINVAL;164165/* If there is no connection, connect. */166if (remote->fd < 0) {167verto_del(remote->io);168remote->io = NULL;169170remote->fd = socket(remote->info->ai_family, remote->info->ai_socktype,171remote->info->ai_protocol);172if (remote->fd < 0)173return errno;174175i = connect(remote->fd, remote->info->ai_addr,176remote->info->ai_addrlen);177if (i < 0) {178i = errno;179remote_disconnect(remote);180return i;181}182}183184if (remote->io == NULL) {185remote->io = verto_add_io(remote->vctx, FLAGS_BASE | flags,186on_io, remote->fd);187if (remote->io == NULL)188return ENOMEM;189verto_set_private(remote->io, remote, NULL);190}191192curflags = verto_get_flags(remote->io);193if ((curflags & flags) != flags)194verto_set_flags(remote->io, FLAGS_BASE | curflags | flags);195196return 0;197}198199/* Remove the specified flags to the remote. This automatically manages the200* lifecycle of the underlying event. */201static void202remote_del_flags(krad_remote *remote, verto_ev_flag flags)203{204if (remote == NULL || remote->io == NULL)205return;206207flags = verto_get_flags(remote->io) & (FLAGS_READ | FLAGS_WRITE) & ~flags;208if (flags == FLAGS_NONE) {209verto_del(remote->io);210remote->io = NULL;211return;212}213214verto_set_flags(remote->io, FLAGS_BASE | flags);215}216217/* Close the connection and start the timers of all outstanding requests. */218static void219remote_shutdown(krad_remote *rr)220{221krb5_error_code retval;222request *r, *next;223224remote_disconnect(rr);225226/* Start timers for all unsent packets. */227K5_TAILQ_FOREACH_SAFE(r, &rr->list, list, next) {228if (r->timer == NULL) {229retval = request_start_timer(r, rr->vctx);230if (retval != 0)231request_finish(r, retval, NULL);232}233}234}235236/* Handle when packets receive no response within their allotted time. */237static void238on_timeout(verto_ctx *ctx, verto_ev *ev)239{240request *req = verto_get_private(ev);241krb5_error_code retval = ETIMEDOUT;242243req->timer = NULL; /* Void the timer event. */244245/* If we have more retries to perform, resend the packet. */246if (req->retries-- > 0) {247req->sent = 0;248retval = remote_add_flags(req->rr, FLAGS_WRITE);249if (retval == 0)250return;251}252253request_finish(req, retval, NULL);254}255256/* Write data to the socket. */257static void258on_io_write(krad_remote *rr)259{260const krb5_data *tmp;261ssize_t written;262request *r;263264K5_TAILQ_FOREACH(r, &rr->list, list) {265tmp = krad_packet_encode(r->request);266267/* If the packet has already been sent, do nothing. */268if (r->sent == tmp->length)269continue;270271/* Send the packet. */272written = sendto(verto_get_fd(rr->io), tmp->data + r->sent,273tmp->length - r->sent, 0, NULL, 0);274if (written < 0) {275/* Should we try again? */276if (errno == EWOULDBLOCK || errno == EAGAIN || errno == ENOBUFS ||277errno == EINTR)278return;279280/* This error can't be worked around. */281remote_shutdown(rr);282return;283}284285/* If the packet was completely sent, set a timeout. */286r->sent += written;287if (r->sent == tmp->length) {288if (request_start_timer(r, rr->vctx) != 0) {289request_finish(r, ENOMEM, NULL);290return;291}292293if (remote_add_flags(rr, FLAGS_READ) != 0) {294remote_shutdown(rr);295return;296}297}298299return;300}301302remote_del_flags(rr, FLAGS_WRITE);303return;304}305306/* Read data from the socket. */307static void308on_io_read(krad_remote *rr)309{310const krad_packet *req = NULL;311krad_packet *rsp = NULL;312krb5_error_code retval;313ssize_t pktlen;314request *tmp, *r;315int i;316317pktlen = sizeof(rr->buffer_) - rr->buffer.length;318if (rr->info->ai_socktype == SOCK_STREAM) {319pktlen = krad_packet_bytes_needed(&rr->buffer);320if (pktlen < 0) {321/* If we received a malformed packet on a stream socket,322* assume the socket to be unrecoverable. */323remote_shutdown(rr);324return;325}326}327328/* Read the packet. */329i = recv(verto_get_fd(rr->io), rr->buffer.data + rr->buffer.length,330pktlen, 0);331332/* On these errors, try again. */333if (i < 0 && (errno == EWOULDBLOCK || errno == EAGAIN || errno == EINTR))334return;335336/* On any other errors or on EOF, the socket is unrecoverable. */337if (i <= 0) {338remote_shutdown(rr);339return;340}341342/* If we have a partial read or just the header, try again. */343rr->buffer.length += i;344pktlen = krad_packet_bytes_needed(&rr->buffer);345if (rr->info->ai_socktype == SOCK_STREAM && pktlen > 0)346return;347348/* Decode the packet. */349tmp = K5_TAILQ_FIRST(&rr->list);350retval = krad_packet_decode_response(rr->kctx, rr->secret, &rr->buffer,351iterator, &tmp, &req, &rsp);352rr->buffer.length = 0;353if (retval != 0)354return;355356/* Match the response with an outstanding request. */357if (req != NULL) {358K5_TAILQ_FOREACH(r, &rr->list, list) {359if (r->request == req &&360r->sent == krad_packet_encode(req)->length) {361request_finish(r, 0, rsp);362break;363}364}365}366367krad_packet_free(rsp);368}369370/* Handle when IO is ready on the socket. */371static void372on_io(verto_ctx *ctx, verto_ev *ev)373{374krad_remote *rr;375376rr = verto_get_private(ev);377378if (verto_get_fd_state(ev) & VERTO_EV_FLAG_IO_WRITE)379on_io_write(rr);380else381on_io_read(rr);382}383384krb5_error_code385kr_remote_new(krb5_context kctx, verto_ctx *vctx, const struct addrinfo *info,386const char *secret, krad_remote **rr)387{388krb5_error_code retval = ENOMEM;389krad_remote *tmp = NULL;390391tmp = calloc(1, sizeof(krad_remote));392if (tmp == NULL)393goto error;394tmp->kctx = kctx;395tmp->vctx = vctx;396tmp->buffer = make_data(tmp->buffer_, 0);397K5_TAILQ_INIT(&tmp->list);398tmp->fd = -1;399400tmp->secret = strdup(secret);401if (tmp->secret == NULL)402goto error;403404tmp->info = k5memdup(info, sizeof(*info), &retval);405if (tmp->info == NULL)406goto error;407408tmp->info->ai_addr = k5memdup(info->ai_addr, info->ai_addrlen, &retval);409if (tmp->info == NULL)410goto error;411tmp->info->ai_next = NULL;412tmp->info->ai_canonname = NULL;413414*rr = tmp;415return 0;416417error:418kr_remote_free(tmp);419return retval;420}421422void423kr_remote_cancel_all(krad_remote *rr)424{425while (!K5_TAILQ_EMPTY(&rr->list))426request_finish(K5_TAILQ_FIRST(&rr->list), ECANCELED, NULL);427}428429void430kr_remote_free(krad_remote *rr)431{432if (rr == NULL)433return;434435kr_remote_cancel_all(rr);436free(rr->secret);437if (rr->info != NULL)438free(rr->info->ai_addr);439free(rr->info);440remote_disconnect(rr);441free(rr);442}443444krb5_error_code445kr_remote_send(krad_remote *rr, krad_code code, krad_attrset *attrs,446krad_cb cb, void *data, int timeout, size_t retries,447const krad_packet **pkt)448{449krad_packet *tmp = NULL;450krb5_error_code retval;451request *r, *new_request = NULL;452453if (rr->info->ai_socktype == SOCK_STREAM)454retries = 0;455456r = K5_TAILQ_FIRST(&rr->list);457retval = krad_packet_new_request(rr->kctx, rr->secret, code, attrs,458iterator, &r, &tmp);459if (retval != 0)460goto error;461462K5_TAILQ_FOREACH(r, &rr->list, list) {463if (r->request == tmp) {464retval = EALREADY;465goto error;466}467}468469timeout = timeout / (retries + 1);470retval = request_new(rr, tmp, timeout, retries, cb, data, &new_request);471if (retval != 0)472goto error;473474retval = remote_add_flags(rr, FLAGS_WRITE);475if (retval != 0)476goto error;477478K5_TAILQ_INSERT_TAIL(&rr->list, new_request, list);479if (pkt != NULL)480*pkt = tmp;481return 0;482483error:484free(new_request);485krad_packet_free(tmp);486return retval;487}488489void490kr_remote_cancel(krad_remote *rr, const krad_packet *pkt)491{492request *r;493494K5_TAILQ_FOREACH(r, &rr->list, list) {495if (r->request == pkt) {496request_finish(r, ECANCELED, NULL);497return;498}499}500}501502krb5_boolean503kr_remote_equals(const krad_remote *rr, const struct addrinfo *info,504const char *secret)505{506struct sockaddr_un *a, *b;507508if (strcmp(rr->secret, secret) != 0)509return FALSE;510511if (info->ai_addrlen != rr->info->ai_addrlen)512return FALSE;513514if (info->ai_family != rr->info->ai_family)515return FALSE;516517if (info->ai_socktype != rr->info->ai_socktype)518return FALSE;519520if (info->ai_protocol != rr->info->ai_protocol)521return FALSE;522523if (info->ai_flags != rr->info->ai_flags)524return FALSE;525526if (memcmp(rr->info->ai_addr, info->ai_addr, info->ai_addrlen) != 0) {527/* AF_UNIX fails the memcmp() test due to uninitialized bytes after the528* socket name. */529if (info->ai_family != AF_UNIX)530return FALSE;531532a = (struct sockaddr_un *)info->ai_addr;533b = (struct sockaddr_un *)rr->info->ai_addr;534if (strncmp(a->sun_path, b->sun_path, sizeof(a->sun_path)) != 0)535return FALSE;536}537538return TRUE;539}540541542