1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ 2 /* lib/krad/remote.c - Protocol code for libkrad */ 3 /* 4 * Copyright 2013 Red Hat, Inc. All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in 14 * the documentation and/or other materials provided with the 15 * distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 18 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 20 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 21 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 25 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30 #include <k5-int.h> 31 #include <k5-queue.h> 32 #include "internal.h" 33 34 #include <string.h> 35 #include <unistd.h> 36 37 #include <sys/un.h> 38 39 #define FLAGS_NONE VERTO_EV_FLAG_NONE 40 #define FLAGS_READ VERTO_EV_FLAG_IO_READ 41 #define FLAGS_WRITE VERTO_EV_FLAG_IO_WRITE 42 #define FLAGS_BASE VERTO_EV_FLAG_PERSIST | VERTO_EV_FLAG_IO_ERROR 43 44 K5_TAILQ_HEAD(request_head, request_st); 45 46 typedef struct request_st request; 47 struct request_st { 48 K5_TAILQ_ENTRY(request_st) list; 49 krad_remote *rr; 50 krad_packet *request; 51 krad_cb cb; 52 void *data; 53 verto_ev *timer; 54 int timeout; 55 size_t retries; 56 size_t sent; 57 }; 58 59 struct krad_remote_st { 60 krb5_context kctx; 61 verto_ctx *vctx; 62 int fd; 63 verto_ev *io; 64 char *secret; 65 struct addrinfo *info; 66 struct request_head list; 67 char buffer_[KRAD_PACKET_SIZE_MAX]; 68 krb5_data buffer; 69 }; 70 71 static void 72 on_io(verto_ctx *ctx, verto_ev *ev); 73 74 static void 75 on_timeout(verto_ctx *ctx, verto_ev *ev); 76 77 /* Iterate over the set of outstanding packets. */ 78 static const krad_packet * 79 iterator(request **out) 80 { 81 request *tmp = *out; 82 83 if (tmp == NULL) 84 return NULL; 85 86 *out = K5_TAILQ_NEXT(tmp, list); 87 return tmp->request; 88 } 89 90 /* Create a new request. */ 91 static krb5_error_code 92 request_new(krad_remote *rr, krad_packet *rqst, int timeout, size_t retries, 93 krad_cb cb, void *data, request **out) 94 { 95 request *tmp; 96 97 tmp = calloc(1, sizeof(request)); 98 if (tmp == NULL) 99 return ENOMEM; 100 101 tmp->rr = rr; 102 tmp->request = rqst; 103 tmp->cb = cb; 104 tmp->data = data; 105 tmp->timeout = timeout; 106 tmp->retries = retries; 107 108 *out = tmp; 109 return 0; 110 } 111 112 /* Finish a request, calling the callback and freeing it. */ 113 static inline void 114 request_finish(request *req, krb5_error_code retval, 115 const krad_packet *response) 116 { 117 if (retval != ETIMEDOUT) 118 K5_TAILQ_REMOVE(&req->rr->list, req, list); 119 120 req->cb(retval, req->request, response, req->data); 121 122 if (retval != ETIMEDOUT) { 123 krad_packet_free(req->request); 124 verto_del(req->timer); 125 free(req); 126 } 127 } 128 129 /* Start the timeout timer for the request. */ 130 static krb5_error_code 131 request_start_timer(request *r, verto_ctx *vctx) 132 { 133 verto_del(r->timer); 134 135 r->timer = verto_add_timeout(vctx, VERTO_EV_FLAG_NONE, on_timeout, 136 r->timeout); 137 if (r->timer != NULL) 138 verto_set_private(r->timer, r, NULL); 139 140 return (r->timer == NULL) ? ENOMEM : 0; 141 } 142 143 /* Disconnect from the remote host. */ 144 static void 145 remote_disconnect(krad_remote *rr) 146 { 147 if (rr->fd >= 0) 148 close(rr->fd); 149 verto_del(rr->io); 150 rr->fd = -1; 151 rr->io = NULL; 152 } 153 154 /* Add the specified flags to the remote. This automatically manages the 155 * lifecycle of the underlying event. Also connects if disconnected. */ 156 static krb5_error_code 157 remote_add_flags(krad_remote *remote, verto_ev_flag flags) 158 { 159 verto_ev_flag curflags = VERTO_EV_FLAG_NONE; 160 int i; 161 162 flags &= (FLAGS_READ | FLAGS_WRITE); 163 if (remote == NULL || flags == FLAGS_NONE) 164 return EINVAL; 165 166 /* If there is no connection, connect. */ 167 if (remote->fd < 0) { 168 verto_del(remote->io); 169 remote->io = NULL; 170 171 remote->fd = socket(remote->info->ai_family, remote->info->ai_socktype, 172 remote->info->ai_protocol); 173 if (remote->fd < 0) 174 return errno; 175 176 i = connect(remote->fd, remote->info->ai_addr, 177 remote->info->ai_addrlen); 178 if (i < 0) { 179 i = errno; 180 remote_disconnect(remote); 181 return i; 182 } 183 } 184 185 if (remote->io == NULL) { 186 remote->io = verto_add_io(remote->vctx, FLAGS_BASE | flags, 187 on_io, remote->fd); 188 if (remote->io == NULL) 189 return ENOMEM; 190 verto_set_private(remote->io, remote, NULL); 191 } 192 193 curflags = verto_get_flags(remote->io); 194 if ((curflags & flags) != flags) 195 verto_set_flags(remote->io, FLAGS_BASE | curflags | flags); 196 197 return 0; 198 } 199 200 /* Remove the specified flags to the remote. This automatically manages the 201 * lifecycle of the underlying event. */ 202 static void 203 remote_del_flags(krad_remote *remote, verto_ev_flag flags) 204 { 205 if (remote == NULL || remote->io == NULL) 206 return; 207 208 flags = verto_get_flags(remote->io) & (FLAGS_READ | FLAGS_WRITE) & ~flags; 209 if (flags == FLAGS_NONE) { 210 verto_del(remote->io); 211 remote->io = NULL; 212 return; 213 } 214 215 verto_set_flags(remote->io, FLAGS_BASE | flags); 216 } 217 218 /* Close the connection and start the timers of all outstanding requests. */ 219 static void 220 remote_shutdown(krad_remote *rr) 221 { 222 krb5_error_code retval; 223 request *r, *next; 224 225 remote_disconnect(rr); 226 227 /* Start timers for all unsent packets. */ 228 K5_TAILQ_FOREACH_SAFE(r, &rr->list, list, next) { 229 if (r->timer == NULL) { 230 retval = request_start_timer(r, rr->vctx); 231 if (retval != 0) 232 request_finish(r, retval, NULL); 233 } 234 } 235 } 236 237 /* Handle when packets receive no response within their allotted time. */ 238 static void 239 on_timeout(verto_ctx *ctx, verto_ev *ev) 240 { 241 request *req = verto_get_private(ev); 242 krb5_error_code retval = ETIMEDOUT; 243 244 req->timer = NULL; /* Void the timer event. */ 245 246 /* If we have more retries to perform, resend the packet. */ 247 if (req->retries-- > 0) { 248 req->sent = 0; 249 retval = remote_add_flags(req->rr, FLAGS_WRITE); 250 if (retval == 0) 251 return; 252 } 253 254 request_finish(req, retval, NULL); 255 } 256 257 /* Write data to the socket. */ 258 static void 259 on_io_write(krad_remote *rr) 260 { 261 const krb5_data *tmp; 262 ssize_t written; 263 request *r; 264 265 K5_TAILQ_FOREACH(r, &rr->list, list) { 266 tmp = krad_packet_encode(r->request); 267 268 /* If the packet has already been sent, do nothing. */ 269 if (r->sent == tmp->length) 270 continue; 271 272 /* Send the packet. */ 273 written = sendto(verto_get_fd(rr->io), tmp->data + r->sent, 274 tmp->length - r->sent, 0, NULL, 0); 275 if (written < 0) { 276 /* Should we try again? */ 277 if (errno == EWOULDBLOCK || errno == EAGAIN || errno == ENOBUFS || 278 errno == EINTR) 279 return; 280 281 /* This error can't be worked around. */ 282 remote_shutdown(rr); 283 return; 284 } 285 286 /* If the packet was completely sent, set a timeout. */ 287 r->sent += written; 288 if (r->sent == tmp->length) { 289 if (request_start_timer(r, rr->vctx) != 0) { 290 request_finish(r, ENOMEM, NULL); 291 return; 292 } 293 294 if (remote_add_flags(rr, FLAGS_READ) != 0) { 295 remote_shutdown(rr); 296 return; 297 } 298 } 299 300 return; 301 } 302 303 remote_del_flags(rr, FLAGS_WRITE); 304 return; 305 } 306 307 /* Read data from the socket. */ 308 static void 309 on_io_read(krad_remote *rr) 310 { 311 const krad_packet *req = NULL; 312 krad_packet *rsp = NULL; 313 krb5_error_code retval; 314 ssize_t pktlen; 315 request *tmp, *r; 316 int i; 317 318 pktlen = sizeof(rr->buffer_) - rr->buffer.length; 319 if (rr->info->ai_socktype == SOCK_STREAM) { 320 pktlen = krad_packet_bytes_needed(&rr->buffer); 321 if (pktlen < 0) { 322 /* If we received a malformed packet on a stream socket, 323 * assume the socket to be unrecoverable. */ 324 remote_shutdown(rr); 325 return; 326 } 327 } 328 329 /* Read the packet. */ 330 i = recv(verto_get_fd(rr->io), rr->buffer.data + rr->buffer.length, 331 pktlen, 0); 332 333 /* On these errors, try again. */ 334 if (i < 0 && (errno == EWOULDBLOCK || errno == EAGAIN || errno == EINTR)) 335 return; 336 337 /* On any other errors or on EOF, the socket is unrecoverable. */ 338 if (i <= 0) { 339 remote_shutdown(rr); 340 return; 341 } 342 343 /* If we have a partial read or just the header, try again. */ 344 rr->buffer.length += i; 345 pktlen = krad_packet_bytes_needed(&rr->buffer); 346 if (rr->info->ai_socktype == SOCK_STREAM && pktlen > 0) 347 return; 348 349 /* Decode the packet. */ 350 tmp = K5_TAILQ_FIRST(&rr->list); 351 retval = krad_packet_decode_response(rr->kctx, rr->secret, &rr->buffer, 352 (krad_packet_iter_cb)iterator, &tmp, 353 &req, &rsp); 354 rr->buffer.length = 0; 355 if (retval != 0) 356 return; 357 358 /* Match the response with an outstanding request. */ 359 if (req != NULL) { 360 K5_TAILQ_FOREACH(r, &rr->list, list) { 361 if (r->request == req && 362 r->sent == krad_packet_encode(req)->length) { 363 request_finish(r, 0, rsp); 364 break; 365 } 366 } 367 } 368 369 krad_packet_free(rsp); 370 } 371 372 /* Handle when IO is ready on the socket. */ 373 static void 374 on_io(verto_ctx *ctx, verto_ev *ev) 375 { 376 krad_remote *rr; 377 378 rr = verto_get_private(ev); 379 380 if (verto_get_fd_state(ev) & VERTO_EV_FLAG_IO_WRITE) 381 on_io_write(rr); 382 else 383 on_io_read(rr); 384 } 385 386 krb5_error_code 387 kr_remote_new(krb5_context kctx, verto_ctx *vctx, const struct addrinfo *info, 388 const char *secret, krad_remote **rr) 389 { 390 krb5_error_code retval = ENOMEM; 391 krad_remote *tmp = NULL; 392 393 tmp = calloc(1, sizeof(krad_remote)); 394 if (tmp == NULL) 395 goto error; 396 tmp->kctx = kctx; 397 tmp->vctx = vctx; 398 tmp->buffer = make_data(tmp->buffer_, 0); 399 K5_TAILQ_INIT(&tmp->list); 400 tmp->fd = -1; 401 402 tmp->secret = strdup(secret); 403 if (tmp->secret == NULL) 404 goto error; 405 406 tmp->info = k5memdup(info, sizeof(*info), &retval); 407 if (tmp->info == NULL) 408 goto error; 409 410 tmp->info->ai_addr = k5memdup(info->ai_addr, info->ai_addrlen, &retval); 411 if (tmp->info == NULL) 412 goto error; 413 tmp->info->ai_next = NULL; 414 tmp->info->ai_canonname = NULL; 415 416 *rr = tmp; 417 return 0; 418 419 error: 420 kr_remote_free(tmp); 421 return retval; 422 } 423 424 void 425 kr_remote_cancel_all(krad_remote *rr) 426 { 427 while (!K5_TAILQ_EMPTY(&rr->list)) 428 request_finish(K5_TAILQ_FIRST(&rr->list), ECANCELED, NULL); 429 } 430 431 void 432 kr_remote_free(krad_remote *rr) 433 { 434 if (rr == NULL) 435 return; 436 437 kr_remote_cancel_all(rr); 438 free(rr->secret); 439 if (rr->info != NULL) 440 free(rr->info->ai_addr); 441 free(rr->info); 442 remote_disconnect(rr); 443 free(rr); 444 } 445 446 krb5_error_code 447 kr_remote_send(krad_remote *rr, krad_code code, krad_attrset *attrs, 448 krad_cb cb, void *data, int timeout, size_t retries, 449 const krad_packet **pkt) 450 { 451 krad_packet *tmp = NULL; 452 krb5_error_code retval; 453 request *r, *new_request = NULL; 454 455 if (rr->info->ai_socktype == SOCK_STREAM) 456 retries = 0; 457 458 r = K5_TAILQ_FIRST(&rr->list); 459 retval = krad_packet_new_request(rr->kctx, rr->secret, code, attrs, 460 (krad_packet_iter_cb)iterator, &r, &tmp); 461 if (retval != 0) 462 goto error; 463 464 K5_TAILQ_FOREACH(r, &rr->list, list) { 465 if (r->request == tmp) { 466 retval = EALREADY; 467 goto error; 468 } 469 } 470 471 timeout = timeout / (retries + 1); 472 retval = request_new(rr, tmp, timeout, retries, cb, data, &new_request); 473 if (retval != 0) 474 goto error; 475 476 retval = remote_add_flags(rr, FLAGS_WRITE); 477 if (retval != 0) 478 goto error; 479 480 K5_TAILQ_INSERT_TAIL(&rr->list, new_request, list); 481 if (pkt != NULL) 482 *pkt = tmp; 483 return 0; 484 485 error: 486 free(new_request); 487 krad_packet_free(tmp); 488 return retval; 489 } 490 491 void 492 kr_remote_cancel(krad_remote *rr, const krad_packet *pkt) 493 { 494 request *r; 495 496 K5_TAILQ_FOREACH(r, &rr->list, list) { 497 if (r->request == pkt) { 498 request_finish(r, ECANCELED, NULL); 499 return; 500 } 501 } 502 } 503 504 krb5_boolean 505 kr_remote_equals(const krad_remote *rr, const struct addrinfo *info, 506 const char *secret) 507 { 508 struct sockaddr_un *a, *b; 509 510 if (strcmp(rr->secret, secret) != 0) 511 return FALSE; 512 513 if (info->ai_addrlen != rr->info->ai_addrlen) 514 return FALSE; 515 516 if (info->ai_family != rr->info->ai_family) 517 return FALSE; 518 519 if (info->ai_socktype != rr->info->ai_socktype) 520 return FALSE; 521 522 if (info->ai_protocol != rr->info->ai_protocol) 523 return FALSE; 524 525 if (info->ai_flags != rr->info->ai_flags) 526 return FALSE; 527 528 if (memcmp(rr->info->ai_addr, info->ai_addr, info->ai_addrlen) != 0) { 529 /* AF_UNIX fails the memcmp() test due to uninitialized bytes after the 530 * socket name. */ 531 if (info->ai_family != AF_UNIX) 532 return FALSE; 533 534 a = (struct sockaddr_un *)info->ai_addr; 535 b = (struct sockaddr_un *)rr->info->ai_addr; 536 if (strncmp(a->sun_path, b->sun_path, sizeof(a->sun_path)) != 0) 537 return FALSE; 538 } 539 540 return TRUE; 541 } 542