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(void * data,krb5_boolean cancel)79 iterator(void *data, krb5_boolean cancel)
80 {
81 request **rptr = data, *req = *rptr;
82
83 if (cancel || req == NULL)
84 return NULL;
85
86 *rptr = K5_TAILQ_NEXT(req, list);
87 return req->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 iterator, &tmp, &req, &rsp);
353 rr->buffer.length = 0;
354 if (retval != 0)
355 return;
356
357 /* Match the response with an outstanding request. */
358 if (req != NULL) {
359 K5_TAILQ_FOREACH(r, &rr->list, list) {
360 if (r->request == req &&
361 r->sent == krad_packet_encode(req)->length) {
362 request_finish(r, 0, rsp);
363 break;
364 }
365 }
366 }
367
368 krad_packet_free(rsp);
369 }
370
371 /* Handle when IO is ready on the socket. */
372 static void
on_io(verto_ctx * ctx,verto_ev * ev)373 on_io(verto_ctx *ctx, verto_ev *ev)
374 {
375 krad_remote *rr;
376
377 rr = verto_get_private(ev);
378
379 if (verto_get_fd_state(ev) & VERTO_EV_FLAG_IO_WRITE)
380 on_io_write(rr);
381 else
382 on_io_read(rr);
383 }
384
385 krb5_error_code
kr_remote_new(krb5_context kctx,verto_ctx * vctx,const struct addrinfo * info,const char * secret,krad_remote ** rr)386 kr_remote_new(krb5_context kctx, verto_ctx *vctx, const struct addrinfo *info,
387 const char *secret, krad_remote **rr)
388 {
389 krb5_error_code retval = ENOMEM;
390 krad_remote *tmp = NULL;
391
392 tmp = calloc(1, sizeof(krad_remote));
393 if (tmp == NULL)
394 goto error;
395 tmp->kctx = kctx;
396 tmp->vctx = vctx;
397 tmp->buffer = make_data(tmp->buffer_, 0);
398 K5_TAILQ_INIT(&tmp->list);
399 tmp->fd = -1;
400
401 tmp->secret = strdup(secret);
402 if (tmp->secret == NULL)
403 goto error;
404
405 tmp->info = k5memdup(info, sizeof(*info), &retval);
406 if (tmp->info == NULL)
407 goto error;
408
409 tmp->info->ai_addr = k5memdup(info->ai_addr, info->ai_addrlen, &retval);
410 if (tmp->info == NULL)
411 goto error;
412 tmp->info->ai_next = NULL;
413 tmp->info->ai_canonname = NULL;
414
415 *rr = tmp;
416 return 0;
417
418 error:
419 kr_remote_free(tmp);
420 return retval;
421 }
422
423 void
kr_remote_cancel_all(krad_remote * rr)424 kr_remote_cancel_all(krad_remote *rr)
425 {
426 while (!K5_TAILQ_EMPTY(&rr->list))
427 request_finish(K5_TAILQ_FIRST(&rr->list), ECANCELED, NULL);
428 }
429
430 void
kr_remote_free(krad_remote * rr)431 kr_remote_free(krad_remote *rr)
432 {
433 if (rr == NULL)
434 return;
435
436 kr_remote_cancel_all(rr);
437 free(rr->secret);
438 if (rr->info != NULL)
439 free(rr->info->ai_addr);
440 free(rr->info);
441 remote_disconnect(rr);
442 free(rr);
443 }
444
445 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)446 kr_remote_send(krad_remote *rr, krad_code code, krad_attrset *attrs,
447 krad_cb cb, void *data, int timeout, size_t retries,
448 const krad_packet **pkt)
449 {
450 krad_packet *tmp = NULL;
451 krb5_error_code retval;
452 request *r, *new_request = NULL;
453
454 if (rr->info->ai_socktype == SOCK_STREAM)
455 retries = 0;
456
457 r = K5_TAILQ_FIRST(&rr->list);
458 retval = krad_packet_new_request(rr->kctx, rr->secret, code, attrs,
459 iterator, &r, &tmp);
460 if (retval != 0)
461 goto error;
462
463 K5_TAILQ_FOREACH(r, &rr->list, list) {
464 if (r->request == tmp) {
465 retval = EALREADY;
466 goto error;
467 }
468 }
469
470 timeout = timeout / (retries + 1);
471 retval = request_new(rr, tmp, timeout, retries, cb, data, &new_request);
472 if (retval != 0)
473 goto error;
474
475 retval = remote_add_flags(rr, FLAGS_WRITE);
476 if (retval != 0)
477 goto error;
478
479 K5_TAILQ_INSERT_TAIL(&rr->list, new_request, list);
480 if (pkt != NULL)
481 *pkt = tmp;
482 return 0;
483
484 error:
485 free(new_request);
486 krad_packet_free(tmp);
487 return retval;
488 }
489
490 void
kr_remote_cancel(krad_remote * rr,const krad_packet * pkt)491 kr_remote_cancel(krad_remote *rr, const krad_packet *pkt)
492 {
493 request *r;
494
495 K5_TAILQ_FOREACH(r, &rr->list, list) {
496 if (r->request == pkt) {
497 request_finish(r, ECANCELED, NULL);
498 return;
499 }
500 }
501 }
502
503 krb5_boolean
kr_remote_equals(const krad_remote * rr,const struct addrinfo * info,const char * secret)504 kr_remote_equals(const krad_remote *rr, const struct addrinfo *info,
505 const char *secret)
506 {
507 struct sockaddr_un *a, *b;
508
509 if (strcmp(rr->secret, secret) != 0)
510 return FALSE;
511
512 if (info->ai_addrlen != rr->info->ai_addrlen)
513 return FALSE;
514
515 if (info->ai_family != rr->info->ai_family)
516 return FALSE;
517
518 if (info->ai_socktype != rr->info->ai_socktype)
519 return FALSE;
520
521 if (info->ai_protocol != rr->info->ai_protocol)
522 return FALSE;
523
524 if (info->ai_flags != rr->info->ai_flags)
525 return FALSE;
526
527 if (memcmp(rr->info->ai_addr, info->ai_addr, info->ai_addrlen) != 0) {
528 /* AF_UNIX fails the memcmp() test due to uninitialized bytes after the
529 * socket name. */
530 if (info->ai_family != AF_UNIX)
531 return FALSE;
532
533 a = (struct sockaddr_un *)info->ai_addr;
534 b = (struct sockaddr_un *)rr->info->ai_addr;
535 if (strncmp(a->sun_path, b->sun_path, sizeof(a->sun_path)) != 0)
536 return FALSE;
537 }
538
539 return TRUE;
540 }
541