xref: /freebsd/crypto/krb5/src/lib/krad/remote.c (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
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