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 *
iterator(request ** out)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
request_new(krad_remote * rr,krad_packet * rqst,int timeout,size_t retries,krad_cb cb,void * data,request ** out)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
request_finish(request * req,krb5_error_code retval,const krad_packet * response)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
request_start_timer(request * r,verto_ctx * vctx)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
remote_disconnect(krad_remote * rr)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
remote_add_flags(krad_remote * remote,verto_ev_flag flags)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
remote_del_flags(krad_remote * remote,verto_ev_flag flags)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
remote_shutdown(krad_remote * rr)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
on_timeout(verto_ctx * ctx,verto_ev * ev)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
on_io_write(krad_remote * rr)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
on_io_read(krad_remote * rr)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
on_io(verto_ctx * ctx,verto_ev * ev)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
kr_remote_new(krb5_context kctx,verto_ctx * vctx,const struct addrinfo * info,const char * secret,krad_remote ** rr)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
kr_remote_cancel_all(krad_remote * rr)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
kr_remote_free(krad_remote * rr)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
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)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
kr_remote_cancel(krad_remote * rr,const krad_packet * pkt)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
kr_remote_equals(const krad_remote * rr,const struct addrinfo * info,const char * secret)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