Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sudo-project
GitHub Repository: sudo-project/sudo
Path: blob/main/lib/eventlog/eventlog.c
1532 views
1
/*
2
* SPDX-License-Identifier: ISC
3
*
4
* Copyright (c) 1994-1996, 1998-2023 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
* Sponsored in part by the Defense Advanced Research Projects
19
* Agency (DARPA) and Air Force Research Laboratory, Air Force
20
* Materiel Command, USAF, under agreement number F39502-99-1-0512.
21
*/
22
23
#include <config.h>
24
25
#include <sys/types.h>
26
#include <sys/socket.h>
27
#include <sys/stat.h>
28
#include <sys/wait.h>
29
#include <netinet/in.h>
30
31
#include <ctype.h>
32
#include <errno.h>
33
#include <fcntl.h>
34
#include <grp.h>
35
#include <locale.h>
36
#include <pwd.h>
37
#include <signal.h>
38
#include <stdio.h>
39
#include <stdlib.h>
40
#include <string.h>
41
#include <syslog.h>
42
#include <time.h>
43
#include <unistd.h>
44
45
#include <pathnames.h>
46
#include <sudo_compat.h>
47
#include <sudo_debug.h>
48
#include <sudo_eventlog.h>
49
#include <sudo_lbuf.h>
50
#include <sudo_fatal.h>
51
#include <sudo_gettext.h>
52
#include <sudo_json.h>
53
#include <sudo_queue.h>
54
#include <sudo_util.h>
55
56
#define IS_SESSID(s) ( \
57
isalnum((unsigned char)(s)[0]) && isalnum((unsigned char)(s)[1]) && \
58
(s)[2] == '/' && \
59
isalnum((unsigned char)(s)[3]) && isalnum((unsigned char)(s)[4]) && \
60
(s)[5] == '/' && \
61
isalnum((unsigned char)(s)[6]) && isalnum((unsigned char)(s)[7]) && \
62
(s)[8] == '\0')
63
64
struct eventlog_args {
65
const char *reason;
66
const char *errstr;
67
const struct timespec *event_time;
68
eventlog_json_callback_t json_info_cb;
69
void *json_info;
70
};
71
72
/*
73
* Allocate and fill in a new logline.
74
*/
75
static bool
76
new_logline(int event_type, int flags, struct eventlog_args *args,
77
const struct eventlog *evlog, struct sudo_lbuf *lbuf)
78
{
79
const struct eventlog_config *evl_conf = eventlog_getconf();
80
const char *iolog_file;
81
const char *tty, *tsid = NULL;
82
char exit_str[STRLEN_MAX_SIGNED(int) + 1];
83
char sessid[7], offsetstr[64] = "";
84
size_t i;
85
debug_decl(new_logline, SUDO_DEBUG_UTIL);
86
87
if (ISSET(flags, EVLOG_RAW) || evlog == NULL) {
88
if (args->reason != NULL) {
89
if (args->errstr != NULL) {
90
sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL, "%s: %s",
91
args->reason, args->errstr);
92
} else {
93
sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL, "%s", args->reason);
94
}
95
if (sudo_lbuf_error(lbuf))
96
goto oom;
97
}
98
debug_return_bool(true);
99
}
100
101
/* A TSID may be a sudoers-style session ID or a free-form string. */
102
iolog_file = evlog->iolog_file;
103
if (iolog_file != NULL) {
104
if (IS_SESSID(iolog_file)) {
105
sessid[0] = iolog_file[0];
106
sessid[1] = iolog_file[1];
107
sessid[2] = iolog_file[3];
108
sessid[3] = iolog_file[4];
109
sessid[4] = iolog_file[6];
110
sessid[5] = iolog_file[7];
111
sessid[6] = '\0';
112
tsid = sessid;
113
} else {
114
tsid = iolog_file;
115
}
116
if (sudo_timespecisset(&evlog->iolog_offset)) {
117
/* Only write up to two significant digits for the decimal part. */
118
if (evlog->iolog_offset.tv_nsec > 10000000) {
119
(void)snprintf(offsetstr, sizeof(offsetstr), "@%lld.%02ld",
120
(long long)evlog->iolog_offset.tv_sec,
121
evlog->iolog_offset.tv_nsec / 10000000);
122
} else if (evlog->iolog_offset.tv_sec != 0) {
123
(void)snprintf(offsetstr, sizeof(offsetstr), "@%lld",
124
(long long)evlog->iolog_offset.tv_sec);
125
}
126
}
127
}
128
129
/* Sudo-format logs use the short form of the ttyname. */
130
if ((tty = evlog->ttyname) != NULL) {
131
if (strncmp(tty, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
132
tty += sizeof(_PATH_DEV) - 1;
133
}
134
135
/*
136
* Format the log line as an lbuf, escaping control characters in
137
* octal form (#0nn). Error checking (ENOMEM) is done at the end.
138
*/
139
if (args->reason != NULL) {
140
sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL, "%s%s", args->reason,
141
args->errstr ? " : " : " ; ");
142
}
143
if (args->errstr != NULL) {
144
sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL, "%s ; ", args->errstr);
145
}
146
if (evlog->submithost != NULL && !evl_conf->omit_hostname) {
147
sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL, "HOST=%s ; ",
148
evlog->submithost);
149
}
150
if (tty != NULL) {
151
sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL, "TTY=%s ; ", tty);
152
}
153
if (evlog->runchroot != NULL) {
154
sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL, "CHROOT=%s ; ",
155
evlog->runchroot);
156
}
157
if (evlog->runcwd != NULL || evlog->cwd != NULL) {
158
if (ISSET(flags, EVLOG_CWD)) {
159
/* For sudoreplay -l output format. */
160
sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL, "CWD=%s ; ",
161
evlog->runcwd ? evlog->runcwd : evlog->cwd);
162
} else if (evlog->runcwd != NULL) {
163
/* For backwards compatibility with sudo log format. */
164
sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL, "PWD=%s ; ",
165
evlog->runcwd);
166
}
167
}
168
if (evlog->runuser != NULL) {
169
sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL, "USER=%s ; ",
170
evlog->runuser);
171
}
172
if (evlog->rungroup != NULL) {
173
sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL, "GROUP=%s ; ",
174
evlog->rungroup);
175
}
176
if (tsid != NULL) {
177
sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL, "TSID=%s%s ; ", tsid,
178
offsetstr);
179
}
180
if (evlog->env_add != NULL && evlog->env_add[0] != NULL) {
181
sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL, "ENV=%s",
182
evlog->env_add[0]);
183
for (i = 1; evlog->env_add[i] != NULL; i++) {
184
sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL, " %s",
185
evlog->env_add[i]);
186
}
187
sudo_lbuf_append(lbuf, " ; ");
188
}
189
if (evlog->command != NULL && evlog->runargv != NULL) {
190
/* Command plus argv. */
191
sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL|LBUF_ESC_BLANK,
192
"COMMAND=%s", evlog->command);
193
if (evlog->runargv[0] != NULL) {
194
for (i = 1; evlog->runargv[i] != NULL; i++) {
195
sudo_lbuf_append(lbuf, " ");
196
if (strchr(evlog->runargv[i], ' ') != NULL) {
197
/* Wrap args containing spaces in single quotes. */
198
sudo_lbuf_append(lbuf, "'");
199
sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL|LBUF_ESC_QUOTE,
200
"%s", evlog->runargv[i]);
201
sudo_lbuf_append(lbuf, "'");
202
} else {
203
/* Escape quotes here too for consistency. */
204
sudo_lbuf_append_esc(lbuf,
205
LBUF_ESC_CNTRL|LBUF_ESC_BLANK|LBUF_ESC_QUOTE,
206
"%s", evlog->runargv[i]);
207
}
208
}
209
}
210
if (event_type == EVLOG_EXIT) {
211
if (evlog->signal_name != NULL) {
212
sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL, " ; SIGNAL=%s",
213
evlog->signal_name);
214
}
215
if (evlog->exit_value != -1) {
216
(void)snprintf(exit_str, sizeof(exit_str), "%d",
217
evlog->exit_value);
218
sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL, " ; EXIT=%s",
219
exit_str);
220
}
221
}
222
} else if (evlog->command != NULL) {
223
/* Just the command, no argv. */
224
sudo_lbuf_append_esc(lbuf, LBUF_ESC_CNTRL, "COMMAND=%s",
225
evlog->command);
226
}
227
228
if (!sudo_lbuf_error(lbuf))
229
debug_return_bool(true);
230
oom:
231
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
232
debug_return_bool(false);
233
}
234
235
bool
236
eventlog_store_sudo(int event_type, const struct eventlog *evlog,
237
struct sudo_lbuf *lbuf)
238
{
239
struct eventlog_args args = { NULL };
240
241
return new_logline(event_type, EVLOG_CWD, &args, evlog, lbuf);
242
}
243
244
static void
245
closefrom_nodebug(int lowfd)
246
{
247
unsigned char *debug_fds;
248
int fd, startfd;
249
debug_decl(closefrom_nodebug, SUDO_DEBUG_UTIL);
250
251
startfd = sudo_debug_get_fds(&debug_fds) + 1;
252
if (lowfd > startfd)
253
startfd = lowfd;
254
255
/* Close fds higher than the debug fds. */
256
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
257
"closing fds >= %d", startfd);
258
closefrom(startfd);
259
260
/* Close fds [lowfd, startfd) that are not in debug_fds. */
261
for (fd = lowfd; fd < startfd; fd++) {
262
if (fd < 0 || sudo_isset(debug_fds, fd))
263
continue;
264
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
265
"closing fd %d", fd);
266
#ifdef __APPLE__
267
/* Avoid potential libdispatch crash when we close its fds. */
268
(void) fcntl(fd, F_SETFD, FD_CLOEXEC);
269
#else
270
(void) close(fd);
271
#endif
272
}
273
debug_return;
274
}
275
276
#define MAX_MAILFLAGS 63
277
278
sudo_noreturn static void
279
exec_mailer(int pipein) // -V1082
280
{
281
const struct eventlog_config *evl_conf = eventlog_getconf();
282
char *last, *mflags, *p, *argv[MAX_MAILFLAGS + 1];
283
const char *mpath = evl_conf->mailerpath;
284
gid_t mailgid = evl_conf->mailgid;
285
size_t i;
286
const char * const root_envp[] = {
287
"HOME=/",
288
"PATH=/usr/bin:/bin:/usr/sbin:/sbin",
289
"LOGNAME=root",
290
"USER=root",
291
# ifdef _AIX
292
"LOGIN=root",
293
# endif
294
NULL
295
};
296
debug_decl(exec_mailer, SUDO_DEBUG_UTIL);
297
298
/* Set stdin to read side of the pipe. */
299
if (dup3(pipein, STDIN_FILENO, 0) == -1) {
300
syslog(LOG_ERR, _("unable to dup stdin: %m")); // -V618
301
sudo_debug_printf(SUDO_DEBUG_ERROR,
302
"unable to dup stdin: %s", strerror(errno));
303
goto bad;
304
}
305
306
/* Build up an argv based on the mailer path and flags */
307
if ((mflags = strdup(evl_conf->mailerflags)) == NULL) {
308
syslog(LOG_ERR, "%s", _("unable to allocate memory"));
309
goto bad;
310
}
311
argv[0] = sudo_basename(mpath);
312
313
i = 1;
314
for (p = strtok_r(mflags, " \t", &last); p != NULL;
315
p = strtok_r(NULL, " \t", &last)) {
316
if (i < MAX_MAILFLAGS)
317
argv[i++] = p;
318
}
319
argv[i] = NULL;
320
321
/*
322
* Depending on the config, either run the mailer as root
323
* (so user cannot kill it) or as the user (for the paranoid).
324
*/
325
if (setuid(ROOT_UID) != 0) {
326
sudo_debug_printf(SUDO_DEBUG_ERROR, "unable to change uid to %u",
327
ROOT_UID);
328
goto bad;
329
}
330
if (setgid(mailgid) != 0) {
331
sudo_debug_printf(SUDO_DEBUG_ERROR, "unable to change gid to %u",
332
(unsigned int)mailgid);
333
goto bad;
334
}
335
if (setgroups(1, &mailgid) != 0) {
336
sudo_debug_printf(SUDO_DEBUG_ERROR, "unable to set groups to %u",
337
(unsigned int)mailgid);
338
goto bad;
339
}
340
if (evl_conf->mailuid != ROOT_UID) {
341
if (setuid(evl_conf->mailuid) != 0) {
342
sudo_debug_printf(SUDO_DEBUG_ERROR, "unable to change uid to %u",
343
(unsigned int)evl_conf->mailuid);
344
goto bad;
345
}
346
}
347
sudo_debug_exit(__func__, __FILE__, __LINE__, sudo_debug_subsys);
348
if (evl_conf->mailuid == ROOT_UID)
349
execve(mpath, argv, (char **)root_envp);
350
else
351
execv(mpath, argv);
352
syslog(LOG_ERR, _("unable to execute %s: %m"), mpath); // -V618
353
sudo_debug_printf(SUDO_DEBUG_ERROR, "unable to execute %s: %s",
354
mpath, strerror(errno));
355
_exit(127);
356
bad:
357
sudo_debug_exit(__func__, __FILE__, __LINE__, sudo_debug_subsys);
358
_exit(127);
359
}
360
361
/* Send a message to the mailto user */
362
static bool
363
send_mail(const struct eventlog *evlog, const char *message)
364
{
365
const struct eventlog_config *evl_conf = eventlog_getconf();
366
const char *cp, *timefmt = evl_conf->time_fmt;
367
struct sigaction sa;
368
char timebuf[1024];
369
sigset_t chldmask;
370
struct tm tm;
371
time_t now;
372
FILE *mail;
373
int fd, pfd[2], status;
374
size_t len;
375
pid_t pid, rv;
376
struct stat sb;
377
#if defined(HAVE_NL_LANGINFO) && defined(CODESET)
378
char *locale;
379
#endif
380
debug_decl(send_mail, SUDO_DEBUG_UTIL);
381
382
/* If mailer is disabled just return. */
383
if (evl_conf->mailerpath == NULL || evl_conf->mailto == NULL)
384
debug_return_bool(true);
385
386
/* Make sure the mailer exists and is a regular file. */
387
if (stat(evl_conf->mailerpath, &sb) != 0 || !S_ISREG(sb.st_mode))
388
debug_return_bool(false);
389
390
time(&now);
391
if (localtime_r(&now, &tm) == NULL)
392
debug_return_bool(false);
393
394
/* Block SIGCHLD for the duration since we call waitpid() below. */
395
sigemptyset(&chldmask);
396
sigaddset(&chldmask, SIGCHLD);
397
(void)sigprocmask(SIG_BLOCK, &chldmask, NULL);
398
399
/* Fork and return, child will daemonize. */
400
switch (pid = sudo_debug_fork()) {
401
case -1:
402
/* Error. */
403
sudo_warn("%s", U_("unable to fork"));
404
405
/* Unblock SIGCHLD and return. */
406
(void)sigprocmask(SIG_UNBLOCK, &chldmask, NULL);
407
debug_return_bool(false);
408
case 0:
409
/* Child. */
410
switch (fork()) {
411
case -1:
412
/* Error. */
413
syslog(LOG_ERR, _("unable to fork: %m")); // -V618
414
sudo_debug_printf(SUDO_DEBUG_ERROR, "unable to fork: %s",
415
strerror(errno));
416
sudo_debug_exit(__func__, __FILE__, __LINE__, sudo_debug_subsys);
417
_exit(EXIT_FAILURE);
418
/* NOTREACHED */
419
case 0:
420
/* Grandchild continues below. */
421
sudo_debug_enter(__func__, __FILE__, __LINE__, sudo_debug_subsys);
422
break;
423
default:
424
/* Parent will wait for us. */
425
_exit(EXIT_SUCCESS);
426
/* NOTREACHED */
427
}
428
break;
429
default:
430
/* Parent. */
431
for (;;) {
432
rv = waitpid(pid, &status, 0);
433
if (rv == -1 && errno != EINTR)
434
break;
435
if (rv != -1 && !WIFSTOPPED(status))
436
break;
437
}
438
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
439
"child (%d) exit value %d", (int)rv, status);
440
441
/* Unblock SIGCHLD and return. */
442
(void)sigprocmask(SIG_UNBLOCK, &chldmask, NULL);
443
debug_return_bool(true);
444
}
445
446
/* Reset SIGCHLD to default and unblock it. */
447
memset(&sa, 0, sizeof(sa));
448
sigemptyset(&sa.sa_mask);
449
sa.sa_flags = SA_RESTART;
450
sa.sa_handler = SIG_DFL;
451
(void)sigaction(SIGCHLD, &sa, NULL);
452
(void)sigprocmask(SIG_UNBLOCK, &chldmask, NULL);
453
454
/* Daemonize - disassociate from session/tty. */
455
if (setsid() == -1)
456
sudo_warn("setsid");
457
if (chdir("/") == -1)
458
sudo_warn("chdir(/)");
459
fd = open(_PATH_DEVNULL, O_RDWR, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
460
if (fd != -1) {
461
(void) dup2(fd, STDIN_FILENO);
462
(void) dup2(fd, STDOUT_FILENO);
463
(void) dup2(fd, STDERR_FILENO);
464
}
465
466
/* Close non-debug fds so we don't leak anything. */
467
closefrom_nodebug(STDERR_FILENO + 1);
468
469
if (pipe2(pfd, O_CLOEXEC) == -1) {
470
syslog(LOG_ERR, _("unable to open pipe: %m")); // -V618
471
sudo_debug_printf(SUDO_DEBUG_ERROR, "unable to open pipe: %s",
472
strerror(errno));
473
sudo_debug_exit(__func__, __FILE__, __LINE__, sudo_debug_subsys);
474
_exit(EXIT_FAILURE);
475
}
476
477
switch (pid = sudo_debug_fork()) {
478
case -1:
479
/* Error. */
480
syslog(LOG_ERR, _("unable to fork: %m")); // -V618
481
sudo_debug_printf(
482
SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
483
"unable to fork");
484
sudo_debug_exit(__func__, __FILE__, __LINE__, sudo_debug_subsys);
485
_exit(EXIT_FAILURE);
486
/* NOTREACHED */
487
case 0:
488
/* Child. */
489
exec_mailer(pfd[0]);
490
/* NOTREACHED */
491
}
492
493
(void) close(pfd[0]);
494
if ((mail = fdopen(pfd[1], "w")) == NULL) {
495
syslog(LOG_ERR, "fdopen: %m");
496
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
497
"unable to fdopen pipe");
498
sudo_debug_exit(__func__, __FILE__, __LINE__, sudo_debug_subsys);
499
_exit(EXIT_FAILURE);
500
}
501
502
/* Pipes are all setup, send message. */
503
(void) fprintf(mail, "To: %s\nFrom: %s\nAuto-Submitted: %s\nSubject: ",
504
evl_conf->mailto,
505
evl_conf->mailfrom ? evl_conf->mailfrom :
506
(evlog ? evlog->submituser : "root"),
507
"auto-generated");
508
for (cp = _(evl_conf->mailsub); *cp; cp++) {
509
/* Expand escapes in the subject */
510
if (*cp == '%' && *(cp+1) != '%') {
511
switch (*(++cp)) {
512
case 'h':
513
if (evlog != NULL)
514
(void) fputs(evlog->submithost, mail);
515
break;
516
case 'u':
517
if (evlog != NULL)
518
(void) fputs(evlog->submituser, mail);
519
break;
520
default:
521
cp--;
522
break;
523
}
524
} else
525
(void) fputc(*cp, mail);
526
}
527
528
#if defined(HAVE_NL_LANGINFO) && defined(CODESET)
529
locale = setlocale(LC_ALL, NULL);
530
if (locale[0] != 'C' || locale[1] != '\0')
531
(void) fprintf(mail, "\nContent-Type: text/plain; charset=\"%s\"\nContent-Transfer-Encoding: 8bit", nl_langinfo(CODESET));
532
#endif /* HAVE_NL_LANGINFO && CODESET */
533
534
timebuf[sizeof(timebuf) - 1] = '\0';
535
len = strftime(timebuf, sizeof(timebuf), timefmt, &tm);
536
if (len == 0 || timebuf[sizeof(timebuf) - 1] != '\0') {
537
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_ERROR,
538
"strftime() failed to format time: %s", timefmt);
539
/* Fall back to default time format string. */
540
timebuf[sizeof(timebuf) - 1] = '\0';
541
len = strftime(timebuf, sizeof(timebuf), "%h %e %T", &tm);
542
if (len == 0 || timebuf[sizeof(timebuf) - 1] != '\0') {
543
timebuf[0] = '\0'; /* give up */
544
}
545
}
546
if (evlog != NULL) {
547
(void) fprintf(mail, "\n\n%s : %s : %s : ", evlog->submithost, timebuf,
548
evlog->submituser);
549
} else {
550
(void) fprintf(mail, "\n\n%s : ", timebuf);
551
}
552
fputs(message, mail);
553
fputs("\n\n", mail);
554
555
fclose(mail);
556
for (;;) {
557
rv = waitpid(pid, &status, 0);
558
if (rv == -1 && errno != EINTR)
559
break;
560
if (rv != -1 && !WIFSTOPPED(status))
561
break;
562
}
563
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
564
"child (%d) exit value %d", (int)rv, status);
565
sudo_debug_exit(__func__, __FILE__, __LINE__, sudo_debug_subsys);
566
_exit(EXIT_SUCCESS);
567
}
568
569
static bool
570
json_add_timestamp(struct json_container *jsonc, const char *name,
571
const struct timespec *ts, bool format_timestamp)
572
{
573
struct json_value json_value;
574
size_t len;
575
debug_decl(json_add_timestamp, SUDO_DEBUG_PLUGIN);
576
577
if (!sudo_json_open_object(jsonc, name))
578
goto oom;
579
580
json_value.type = JSON_NUMBER;
581
json_value.u.number = ts->tv_sec;
582
if (!sudo_json_add_value(jsonc, "seconds", &json_value))
583
goto oom;
584
585
json_value.type = JSON_NUMBER;
586
json_value.u.number = ts->tv_nsec;
587
if (!sudo_json_add_value(jsonc, "nanoseconds", &json_value))
588
goto oom;
589
590
if (format_timestamp) {
591
const struct eventlog_config *evl_conf = eventlog_getconf();
592
const char *timefmt = evl_conf->time_fmt;
593
time_t secs = ts->tv_sec;
594
char timebuf[1024];
595
struct tm tm;
596
597
if (gmtime_r(&secs, &tm) != NULL) {
598
timebuf[sizeof(timebuf) - 1] = '\0';
599
len = strftime(timebuf, sizeof(timebuf), "%Y%m%d%H%M%SZ", &tm);
600
if (len != 0 && timebuf[sizeof(timebuf) - 1] == '\0') {
601
json_value.type = JSON_STRING;
602
json_value.u.string = timebuf; // -V507
603
if (!sudo_json_add_value(jsonc, "iso8601", &json_value))
604
goto oom;
605
}
606
}
607
608
if (localtime_r(&secs, &tm) != NULL) {
609
timebuf[sizeof(timebuf) - 1] = '\0';
610
len = strftime(timebuf, sizeof(timebuf), timefmt, &tm);
611
if (len != 0 && timebuf[sizeof(timebuf) - 1] == '\0') {
612
json_value.type = JSON_STRING;
613
json_value.u.string = timebuf; // -V507
614
if (!sudo_json_add_value(jsonc, "localtime", &json_value))
615
goto oom;
616
}
617
}
618
}
619
620
if (!sudo_json_close_object(jsonc))
621
goto oom;
622
623
debug_return_bool(true);
624
oom:
625
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
626
"%s: %s", __func__, "unable to allocate memory");
627
debug_return_bool(false);
628
}
629
630
/*
631
* Store the contents of struct eventlog as JSON.
632
* The event_time and iolog_path members are not stored, they should
633
* be stored and formatted by the caller.
634
*/
635
bool
636
eventlog_store_json(struct json_container *jsonc, const struct eventlog *evlog)
637
{
638
struct json_value json_value;
639
size_t i;
640
char *cp;
641
debug_decl(eventlog_store_json, SUDO_DEBUG_UTIL);
642
643
/* Required settings. */
644
if (evlog == NULL || evlog->submituser == NULL)
645
debug_return_bool(false);
646
647
/*
648
* The most important values are written first in case
649
* the log record gets truncated.
650
* Note: event_time and iolog_path are not stored here.
651
*/
652
653
json_value.type = JSON_STRING;
654
json_value.u.string = evlog->submituser;
655
if (!sudo_json_add_value(jsonc, "submituser", &json_value))
656
goto oom;
657
658
if (evlog->command != NULL) {
659
json_value.type = JSON_STRING;
660
json_value.u.string = evlog->command;
661
if (!sudo_json_add_value(jsonc, "command", &json_value))
662
goto oom;
663
}
664
665
if (evlog->runuser != NULL) {
666
json_value.type = JSON_STRING;
667
json_value.u.string = evlog->runuser;
668
if (!sudo_json_add_value(jsonc, "runuser", &json_value))
669
goto oom;
670
}
671
672
if (evlog->rungroup != NULL) {
673
json_value.type = JSON_STRING;
674
json_value.u.string = evlog->rungroup;
675
if (!sudo_json_add_value(jsonc, "rungroup", &json_value))
676
goto oom;
677
}
678
679
if (evlog->runchroot != NULL) {
680
json_value.type = JSON_STRING;
681
json_value.u.string = evlog->runchroot;
682
if (!sudo_json_add_value(jsonc, "runchroot", &json_value))
683
goto oom;
684
}
685
686
if (evlog->runcwd != NULL) {
687
json_value.type = JSON_STRING;
688
json_value.u.string = evlog->runcwd;
689
if (!sudo_json_add_value(jsonc, "runcwd", &json_value))
690
goto oom;
691
}
692
693
if (evlog->source != NULL) {
694
json_value.type = JSON_STRING;
695
json_value.u.string = evlog->source;
696
if (!sudo_json_add_value(jsonc, "source", &json_value))
697
goto oom;
698
}
699
700
if (evlog->ttyname != NULL) {
701
json_value.type = JSON_STRING;
702
json_value.u.string = evlog->ttyname;
703
if (!sudo_json_add_value(jsonc, "ttyname", &json_value))
704
goto oom;
705
}
706
707
if (evlog->submithost != NULL) {
708
json_value.type = JSON_STRING;
709
json_value.u.string = evlog->submithost;
710
if (!sudo_json_add_value(jsonc, "submithost", &json_value))
711
goto oom;
712
}
713
714
if (evlog->cwd != NULL) {
715
json_value.type = JSON_STRING;
716
json_value.u.string = evlog->cwd;
717
if (!sudo_json_add_value(jsonc, "submitcwd", &json_value))
718
goto oom;
719
}
720
721
if (evlog->rungroup!= NULL && evlog->rungid != (gid_t)-1) {
722
json_value.type = JSON_ID;
723
json_value.u.id = evlog->rungid;
724
if (!sudo_json_add_value(jsonc, "rungid", &json_value))
725
goto oom;
726
}
727
728
if (evlog->runuid != (uid_t)-1) {
729
json_value.type = JSON_ID;
730
json_value.u.id = evlog->runuid;
731
if (!sudo_json_add_value(jsonc, "runuid", &json_value))
732
goto oom;
733
}
734
735
json_value.type = JSON_NUMBER;
736
json_value.u.number = evlog->columns;
737
if (!sudo_json_add_value(jsonc, "columns", &json_value))
738
goto oom;
739
740
json_value.type = JSON_NUMBER;
741
json_value.u.number = evlog->lines;
742
if (!sudo_json_add_value(jsonc, "lines", &json_value))
743
goto oom;
744
745
if (evlog->runargv != NULL) {
746
if (!sudo_json_open_array(jsonc, "runargv"))
747
goto oom;
748
for (i = 0; (cp = evlog->runargv[i]) != NULL; i++) {
749
json_value.type = JSON_STRING;
750
json_value.u.string = cp;
751
if (!sudo_json_add_value(jsonc, NULL, &json_value))
752
goto oom;
753
}
754
if (!sudo_json_close_array(jsonc))
755
goto oom;
756
}
757
758
if (evlog->runenv != NULL) {
759
if (!sudo_json_open_array(jsonc, "runenv"))
760
goto oom;
761
for (i = 0; (cp = evlog->runenv[i]) != NULL; i++) {
762
json_value.type = JSON_STRING;
763
json_value.u.string = cp;
764
if (!sudo_json_add_value(jsonc, NULL, &json_value))
765
goto oom;
766
}
767
if (!sudo_json_close_array(jsonc))
768
goto oom;
769
}
770
771
if (evlog->submitenv != NULL) {
772
if (!sudo_json_open_array(jsonc, "submitenv"))
773
goto oom;
774
for (i = 0; (cp = evlog->submitenv[i]) != NULL; i++) {
775
json_value.type = JSON_STRING;
776
json_value.u.string = cp;
777
if (!sudo_json_add_value(jsonc, NULL, &json_value))
778
goto oom;
779
}
780
if (!sudo_json_close_array(jsonc))
781
goto oom;
782
}
783
784
debug_return_bool(true);
785
786
oom:
787
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
788
debug_return_bool(false);
789
}
790
791
static bool
792
default_json_cb(struct json_container *jsonc, void *v)
793
{
794
return eventlog_store_json(jsonc, v);
795
}
796
797
static char *
798
format_json(int event_type, struct eventlog_args *args,
799
const struct eventlog *evlog, bool compact)
800
{
801
eventlog_json_callback_t info_cb = args->json_info_cb;
802
void *info = args->json_info;
803
struct json_container jsonc = { 0 };
804
struct json_value json_value;
805
const char *time_str, *type_str;
806
struct timespec now;
807
debug_decl(format_json, SUDO_DEBUG_UTIL);
808
809
if (info_cb == NULL) {
810
info_cb = default_json_cb;
811
info = (void *)evlog;
812
}
813
814
if (sudo_gettime_real(&now) == -1) {
815
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
816
"unable to read the clock");
817
debug_return_str(NULL);
818
}
819
820
switch (event_type) {
821
case EVLOG_ACCEPT:
822
type_str = "accept";
823
time_str = "submit_time";
824
break;
825
case EVLOG_REJECT:
826
type_str = "reject";
827
time_str = "submit_time";
828
break;
829
case EVLOG_ALERT:
830
type_str = "alert";
831
time_str = "alert_time";
832
break;
833
case EVLOG_EXIT:
834
type_str = "exit";
835
time_str = "exit_time";
836
break;
837
default:
838
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
839
"unexpected event type %d", event_type);
840
debug_return_str(NULL);
841
}
842
843
if (!sudo_json_init(&jsonc, 4, compact, false, false))
844
goto bad;
845
if (!sudo_json_open_object(&jsonc, type_str))
846
goto bad;
847
848
if (evlog != NULL && evlog->uuid_str[0] != '\0') {
849
json_value.type = JSON_STRING;
850
json_value.u.string = evlog->uuid_str;
851
if (!sudo_json_add_value(&jsonc, "uuid", &json_value))
852
goto bad;
853
}
854
855
/* Reject and Alert events include a reason and optional error string. */
856
if (args->reason != NULL) {
857
char *ereason = NULL;
858
859
if (args->errstr != NULL) {
860
const int len = asprintf(&ereason, _("%s: %s"), args->reason,
861
args->errstr);
862
if (len == -1) {
863
sudo_warnx(U_("%s: %s"), __func__,
864
U_("unable to allocate memory"));
865
goto bad;
866
}
867
}
868
json_value.type = JSON_STRING;
869
json_value.u.string = ereason ? ereason : args->reason;
870
if (!sudo_json_add_value(&jsonc, "reason", &json_value)) {
871
free(ereason);
872
goto bad;
873
}
874
free(ereason);
875
}
876
877
/* Log event time on server (set earlier) */
878
if (!json_add_timestamp(&jsonc, "server_time", &now, true)) {
879
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
880
"unable format timestamp");
881
goto bad;
882
}
883
884
/* Log event time from client */
885
if (args->event_time != NULL) {
886
if (!json_add_timestamp(&jsonc, time_str, args->event_time, true)) {
887
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
888
"unable format timestamp");
889
goto bad;
890
}
891
}
892
893
if (event_type == EVLOG_EXIT && evlog != NULL) {
894
/* Exit events don't need evlog details if there is a UUID. */
895
if (evlog->uuid_str[0] != '\0') {
896
if (args->json_info == NULL)
897
info = NULL;
898
}
899
900
if (sudo_timespecisset(&evlog->run_time)) {
901
if (!json_add_timestamp(&jsonc, "run_time", &evlog->run_time, false)) {
902
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
903
"unable format timestamp");
904
goto bad;
905
}
906
}
907
if (evlog->signal_name != NULL) {
908
json_value.type = JSON_STRING;
909
json_value.u.string = evlog->signal_name;
910
if (!sudo_json_add_value(&jsonc, "signal", &json_value))
911
goto bad;
912
913
json_value.type = JSON_BOOL;
914
json_value.u.boolean = evlog->dumped_core;
915
if (!sudo_json_add_value(&jsonc, "dumped_core", &json_value))
916
goto bad;
917
}
918
json_value.type = JSON_NUMBER;
919
json_value.u.number = evlog->exit_value;
920
if (!sudo_json_add_value(&jsonc, "exit_value", &json_value))
921
goto bad;
922
}
923
924
/* Event log info may be missing for alert messages. */
925
if (evlog != NULL) {
926
if (evlog->peeraddr != NULL) {
927
json_value.type = JSON_STRING;
928
json_value.u.string = evlog->peeraddr;
929
if (!sudo_json_add_value(&jsonc, "peeraddr", &json_value))
930
goto bad;
931
}
932
933
if (evlog->iolog_path != NULL) {
934
json_value.type = JSON_STRING;
935
json_value.u.string = evlog->iolog_path;
936
if (!sudo_json_add_value(&jsonc, "iolog_path", &json_value))
937
goto bad;
938
939
if (sudo_timespecisset(&evlog->iolog_offset)) {
940
if (!json_add_timestamp(&jsonc, "iolog_offset", &evlog->iolog_offset, false)) {
941
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
942
"unable format timestamp");
943
goto bad;
944
}
945
}
946
}
947
948
if (event_type == EVLOG_EXIT) {
949
/* Exit events don't need evlog details if there is a UUID. */
950
if (evlog->uuid_str[0] != '\0') {
951
if (args->json_info == NULL)
952
info = NULL;
953
}
954
955
if (sudo_timespecisset(&evlog->run_time)) {
956
if (!json_add_timestamp(&jsonc, "run_time", &evlog->run_time,
957
false)) {
958
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
959
"unable format timestamp");
960
goto bad;
961
}
962
}
963
if (evlog->signal_name != NULL) {
964
json_value.type = JSON_STRING;
965
json_value.u.string = evlog->signal_name;
966
if (!sudo_json_add_value(&jsonc, "signal", &json_value))
967
goto bad;
968
969
json_value.type = JSON_BOOL;
970
json_value.u.boolean = evlog->dumped_core;
971
if (!sudo_json_add_value(&jsonc, "dumped_core", &json_value))
972
goto bad;
973
}
974
json_value.type = JSON_NUMBER;
975
json_value.u.number = evlog->exit_value;
976
if (!sudo_json_add_value(&jsonc, "exit_value", &json_value))
977
goto bad;
978
}
979
}
980
981
/* Write log info. */
982
if (info != NULL) {
983
if (!info_cb(&jsonc, info))
984
goto bad;
985
}
986
987
if (!sudo_json_close_object(&jsonc))
988
goto bad;
989
990
/* Caller is responsible for freeing the buffer. */
991
debug_return_str(sudo_json_get_buf(&jsonc));
992
993
bad:
994
sudo_json_free(&jsonc);
995
debug_return_str(NULL);
996
}
997
998
/*
999
* Log a message to syslog, prepending the username and splitting the
1000
* message into parts if it is longer than syslog_maxlen.
1001
*/
1002
static bool
1003
do_syslog_sudo(int pri, char *logline, const struct eventlog *evlog)
1004
{
1005
const struct eventlog_config *evl_conf = eventlog_getconf();
1006
size_t len, maxlen;
1007
char *p, *tmp, save;
1008
const char *fmt;
1009
debug_decl(do_syslog_sudo, SUDO_DEBUG_UTIL);
1010
1011
evl_conf->open_log(EVLOG_SYSLOG, NULL);
1012
1013
if (evlog == NULL) {
1014
/* Not a command, just log it as-is. */
1015
syslog(pri, "%s", logline);
1016
goto done;
1017
}
1018
1019
/*
1020
* Log the full line, breaking into multiple syslog(3) calls if necessary
1021
*/
1022
fmt = _("%8s : %s");
1023
maxlen = evl_conf->syslog_maxlen -
1024
(strlen(fmt) - 5 + strlen(evlog->submituser));
1025
for (p = logline; *p != '\0'; ) {
1026
len = strlen(p);
1027
if (len > maxlen) {
1028
/*
1029
* Break up the line into what will fit on one syslog(3) line
1030
* Try to avoid breaking words into several lines if possible.
1031
*/
1032
tmp = memrchr(p, ' ', maxlen);
1033
if (tmp == NULL)
1034
tmp = p + maxlen;
1035
1036
/* NULL terminate line, but save the char to restore later */
1037
save = *tmp;
1038
*tmp = '\0';
1039
1040
syslog(pri, fmt, evlog->submituser, p);
1041
1042
*tmp = save; /* restore saved character */
1043
1044
/* Advance p and eliminate leading whitespace */
1045
for (p = tmp; *p == ' '; p++)
1046
continue;
1047
} else {
1048
syslog(pri, fmt, evlog->submituser, p);
1049
p += len;
1050
}
1051
fmt = _("%8s : (command continued) %s");
1052
maxlen = evl_conf->syslog_maxlen -
1053
(strlen(fmt) - 5 + strlen(evlog->submituser));
1054
}
1055
done:
1056
evl_conf->close_log(EVLOG_SYSLOG, NULL);
1057
1058
debug_return_bool(true);
1059
}
1060
1061
static bool
1062
do_syslog_json(int pri, int event_type, struct eventlog_args *args,
1063
const struct eventlog *evlog)
1064
{
1065
const struct eventlog_config *evl_conf = eventlog_getconf();
1066
char *json_str;
1067
debug_decl(do_syslog_json, SUDO_DEBUG_UTIL);
1068
1069
/* Format as a compact JSON message (no newlines) */
1070
json_str = format_json(event_type, args, evlog, true);
1071
if (json_str == NULL)
1072
debug_return_bool(false);
1073
1074
/* Syslog it in a sudo object with a @cee: prefix. */
1075
/* TODO: use evl_conf->syslog_maxlen to break up long messages. */
1076
evl_conf->open_log(EVLOG_SYSLOG, NULL);
1077
syslog(pri, "@cee:{\"sudo\":{%s}}", json_str);
1078
evl_conf->close_log(EVLOG_SYSLOG, NULL);
1079
free(json_str);
1080
debug_return_bool(true);
1081
}
1082
1083
/*
1084
* Log a message to syslog in either sudo or JSON format.
1085
*/
1086
static bool
1087
do_syslog(int event_type, int flags, struct eventlog_args *args,
1088
const struct eventlog *evlog)
1089
{
1090
const struct eventlog_config *evl_conf = eventlog_getconf();
1091
struct sudo_lbuf lbuf;
1092
bool ret = false;
1093
int pri;
1094
debug_decl(do_syslog, SUDO_DEBUG_UTIL);
1095
1096
sudo_lbuf_init(&lbuf, NULL, 0, NULL, 0);
1097
1098
/* Sudo format logs and mailed logs use the same log line format. */
1099
if (evl_conf->format == EVLOG_SUDO || ISSET(flags, EVLOG_MAIL)) {
1100
if (!new_logline(event_type, flags, args, evlog, &lbuf))
1101
goto done;
1102
1103
if (ISSET(flags, EVLOG_MAIL)) {
1104
if (!send_mail(evlog, lbuf.buf)) {
1105
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
1106
"unable to mail log line");
1107
}
1108
if (ISSET(flags, EVLOG_MAIL_ONLY)) {
1109
ret = true;
1110
goto done;
1111
}
1112
}
1113
}
1114
1115
switch (event_type) {
1116
case EVLOG_ACCEPT:
1117
case EVLOG_EXIT:
1118
pri = evl_conf->syslog_acceptpri;
1119
break;
1120
case EVLOG_REJECT:
1121
pri = evl_conf->syslog_rejectpri;
1122
break;
1123
case EVLOG_ALERT:
1124
pri = evl_conf->syslog_alertpri;
1125
break;
1126
default:
1127
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
1128
"unexpected event type %d", event_type);
1129
pri = -1;
1130
break;
1131
}
1132
if (pri == -1) {
1133
/* syslog disabled for this message type */
1134
ret = true;
1135
goto done;
1136
}
1137
1138
switch (evl_conf->format) {
1139
case EVLOG_SUDO:
1140
ret = do_syslog_sudo(pri, lbuf.buf, evlog);
1141
break;
1142
case EVLOG_JSON_COMPACT:
1143
case EVLOG_JSON_PRETTY:
1144
ret = do_syslog_json(pri, event_type, args, evlog);
1145
break;
1146
default:
1147
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
1148
"unexpected eventlog format %d", evl_conf->format);
1149
break;
1150
}
1151
done:
1152
sudo_lbuf_destroy(&lbuf);
1153
debug_return_bool(ret);
1154
}
1155
1156
static bool
1157
do_logfile_sudo(const char *logline, const struct eventlog *evlog,
1158
const struct timespec *event_time)
1159
{
1160
const struct eventlog_config *evl_conf = eventlog_getconf();
1161
char *full_line, timebuf[8192], *timestr = NULL;
1162
const char *timefmt = evl_conf->time_fmt;
1163
const char *logfile = evl_conf->logpath;
1164
struct tm tm;
1165
bool ret = false;
1166
FILE *fp;
1167
int len;
1168
debug_decl(do_logfile_sudo, SUDO_DEBUG_UTIL);
1169
1170
if ((fp = evl_conf->open_log(EVLOG_FILE, logfile)) == NULL)
1171
debug_return_bool(false);
1172
1173
if (!sudo_lock_file(fileno(fp), SUDO_LOCK)) {
1174
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
1175
"unable to lock log file %s", logfile);
1176
goto done;
1177
}
1178
1179
if (event_time != NULL) {
1180
time_t tv_sec = event_time->tv_sec;
1181
if (localtime_r(&tv_sec, &tm) != NULL) {
1182
/* strftime() does not guarantee to NUL-terminate so we must check. */
1183
timebuf[sizeof(timebuf) - 1] = '\0';
1184
if (strftime(timebuf, sizeof(timebuf), timefmt, &tm) != 0 &&
1185
timebuf[sizeof(timebuf) - 1] == '\0') {
1186
timestr = timebuf;
1187
}
1188
}
1189
}
1190
if (evlog != NULL) {
1191
len = asprintf(&full_line, "%s : %s : %s",
1192
timestr ? timestr : "invalid date", evlog->submituser, logline);
1193
} else {
1194
len = asprintf(&full_line, "%s : %s",
1195
timestr ? timestr : "invalid date", logline);
1196
}
1197
if (len == -1) {
1198
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
1199
goto done;
1200
}
1201
eventlog_writeln(fp, full_line, (size_t)len, evl_conf->file_maxlen);
1202
free(full_line);
1203
(void)fflush(fp);
1204
if (ferror(fp)) {
1205
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
1206
"unable to write log file %s", logfile);
1207
goto done;
1208
}
1209
ret = true;
1210
1211
done:
1212
(void)sudo_lock_file(fileno(fp), SUDO_UNLOCK);
1213
evl_conf->close_log(EVLOG_FILE, fp);
1214
debug_return_bool(ret);
1215
}
1216
1217
static bool
1218
do_logfile_json(enum eventlog_format format, int event_type,
1219
struct eventlog_args *args, const struct eventlog *evlog)
1220
{
1221
const struct eventlog_config *evl_conf = eventlog_getconf();
1222
const char *logfile = evl_conf->logpath;
1223
const bool compact = format == EVLOG_JSON_COMPACT;
1224
struct stat sb;
1225
char *json_str;
1226
int ret = false;
1227
FILE *fp;
1228
debug_decl(do_logfile_json, SUDO_DEBUG_UTIL);
1229
1230
if ((fp = evl_conf->open_log(EVLOG_FILE, logfile)) == NULL)
1231
debug_return_bool(false);
1232
1233
json_str = format_json(event_type, args, evlog, compact);
1234
if (json_str == NULL)
1235
goto done;
1236
1237
if (!sudo_lock_file(fileno(fp), SUDO_LOCK)) {
1238
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
1239
"unable to lock log file %s", logfile);
1240
goto done;
1241
}
1242
1243
if (!compact) {
1244
/* Note: assumes file ends in "\n}\n" */
1245
if (fstat(fileno(fp), &sb) == -1) {
1246
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
1247
"unable to stat %s", logfile);
1248
goto done;
1249
}
1250
if (sb.st_size == 0) {
1251
/* New file */
1252
putc('{', fp);
1253
} else if (fseeko(fp, -3, SEEK_END) == 0) {
1254
/* Continue file, overwrite the final "\n}\n" */
1255
putc(',', fp);
1256
} else {
1257
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
1258
"unable to seek %s", logfile);
1259
goto done;
1260
}
1261
fputs(json_str, fp);
1262
fputs("\n}\n", fp); /* close JSON */
1263
} else {
1264
/* Compact (minified) JSON records, one per line. */
1265
putc('{', fp);
1266
fputs(json_str, fp);
1267
fputs("}\n", fp);
1268
}
1269
fflush(fp);
1270
/* XXX - check for file error and recover */
1271
1272
ret = true;
1273
1274
done:
1275
free(json_str);
1276
(void)sudo_lock_file(fileno(fp), SUDO_UNLOCK);
1277
evl_conf->close_log(EVLOG_FILE, fp);
1278
debug_return_bool(ret);
1279
}
1280
1281
static bool
1282
do_logfile(int event_type, int flags, struct eventlog_args *args,
1283
const struct eventlog *evlog)
1284
{
1285
const struct eventlog_config *evl_conf = eventlog_getconf();
1286
struct sudo_lbuf lbuf;
1287
bool ret = false;
1288
debug_decl(do_logfile, SUDO_DEBUG_UTIL);
1289
1290
sudo_lbuf_init(&lbuf, NULL, 0, NULL, 0);
1291
1292
/* Sudo format logs and mailed logs use the same log line format. */
1293
if (evl_conf->format == EVLOG_SUDO || ISSET(flags, EVLOG_MAIL)) {
1294
if (!new_logline(event_type, flags, args, evlog, &lbuf))
1295
goto done;
1296
1297
if (ISSET(flags, EVLOG_MAIL)) {
1298
if (!send_mail(evlog, lbuf.buf)) {
1299
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
1300
"unable to mail log line");
1301
}
1302
if (ISSET(flags, EVLOG_MAIL_ONLY)) {
1303
ret = true;
1304
goto done;
1305
}
1306
}
1307
}
1308
1309
switch (evl_conf->format) {
1310
case EVLOG_SUDO:
1311
ret = do_logfile_sudo(lbuf.buf ? lbuf.buf : args->reason, evlog,
1312
args->event_time);
1313
break;
1314
case EVLOG_JSON_COMPACT:
1315
case EVLOG_JSON_PRETTY:
1316
ret = do_logfile_json(evl_conf->format, event_type, args, evlog);
1317
break;
1318
default:
1319
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
1320
"unexpected eventlog format %d", evl_conf->format);
1321
break;
1322
}
1323
1324
done:
1325
sudo_lbuf_destroy(&lbuf);
1326
debug_return_bool(ret);
1327
}
1328
1329
bool
1330
eventlog_accept(const struct eventlog *evlog, int flags,
1331
eventlog_json_callback_t info_cb, void *info)
1332
{
1333
const struct eventlog_config *evl_conf = eventlog_getconf();
1334
const int log_type = evl_conf->type;
1335
struct eventlog_args args = { NULL };
1336
bool ret = true;
1337
debug_decl(eventlog_accept, SUDO_DEBUG_UTIL);
1338
1339
args.event_time = &evlog->event_time;
1340
args.json_info_cb = info_cb;
1341
args.json_info = info;
1342
1343
if (ISSET(log_type, EVLOG_SYSLOG)) {
1344
if (!do_syslog(EVLOG_ACCEPT, flags, &args, evlog))
1345
ret = false;
1346
CLR(flags, EVLOG_MAIL);
1347
}
1348
if (ISSET(log_type, EVLOG_FILE)) {
1349
if (!do_logfile(EVLOG_ACCEPT, flags, &args, evlog))
1350
ret = false;
1351
}
1352
1353
debug_return_bool(ret);
1354
}
1355
1356
bool
1357
eventlog_reject(const struct eventlog *evlog, int flags, const char *reason,
1358
eventlog_json_callback_t info_cb, void *info)
1359
{
1360
const struct eventlog_config *evl_conf = eventlog_getconf();
1361
const int log_type = evl_conf->type;
1362
struct eventlog_args args = { NULL };
1363
bool ret = true;
1364
debug_decl(eventlog_reject, SUDO_DEBUG_UTIL);
1365
1366
args.reason = reason;
1367
args.event_time = &evlog->event_time;
1368
args.json_info_cb = info_cb;
1369
args.json_info = info;
1370
1371
if (ISSET(log_type, EVLOG_SYSLOG)) {
1372
if (!do_syslog(EVLOG_REJECT, flags, &args, evlog))
1373
ret = false;
1374
CLR(flags, EVLOG_MAIL);
1375
}
1376
if (ISSET(log_type, EVLOG_FILE)) {
1377
if (!do_logfile(EVLOG_REJECT, flags, &args, evlog))
1378
ret = false;
1379
}
1380
1381
debug_return_bool(ret);
1382
}
1383
1384
bool
1385
eventlog_alert(const struct eventlog *evlog, int flags,
1386
struct timespec *alert_time, const char *reason, const char *errstr)
1387
{
1388
const struct eventlog_config *evl_conf = eventlog_getconf();
1389
const int log_type = evl_conf->type;
1390
struct eventlog_args args = { NULL };
1391
bool ret = true;
1392
debug_decl(eventlog_alert, SUDO_DEBUG_UTIL);
1393
1394
args.reason = reason;
1395
args.errstr = errstr;
1396
args.event_time = alert_time;
1397
1398
if (ISSET(log_type, EVLOG_SYSLOG)) {
1399
if (!do_syslog(EVLOG_ALERT, flags, &args, evlog))
1400
ret = false;
1401
CLR(flags, EVLOG_MAIL);
1402
}
1403
if (ISSET(log_type, EVLOG_FILE)) {
1404
if (!do_logfile(EVLOG_ALERT, flags, &args, evlog))
1405
ret = false;
1406
}
1407
1408
debug_return_bool(ret);
1409
}
1410
1411
bool
1412
eventlog_mail(const struct eventlog *evlog, int flags,
1413
struct timespec *event_time, const char *reason, const char *errstr,
1414
char * const extra[])
1415
{
1416
struct eventlog_args args = { NULL };
1417
struct sudo_lbuf lbuf;
1418
bool ret = false;
1419
debug_decl(eventlog_mail, SUDO_DEBUG_UTIL);
1420
1421
args.reason = reason;
1422
args.errstr = errstr;
1423
args.event_time = event_time;
1424
1425
sudo_lbuf_init(&lbuf, NULL, 0, NULL, 0);
1426
if (!new_logline(EVLOG_ALERT, flags, &args, evlog, &lbuf))
1427
goto done;
1428
1429
if (extra != NULL) {
1430
/* Each extra message is written on its own line. */
1431
while (*extra != NULL) {
1432
sudo_lbuf_append(&lbuf, "\n");
1433
sudo_lbuf_append_esc(&lbuf, LBUF_ESC_CNTRL, "%s", *extra);
1434
if (sudo_lbuf_error(&lbuf)) {
1435
sudo_debug_printf(
1436
SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
1437
"unable to format mail message");
1438
goto done;
1439
}
1440
extra++;
1441
}
1442
}
1443
1444
ret = send_mail(evlog, lbuf.buf);
1445
if (!ret) {
1446
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
1447
"unable to mail log line");
1448
}
1449
1450
done:
1451
sudo_lbuf_destroy(&lbuf);
1452
debug_return_bool(ret);
1453
}
1454
1455
bool
1456
eventlog_exit(const struct eventlog *evlog, int flags)
1457
{
1458
const struct eventlog_config *evl_conf = eventlog_getconf();
1459
const int log_type = evl_conf->type;
1460
struct eventlog_args args = { NULL };
1461
struct timespec exit_time;
1462
bool ret = true;
1463
debug_decl(eventlog_exit, SUDO_DEBUG_UTIL);
1464
1465
/* We expect evlog->event_time to be the command start time. */
1466
if (sudo_timespecisset(&evlog->run_time)) {
1467
sudo_timespecadd(&evlog->event_time, &evlog->run_time, &exit_time);
1468
args.event_time = &exit_time;
1469
}
1470
1471
if (ISSET(log_type, EVLOG_SYSLOG)) {
1472
if (!do_syslog(EVLOG_EXIT, flags, &args, evlog))
1473
ret = false;
1474
CLR(flags, EVLOG_MAIL);
1475
}
1476
if (ISSET(log_type, EVLOG_FILE)) {
1477
if (!do_logfile(EVLOG_EXIT, flags, &args, evlog))
1478
ret = false;
1479
}
1480
1481
debug_return_bool(ret);
1482
}
1483
1484