Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sudo-project
GitHub Repository: sudo-project/sudo
Path: blob/main/logsrvd/iolog_writer.c
1532 views
1
/*
2
* SPDX-License-Identifier: ISC
3
*
4
* Copyright (c) 2019-2022 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 <netinet/in.h>
25
26
#include <errno.h>
27
#include <fcntl.h>
28
#include <inttypes.h>
29
#include <limits.h>
30
#ifdef HAVE_STDBOOL_H
31
# include <stdbool.h>
32
#else
33
# include <compat/stdbool.h>
34
#endif /* HAVE_STDBOOL_H */
35
#include <stdio.h>
36
#include <stdlib.h>
37
#include <string.h>
38
#include <time.h>
39
#include <unistd.h>
40
41
#include <sudo_compat.h>
42
#include <sudo_debug.h>
43
#include <sudo_eventlog.h>
44
#include <sudo_gettext.h>
45
#include <sudo_iolog.h>
46
#include <sudo_fatal.h>
47
#include <sudo_queue.h>
48
#include <sudo_util.h>
49
50
#include <logsrvd.h>
51
52
static bool
53
type_matches(const InfoMessage *info, const char *source,
54
InfoMessage__ValueCase value_case)
55
{
56
const void *val = info->u.strval; /* same for strlistval */
57
debug_decl(type_matches, SUDO_DEBUG_UTIL);
58
59
if (info->key == NULL) {
60
sudo_warnx(U_("%s: protocol error: NULL key"), source);
61
debug_return_bool(false);
62
}
63
if (info->value_case != value_case) {
64
sudo_warnx(U_("%s: protocol error: wrong type for %s"),
65
source, info->key);
66
debug_return_bool(false);
67
}
68
if (value_case != INFO_MESSAGE__VALUE_NUMVAL && val == NULL) {
69
sudo_warnx(U_("%s: protocol error: NULL value found in %s"),
70
source, info->key);
71
debug_return_bool(false);
72
}
73
debug_return_bool(true);
74
}
75
76
/*
77
* Copy the specified string list.
78
* The input string list need not be NULL-terminated.
79
* Returns a NULL-terminated string vector.
80
*/
81
static char **
82
strlist_copy(const InfoMessage__StringList *strlist)
83
{
84
char **dst, **src = strlist->strings;
85
size_t i, len = strlist->n_strings;
86
debug_decl(strlist_copy, SUDO_DEBUG_UTIL);
87
88
dst = reallocarray(NULL, len + 1, sizeof(char *));
89
if (dst == NULL) {
90
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
91
goto bad;
92
}
93
for (i = 0; i < len; i++) {
94
if ((dst[i] = strdup(src[i])) == NULL) {
95
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
96
goto bad;
97
}
98
}
99
dst[i] = NULL;
100
debug_return_ptr(dst);
101
102
bad:
103
if (dst != NULL) {
104
while (i)
105
free(dst[--i]);
106
free(dst);
107
}
108
debug_return_ptr(NULL);
109
}
110
111
/*
112
* Free a NULL-terminated string vector.
113
*/
114
static void
115
strvec_free(char **vec)
116
{
117
if (vec != NULL) {
118
char **vp;
119
for (vp = vec; *vp != NULL; vp++)
120
free(*vp);
121
free(vec);
122
}
123
}
124
125
/*
126
* Fill in eventlog details from an AcceptMessage
127
* Caller is responsible for freeing strings in struct eventlog.
128
* Returns true on success and false on failure.
129
*/
130
struct eventlog *
131
evlog_new(const TimeSpec *submit_time, InfoMessage * const *info_msgs,
132
size_t infolen, struct connection_closure *closure)
133
{
134
const char *source = closure->journal_path ? closure->journal_path :
135
closure->ipaddr;
136
struct eventlog *evlog;
137
unsigned char uuid[16];
138
size_t idx;
139
debug_decl(evlog_new, SUDO_DEBUG_UTIL);
140
141
evlog = calloc(1, sizeof(*evlog));
142
if (evlog == NULL) {
143
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
144
goto bad;
145
}
146
147
/* Create a UUID to store in the event log. */
148
sudo_uuid_create(uuid);
149
if (sudo_uuid_to_string(uuid, evlog->uuid_str, sizeof(evlog->uuid_str)) == NULL) {
150
sudo_warnx("%s", U_("unable to generate UUID"));
151
goto bad;
152
}
153
154
/* Create a UUID to store in the event log. */
155
sudo_uuid_create(uuid);
156
if (sudo_uuid_to_string(uuid, evlog->uuid_str, sizeof(evlog->uuid_str)) == NULL) {
157
sudo_warnx("%s", U_("unable to generate UUID"));
158
goto bad;
159
}
160
161
/* Client/peer IP address. */
162
if ((evlog->peeraddr = strdup(closure->ipaddr)) == NULL) {
163
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
164
goto bad;
165
}
166
167
/* Submit time. */
168
if (submit_time != NULL) {
169
evlog->event_time.tv_sec = (time_t)submit_time->tv_sec;
170
evlog->event_time.tv_nsec = (long)submit_time->tv_nsec;
171
}
172
173
/* Default values */
174
evlog->lines = 24;
175
evlog->columns = 80;
176
evlog->runuid = (uid_t)-1;
177
evlog->rungid = (gid_t)-1;
178
evlog->exit_value = -1;
179
180
/* Pull out values by key from info array. */
181
for (idx = 0; idx < infolen; idx++) {
182
const InfoMessage *info = info_msgs[idx];
183
const char *key;
184
185
if (info == NULL)
186
continue;
187
key = info->key;
188
switch (key[0]) {
189
case 'c':
190
if (strcmp(key, "columns") == 0) {
191
if (type_matches(info, source, INFO_MESSAGE__VALUE_NUMVAL)) {
192
if (info->u.numval <= 0 || info->u.numval > INT_MAX) {
193
errno = ERANGE;
194
sudo_warn(U_("%s: %s"), source, "columns");
195
} else {
196
evlog->columns = (int)info->u.numval;
197
}
198
}
199
continue;
200
}
201
if (strcmp(key, "command") == 0) {
202
if (type_matches(info, source, INFO_MESSAGE__VALUE_STRVAL)) {
203
free(evlog->command);
204
if ((evlog->command = strdup(info->u.strval)) == NULL) {
205
sudo_warnx(U_("%s: %s"), __func__,
206
U_("unable to allocate memory"));
207
goto bad;
208
}
209
}
210
continue;
211
}
212
break;
213
case 'l':
214
if (strcmp(key, "lines") == 0) {
215
if (type_matches(info, source, INFO_MESSAGE__VALUE_NUMVAL)) {
216
if (info->u.numval <= 0 || info->u.numval > INT_MAX) {
217
errno = ERANGE;
218
sudo_warn(U_("%s: %s"), source, "lines");
219
} else {
220
evlog->lines = (int)info->u.numval;
221
}
222
}
223
continue;
224
}
225
break;
226
case 'r':
227
if (strcmp(key, "runargv") == 0) {
228
if (type_matches(info, source, INFO_MESSAGE__VALUE_STRLISTVAL)) {
229
strvec_free(evlog->runargv);
230
evlog->runargv = strlist_copy(info->u.strlistval);
231
if (evlog->runargv == NULL)
232
goto bad;
233
}
234
continue;
235
}
236
if (strcmp(key, "runchroot") == 0) {
237
if (type_matches(info, source, INFO_MESSAGE__VALUE_STRVAL)) {
238
free(evlog->runchroot);
239
if ((evlog->runchroot = strdup(info->u.strval)) == NULL) {
240
sudo_warnx(U_("%s: %s"), __func__,
241
U_("unable to allocate memory"));
242
goto bad;
243
}
244
}
245
continue;
246
}
247
if (strcmp(key, "runcwd") == 0) {
248
if (type_matches(info, source, INFO_MESSAGE__VALUE_STRVAL)) {
249
free(evlog->runcwd);
250
if ((evlog->runcwd = strdup(info->u.strval)) == NULL) {
251
sudo_warnx(U_("%s: %s"), __func__,
252
U_("unable to allocate memory"));
253
goto bad;
254
}
255
}
256
continue;
257
}
258
if (strcmp(key, "runenv") == 0) {
259
if (type_matches(info, source, INFO_MESSAGE__VALUE_STRLISTVAL)) {
260
strvec_free(evlog->runenv);
261
evlog->runenv = strlist_copy(info->u.strlistval);
262
if (evlog->runenv == NULL)
263
goto bad;
264
}
265
continue;
266
}
267
if (strcmp(key, "rungid") == 0) {
268
if (type_matches(info, source, INFO_MESSAGE__VALUE_NUMVAL)) {
269
if (info->u.numval < 0 || info->u.numval > UINT_MAX) {
270
errno = ERANGE;
271
sudo_warn(U_("%s: %s"), source, "rungid");
272
} else {
273
evlog->rungid = (gid_t)info->u.numval;
274
}
275
}
276
continue;
277
}
278
if (strcmp(key, "rungroup") == 0) {
279
if (type_matches(info, source, INFO_MESSAGE__VALUE_STRVAL)) {
280
free(evlog->rungroup);
281
if ((evlog->rungroup = strdup(info->u.strval)) == NULL) {
282
sudo_warnx(U_("%s: %s"), __func__,
283
U_("unable to allocate memory"));
284
goto bad;
285
}
286
}
287
continue;
288
}
289
if (strcmp(key, "runuid") == 0) {
290
if (type_matches(info, source, INFO_MESSAGE__VALUE_NUMVAL)) {
291
if (info->u.numval < 0 || info->u.numval > UINT_MAX) {
292
errno = ERANGE;
293
sudo_warn(U_("%s: %s"), source, "runuid");
294
} else {
295
evlog->runuid = (uid_t)info->u.numval;
296
}
297
}
298
continue;
299
}
300
if (strcmp(key, "runuser") == 0) {
301
if (type_matches(info, source, INFO_MESSAGE__VALUE_STRVAL)) {
302
free(evlog->runuser);
303
if ((evlog->runuser = strdup(info->u.strval)) == NULL) {
304
sudo_warnx(U_("%s: %s"), __func__,
305
U_("unable to allocate memory"));
306
goto bad;
307
}
308
}
309
continue;
310
}
311
break;
312
case 's':
313
if (strcmp(key, "source") == 0) {
314
if (type_matches(info, source, INFO_MESSAGE__VALUE_STRVAL)) {
315
free(evlog->source);
316
if ((evlog->source = strdup(info->u.strval)) == NULL) {
317
sudo_warnx(U_("%s: %s"), __func__,
318
U_("unable to allocate memory"));
319
goto bad;
320
}
321
}
322
continue;
323
}
324
if (strcmp(key, "submitcwd") == 0) {
325
if (type_matches(info, source, INFO_MESSAGE__VALUE_STRVAL)) {
326
free(evlog->cwd);
327
if ((evlog->cwd = strdup(info->u.strval)) == NULL) {
328
sudo_warnx(U_("%s: %s"), __func__,
329
U_("unable to allocate memory"));
330
goto bad;
331
}
332
}
333
continue;
334
}
335
if (strcmp(key, "submitenv") == 0) {
336
if (type_matches(info, source, INFO_MESSAGE__VALUE_STRLISTVAL)) {
337
strvec_free(evlog->submitenv);
338
evlog->submitenv = strlist_copy(info->u.strlistval);
339
if (evlog->submitenv == NULL)
340
goto bad;
341
}
342
continue;
343
}
344
if (strcmp(key, "submitgroup") == 0) {
345
if (type_matches(info, source, INFO_MESSAGE__VALUE_STRVAL)) {
346
free(evlog->submitgroup);
347
if ((evlog->submitgroup = strdup(info->u.strval)) == NULL) {
348
sudo_warnx(U_("%s: %s"), __func__,
349
U_("unable to allocate memory"));
350
goto bad;
351
}
352
}
353
continue;
354
}
355
if (strcmp(key, "submithost") == 0) {
356
if (type_matches(info, source, INFO_MESSAGE__VALUE_STRVAL)) {
357
free(evlog->submithost);
358
if ((evlog->submithost = strdup(info->u.strval)) == NULL) {
359
sudo_warnx(U_("%s: %s"), __func__,
360
U_("unable to allocate memory"));
361
goto bad;
362
}
363
}
364
continue;
365
}
366
if (strcmp(key, "submituser") == 0) {
367
if (type_matches(info, source, INFO_MESSAGE__VALUE_STRVAL)) {
368
free(evlog->submituser);
369
if ((evlog->submituser = strdup(info->u.strval)) == NULL) {
370
sudo_warnx(U_("%s: %s"), __func__,
371
U_("unable to allocate memory"));
372
goto bad;
373
}
374
}
375
continue;
376
}
377
break;
378
case 't':
379
if (strcmp(key, "ttyname") == 0) {
380
if (type_matches(info, source, INFO_MESSAGE__VALUE_STRVAL)) {
381
free(evlog->ttyname);
382
if ((evlog->ttyname = strdup(info->u.strval)) == NULL) {
383
sudo_warnx(U_("%s: %s"), __func__,
384
U_("unable to allocate memory"));
385
goto bad;
386
}
387
}
388
continue;
389
}
390
break;
391
}
392
}
393
394
/* Check for required settings */
395
if (evlog->submituser == NULL) {
396
sudo_warnx(U_("%s: protocol error: %s missing from AcceptMessage"),
397
source, "submituser");
398
goto bad;
399
}
400
if (evlog->submithost == NULL) {
401
sudo_warnx(U_("%s: protocol error: %s missing from AcceptMessage"),
402
source, "submithost");
403
goto bad;
404
}
405
if (evlog->runuser == NULL) {
406
sudo_warnx(U_("%s: protocol error: %s missing from AcceptMessage"),
407
source, "runuser");
408
goto bad;
409
}
410
if (evlog->command == NULL) {
411
sudo_warnx(U_("%s: protocol error: %s missing from AcceptMessage"),
412
source, "command");
413
goto bad;
414
}
415
416
/* Other settings that must exist for event logging. */
417
if (evlog->cwd == NULL) {
418
if ((evlog->cwd = strdup("unknown")) == NULL) {
419
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
420
goto bad;
421
}
422
}
423
if (evlog->runcwd == NULL) {
424
if ((evlog->runcwd = strdup(evlog->cwd)) == NULL) {
425
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
426
goto bad;
427
}
428
}
429
if (evlog->submitgroup == NULL) {
430
/* TODO: make submitgroup required */
431
if ((evlog->submitgroup = strdup("unknown")) == NULL) {
432
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
433
goto bad;
434
}
435
}
436
if (evlog->ttyname == NULL) {
437
if ((evlog->ttyname = strdup("unknown")) == NULL) {
438
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
439
goto bad;
440
}
441
}
442
443
debug_return_ptr(evlog);
444
445
bad:
446
eventlog_free(evlog);
447
debug_return_ptr(NULL);
448
}
449
450
struct iolog_path_closure {
451
char *iolog_dir;
452
struct eventlog *evlog;
453
};
454
455
static size_t
456
fill_seq(char * restrict str, size_t strsize, void * restrict v)
457
{
458
struct iolog_path_closure *closure = v;
459
char *sessid = closure->evlog->sessid;
460
int len;
461
debug_decl(fill_seq, SUDO_DEBUG_UTIL);
462
463
if (sessid[0] == '\0') {
464
if (!iolog_nextid(closure->iolog_dir, sessid))
465
debug_return_size_t((size_t)-1);
466
}
467
468
/* Path is of the form /var/log/sudo-io/00/00/01. */
469
len = snprintf(str, strsize, "%c%c/%c%c/%c%c", sessid[0],
470
sessid[1], sessid[2], sessid[3], sessid[4], sessid[5]);
471
if (len < 0 || len >= (ssize_t)strsize) {
472
sudo_warnx(U_("%s: unable to format session id"), __func__);
473
debug_return_size_t(strsize); /* handle non-standard snprintf() */
474
}
475
debug_return_size_t((size_t)len);
476
}
477
478
static size_t
479
fill_user(char * restrict str, size_t strsize, void * restrict v)
480
{
481
struct iolog_path_closure *closure = v;
482
const struct eventlog *evlog = closure->evlog;
483
debug_decl(fill_user, SUDO_DEBUG_UTIL);
484
485
if (evlog->submituser == NULL) {
486
sudo_warnx(U_("%s: %s is not set"), __func__, "submituser");
487
debug_return_size_t(strsize);
488
}
489
debug_return_size_t(strlcpy_no_slash(str, evlog->submituser, strsize));
490
}
491
492
static size_t
493
fill_group(char * restrict str, size_t strsize, void * restrict v)
494
{
495
struct iolog_path_closure *closure = v;
496
const struct eventlog *evlog = closure->evlog;
497
debug_decl(fill_group, SUDO_DEBUG_UTIL);
498
499
if (evlog->submitgroup == NULL) {
500
sudo_warnx(U_("%s: %s is not set"), __func__, "submitgroup");
501
debug_return_size_t(strsize);
502
}
503
debug_return_size_t(strlcpy_no_slash(str, evlog->submitgroup, strsize));
504
}
505
506
static size_t
507
fill_runas_user(char * restrict str, size_t strsize, void * restrict v)
508
{
509
struct iolog_path_closure *closure = v;
510
const struct eventlog *evlog = closure->evlog;
511
debug_decl(fill_runas_user, SUDO_DEBUG_UTIL);
512
513
if (evlog->runuser == NULL) {
514
sudo_warnx(U_("%s: %s is not set"), __func__, "runuser");
515
debug_return_size_t(strsize);
516
}
517
debug_return_size_t(strlcpy_no_slash(str, evlog->runuser, strsize));
518
}
519
520
static size_t
521
fill_runas_group(char * restrict str, size_t strsize, void * restrict v)
522
{
523
struct iolog_path_closure *closure = v;
524
const struct eventlog *evlog = closure->evlog;
525
debug_decl(fill_runas_group, SUDO_DEBUG_UTIL);
526
527
/* FIXME: rungroup not guaranteed to be set */
528
if (evlog->rungroup == NULL) {
529
sudo_warnx(U_("%s: %s is not set"), __func__, "rungroup");
530
debug_return_size_t(strsize);
531
}
532
debug_return_size_t(strlcpy_no_slash(str, evlog->rungroup, strsize));
533
}
534
535
static size_t
536
fill_hostname(char * restrict str, size_t strsize, void * restrict v)
537
{
538
struct iolog_path_closure *closure = v;
539
const struct eventlog *evlog = closure->evlog;
540
debug_decl(fill_hostname, SUDO_DEBUG_UTIL);
541
542
if (evlog->submithost == NULL) {
543
sudo_warnx(U_("%s: %s is not set"), __func__, "submithost");
544
debug_return_size_t(strsize);
545
}
546
debug_return_size_t(strlcpy_no_slash(str, evlog->submithost, strsize));
547
}
548
549
static size_t
550
fill_command(char * restrict str, size_t strsize, void * restrict v)
551
{
552
struct iolog_path_closure *closure = v;
553
const struct eventlog *evlog = closure->evlog;
554
const char *cmnd_base;
555
debug_decl(fill_command, SUDO_DEBUG_UTIL);
556
557
if (evlog->command == NULL) {
558
sudo_warnx(U_("%s: %s is not set"), __func__, "command");
559
debug_return_size_t(strsize);
560
}
561
cmnd_base = sudo_basename(evlog->command);
562
debug_return_size_t(strlcpy_no_slash(str, cmnd_base, strsize));
563
}
564
565
/* Note: "seq" must be first in the list. */
566
static const struct iolog_path_escape path_escapes[] = {
567
{ "seq", fill_seq },
568
{ "user", fill_user },
569
{ "group", fill_group },
570
{ "runas_user", fill_runas_user },
571
{ "runas_group", fill_runas_group },
572
{ "hostname", fill_hostname },
573
{ "command", fill_command },
574
{ NULL, NULL }
575
};
576
577
/*
578
* Create I/O log path
579
* Sets iolog_path, iolog_file and iolog_dir_fd in the closure
580
*/
581
static bool
582
create_iolog_path(struct connection_closure *closure)
583
{
584
struct eventlog *evlog = closure->evlog;
585
struct iolog_path_closure path_closure;
586
char expanded_dir[PATH_MAX], expanded_file[PATH_MAX], pathbuf[PATH_MAX];
587
int len;
588
debug_decl(create_iolog_path, SUDO_DEBUG_UTIL);
589
590
path_closure.evlog = evlog;
591
path_closure.iolog_dir = expanded_dir;
592
593
if (!expand_iolog_path(logsrvd_conf_iolog_dir(), expanded_dir,
594
sizeof(expanded_dir), &path_escapes[1], &path_closure)) {
595
sudo_warnx(U_("unable to expand iolog path %s"),
596
logsrvd_conf_iolog_dir());
597
goto bad;
598
}
599
if (contains_dot_dot(expanded_dir)) {
600
sudo_warnx(U_("unable to expand iolog path %s: path traversal attack"),
601
logsrvd_conf_iolog_dir());
602
goto bad;
603
}
604
605
if (!expand_iolog_path(logsrvd_conf_iolog_file(), expanded_file,
606
sizeof(expanded_file), &path_escapes[0], &path_closure)) {
607
sudo_warnx(U_("unable to expand iolog path %s"),
608
logsrvd_conf_iolog_file());
609
goto bad;
610
}
611
if (contains_dot_dot(expanded_file)) {
612
sudo_warnx(U_("unable to expand iolog path %s: path traversal attack"),
613
logsrvd_conf_iolog_file());
614
goto bad;
615
}
616
617
len = snprintf(pathbuf, sizeof(pathbuf), "%s/%s", expanded_dir,
618
expanded_file);
619
if (len < 0 || len >= ssizeof(pathbuf)) {
620
errno = ENAMETOOLONG;
621
sudo_warn("%s/%s", expanded_dir, expanded_file);
622
goto bad;
623
}
624
625
/*
626
* Create log path, along with any intermediate subdirs.
627
* Calls mkdtemp() if pathbuf ends in XXXXXX.
628
*/
629
if (!iolog_mkpath(pathbuf)) {
630
sudo_warn(U_("unable to create iolog path %s"), pathbuf);
631
goto bad;
632
}
633
if ((evlog->iolog_path = strdup(pathbuf)) == NULL) {
634
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
635
goto bad;
636
}
637
evlog->iolog_file = evlog->iolog_path + strlen(expanded_dir) + 1;
638
639
/* We use iolog_dir_fd in calls to openat(2) */
640
closure->iolog_dir_fd =
641
iolog_openat(AT_FDCWD, evlog->iolog_path, O_RDONLY|O_DIRECTORY);
642
if (closure->iolog_dir_fd == -1) {
643
sudo_warn("%s", evlog->iolog_path);
644
goto bad;
645
}
646
647
debug_return_bool(true);
648
bad:
649
free(evlog->iolog_path);
650
evlog->iolog_path = NULL;
651
debug_return_bool(false);
652
}
653
654
bool
655
iolog_create(int iofd, struct connection_closure *closure)
656
{
657
debug_decl(iolog_create, SUDO_DEBUG_UTIL);
658
659
if (iofd < 0 || iofd >= IOFD_MAX) {
660
sudo_warnx(U_("invalid iofd %d"), iofd);
661
debug_return_bool(false);
662
}
663
664
closure->iolog_files[iofd].enabled = true;
665
debug_return_bool(iolog_open(&closure->iolog_files[iofd],
666
closure->iolog_dir_fd, iofd, "w"));
667
}
668
669
void
670
iolog_close_all(struct connection_closure *closure)
671
{
672
const char *errstr;
673
unsigned int i;
674
debug_decl(iolog_close_all, SUDO_DEBUG_UTIL);
675
676
for (i = 0; i < IOFD_MAX; i++) {
677
if (!closure->iolog_files[i].enabled)
678
continue;
679
if (!iolog_close(&closure->iolog_files[i], &errstr)) {
680
sudo_warnx(U_("error closing iofd %u: %s"), i, errstr);
681
}
682
}
683
if (closure->iolog_dir_fd != -1)
684
close(closure->iolog_dir_fd);
685
686
debug_return;
687
}
688
689
bool
690
iolog_flush_all(struct connection_closure *closure)
691
{
692
const char *errstr;
693
bool ret = true;
694
unsigned int i;
695
debug_decl(iolog_flush_all, SUDO_DEBUG_UTIL);
696
697
for (i = 0; i < IOFD_MAX; i++) {
698
if (!closure->iolog_files[i].enabled)
699
continue;
700
if (!iolog_flush(&closure->iolog_files[i], &errstr)) {
701
sudo_warnx(U_("error flushing iofd %u: %s"), i, errstr);
702
ret = false;
703
}
704
}
705
706
debug_return_bool(ret);
707
}
708
709
static bool
710
iolog_store_uuid(int dfd, struct connection_closure *closure)
711
{
712
char uuid_str[37];
713
int fd = -1;
714
debug_decl(iolog_create_uuid, SUDO_DEBUG_UTIL);
715
716
/* Create a UUID to store in the I/O log. */
717
sudo_uuid_create(closure->uuid);
718
if (sudo_uuid_to_string(closure->uuid, uuid_str, sizeof(uuid_str)) == NULL) {
719
sudo_warnx("%s", U_("unable to generate UUID"));
720
goto bad;
721
}
722
723
/* Write UUID in string form to the I/O log directory. */
724
fd = iolog_openat(dfd, "uuid", O_CREAT|O_TRUNC|O_WRONLY);
725
if (fd == -1) {
726
sudo_warn(U_("unable to open %s/%s"), closure->evlog->iolog_path,
727
"uuid");
728
goto bad;
729
}
730
if (fchown(fd, iolog_get_uid(), iolog_get_gid()) != 0) {
731
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
732
"%s: unable to fchown %d:%d %s/uuid", __func__,
733
(int)iolog_get_uid(), (int)iolog_get_gid(),
734
closure->evlog->iolog_path);
735
}
736
if (write(fd, uuid_str, sizeof(uuid_str) - 1) != ssizeof(uuid_str) - 1) {
737
sudo_warn(U_("unable to write %s/%s"), closure->evlog->iolog_path,
738
"uuid");
739
goto bad;
740
}
741
close(fd);
742
743
debug_return_bool(true);
744
bad:
745
if (fd != -1)
746
close(fd);
747
debug_return_bool(false);
748
}
749
750
bool
751
iolog_init(const AcceptMessage *msg, struct connection_closure *closure)
752
{
753
struct eventlog *evlog = closure->evlog;
754
debug_decl(iolog_init, SUDO_DEBUG_UTIL);
755
756
/* Create I/O log path */
757
if (!create_iolog_path(closure))
758
debug_return_bool(false);
759
760
/* Create and store I/O log UUID */
761
if (!iolog_store_uuid(closure->iolog_dir_fd, closure))
762
debug_return_bool(false);
763
764
/* Write sudo I/O log info file */
765
if (!iolog_write_info_file(closure->iolog_dir_fd, evlog))
766
debug_return_bool(false);
767
768
/*
769
* Create timing, stdout, stderr and ttyout files for sudoreplay.
770
* Others will be created on demand.
771
*/
772
if (!iolog_create(IOFD_TIMING, closure) ||
773
!iolog_create(IOFD_STDOUT, closure) ||
774
!iolog_create(IOFD_STDERR, closure) ||
775
!iolog_create(IOFD_TTYOUT, closure))
776
debug_return_bool(false);
777
778
/* Ready to log I/O buffers. */
779
debug_return_bool(true);
780
}
781
782
/*
783
* Copy len bytes from src to dst.
784
*/
785
static bool
786
iolog_copy(struct iolog_file *src, struct iolog_file *dst, off_t remainder,
787
const char **errstr)
788
{
789
char buf[64 * 1024];
790
ssize_t nread;
791
debug_decl(iolog_copy, SUDO_DEBUG_UTIL);
792
793
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
794
"copying %lld bytes", (long long)remainder);
795
while (remainder > 0) {
796
const size_t toread = MIN((size_t)remainder, sizeof(buf));
797
nread = iolog_read(src, buf, toread, errstr);
798
if (nread == -1)
799
debug_return_bool(false);
800
remainder -= nread;
801
802
do {
803
ssize_t nwritten = iolog_write(dst, buf, (size_t)nread, errstr);
804
if (nwritten == -1)
805
debug_return_bool(false);
806
nread -= nwritten;
807
} while (nread > 0);
808
}
809
810
debug_return_bool(true);
811
}
812
813
/*
814
* Like rename(2) but changes UID as needed.
815
*/
816
static bool
817
iolog_rename(const char *from, const char *to)
818
{
819
bool ok, uid_changed = false;
820
debug_decl(iolog_rename, SUDO_DEBUG_UTIL);
821
822
ok = rename(from, to) == 0;
823
if (!ok && errno == EACCES) {
824
uid_changed = iolog_swapids(false);
825
if (uid_changed)
826
ok = rename(from, to) == 0;
827
}
828
829
if (uid_changed) {
830
if (!iolog_swapids(true))
831
ok = false;
832
}
833
debug_return_bool(ok);
834
}
835
836
/* Compressed logs don't support random access, need to rewrite them. */
837
bool
838
iolog_rewrite(const struct timespec *target, struct connection_closure *closure)
839
{
840
const struct eventlog *evlog = closure->evlog;
841
struct iolog_file new_iolog_files[IOFD_MAX];
842
off_t iolog_file_sizes[IOFD_MAX] = { 0 };
843
struct timing_closure timing;
844
int iofd, len, tmpdir_fd = -1;
845
const char *name, *errstr;
846
char tmpdir[PATH_MAX];
847
bool ret = false;
848
debug_decl(iolog_rewrite, SUDO_DEBUG_UTIL);
849
850
memset(&timing, 0, sizeof(timing));
851
timing.decimal = ".";
852
853
/* Parse timing file until we reach the target point. */
854
/* TODO: use iolog_seekto with a callback? */
855
for (;;) {
856
/* Read next record from timing file. */
857
if (iolog_read_timing_record(&closure->iolog_files[IOFD_TIMING], &timing) != 0)
858
goto done;
859
sudo_timespecadd(&timing.delay, &closure->elapsed_time,
860
&closure->elapsed_time);
861
if (timing.event < IOFD_TIMING) {
862
if (!closure->iolog_files[timing.event].enabled) {
863
/* Missing log file. */
864
sudo_warnx(U_("invalid I/O log %s: %s referenced but not present"),
865
evlog->iolog_path, iolog_fd_to_name(timing.event));
866
goto done;
867
}
868
iolog_file_sizes[timing.event] += (off_t)timing.u.nbytes;
869
}
870
871
if (sudo_timespeccmp(&closure->elapsed_time, target, >=)) {
872
if (sudo_timespeccmp(&closure->elapsed_time, target, ==))
873
break;
874
875
/* Mismatch between resume point and stored log. */
876
sudo_warnx(U_("%s: unable to find resume point [%lld, %ld]"),
877
evlog->iolog_path, (long long)target->tv_sec, target->tv_nsec);
878
goto done;
879
}
880
}
881
iolog_file_sizes[IOFD_TIMING] =
882
iolog_seek(&closure->iolog_files[IOFD_TIMING], 0, SEEK_CUR);
883
iolog_rewind(&closure->iolog_files[IOFD_TIMING]);
884
885
/* Create new I/O log files in a temporary directory. */
886
len = snprintf(tmpdir, sizeof(tmpdir), "%s/restart.XXXXXX",
887
evlog->iolog_path);
888
if (len < 0 || len >= ssizeof(tmpdir)) {
889
errno = ENAMETOOLONG;
890
sudo_warn("%s/restart.XXXXXX", evlog->iolog_path);
891
goto done;
892
}
893
if (!iolog_mkdtemp(tmpdir)) {
894
sudo_warn(U_("unable to mkdir %s"), tmpdir);
895
goto done;
896
}
897
tmpdir_fd = iolog_openat(AT_FDCWD, tmpdir, O_RDONLY|O_DIRECTORY);
898
if (tmpdir_fd == -1) {
899
sudo_warn(U_("unable to open %s"), tmpdir);
900
goto done;
901
}
902
903
/* Create new copies of the existing iologs */
904
memset(new_iolog_files, 0, sizeof(new_iolog_files));
905
for (iofd = 0; iofd < IOFD_MAX; iofd++) {
906
if (!closure->iolog_files[iofd].enabled)
907
continue;
908
new_iolog_files[iofd].enabled = true;
909
if (!iolog_open(&new_iolog_files[iofd], tmpdir_fd, iofd, "w")) {
910
if (errno != ENOENT) {
911
sudo_warn(U_("unable to open %s/%s"),
912
tmpdir, iolog_fd_to_name(iofd));
913
goto done;
914
}
915
}
916
}
917
918
for (iofd = 0; iofd < IOFD_MAX; iofd++) {
919
if (!closure->iolog_files[iofd].enabled)
920
continue;
921
if (!iolog_copy(&closure->iolog_files[iofd], &new_iolog_files[iofd],
922
iolog_file_sizes[iofd], &errstr)) {
923
name = iolog_fd_to_name(iofd);
924
sudo_warnx(U_("unable to copy %s/%s to %s/%s: %s"),
925
evlog->iolog_path, name, tmpdir, name, errstr);
926
goto done;
927
}
928
}
929
930
/* Move copied log files into place. */
931
for (iofd = 0; iofd < IOFD_MAX; iofd++) {
932
char from[PATH_MAX], to[PATH_MAX];
933
934
if (!closure->iolog_files[iofd].enabled)
935
continue;
936
937
/* This would be easier with renameat(2), old systems are annoying. */
938
name = iolog_fd_to_name(iofd);
939
len = snprintf(from, sizeof(from), "%s/%s", tmpdir, name);
940
if (len < 0 || len >= ssizeof(from)) {
941
errno = ENAMETOOLONG;
942
sudo_warn("%s/%s", tmpdir, name);
943
goto done;
944
}
945
len = snprintf(to, sizeof(to), "%s/%s", evlog->iolog_path,
946
name);
947
if (len < 0 || len >= ssizeof(from)) {
948
errno = ENAMETOOLONG;
949
sudo_warn("%s/%s", evlog->iolog_path, name);
950
goto done;
951
}
952
if (!iolog_rename(from, to)) {
953
sudo_warn(U_("unable to rename %s to %s"), from, to);
954
goto done;
955
}
956
}
957
958
for (iofd = 0; iofd < IOFD_MAX; iofd++) {
959
if (!closure->iolog_files[iofd].enabled)
960
continue;
961
(void)iolog_close(&closure->iolog_files[iofd], &errstr);
962
closure->iolog_files[iofd] = new_iolog_files[iofd];
963
new_iolog_files[iofd].enabled = false;
964
}
965
966
/* Ready to log I/O buffers. */
967
ret = true;
968
done:
969
if (tmpdir_fd != -1) {
970
if (!ret) {
971
for (iofd = 0; iofd < IOFD_MAX; iofd++) {
972
if (!new_iolog_files[iofd].enabled)
973
continue;
974
(void)iolog_close(&new_iolog_files[iofd], &errstr);
975
(void)unlinkat(tmpdir_fd, iolog_fd_to_name(iofd), 0);
976
}
977
}
978
close(tmpdir_fd);
979
(void)rmdir(tmpdir);
980
}
981
debug_return_bool(ret);
982
}
983
984
/*
985
* Add given delta to elapsed time.
986
* We cannot use timespecadd here since delta is not struct timespec.
987
*/
988
void
989
update_elapsed_time(const TimeSpec *delta, struct timespec *elapsed)
990
{
991
debug_decl(update_elapsed_time, SUDO_DEBUG_UTIL);
992
993
/* Cannot use timespecadd since msg doesn't use struct timespec. */
994
elapsed->tv_sec += (time_t)delta->tv_sec;
995
elapsed->tv_nsec += (long)delta->tv_nsec;
996
while (elapsed->tv_nsec >= 1000000000) {
997
elapsed->tv_sec++;
998
elapsed->tv_nsec -= 1000000000;
999
}
1000
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
1001
"%s: delta [%lld, %d], elapsed time now [%lld, %ld]",
1002
__func__, (long long)delta->tv_sec, delta->tv_nsec,
1003
(long long)elapsed->tv_sec, elapsed->tv_nsec);
1004
1005
debug_return;
1006
}
1007
1008