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