Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/crypto/krb5/src/lib/krad/remote.c
39537 views
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(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
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
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
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
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
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
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
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
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
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
542