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