Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sudo-project
GitHub Repository: sudo-project/sudo
Path: blob/main/logsrvd/sendlog.c
1532 views
1
/*
2
* SPDX-License-Identifier: ISC
3
*
4
* Copyright (c) 2019-2023, 2025 Todd C. Miller <[email protected]>
5
*
6
* Permission to use, copy, modify, and distribute this software for any
7
* purpose with or without fee is hereby granted, provided that the above
8
* copyright notice and this permission notice appear in all copies.
9
*
10
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17
*/
18
19
#include <config.h>
20
21
#include <sys/stat.h>
22
#include <sys/types.h>
23
#include <sys/socket.h>
24
#include <sys/time.h>
25
#include <netinet/in.h>
26
#include <netinet/tcp.h>
27
#include <arpa/inet.h>
28
29
#include <errno.h>
30
#include <fcntl.h>
31
#include <limits.h>
32
#include <netdb.h>
33
#ifdef HAVE_STDBOOL_H
34
# include <stdbool.h>
35
#else
36
# include <compat/stdbool.h>
37
#endif /* HAVE_STDBOOL_H */
38
#if defined(HAVE_STDINT_H)
39
# include <stdint.h>
40
#elif defined(HAVE_INTTYPES_H)
41
# include <inttypes.h>
42
#endif
43
#include <stdio.h>
44
#include <stdlib.h>
45
#include <string.h>
46
#include <time.h>
47
#include <unistd.h>
48
#ifndef HAVE_GETADDRINFO
49
# include <compat/getaddrinfo.h>
50
#endif
51
#ifdef HAVE_GETOPT_LONG
52
# include <getopt.h>
53
# else
54
# include <compat/getopt.h>
55
#endif /* HAVE_GETOPT_LONG */
56
57
#include <sudo_compat.h>
58
#include <sudo_conf.h>
59
#include <sudo_debug.h>
60
#include <sudo_event.h>
61
#include <sudo_eventlog.h>
62
#include <sudo_fatal.h>
63
#include <sudo_gettext.h>
64
#include <sudo_iolog.h>
65
#include <sudo_util.h>
66
67
#include "sendlog.h"
68
#include <hostcheck.h>
69
70
#if defined(HAVE_OPENSSL)
71
# define TLS_HANDSHAKE_TIMEO_SEC 10
72
#endif
73
74
TAILQ_HEAD(connection_list, client_closure);
75
static struct connection_list connections = TAILQ_HEAD_INITIALIZER(connections);
76
77
static struct peer_info server_info = { "localhost" };
78
static char *iolog_dir;
79
static bool testrun = false;
80
static int nr_of_conns = 1;
81
static int finished_transmissions = 0;
82
83
#if defined(HAVE_OPENSSL)
84
static SSL_CTX *ssl_ctx = NULL;
85
static const char *ca_bundle = NULL;
86
static const char *cert = NULL;
87
static const char *key = NULL;
88
static bool verify_server = true;
89
#endif
90
91
/* Server callback may redirect to client callback for TLS. */
92
static void client_msg_cb(int fd, int what, void *v);
93
static void server_msg_cb(int fd, int what, void *v);
94
95
static void
96
display_usage(FILE *fp)
97
{
98
#if defined(HAVE_OPENSSL)
99
fprintf(fp, "usage: %s [-AnV] [-b ca_bundle] [-c cert_file] [-h host] "
100
"[-i iolog-id] [-k key_file] [-p port] "
101
#else
102
fprintf(fp, "usage: %s [-AnV] [-h host] [-i iolog-id] [-p port] "
103
#endif
104
"[-r restart-point] [-R reject-reason] [-s stop-point] [-t number] /path/to/iolog\n",
105
getprogname());
106
}
107
108
sudo_noreturn static void
109
usage(void)
110
{
111
display_usage(stderr);
112
exit(EXIT_FAILURE);
113
}
114
115
sudo_noreturn static void
116
help(void)
117
{
118
printf("%s - %s\n\n", getprogname(),
119
_("send sudo I/O log to remote server"));
120
display_usage(stdout);
121
printf("\n%s\n", _("Options:"));
122
printf(" --help %s\n",
123
_("display help message and exit"));
124
printf(" -A, --accept %s\n",
125
_("only send an accept event (no I/O)"));
126
#if defined(HAVE_OPENSSL)
127
printf(" -b, --ca-bundle %s\n",
128
_("certificate bundle file to verify server's cert against"));
129
printf(" -c, --cert %s\n",
130
_("certificate file for TLS handshake"));
131
#endif
132
printf(" -h, --host %s\n",
133
_("host to send logs to"));
134
printf(" -i, --iolog_id %s\n",
135
_("remote ID of I/O log to be resumed"));
136
#if defined(HAVE_OPENSSL)
137
printf(" -k, --key %s\n",
138
_("private key file"));
139
printf(" -n, --no-verify %s\n",
140
_("do not verify server certificate"));
141
#endif
142
printf(" -p, --port %s\n",
143
_("port to use when connecting to host"));
144
printf(" -r, --restart %s\n",
145
_("restart previous I/O log transfer"));
146
printf(" -R, --reject %s\n",
147
_("reject the command with the given reason"));
148
printf(" -s, --stop-after %s\n",
149
_("stop transfer after reaching this time"));
150
printf(" -t, --test %s\n",
151
_("test audit server by sending selected I/O log n times in parallel"));
152
printf(" -V, --version %s\n",
153
_("display version information and exit"));
154
putchar('\n');
155
exit(EXIT_SUCCESS);
156
}
157
158
/*
159
* Connect to specified host:port
160
* If host has multiple addresses, the first one that connects is used.
161
* Returns open socket or -1 on error.
162
*/
163
static int
164
connect_server(struct peer_info *server, const char *port)
165
{
166
struct addrinfo hints, *res, *res0;
167
const char *addr, *cause = "getaddrinfo";
168
int error, sock, save_errno;
169
debug_decl(connect_server, SUDO_DEBUG_UTIL);
170
171
memset(&hints, 0, sizeof(hints));
172
hints.ai_family = AF_UNSPEC;
173
hints.ai_socktype = SOCK_STREAM;
174
error = getaddrinfo(server->name, port, &hints, &res0);
175
if (error != 0) {
176
sudo_warnx(U_("unable to look up %s:%s: %s"), server->name, port,
177
gai_strerror(error));
178
debug_return_int(-1);
179
}
180
181
sock = -1;
182
for (res = res0; res; res = res->ai_next) {
183
sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
184
if (sock == -1) {
185
cause = "socket";
186
continue;
187
}
188
if (connect(sock, res->ai_addr, res->ai_addrlen) == -1) {
189
cause = "connect";
190
save_errno = errno;
191
close(sock);
192
errno = save_errno;
193
sock = -1;
194
continue;
195
}
196
if (server->ipaddr[0] == '\0') {
197
switch (res->ai_family) {
198
case AF_INET:
199
addr = (char *)&((struct sockaddr_in *)res->ai_addr)->sin_addr;
200
break;
201
case AF_INET6:
202
addr = (char *)&((struct sockaddr_in6 *)res->ai_addr)->sin6_addr;
203
break;
204
default:
205
cause = "ai_family";
206
save_errno = EAFNOSUPPORT;
207
close(sock);
208
errno = save_errno;
209
sock = -1;
210
continue;
211
}
212
if (inet_ntop(res->ai_family, addr, server->ipaddr,
213
sizeof(server->ipaddr)) == NULL) {
214
sudo_warnx("%s", U_("unable to get server IP addr"));
215
}
216
}
217
break; /* success */
218
}
219
freeaddrinfo(res0);
220
221
if (sock != -1) {
222
int flags = fcntl(sock, F_GETFL, 0);
223
if (flags == -1 || fcntl(sock, F_SETFL, flags | O_NONBLOCK) == -1) {
224
cause = "fcntl(O_NONBLOCK)";
225
save_errno = errno;
226
close(sock);
227
errno = save_errno;
228
sock = -1;
229
}
230
}
231
if (sock == -1)
232
sudo_warn("%s", cause);
233
234
debug_return_int(sock);
235
}
236
237
/*
238
* Get a buffer from the free list if possible, else allocate a new one.
239
*/
240
static struct connection_buffer *
241
get_free_buf(size_t len, struct client_closure *closure)
242
{
243
struct connection_buffer *buf;
244
debug_decl(get_free_buf, SUDO_DEBUG_UTIL);
245
246
buf = TAILQ_FIRST(&closure->free_bufs);
247
if (buf != NULL) {
248
TAILQ_REMOVE(&closure->free_bufs, buf, entries);
249
} else {
250
if ((buf = calloc(1, sizeof(*buf))) == NULL) {
251
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
252
debug_return_ptr(NULL);
253
}
254
}
255
256
if (len > buf->size) {
257
free(buf->data);
258
buf->size = sudo_pow2_roundup(len);
259
if (buf->size < len || (buf->data = malloc(buf->size)) == NULL) {
260
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
261
free(buf);
262
buf = NULL;
263
}
264
}
265
266
debug_return_ptr(buf);
267
}
268
269
/*
270
* Read the next I/O buffer as described by closure->timing.
271
*/
272
static bool
273
read_io_buf(struct client_closure *closure)
274
{
275
struct timing_closure *timing = &closure->timing;
276
const char *errstr = NULL;
277
size_t nread;
278
debug_decl(read_io_buf, SUDO_DEBUG_UTIL);
279
280
if (!closure->iolog_files[timing->event].enabled) {
281
errno = ENOENT;
282
sudo_warn("%s/%s", iolog_dir, iolog_fd_to_name(timing->event));
283
debug_return_bool(false);
284
}
285
286
/* Expand buf as needed. */
287
if (timing->u.nbytes > closure->bufsize) {
288
const size_t new_size = sudo_pow2_roundup(timing->u.nbytes);
289
if (new_size < timing->u.nbytes) {
290
/* overflow */
291
errno = ENOMEM;
292
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
293
timing->u.nbytes = 0;
294
debug_return_bool(false);
295
}
296
free(closure->buf);
297
if ((closure->buf = malloc(new_size)) == NULL) {
298
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
299
closure->bufsize = 0;
300
timing->u.nbytes = 0;
301
debug_return_bool(false);
302
}
303
closure->bufsize = new_size;
304
}
305
306
nread = (size_t)iolog_read(&closure->iolog_files[timing->event],
307
closure->buf, timing->u.nbytes, &errstr);
308
if (nread != timing->u.nbytes) {
309
if (nread != (size_t)-1)
310
errstr = strerror(EINVAL);
311
sudo_warnx(U_("unable to read %s/%s: %s"), iolog_dir,
312
iolog_fd_to_name(timing->event), errstr);
313
debug_return_bool(false);
314
}
315
debug_return_bool(true);
316
}
317
318
/*
319
* Format a ClientMessage and store the wire format message in buf.
320
* Returns true on success, false on failure.
321
*/
322
static bool
323
fmt_client_message(struct client_closure *closure, ClientMessage *msg)
324
{
325
struct connection_buffer *buf = NULL;
326
uint32_t msg_len;
327
bool ret = false;
328
size_t len;
329
debug_decl(fmt_client_message, SUDO_DEBUG_UTIL);
330
331
len = client_message__get_packed_size(msg);
332
if (len > MESSAGE_SIZE_MAX) {
333
sudo_warnx(U_("client message too large: %zu"), len);
334
goto done;
335
}
336
/* Wire message size is used for length encoding, precedes message. */
337
msg_len = htonl((uint32_t)len);
338
len += sizeof(msg_len);
339
340
if (!TAILQ_EMPTY(&closure->write_bufs)) {
341
buf = TAILQ_FIRST(&closure->write_bufs);
342
if (len > buf->size - buf->len) {
343
/* Too small. */
344
buf = NULL;
345
}
346
}
347
if (buf == NULL) {
348
if ((buf = get_free_buf(len, closure)) == NULL) {
349
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
350
goto done;
351
}
352
TAILQ_INSERT_TAIL(&closure->write_bufs, buf, entries);
353
}
354
355
memcpy(buf->data + buf->len, &msg_len, sizeof(msg_len));
356
client_message__pack(msg, buf->data + buf->len + sizeof(msg_len));
357
buf->len += len;
358
359
ret = true;
360
361
done:
362
debug_return_bool(ret);
363
}
364
365
static bool
366
fmt_client_hello(struct client_closure *closure)
367
{
368
ClientMessage client_msg = CLIENT_MESSAGE__INIT;
369
ClientHello hello_msg = CLIENT_HELLO__INIT;
370
bool ret = false;
371
debug_decl(fmt_client_hello, SUDO_DEBUG_UTIL);
372
373
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: sending ClientHello", __func__);
374
hello_msg.client_id = (char *)"Sudo Sendlog " PACKAGE_VERSION;
375
376
/* Schedule ClientMessage */
377
client_msg.u.hello_msg = &hello_msg;
378
client_msg.type_case = CLIENT_MESSAGE__TYPE_HELLO_MSG;
379
ret = fmt_client_message(closure, &client_msg);
380
if (ret) {
381
if (sudo_ev_add(closure->evbase, closure->read_ev, NULL, false) == -1)
382
ret = false;
383
if (sudo_ev_add(closure->evbase, closure->write_ev, NULL, false) == -1)
384
ret = false;
385
}
386
387
debug_return_bool(ret);
388
}
389
390
#if defined(HAVE_OPENSSL)
391
/* Wrapper for fmt_client_hello() called via tls_connect_cb() */
392
static bool
393
tls_start_fn(struct tls_client_closure *tls_client)
394
{
395
return fmt_client_hello(tls_client->parent_closure);
396
}
397
#endif /* HAVE_OPENSSL */
398
399
static void
400
free_info_messages(InfoMessage **info_msgs, size_t n_info_msgs)
401
{
402
debug_decl(free_info_messages, SUDO_DEBUG_UTIL);
403
404
if (info_msgs != NULL) {
405
while (n_info_msgs) {
406
if (info_msgs[--n_info_msgs]->value_case == INFO_MESSAGE__VALUE_STRLISTVAL) {
407
/* Only strlistval was dynamically allocated */
408
free(info_msgs[n_info_msgs]->u.strlistval->strings);
409
free(info_msgs[n_info_msgs]->u.strlistval);
410
}
411
free(info_msgs[n_info_msgs]);
412
}
413
free(info_msgs);
414
}
415
416
debug_return;
417
}
418
419
/*
420
* Convert a NULL-terminated string vector (argv, envp) to a
421
* StringList with an associated size.
422
* Performs a shallow copy of the strings (copies pointers).
423
*/
424
static InfoMessage__StringList *
425
vec_to_stringlist(char * const *vec)
426
{
427
InfoMessage__StringList *strlist;
428
size_t len;
429
debug_decl(vec_to_stringlist, SUDO_DEBUG_UTIL);
430
431
strlist = malloc(sizeof(*strlist));
432
if (strlist == NULL)
433
goto done;
434
info_message__string_list__init(strlist);
435
436
/* Convert vec into a StringList. */
437
for (len = 0; vec[len] != NULL; len++) {
438
continue;
439
}
440
strlist->strings = reallocarray(NULL, len, sizeof(char *));
441
if (strlist->strings == NULL) {
442
free(strlist);
443
strlist = NULL;
444
goto done;
445
}
446
strlist->n_strings = len;
447
for (len = 0; vec[len] != NULL; len++) {
448
strlist->strings[len] = vec[len];
449
}
450
451
done:
452
debug_return_ptr(strlist);
453
}
454
455
/*
456
* Split command + args separated by whitespace into a StringList.
457
* Returns a StringList containing command and args, reusing the contents
458
* of "command", which is modified.
459
*/
460
static InfoMessage__StringList *
461
command_to_stringlist(char *command)
462
{
463
InfoMessage__StringList *strlist;
464
char *cp;
465
size_t len;
466
debug_decl(command_to_stringlist, SUDO_DEBUG_UTIL);
467
468
strlist = malloc(sizeof(*strlist));
469
if (strlist == NULL)
470
debug_return_ptr(NULL);
471
info_message__string_list__init(strlist);
472
473
for (cp = command, len = 0;;) {
474
len++;
475
if ((cp = strchr(cp, ' ')) == NULL)
476
break;
477
cp++;
478
}
479
strlist->strings = reallocarray(NULL, len, sizeof(char *));
480
if (strlist->strings == NULL) {
481
free(strlist);
482
debug_return_ptr(NULL);
483
}
484
strlist->n_strings = len;
485
486
for (cp = command, len = 0;;) {
487
strlist->strings[len++] = cp;
488
if ((cp = strchr(cp, ' ')) == NULL)
489
break;
490
*cp++ = '\0';
491
}
492
493
debug_return_ptr(strlist);
494
}
495
496
/*
497
* Build runargv StringList using either argv or command in evlog.
498
* Truncated command in evlog after first space as a side effect.
499
*/
500
static InfoMessage__StringList *
501
fmt_runargv(const struct eventlog *evlog)
502
{
503
InfoMessage__StringList *runargv;
504
debug_decl(fmt_runargv, SUDO_DEBUG_UTIL);
505
506
/* We may have runargv from the log.json file. */
507
if (evlog->runargv != NULL && evlog->runargv[0] != NULL) {
508
/* Convert evlog->runargv into a StringList. */
509
runargv = vec_to_stringlist(evlog->runargv);
510
if (runargv != NULL) {
511
/* Make sure command doesn't include arguments. */
512
char *cp = strchr(evlog->command, ' ');
513
if (cp != NULL)
514
*cp = '\0';
515
}
516
} else {
517
/* No log.json file, split command into a StringList. */
518
runargv = command_to_stringlist(evlog->command);
519
}
520
521
debug_return_ptr(runargv);
522
}
523
524
/*
525
* Build runenv StringList from env in evlog, if present.
526
*/
527
static InfoMessage__StringList *
528
fmt_runenv(const struct eventlog *evlog)
529
{
530
debug_decl(fmt_runenv, SUDO_DEBUG_UTIL);
531
532
/* Only present in log.json. */
533
if (evlog->runenv == NULL || evlog->runenv[0] == NULL)
534
debug_return_ptr(NULL);
535
536
debug_return_ptr(vec_to_stringlist(evlog->runenv));
537
}
538
539
/*
540
* Build submitenv StringList from env in evlog, if present.
541
*/
542
static InfoMessage__StringList *
543
fmt_submitenv(const struct eventlog *evlog)
544
{
545
debug_decl(fmt_submitenv, SUDO_DEBUG_UTIL);
546
547
/* Only present in log.json. */
548
if (evlog->submitenv == NULL || evlog->submitenv[0] == NULL)
549
debug_return_ptr(NULL);
550
551
debug_return_ptr(vec_to_stringlist(evlog->submitenv));
552
}
553
554
static InfoMessage **
555
fmt_info_messages(const struct eventlog *evlog, char *hostname,
556
size_t *n_info_msgs)
557
{
558
InfoMessage__StringList *runargv = NULL;
559
InfoMessage__StringList *runenv = NULL;
560
InfoMessage__StringList *submitenv = NULL;
561
InfoMessage **info_msgs = NULL;
562
size_t info_msgs_size, n = 0;
563
debug_decl(fmt_info_messages, SUDO_DEBUG_UTIL);
564
565
runargv = fmt_runargv(evlog);
566
if (runargv == NULL)
567
goto oom;
568
569
/* runenv and submitenv are only present in log.json */
570
runenv = fmt_runenv(evlog);
571
submitenv = fmt_submitenv(evlog);
572
573
/* The sudo I/O log info file has limited info. */
574
info_msgs_size = 15;
575
info_msgs = calloc(info_msgs_size, sizeof(InfoMessage *));
576
if (info_msgs == NULL)
577
goto oom;
578
for (n = 0; n < info_msgs_size; n++) {
579
info_msgs[n] = malloc(sizeof(InfoMessage));
580
if (info_msgs[n] == NULL)
581
goto oom;
582
info_message__init(info_msgs[n]);
583
}
584
585
#define fill_str(_n, _v) do { \
586
info_msgs[n]->key = (char *)(_n); \
587
info_msgs[n]->u.strval = (_v); \
588
info_msgs[n]->value_case = INFO_MESSAGE__VALUE_STRVAL; \
589
n++; \
590
} while (0)
591
592
#define fill_strlist(_n, _v) do { \
593
info_msgs[n]->key = (char *)(_n); \
594
info_msgs[n]->u.strlistval = (_v); \
595
info_msgs[n]->value_case = INFO_MESSAGE__VALUE_STRLISTVAL; \
596
n++; \
597
} while (0)
598
599
#define fill_num(_n, _v) do { \
600
info_msgs[n]->key = (char *)(_n); \
601
info_msgs[n]->u.numval = (_v); \
602
info_msgs[n]->value_case = INFO_MESSAGE__VALUE_NUMVAL; \
603
n++; \
604
} while (0)
605
606
/*
607
* Fill in info_msgs. For legacy I/O log files, only command, runargv,
608
* runuser, submitcwd, submithost, submituser, ttyname may be present.
609
*/
610
n = 0;
611
fill_num("columns", evlog->columns);
612
fill_str("command", evlog->command);
613
fill_num("lines", evlog->lines);
614
fill_strlist("runargv", runargv);
615
runargv = NULL;
616
if (submitenv != NULL) {
617
fill_strlist("submitenv", submitenv);
618
submitenv = NULL;
619
}
620
if (runenv != NULL) {
621
fill_strlist("runenv", runenv);
622
runenv = NULL;
623
}
624
if (evlog->rungid != (gid_t)-1) {
625
fill_num("rungid", evlog->rungid);
626
}
627
if (evlog->rungroup != NULL) {
628
fill_str("rungroup", evlog->rungroup);
629
}
630
if (evlog->runuid != (uid_t)-1) {
631
fill_num("runuid", evlog->runuid);
632
}
633
fill_str("runuser", evlog->runuser);
634
if (evlog->source != NULL) {
635
fill_str("source", evlog->source);
636
}
637
fill_str("submitcwd", evlog->cwd);
638
fill_str("submithost", hostname);
639
fill_str("submituser", evlog->submituser);
640
fill_str("ttyname", evlog->ttyname);
641
642
/* Update n_info_msgs. */
643
*n_info_msgs = n;
644
645
/* Avoid leaking unused info_msg structs. */
646
while (n < info_msgs_size) {
647
free(info_msgs[n++]);
648
}
649
650
debug_return_ptr(info_msgs);
651
652
oom:
653
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
654
free_info_messages(info_msgs, n);
655
if (runargv != NULL) {
656
free(runargv->strings);
657
free(runargv);
658
}
659
if (runenv != NULL) {
660
free(runenv->strings);
661
free(runenv);
662
}
663
if (submitenv != NULL) {
664
free(submitenv->strings);
665
free(submitenv);
666
}
667
*n_info_msgs = 0;
668
debug_return_ptr(NULL);
669
}
670
671
/*
672
* Build and format a RejectMessage wrapped in a ClientMessage.
673
* Stores the wire format message in the closure's write buffer.
674
* Returns true on success, false on failure.
675
*/
676
static bool
677
fmt_reject_message(struct client_closure *closure)
678
{
679
ClientMessage client_msg = CLIENT_MESSAGE__INIT;
680
RejectMessage reject_msg = REJECT_MESSAGE__INIT;
681
TimeSpec ts = TIME_SPEC__INIT;
682
size_t n_info_msgs;
683
bool ret = false;
684
char *hostname;
685
debug_decl(fmt_reject_message, SUDO_DEBUG_UTIL);
686
687
/*
688
* Fill in RejectMessage and add it to ClientMessage.
689
*/
690
if ((hostname = sudo_gethostname()) == NULL) {
691
sudo_warn("gethostname");
692
debug_return_bool(false);
693
}
694
695
/* Sudo I/O logs only store start time in seconds. */
696
ts.tv_sec = (int64_t)closure->evlog->event_time.tv_sec;
697
ts.tv_nsec = (int32_t)closure->evlog->event_time.tv_nsec;
698
reject_msg.submit_time = &ts;
699
700
/* Why the command was rejected. */
701
reject_msg.reason = closure->reject_reason;
702
703
reject_msg.info_msgs = fmt_info_messages(closure->evlog, hostname,
704
&n_info_msgs);
705
if (reject_msg.info_msgs == NULL)
706
goto done;
707
708
/* Update n_info_msgs. */
709
reject_msg.n_info_msgs = n_info_msgs;
710
711
sudo_debug_printf(SUDO_DEBUG_INFO,
712
"%s: sending RejectMessage, array length %zu", __func__, n_info_msgs);
713
714
/* Schedule ClientMessage */
715
client_msg.u.reject_msg = &reject_msg;
716
client_msg.type_case = CLIENT_MESSAGE__TYPE_REJECT_MSG;
717
ret = fmt_client_message(closure, &client_msg);
718
if (ret) {
719
if (sudo_ev_add(closure->evbase, closure->write_ev, NULL, false) == -1)
720
ret = false;
721
}
722
723
done:
724
free_info_messages(reject_msg.info_msgs, n_info_msgs);
725
free(hostname);
726
727
debug_return_bool(ret);
728
}
729
730
/*
731
* Build and format an AcceptMessage wrapped in a ClientMessage.
732
* Stores the wire format message in the closure's write buffer.
733
* Returns true on success, false on failure.
734
*/
735
static bool
736
fmt_accept_message(struct client_closure *closure)
737
{
738
ClientMessage client_msg = CLIENT_MESSAGE__INIT;
739
AcceptMessage accept_msg = ACCEPT_MESSAGE__INIT;
740
TimeSpec ts = TIME_SPEC__INIT;
741
bool ret = false;
742
char *hostname;
743
debug_decl(fmt_accept_message, SUDO_DEBUG_UTIL);
744
745
/*
746
* Fill in AcceptMessage and add it to ClientMessage.
747
*/
748
if ((hostname = sudo_gethostname()) == NULL) {
749
sudo_warn("gethostname");
750
debug_return_bool(false);
751
}
752
ts.tv_sec = (int64_t)closure->evlog->event_time.tv_sec;
753
ts.tv_nsec = (int32_t)closure->evlog->event_time.tv_nsec;
754
accept_msg.submit_time = &ts;
755
756
/* Client will send IoBuffer messages. */
757
accept_msg.expect_iobufs = !closure->accept_only;
758
759
accept_msg.info_msgs = fmt_info_messages(closure->evlog, hostname,
760
&accept_msg.n_info_msgs);
761
if (accept_msg.info_msgs == NULL)
762
goto done;
763
764
sudo_debug_printf(SUDO_DEBUG_INFO,
765
"%s: sending AcceptMessage, array length %zu", __func__,
766
accept_msg.n_info_msgs);
767
768
/* Schedule ClientMessage */
769
client_msg.u.accept_msg = &accept_msg;
770
client_msg.type_case = CLIENT_MESSAGE__TYPE_ACCEPT_MSG;
771
ret = fmt_client_message(closure, &client_msg);
772
if (ret) {
773
if (sudo_ev_add(closure->evbase, closure->write_ev, NULL, false) == -1)
774
ret = false;
775
}
776
777
done:
778
free_info_messages(accept_msg.info_msgs, accept_msg.n_info_msgs);
779
free(hostname);
780
781
debug_return_bool(ret);
782
}
783
784
/*
785
* Build and format a RestartMessage wrapped in a ClientMessage.
786
* Stores the wire format message in the closure's write buffer.
787
* Returns true on success, false on failure.
788
*/
789
static bool
790
fmt_restart_message(struct client_closure *closure)
791
{
792
ClientMessage client_msg = CLIENT_MESSAGE__INIT;
793
RestartMessage restart_msg = RESTART_MESSAGE__INIT;
794
TimeSpec ts = TIME_SPEC__INIT;
795
bool ret = false;
796
debug_decl(fmt_restart_message, SUDO_DEBUG_UTIL);
797
798
sudo_debug_printf(SUDO_DEBUG_INFO,
799
"%s: sending RestartMessage, [%lld, %ld]", __func__,
800
(long long)closure->restart.tv_sec, closure->restart.tv_nsec);
801
802
ts.tv_sec = (int64_t)closure->restart.tv_sec;
803
ts.tv_nsec = (int32_t)closure->restart.tv_nsec;
804
restart_msg.resume_point = &ts;
805
restart_msg.log_id = (char *)closure->iolog_id;
806
807
/* Schedule ClientMessage */
808
client_msg.u.restart_msg = &restart_msg;
809
client_msg.type_case = CLIENT_MESSAGE__TYPE_RESTART_MSG;
810
ret = fmt_client_message(closure, &client_msg);
811
if (ret) {
812
if (sudo_ev_add(closure->evbase, closure->write_ev, NULL, false) == -1)
813
ret = false;
814
}
815
816
debug_return_bool(ret);
817
}
818
819
/*
820
* Build and format an ExitMessage wrapped in a ClientMessage.
821
* Stores the wire format message in the closure's write buffer list.
822
* Returns true on success, false on failure.
823
*/
824
static bool
825
fmt_exit_message(struct client_closure *closure)
826
{
827
ClientMessage client_msg = CLIENT_MESSAGE__INIT;
828
ExitMessage exit_msg = EXIT_MESSAGE__INIT;
829
TimeSpec run_time = TIME_SPEC__INIT;
830
struct eventlog *evlog = closure->evlog;
831
bool ret = false;
832
debug_decl(fmt_exit_message, SUDO_DEBUG_UTIL);
833
834
if (evlog->exit_value != -1)
835
exit_msg.exit_value = evlog->exit_value;
836
if (sudo_timespecisset(&evlog->run_time)) {
837
run_time.tv_sec = (int64_t)evlog->run_time.tv_sec;
838
run_time.tv_nsec = (int32_t)evlog->run_time.tv_nsec;
839
exit_msg.run_time = &run_time;
840
}
841
if (evlog->signal_name != NULL) {
842
exit_msg.signal = evlog->signal_name;
843
exit_msg.dumped_core = evlog->dumped_core;
844
}
845
846
if (evlog->signal_name != NULL) {
847
sudo_debug_printf(SUDO_DEBUG_INFO,
848
"%s: sending ExitMessage, signal %s, run_time [%lld, %ld]",
849
__func__, evlog->signal_name, (long long)evlog->run_time.tv_sec,
850
evlog->run_time.tv_nsec);
851
} else {
852
sudo_debug_printf(SUDO_DEBUG_INFO,
853
"%s: sending ExitMessage, exit value %d, run_time [%lld, %ld]",
854
__func__, evlog->exit_value, (long long)evlog->run_time.tv_sec,
855
evlog->run_time.tv_nsec);
856
}
857
858
/* Send ClientMessage */
859
client_msg.u.exit_msg = &exit_msg;
860
client_msg.type_case = CLIENT_MESSAGE__TYPE_EXIT_MSG;
861
if (!fmt_client_message(closure, &client_msg))
862
goto done;
863
864
ret = true;
865
866
done:
867
debug_return_bool(ret);
868
}
869
870
/*
871
* Build and format an IoBuffer wrapped in a ClientMessage.
872
* Stores the wire format message in the closure's write buffer list.
873
* Returns true on success, false on failure.
874
*/
875
static bool
876
fmt_io_buf(int type, struct client_closure *closure)
877
{
878
ClientMessage client_msg = CLIENT_MESSAGE__INIT;
879
IoBuffer iobuf_msg = IO_BUFFER__INIT;
880
TimeSpec delay = TIME_SPEC__INIT;
881
bool ret = false;
882
debug_decl(fmt_io_buf, SUDO_DEBUG_UTIL);
883
884
if (!read_io_buf(closure))
885
goto done;
886
887
/* Fill in IoBuffer. */
888
/* TODO: split buffer if it is too large */
889
delay.tv_sec = (int64_t)closure->timing.delay.tv_sec;
890
delay.tv_nsec = (int32_t)closure->timing.delay.tv_nsec;
891
iobuf_msg.delay = &delay;
892
iobuf_msg.data.data = (void *)closure->buf;
893
iobuf_msg.data.len = closure->timing.u.nbytes;
894
895
sudo_debug_printf(SUDO_DEBUG_INFO,
896
"%s: sending IoBuffer length %zu, type %d, size %zu", __func__,
897
iobuf_msg.data.len, type, io_buffer__get_packed_size(&iobuf_msg));
898
899
/* Send ClientMessage, it doesn't matter which IoBuffer we set. */
900
client_msg.u.ttyout_buf = &iobuf_msg;
901
client_msg.type_case = type;
902
if (!fmt_client_message(closure, &client_msg))
903
goto done;
904
905
ret = true;
906
907
done:
908
debug_return_bool(ret);
909
}
910
911
/*
912
* Build and format a ChangeWindowSize message wrapped in a ClientMessage.
913
* Stores the wire format message in the closure's write buffer list.
914
* Returns true on success, false on failure.
915
*/
916
static bool
917
fmt_winsize(struct client_closure *closure)
918
{
919
ClientMessage client_msg = CLIENT_MESSAGE__INIT;
920
ChangeWindowSize winsize_msg = CHANGE_WINDOW_SIZE__INIT;
921
TimeSpec delay = TIME_SPEC__INIT;
922
struct timing_closure *timing = &closure->timing;
923
bool ret = false;
924
debug_decl(fmt_winsize, SUDO_DEBUG_UTIL);
925
926
/* Fill in ChangeWindowSize message. */
927
delay.tv_sec = (int64_t)timing->delay.tv_sec;
928
delay.tv_nsec = (int32_t)timing->delay.tv_nsec;
929
winsize_msg.delay = &delay;
930
winsize_msg.rows = timing->u.winsize.lines;
931
winsize_msg.cols = timing->u.winsize.cols;
932
933
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: sending ChangeWindowSize, %dx%d",
934
__func__, winsize_msg.rows, winsize_msg.cols);
935
936
/* Send ClientMessage */
937
client_msg.u.winsize_event = &winsize_msg;
938
client_msg.type_case = CLIENT_MESSAGE__TYPE_WINSIZE_EVENT;
939
if (!fmt_client_message(closure, &client_msg))
940
goto done;
941
942
ret = true;
943
944
done:
945
debug_return_bool(ret);
946
}
947
948
/*
949
* Build and format a CommandSuspend message wrapped in a ClientMessage.
950
* Stores the wire format message in the closure's write buffer list.
951
* Returns true on success, false on failure.
952
*/
953
static bool
954
fmt_suspend(struct client_closure *closure)
955
{
956
ClientMessage client_msg = CLIENT_MESSAGE__INIT;
957
CommandSuspend suspend_msg = COMMAND_SUSPEND__INIT;
958
TimeSpec delay = TIME_SPEC__INIT;
959
struct timing_closure *timing = &closure->timing;
960
bool ret = false;
961
debug_decl(fmt_suspend, SUDO_DEBUG_UTIL);
962
963
/* Fill in CommandSuspend message. */
964
delay.tv_sec = (int64_t)timing->delay.tv_sec;
965
delay.tv_nsec = (int32_t)timing->delay.tv_nsec;
966
suspend_msg.delay = &delay;
967
if (sig2str(timing->u.signo, closure->buf) == -1)
968
goto done;
969
suspend_msg.signal = closure->buf;
970
971
sudo_debug_printf(SUDO_DEBUG_INFO,
972
"%s: sending CommandSuspend, SIG%s", __func__, suspend_msg.signal);
973
974
/* Send ClientMessage */
975
client_msg.u.suspend_event = &suspend_msg;
976
client_msg.type_case = CLIENT_MESSAGE__TYPE_SUSPEND_EVENT;
977
if (!fmt_client_message(closure, &client_msg))
978
goto done;
979
980
ret = true;
981
982
done:
983
debug_return_bool(ret);
984
}
985
986
/*
987
* Read the next entry for the I/O log timing file and format a ClientMessage.
988
* Stores the wire format message in the closure's write buffer list.
989
* Returns true on success, false on failure.
990
*/
991
static bool
992
fmt_next_iolog(struct client_closure *closure)
993
{
994
struct timing_closure *timing = &closure->timing;
995
bool ret = false;
996
debug_decl(fmt_next_iolog, SUDO_DEBUG_UTIL);
997
998
for (;;) {
999
const int timing_status = iolog_read_timing_record(
1000
&closure->iolog_files[IOFD_TIMING], timing);
1001
switch (timing_status) {
1002
case 0:
1003
/* OK */
1004
break;
1005
case 1:
1006
/* no more IO buffers */
1007
closure->state = SEND_EXIT;
1008
debug_return_bool(fmt_exit_message(closure));
1009
case -1:
1010
default:
1011
debug_return_bool(false);
1012
}
1013
1014
/* Track elapsed time for comparison with commit points. */
1015
sudo_timespecadd(&closure->elapsed, &timing->delay, &closure->elapsed);
1016
1017
/* If there is a stopping point, make sure we haven't reached it. */
1018
if (sudo_timespecisset(&closure->stop_after)) {
1019
if (sudo_timespeccmp(&closure->elapsed, &closure->stop_after, >)) {
1020
/* Reached limit, force premature end. */
1021
sudo_timespecsub(&closure->elapsed, &timing->delay,
1022
&closure->elapsed);
1023
debug_return_bool(false);
1024
}
1025
}
1026
1027
/* If we have a restart point, ignore records until we hit it. */
1028
if (sudo_timespecisset(&closure->restart)) {
1029
if (sudo_timespeccmp(&closure->restart, &closure->elapsed, >=))
1030
continue;
1031
sudo_timespecclear(&closure->restart); /* caught up */
1032
}
1033
1034
switch (timing->event) {
1035
case IO_EVENT_STDIN:
1036
ret = fmt_io_buf(CLIENT_MESSAGE__TYPE_STDIN_BUF, closure);
1037
break;
1038
case IO_EVENT_STDOUT:
1039
ret = fmt_io_buf(CLIENT_MESSAGE__TYPE_STDOUT_BUF, closure);
1040
break;
1041
case IO_EVENT_STDERR:
1042
ret = fmt_io_buf(CLIENT_MESSAGE__TYPE_STDERR_BUF, closure);
1043
break;
1044
case IO_EVENT_TTYIN:
1045
ret = fmt_io_buf(CLIENT_MESSAGE__TYPE_TTYIN_BUF, closure);
1046
break;
1047
case IO_EVENT_TTYOUT:
1048
ret = fmt_io_buf(CLIENT_MESSAGE__TYPE_TTYOUT_BUF, closure);
1049
break;
1050
case IO_EVENT_WINSIZE:
1051
ret = fmt_winsize(closure);
1052
break;
1053
case IO_EVENT_SUSPEND:
1054
ret = fmt_suspend(closure);
1055
break;
1056
default:
1057
sudo_warnx(U_("unexpected I/O event %d"), timing->event);
1058
break;
1059
}
1060
1061
/* Keep filling write buffer as long as we only have one of them. */
1062
if (!ret)
1063
break;
1064
if (TAILQ_NEXT(TAILQ_FIRST(&closure->write_bufs), entries) != NULL)
1065
break;
1066
}
1067
1068
debug_return_bool(ret);
1069
}
1070
1071
/*
1072
* Additional work to do after a ClientMessage was sent to the server.
1073
* Advances state and formats the next ClientMessage (if any).
1074
*/
1075
static bool
1076
client_message_completion(struct client_closure *closure)
1077
{
1078
debug_decl(client_message_completion, SUDO_DEBUG_UTIL);
1079
1080
switch (closure->state) {
1081
case RECV_HELLO:
1082
/* Wait for ServerHello, nothing to write until then. */
1083
sudo_ev_del(closure->evbase, closure->write_ev);
1084
break;
1085
case SEND_ACCEPT:
1086
if (closure->accept_only) {
1087
closure->state = SEND_EXIT;
1088
debug_return_bool(fmt_exit_message(closure));
1089
}
1090
FALLTHROUGH;
1091
case SEND_RESTART:
1092
closure->state = SEND_IO;
1093
FALLTHROUGH;
1094
case SEND_IO:
1095
/* fmt_next_iolog() will advance state on EOF. */
1096
if (!fmt_next_iolog(closure))
1097
debug_return_bool(false);
1098
break;
1099
case SEND_REJECT:
1100
/* Done writing, wait for server to close connection. */
1101
sudo_ev_del(closure->evbase, closure->write_ev);
1102
closure->state = FINISHED;
1103
break;
1104
case SEND_EXIT:
1105
/* Done writing, wait for final commit point if sending I/O. */
1106
sudo_ev_del(closure->evbase, closure->write_ev);
1107
closure->state = closure->accept_only ? FINISHED : CLOSING;
1108
break;
1109
default:
1110
sudo_warnx(U_("%s: unexpected state %d"), __func__, closure->state);
1111
debug_return_bool(false);
1112
}
1113
debug_return_bool(true);
1114
}
1115
1116
/*
1117
* Respond to a ServerHello message from the server.
1118
* Returns true on success, false on error.
1119
*/
1120
static bool
1121
handle_server_hello(const ServerHello *msg, struct client_closure *closure)
1122
{
1123
size_t n;
1124
debug_decl(handle_server_hello, SUDO_DEBUG_UTIL);
1125
1126
if (closure->state != RECV_HELLO) {
1127
sudo_warnx(U_("%s: unexpected state %d"), __func__, closure->state);
1128
debug_return_bool(false);
1129
}
1130
1131
/* Check that ServerHello is valid. */
1132
if (msg == NULL || msg->server_id == NULL || msg->server_id[0] == '\0') {
1133
sudo_warnx("%s", U_("invalid ServerHello"));
1134
debug_return_bool(false);
1135
}
1136
1137
if (!testrun) {
1138
printf("Server ID: %s\n", msg->server_id);
1139
/* TODO: handle redirect */
1140
if (msg->redirect != NULL && msg->redirect[0] != '\0')
1141
printf("Redirect: %s\n", msg->redirect);
1142
for (n = 0; n < msg->n_servers; n++) {
1143
printf("Server %u: %s\n", (unsigned int)n + 1, msg->servers[n]);
1144
}
1145
}
1146
1147
debug_return_bool(true);
1148
}
1149
1150
/*
1151
* Respond to a commit_point ServerMessage from the server.
1152
* Returns true on success, false on error.
1153
*/
1154
static bool
1155
handle_commit_point(const TimeSpec *commit_point,
1156
struct client_closure *closure)
1157
{
1158
debug_decl(handle_commit_point, SUDO_DEBUG_UTIL);
1159
1160
/* Only valid after we have sent an IO buffer. */
1161
if (closure->state < SEND_IO) {
1162
sudo_warnx(U_("%s: unexpected state %d"), __func__, closure->state);
1163
debug_return_bool(false);
1164
}
1165
1166
/* Check that ServerMessage's commit_point is valid. */
1167
if (commit_point == NULL) {
1168
sudo_warnx(U_("%s: invalid ServerMessage, missing commit_point"),
1169
server_info.name);
1170
debug_return_bool(false);
1171
}
1172
1173
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: commit point: [%lld, %d]",
1174
__func__, (long long)commit_point->tv_sec, commit_point->tv_nsec);
1175
closure->committed.tv_sec = (time_t)commit_point->tv_sec;
1176
closure->committed.tv_nsec = (long)commit_point->tv_nsec;
1177
1178
debug_return_bool(true);
1179
}
1180
1181
/*
1182
* Respond to a log_id ServerMessage from the relay.
1183
* Always returns true.
1184
*/
1185
static bool
1186
handle_log_id(const char *id, struct client_closure *closure)
1187
{
1188
debug_decl(handle_log_id, SUDO_DEBUG_UTIL);
1189
1190
if (!testrun)
1191
printf("Remote log ID: %s\n", id);
1192
1193
debug_return_bool(true);
1194
}
1195
1196
/*
1197
* Respond to a ServerError message from the server.
1198
* Always returns false.
1199
*/
1200
static bool
1201
handle_server_error(const char *errmsg, struct client_closure *closure)
1202
{
1203
debug_decl(handle_server_error, SUDO_DEBUG_UTIL);
1204
1205
sudo_warnx(U_("error message received from server: %s"), errmsg);
1206
debug_return_bool(false);
1207
}
1208
1209
/*
1210
* Respond to a ServerAbort message from the server.
1211
* Always returns false.
1212
*/
1213
static bool
1214
handle_server_abort(const char *errmsg, struct client_closure *closure)
1215
{
1216
debug_decl(handle_server_abort, SUDO_DEBUG_UTIL);
1217
1218
sudo_warnx(U_("abort message received from server: %s"), errmsg);
1219
debug_return_bool(false);
1220
}
1221
1222
/*
1223
* Respond to a ServerMessage from the server.
1224
* Returns true on success, false on error.
1225
*/
1226
static bool
1227
handle_server_message(const uint8_t *buf, size_t len,
1228
struct client_closure *closure)
1229
{
1230
ServerMessage *msg;
1231
bool ret = false;
1232
debug_decl(handle_server_message, SUDO_DEBUG_UTIL);
1233
1234
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: unpacking ServerMessage", __func__);
1235
msg = server_message__unpack(NULL, len, buf);
1236
if (msg == NULL) {
1237
sudo_warnx(U_("unable to unpack %s size %zu"), "ServerMessage", len);
1238
debug_return_bool(false);
1239
}
1240
1241
switch (msg->type_case) {
1242
case SERVER_MESSAGE__TYPE_HELLO:
1243
if ((ret = handle_server_hello(msg->u.hello, closure))) {
1244
if (sudo_timespecisset(&closure->restart)) {
1245
closure->state = SEND_RESTART;
1246
ret = fmt_restart_message(closure);
1247
} else if (closure->reject_reason != NULL) {
1248
closure->state = SEND_REJECT;
1249
ret = fmt_reject_message(closure);
1250
} else {
1251
closure->state = SEND_ACCEPT;
1252
ret = fmt_accept_message(closure);
1253
}
1254
}
1255
break;
1256
case SERVER_MESSAGE__TYPE_COMMIT_POINT:
1257
ret = handle_commit_point(msg->u.commit_point, closure);
1258
if (sudo_timespeccmp(&closure->elapsed, &closure->committed, ==)) {
1259
sudo_ev_del(closure->evbase, closure->read_ev);
1260
closure->state = FINISHED;
1261
if (++finished_transmissions == nr_of_conns)
1262
sudo_ev_loopexit(closure->evbase);
1263
}
1264
break;
1265
case SERVER_MESSAGE__TYPE_LOG_ID:
1266
ret = handle_log_id(msg->u.log_id, closure);
1267
break;
1268
case SERVER_MESSAGE__TYPE_ERROR:
1269
ret = handle_server_error(msg->u.error, closure);
1270
closure->state = ERROR;
1271
break;
1272
case SERVER_MESSAGE__TYPE_ABORT:
1273
ret = handle_server_abort(msg->u.abort, closure);
1274
closure->state = ERROR;
1275
break;
1276
default:
1277
sudo_warnx(U_("%s: unexpected type_case value %d"),
1278
__func__, msg->type_case);
1279
break;
1280
}
1281
1282
server_message__free_unpacked(msg, NULL);
1283
debug_return_bool(ret);
1284
}
1285
1286
/*
1287
* Read and unpack a ServerMessage (read callback).
1288
*/
1289
static void
1290
server_msg_cb(int fd, int what, void *v)
1291
{
1292
struct client_closure *closure = v;
1293
struct connection_buffer *buf = &closure->read_buf;
1294
size_t nread;
1295
uint32_t msg_len;
1296
debug_decl(server_msg_cb, SUDO_DEBUG_UTIL);
1297
1298
/* For TLS we may need to read as part of SSL_write_ex(). */
1299
if (closure->write_instead_of_read) {
1300
closure->write_instead_of_read = false;
1301
client_msg_cb(fd, what, v);
1302
debug_return;
1303
}
1304
1305
if (what == SUDO_EV_TIMEOUT) {
1306
sudo_warnx("%s", U_("timeout reading from server"));
1307
goto bad;
1308
}
1309
1310
#if defined(HAVE_OPENSSL)
1311
if (cert != NULL) {
1312
SSL *ssl = closure->tls_client.ssl;
1313
int result;
1314
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: reading ServerMessage (TLS)", __func__);
1315
result = SSL_read_ex(ssl, buf->data + buf->len, buf->size - buf->len,
1316
&nread);
1317
if (result <= 0) {
1318
unsigned long errcode;
1319
const char *errstr;
1320
1321
switch (SSL_get_error(ssl, result)) {
1322
case SSL_ERROR_ZERO_RETURN:
1323
/* ssl connection shutdown cleanly */
1324
nread = 0;
1325
break;
1326
case SSL_ERROR_WANT_READ:
1327
/* ssl wants to read more, read event is always active */
1328
sudo_debug_printf(SUDO_DEBUG_NOTICE|SUDO_DEBUG_LINENO,
1329
"SSL_read_ex returns SSL_ERROR_WANT_READ");
1330
debug_return;
1331
case SSL_ERROR_WANT_WRITE:
1332
/* ssl wants to write, schedule a write if not pending */
1333
sudo_debug_printf(SUDO_DEBUG_NOTICE|SUDO_DEBUG_LINENO,
1334
"SSL_read_ex returns SSL_ERROR_WANT_WRITE");
1335
if (!sudo_ev_pending(closure->write_ev, SUDO_EV_WRITE, NULL)) {
1336
/* Enable a temporary write event. */
1337
if (sudo_ev_add(closure->evbase, closure->write_ev, NULL, false) == -1) {
1338
sudo_warnx("%s", U_("unable to add event to queue"));
1339
goto bad;
1340
}
1341
closure->temporary_write_event = true;
1342
}
1343
/* Redirect write event to finish SSL_read_ex() */
1344
closure->read_instead_of_write = true;
1345
debug_return;
1346
case SSL_ERROR_SSL:
1347
/*
1348
* For TLS 1.3, if the cert verify function on the server
1349
* returns an error, OpenSSL will send an internal error
1350
* alert when we read ServerHello. Convert to a more useful
1351
* message and hope that no actual internal error occurs.
1352
*/
1353
errcode = ERR_get_error();
1354
#if !defined(HAVE_WOLFSSL)
1355
if (closure->state == RECV_HELLO &&
1356
ERR_GET_REASON(errcode) == SSL_R_TLSV1_ALERT_INTERNAL_ERROR) {
1357
errstr = U_("host name does not match certificate");
1358
} else
1359
#endif
1360
{
1361
errstr = ERR_reason_error_string(errcode);
1362
}
1363
sudo_warnx("%s", errstr ? errstr : strerror(errno));
1364
goto bad;
1365
case SSL_ERROR_SYSCALL:
1366
sudo_warn("SSL_read_ex");
1367
goto bad;
1368
default:
1369
errstr = ERR_reason_error_string(ERR_get_error());
1370
sudo_warnx("SSL_read_ex: %s",
1371
errstr ? errstr : strerror(errno));
1372
goto bad;
1373
}
1374
}
1375
} else
1376
#endif
1377
{
1378
ssize_t n;
1379
1380
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: reading ServerMessage", __func__);
1381
n = read(fd, buf->data + buf->len, buf->size - buf->len);
1382
if (n < 0) {
1383
if (errno == EAGAIN || errno == EINTR)
1384
debug_return;
1385
sudo_warn("read");
1386
goto bad;
1387
}
1388
nread = (size_t)n;
1389
}
1390
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: received %zd bytes from server",
1391
__func__, nread);
1392
if (nread == 0) {
1393
if (closure->state != FINISHED)
1394
sudo_warnx("%s", U_("premature EOF"));
1395
goto bad;
1396
}
1397
if (nread > SIZE_MAX - buf->len) {
1398
sudo_warnx(U_("internal error, %s overflow"), __func__);
1399
goto bad;
1400
}
1401
buf->len += nread;
1402
1403
while (buf->len - buf->off >= sizeof(msg_len)) {
1404
/* Read wire message size (uint32_t in network byte order). */
1405
memcpy(&msg_len, buf->data + buf->off, sizeof(msg_len));
1406
msg_len = ntohl(msg_len);
1407
1408
if (msg_len > MESSAGE_SIZE_MAX) {
1409
sudo_warnx(U_("server message too large: %u"), msg_len);
1410
goto bad;
1411
}
1412
1413
if (msg_len + sizeof(msg_len) > buf->len - buf->off) {
1414
/* Incomplete message, we'll read the rest next time. */
1415
if (!expand_buf(buf, msg_len + sizeof(msg_len)))
1416
goto bad;
1417
debug_return;
1418
}
1419
1420
/* Parse ServerMessage, could be zero bytes. */
1421
sudo_debug_printf(SUDO_DEBUG_INFO,
1422
"%s: parsing ServerMessage, size %u", __func__, msg_len);
1423
buf->off += sizeof(msg_len);
1424
if (!handle_server_message(buf->data + buf->off, msg_len, closure))
1425
goto bad;
1426
buf->off += msg_len;
1427
}
1428
if (buf->len != buf->off) {
1429
memmove(buf->data, buf->data + buf->off, buf->len - buf->off);
1430
}
1431
buf->len -= buf->off;
1432
buf->off = 0;
1433
debug_return;
1434
bad:
1435
sudo_ev_del(closure->evbase, closure->read_ev);
1436
debug_return;
1437
}
1438
1439
/*
1440
* Send a ClientMessage to the server (write callback).
1441
*/
1442
static void
1443
client_msg_cb(int fd, int what, void *v)
1444
{
1445
struct client_closure *closure = v;
1446
struct connection_buffer *buf;
1447
size_t nwritten;
1448
debug_decl(client_msg_cb, SUDO_DEBUG_UTIL);
1449
1450
if ((buf = TAILQ_FIRST(&closure->write_bufs)) == NULL) {
1451
sudo_warnx(U_("missing write buffer for client %s"), "localhost");
1452
goto bad;
1453
}
1454
1455
/* For TLS we may need to write as part of SSL_read_ex(). */
1456
if (closure->read_instead_of_write) {
1457
closure->read_instead_of_write = false;
1458
/* Delete write event if it was only due to SSL_read_ex(). */
1459
if (closure->temporary_write_event) {
1460
closure->temporary_write_event = false;
1461
sudo_ev_del(closure->evbase, closure->write_ev);
1462
}
1463
server_msg_cb(fd, what, v);
1464
debug_return;
1465
}
1466
1467
if (what == SUDO_EV_TIMEOUT) {
1468
sudo_warnx("%s", U_("timeout writing to server"));
1469
goto bad;
1470
}
1471
1472
sudo_debug_printf(SUDO_DEBUG_INFO,
1473
"%s: sending %zu bytes to server", __func__, buf->len - buf->off);
1474
1475
#if defined(HAVE_OPENSSL)
1476
if (cert != NULL) {
1477
SSL *ssl = closure->tls_client.ssl;
1478
const int result = SSL_write_ex(ssl, buf->data + buf->off,
1479
buf->len - buf->off, &nwritten);
1480
if (result <= 0) {
1481
const char *errstr;
1482
1483
switch (SSL_get_error(ssl, result)) {
1484
case SSL_ERROR_ZERO_RETURN:
1485
/* ssl connection shutdown */
1486
goto bad;
1487
case SSL_ERROR_WANT_READ:
1488
/* ssl wants to read, read event always active */
1489
sudo_debug_printf(SUDO_DEBUG_NOTICE|SUDO_DEBUG_LINENO,
1490
"SSL_write_ex returns SSL_ERROR_WANT_READ");
1491
/* Redirect read event to finish SSL_write_ex() */
1492
closure->write_instead_of_read = true;
1493
debug_return;
1494
case SSL_ERROR_WANT_WRITE:
1495
/* ssl wants to write more, write event remains active */
1496
sudo_debug_printf(SUDO_DEBUG_NOTICE|SUDO_DEBUG_LINENO,
1497
"SSL_write_ex returns SSL_ERROR_WANT_WRITE");
1498
debug_return;
1499
case SSL_ERROR_SYSCALL:
1500
sudo_warn("SSL_write_ex");
1501
goto bad;
1502
default:
1503
errstr = ERR_reason_error_string(ERR_get_error());
1504
sudo_warnx("SSL_write_ex: %s",
1505
errstr ? errstr : strerror(errno));
1506
goto bad;
1507
}
1508
}
1509
} else
1510
#endif
1511
{
1512
const ssize_t n = write(fd, buf->data + buf->off, buf->len - buf->off);
1513
if (n < 0) {
1514
if (errno == EAGAIN || errno == EINTR)
1515
debug_return;
1516
sudo_warn("write");
1517
goto bad;
1518
}
1519
nwritten = (size_t)n;
1520
}
1521
if (nwritten > SIZE_MAX - buf->off) {
1522
sudo_warnx(U_("internal error, %s overflow"), __func__);
1523
goto bad;
1524
}
1525
buf->off += nwritten;
1526
1527
if (buf->off == buf->len) {
1528
/* sent entire message */
1529
sudo_debug_printf(SUDO_DEBUG_INFO,
1530
"%s: finished sending %zu bytes to server", __func__, buf->len);
1531
buf->off = 0;
1532
buf->len = 0;
1533
TAILQ_REMOVE(&closure->write_bufs, buf, entries);
1534
TAILQ_INSERT_TAIL(&closure->free_bufs, buf, entries);
1535
if (TAILQ_EMPTY(&closure->write_bufs)) {
1536
/* Write queue empty, check state. */
1537
if (!client_message_completion(closure))
1538
goto bad;
1539
}
1540
}
1541
debug_return;
1542
1543
bad:
1544
sudo_ev_del(closure->evbase, closure->read_ev);
1545
sudo_ev_del(closure->evbase, closure->write_ev);
1546
debug_return;
1547
}
1548
1549
/*
1550
* Parse a timespec on the command line of the form
1551
* seconds[,nanoseconds]
1552
*/
1553
static bool
1554
parse_timespec(struct timespec *ts, char *strval)
1555
{
1556
const char *errstr;
1557
char *nsecstr;
1558
debug_decl(parse_timespec, SUDO_DEBUG_UTIL);
1559
1560
if ((nsecstr = strchr(strval, ',')) != NULL)
1561
*nsecstr++ = '\0';
1562
1563
ts->tv_nsec = 0;
1564
ts->tv_sec = (time_t)sudo_strtonum(strval, 0, TIME_T_MAX, &errstr);
1565
if (errstr != NULL) {
1566
sudo_warnx(U_("%s: %s"), strval, U_(errstr));
1567
debug_return_bool(false);
1568
}
1569
1570
if (nsecstr != NULL) {
1571
ts->tv_nsec = (long)sudo_strtonum(nsecstr, 0, LONG_MAX, &errstr);
1572
if (errstr != NULL) {
1573
sudo_warnx(U_("%s: %s"), nsecstr, U_(errstr));
1574
debug_return_bool(false);
1575
}
1576
}
1577
1578
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: parsed timespec [%lld, %ld]",
1579
__func__, (long long)ts->tv_sec, ts->tv_nsec);
1580
debug_return_bool(true);
1581
}
1582
1583
/*
1584
* Free client closure contents.
1585
*/
1586
static void
1587
client_closure_free(struct client_closure *closure)
1588
{
1589
struct connection_buffer *buf;
1590
debug_decl(connection_closure_free, SUDO_DEBUG_UTIL);
1591
1592
if (closure != NULL) {
1593
TAILQ_REMOVE(&connections, closure, entries);
1594
#if defined(HAVE_OPENSSL)
1595
if (closure->tls_client.ssl != NULL) {
1596
if (SSL_shutdown(closure->tls_client.ssl) == 0)
1597
SSL_shutdown(closure->tls_client.ssl);
1598
SSL_free(closure->tls_client.ssl);
1599
}
1600
sudo_ev_free(closure->tls_client.tls_connect_ev);
1601
#endif
1602
sudo_ev_free(closure->read_ev);
1603
sudo_ev_free(closure->write_ev);
1604
free(closure->read_buf.data);
1605
free(closure->buf);
1606
while ((buf = TAILQ_FIRST(&closure->write_bufs)) != NULL) {
1607
sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
1608
"discarding write buffer %p, len %zu", buf, buf->len - buf->off);
1609
TAILQ_REMOVE(&closure->write_bufs, buf, entries);
1610
free(buf->data);
1611
free(buf);
1612
}
1613
while ((buf = TAILQ_FIRST(&closure->free_bufs)) != NULL) {
1614
TAILQ_REMOVE(&closure->free_bufs, buf, entries);
1615
free(buf->data);
1616
free(buf);
1617
}
1618
shutdown(closure->sock, SHUT_RDWR);
1619
close(closure->sock);
1620
free(closure);
1621
}
1622
1623
debug_return;
1624
}
1625
1626
/*
1627
* Initialize a new client closure
1628
*/
1629
static struct client_closure *
1630
client_closure_alloc(int sock, struct sudo_event_base *base,
1631
struct timespec *restart, struct timespec *stop_after, const char *iolog_id,
1632
char *reject_reason, bool accept_only, struct eventlog *evlog)
1633
{
1634
struct connection_buffer *buf;
1635
struct client_closure *closure;
1636
debug_decl(client_closure_alloc, SUDO_DEBUG_UTIL);
1637
1638
if ((closure = calloc(1, sizeof(*closure))) == NULL)
1639
debug_return_ptr(NULL);
1640
1641
closure->sock = sock;
1642
closure->evbase = base;
1643
TAILQ_INIT(&closure->write_bufs);
1644
TAILQ_INIT(&closure->free_bufs);
1645
1646
TAILQ_INSERT_TAIL(&connections, closure, entries);
1647
1648
closure->state = RECV_HELLO;
1649
closure->accept_only = accept_only;
1650
closure->reject_reason = reject_reason;
1651
closure->evlog = evlog;
1652
1653
closure->restart.tv_sec = restart->tv_sec;
1654
closure->restart.tv_nsec = restart->tv_nsec;
1655
closure->stop_after.tv_sec = stop_after->tv_sec;
1656
closure->stop_after.tv_nsec = stop_after->tv_nsec;
1657
1658
closure->iolog_id = iolog_id;
1659
1660
closure->read_buf.size = 8 * 1024;
1661
closure->read_buf.data = malloc(closure->read_buf.size);
1662
if (closure->read_buf.data == NULL)
1663
goto bad;
1664
1665
closure->read_ev = sudo_ev_alloc(sock, SUDO_EV_READ|SUDO_EV_PERSIST,
1666
server_msg_cb, closure);
1667
if (closure->read_ev == NULL)
1668
goto bad;
1669
1670
buf = get_free_buf(64 * 1024, closure);
1671
if (buf == NULL)
1672
goto bad;
1673
TAILQ_INSERT_TAIL(&closure->free_bufs, buf, entries);
1674
1675
closure->write_ev = sudo_ev_alloc(sock, SUDO_EV_WRITE|SUDO_EV_PERSIST,
1676
client_msg_cb, closure);
1677
if (closure->write_ev == NULL)
1678
goto bad;
1679
1680
#if defined(HAVE_OPENSSL)
1681
if (cert != NULL) {
1682
closure->tls_client.tls_connect_ev = sudo_ev_alloc(sock, SUDO_EV_WRITE,
1683
tls_connect_cb, &closure->tls_client);
1684
if (closure->tls_client.tls_connect_ev == NULL)
1685
goto bad;
1686
closure->tls_client.evbase = base;
1687
closure->tls_client.parent_closure = closure;
1688
closure->tls_client.peer_name = &server_info;
1689
closure->tls_client.connect_timeout.tv_sec = TLS_HANDSHAKE_TIMEO_SEC;
1690
closure->tls_client.start_fn = tls_start_fn;
1691
}
1692
#endif
1693
1694
debug_return_ptr(closure);
1695
bad:
1696
client_closure_free(closure);
1697
debug_return_ptr(NULL);
1698
}
1699
1700
#if defined(HAVE_OPENSSL)
1701
static const char short_opts[] = "Ah:i:np:r:R:s:t:b:c:k:V";
1702
#else
1703
static const char short_opts[] = "Ah:i:Ip:r:R:t:s:V";
1704
#endif
1705
static struct option long_opts[] = {
1706
{ "accept", no_argument, NULL, 'A' },
1707
{ "help", no_argument, NULL, 1 },
1708
{ "host", required_argument, NULL, 'h' },
1709
{ "iolog-id", required_argument, NULL, 'i' },
1710
{ "port", required_argument, NULL, 'p' },
1711
{ "restart", required_argument, NULL, 'r' },
1712
{ "reject", required_argument, NULL, 'R' },
1713
{ "stop-after", required_argument, NULL, 's' },
1714
{ "test", optional_argument, NULL, 't' },
1715
#if defined(HAVE_OPENSSL)
1716
{ "ca-bundle", required_argument, NULL, 'b' },
1717
{ "cert", required_argument, NULL, 'c' },
1718
{ "key", required_argument, NULL, 'k' },
1719
{ "no-verify", no_argument, NULL, 'n' },
1720
#endif
1721
{ "version", no_argument, NULL, 'V' },
1722
{ NULL, no_argument, NULL, 0 },
1723
};
1724
1725
sudo_dso_public int main(int argc, char *argv[]);
1726
1727
int
1728
main(int argc, char *argv[])
1729
{
1730
struct client_closure *closure = NULL;
1731
struct sudo_event_base *evbase;
1732
struct eventlog *evlog;
1733
const char *port = NULL;
1734
struct timespec restart = { 0, 0 };
1735
struct timespec stop_after = { 0, 0 };
1736
bool accept_only = false;
1737
char *reject_reason = NULL;
1738
const char *iolog_id = NULL;
1739
const char *errstr;
1740
int ch, sock, iolog_dir_fd, finished;
1741
debug_decl_vars(main, SUDO_DEBUG_MAIN);
1742
1743
#if defined(SUDO_DEVEL) && defined(__OpenBSD__)
1744
{
1745
extern char *malloc_options;
1746
malloc_options = "S";
1747
}
1748
#endif
1749
1750
signal(SIGPIPE, SIG_IGN);
1751
1752
initprogname(argc > 0 ? argv[0] : "sudo_sendlog");
1753
setlocale(LC_ALL, "");
1754
bindtextdomain("sudo", LOCALEDIR); /* XXX - add logsrvd domain */
1755
textdomain("sudo");
1756
1757
/* Read sudo.conf and initialize the debug subsystem. */
1758
if (sudo_conf_read(NULL, SUDO_CONF_DEBUG) == -1)
1759
return EXIT_FAILURE;
1760
sudo_debug_register(getprogname(), NULL, NULL,
1761
sudo_conf_debug_files(getprogname()), -1);
1762
1763
if (protobuf_c_version_number() < 1003000)
1764
sudo_fatalx("%s", U_("Protobuf-C version 1.3 or higher required"));
1765
1766
while ((ch = getopt_long(argc, argv, short_opts, long_opts, NULL)) != -1) {
1767
switch (ch) {
1768
case 'A':
1769
accept_only = true;
1770
break;
1771
case 'h':
1772
server_info.name = optarg;
1773
break;
1774
case 'i':
1775
iolog_id = optarg;
1776
break;
1777
case 'p':
1778
port = optarg;
1779
break;
1780
case 'R':
1781
reject_reason = optarg;
1782
break;
1783
case 'r':
1784
if (!parse_timespec(&restart, optarg))
1785
goto bad;
1786
break;
1787
case 's':
1788
if (!parse_timespec(&stop_after, optarg))
1789
goto bad;
1790
break;
1791
case 't':
1792
nr_of_conns = (int)sudo_strtonum(optarg, 1, INT_MAX, &errstr);
1793
if (errstr != NULL) {
1794
sudo_warnx(U_("%s: %s"), optarg, U_(errstr));
1795
goto bad;
1796
}
1797
testrun = true;
1798
break;
1799
case 1:
1800
help();
1801
/* NOTREACHED */
1802
#if defined(HAVE_OPENSSL)
1803
case 'b':
1804
ca_bundle = optarg;
1805
break;
1806
case 'c':
1807
cert = optarg;
1808
break;
1809
case 'k':
1810
key = optarg;
1811
break;
1812
case 'n':
1813
verify_server = false;
1814
break;
1815
#endif
1816
case 'V':
1817
(void)printf(_("%s version %s\n"), getprogname(),
1818
PACKAGE_VERSION);
1819
return 0;
1820
default:
1821
usage();
1822
/* NOTREACHED */
1823
}
1824
}
1825
argc -= optind;
1826
argv += optind;
1827
1828
#if defined(HAVE_OPENSSL)
1829
/* if no key file is given explicitly, try to load the key from the cert */
1830
if (cert != NULL) {
1831
if (key == NULL)
1832
key = cert;
1833
if (port == NULL)
1834
port = DEFAULT_PORT_TLS;
1835
}
1836
#endif
1837
if (port == NULL)
1838
port = DEFAULT_PORT;
1839
1840
if (sudo_timespecisset(&restart) != (iolog_id != NULL)) {
1841
sudo_warnx("%s", U_("both restart point and iolog ID must be specified"));
1842
usage();
1843
}
1844
if (sudo_timespecisset(&restart) && (accept_only || reject_reason)) {
1845
sudo_warnx("%s", U_("a restart point may not be set when no I/O is sent"));
1846
usage();
1847
}
1848
1849
/* Remaining arg should be to I/O log dir to send. */
1850
if (argc != 1)
1851
usage();
1852
iolog_dir = argv[0];
1853
if ((iolog_dir_fd = open(iolog_dir, O_RDONLY|O_DIRECTORY)) == -1) {
1854
sudo_warn("%s", iolog_dir);
1855
goto bad;
1856
}
1857
1858
/* Parse I/O log info file. */
1859
if ((evlog = iolog_parse_loginfo(iolog_dir_fd, iolog_dir)) == NULL)
1860
goto bad;
1861
1862
if ((evbase = sudo_ev_base_alloc()) == NULL)
1863
sudo_fatal(U_("%s: %s"), __func__, U_("unable to allocate memory"));
1864
1865
if (testrun)
1866
printf("connecting clients...\n");
1867
1868
for (int i = 0; i < nr_of_conns; i++) {
1869
sock = connect_server(&server_info, port);
1870
if (sock == -1)
1871
goto bad;
1872
1873
if (!testrun)
1874
printf("Connected to %s:%s\n", server_info.name, port);
1875
1876
closure = client_closure_alloc(sock, evbase, &restart, &stop_after,
1877
iolog_id, reject_reason, accept_only, evlog);
1878
if (closure == NULL)
1879
goto bad;
1880
1881
/* Open the I/O log files and seek to restart point if there is one. */
1882
if (!iolog_open_all(iolog_dir_fd, iolog_dir, closure->iolog_files, "r"))
1883
goto bad;
1884
if (sudo_timespecisset(&closure->restart)) {
1885
if (!iolog_seekto(iolog_dir_fd, iolog_dir, closure->iolog_files,
1886
&closure->elapsed, &closure->restart))
1887
goto bad;
1888
}
1889
1890
#if defined(HAVE_OPENSSL)
1891
if (cert != NULL) {
1892
if (!tls_client_setup(closure->sock, ca_bundle, cert, key, NULL,
1893
NULL, NULL, true, verify_server, &closure->tls_client))
1894
goto bad;
1895
} else
1896
#endif
1897
{
1898
/* No TLS, send ClientHello */
1899
if (!fmt_client_hello(closure))
1900
goto bad;
1901
}
1902
}
1903
1904
if (testrun)
1905
puts("sending logs...");
1906
1907
struct timespec t_start, t_end, t_result;
1908
sudo_gettime_real(&t_start);
1909
1910
sudo_ev_dispatch(evbase);
1911
sudo_ev_base_free(evbase);
1912
1913
sudo_gettime_real(&t_end);
1914
sudo_timespecsub(&t_end, &t_start, &t_result);
1915
1916
finished = 0;
1917
while ((closure = TAILQ_FIRST(&connections)) != NULL) {
1918
if (closure->state == FINISHED) {
1919
finished++;
1920
} else {
1921
sudo_warnx(U_("exited prematurely with state %d"), closure->state);
1922
sudo_warnx(U_("elapsed time sent to server [%lld, %ld]"),
1923
(long long)closure->elapsed.tv_sec, closure->elapsed.tv_nsec);
1924
sudo_warnx(U_("commit point received from server [%lld, %ld]"),
1925
(long long)closure->committed.tv_sec, closure->committed.tv_nsec);
1926
}
1927
client_closure_free(closure);
1928
}
1929
eventlog_free(evlog);
1930
#if defined(HAVE_OPENSSL)
1931
SSL_CTX_free(ssl_ctx);
1932
#endif
1933
1934
if (finished != 0) {
1935
printf("%d I/O log%s transmitted successfully in %lld.%.9ld seconds\n",
1936
finished, nr_of_conns > 1 ? "s" : "",
1937
(long long)t_result.tv_sec, t_result.tv_nsec);
1938
debug_return_int(EXIT_SUCCESS);
1939
}
1940
1941
bad:
1942
debug_return_int(EXIT_FAILURE);
1943
}
1944
1945