mirror of https://github.com/sudo-project/sudo.git
330 lines
9.1 KiB
C
330 lines
9.1 KiB
C
/*
|
|
* SPDX-License-Identifier: ISC
|
|
*
|
|
* Copyright (c) 2018-2023 Todd C. Miller <Todd.Miller@sudo.ws>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
/*
|
|
* This is an open source non-commercial project. Dear PVS-Studio, please check it.
|
|
* PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include <sys/types.h>
|
|
#if defined(MAJOR_IN_MKDEV)
|
|
# include <sys/mkdev.h>
|
|
#elif defined(MAJOR_IN_SYSMACROS)
|
|
# include <sys/sysmacros.h>
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <pwd.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#include <sudoers.h>
|
|
#include <timestamp.h>
|
|
|
|
struct timestamp_entry_common {
|
|
unsigned short version; /* version number */
|
|
unsigned short size; /* entry size */
|
|
unsigned short type; /* TS_GLOBAL, TS_TTY, TS_PPID */
|
|
unsigned short flags; /* TS_DISABLED, TS_ANYUID */
|
|
};
|
|
|
|
union timestamp_entry_storage {
|
|
struct timestamp_entry_common common;
|
|
struct timestamp_entry_v1 v1;
|
|
struct timestamp_entry v2;
|
|
};
|
|
|
|
sudo_dso_public int main(int argc, char *argv[]);
|
|
|
|
static void dump_entry(struct timestamp_entry *entry, off_t pos);
|
|
static bool valid_entry(union timestamp_entry_storage *u, off_t pos);
|
|
static bool convert_entry(union timestamp_entry_storage *record, struct timespec *off);
|
|
sudo_noreturn static void usage(void);
|
|
|
|
/*
|
|
* tsdump: a simple utility to dump the contents of a time stamp file.
|
|
* Unlock sudo, does not perform any locking of the time stamp file.
|
|
*/
|
|
|
|
int
|
|
main(int argc, char *argv[])
|
|
{
|
|
int ch, fd;
|
|
const char *user = NULL;
|
|
char *fname = NULL;
|
|
union timestamp_entry_storage cur;
|
|
struct timespec now, timediff;
|
|
debug_decl(main, SUDOERS_DEBUG_MAIN);
|
|
|
|
#if defined(SUDO_DEVEL) && defined(__OpenBSD__)
|
|
malloc_options = "S";
|
|
#endif
|
|
|
|
initprogname(argc > 0 ? argv[0] : "tsdump");
|
|
|
|
bindtextdomain("sudoers", LOCALEDIR);
|
|
textdomain("sudoers");
|
|
|
|
/* Initialize the debug subsystem. */
|
|
if (sudo_conf_read(NULL, SUDO_CONF_DEBUG) == -1)
|
|
return EXIT_FAILURE;
|
|
if (!sudoers_debug_register(getprogname(), sudo_conf_debug_files(getprogname())))
|
|
return EXIT_FAILURE;
|
|
|
|
while ((ch = getopt(argc, argv, "f:u:")) != -1) {
|
|
switch (ch) {
|
|
case 'f':
|
|
fname = optarg;
|
|
break;
|
|
case 'u':
|
|
user = optarg;
|
|
break;
|
|
default:
|
|
usage();
|
|
}
|
|
}
|
|
argc -= optind;
|
|
argv += optind;
|
|
|
|
if (fname != NULL && user != NULL) {
|
|
sudo_warnx("the -f and -u flags are mutually exclusive");
|
|
usage();
|
|
}
|
|
|
|
/* Calculate the difference between real time and mono time. */
|
|
if (sudo_gettime_real(&now) == -1)
|
|
sudo_fatal("unable to get current time");
|
|
if (sudo_gettime_mono(&timediff) == -1)
|
|
sudo_fatal("unable to read the clock");
|
|
sudo_timespecsub(&now, &timediff, &timediff);
|
|
|
|
if (fname == NULL) {
|
|
uid_t uid;
|
|
int len;
|
|
|
|
if (user == NULL) {
|
|
uid = geteuid();
|
|
} else {
|
|
struct passwd *pw = getpwnam(user);
|
|
if (pw == NULL)
|
|
sudo_fatalx(U_("unknown user %s"), user);
|
|
uid = pw->pw_uid;
|
|
}
|
|
len = asprintf(&fname, "%s/%u", _PATH_SUDO_TIMEDIR, (unsigned int)uid);
|
|
if (len == -1)
|
|
sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
|
|
}
|
|
|
|
fd = open(fname, O_RDONLY);
|
|
if (fd == -1)
|
|
sudo_fatal(U_("unable to open %s"), fname);
|
|
|
|
for (;;) {
|
|
off_t pos = lseek(fd, 0, SEEK_CUR);
|
|
ssize_t nread;
|
|
bool valid;
|
|
|
|
if ((nread = read(fd, &cur, sizeof(cur))) == 0)
|
|
break;
|
|
if (nread == -1)
|
|
sudo_fatal(U_("unable to read %s"), fname);
|
|
|
|
valid = valid_entry(&cur, pos);
|
|
if (cur.common.size != 0 && cur.common.size != sizeof(cur)) {
|
|
off_t offset = (off_t)cur.common.size - (off_t)sizeof(cur);
|
|
if (lseek(fd, offset, SEEK_CUR) == -1)
|
|
sudo_fatal("unable to seek %d bytes", (int)offset);
|
|
}
|
|
if (valid) {
|
|
/* Convert entry to latest version as needed. */
|
|
if (!convert_entry(&cur, &timediff))
|
|
continue;
|
|
dump_entry(&cur.v2, pos);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool
|
|
valid_entry(union timestamp_entry_storage *u, off_t pos)
|
|
{
|
|
struct timestamp_entry *entry = (struct timestamp_entry *)u;
|
|
debug_decl(valid_entry, SUDOERS_DEBUG_UTIL);
|
|
|
|
switch (entry->version) {
|
|
case 1:
|
|
if (entry->size != sizeof(struct timestamp_entry_v1)) {
|
|
sudo_warn("wrong sized v1 record @ %lld, got %hu, expected %zu",
|
|
(long long)pos, entry->size, sizeof(struct timestamp_entry_v1));
|
|
debug_return_bool(false);
|
|
}
|
|
break;
|
|
case 2:
|
|
if (entry->size != sizeof(struct timestamp_entry)) {
|
|
sudo_warn("wrong sized v2 record @ %lld, got %hu, expected %zu",
|
|
(long long)pos, entry->size, sizeof(struct timestamp_entry));
|
|
debug_return_bool(false);
|
|
}
|
|
break;
|
|
default:
|
|
sudo_warn("unknown time stamp entry version %d @ %lld",
|
|
(int)entry->version, (long long)pos);
|
|
debug_return_bool(false);
|
|
break;
|
|
}
|
|
debug_return_bool(true);
|
|
}
|
|
|
|
static const char *
|
|
type2string(int type)
|
|
{
|
|
static char name[64];
|
|
debug_decl(type2string, SUDOERS_DEBUG_UTIL);
|
|
|
|
switch (type) {
|
|
case TS_LOCKEXCL:
|
|
debug_return_const_str("TS_LOCKEXCL");
|
|
case TS_GLOBAL:
|
|
debug_return_const_str("TS_GLOBAL");
|
|
case TS_TTY:
|
|
debug_return_const_str("TS_TTY");
|
|
case TS_PPID:
|
|
debug_return_const_str("TS_PPID");
|
|
}
|
|
(void)snprintf(name, sizeof(name), "UNKNOWN (0x%x)", type);
|
|
debug_return_const_str(name);
|
|
}
|
|
|
|
static void
|
|
print_flags(unsigned int flags)
|
|
{
|
|
bool first = true;
|
|
debug_decl(print_flags, SUDOERS_DEBUG_UTIL);
|
|
|
|
printf("flags: ");
|
|
if (ISSET(flags, TS_DISABLED)) {
|
|
printf("%sTS_DISABLED", first ? "" : ", ");
|
|
CLR(flags, TS_DISABLED);
|
|
first = false;
|
|
}
|
|
if (ISSET(flags, TS_ANYUID)) {
|
|
/* TS_ANYUID should never appear on disk. */
|
|
printf("%sTS_ANYUID", first ? "" : ", ");
|
|
CLR(flags, TS_ANYUID);
|
|
first = false;
|
|
}
|
|
if (flags != 0)
|
|
printf("%s0x%x", first ? "" : ", ", flags);
|
|
putchar('\n');
|
|
|
|
debug_return;
|
|
}
|
|
|
|
/*
|
|
* Convert an older entry to current.
|
|
* Also adjusts time stamps on Linux to be wallclock time.
|
|
*/
|
|
static bool
|
|
convert_entry(union timestamp_entry_storage *record, struct timespec *off)
|
|
{
|
|
union timestamp_entry_storage orig;
|
|
debug_decl(convert_entry, SUDOERS_DEBUG_UTIL);
|
|
|
|
if (record->common.version != TS_VERSION) {
|
|
if (record->common.version != 1) {
|
|
sudo_warnx("unexpected record version %hu", record->common.version);
|
|
debug_return_bool(false);
|
|
}
|
|
|
|
/* The first four fields are the same regardless of version. */
|
|
memcpy(&orig, record, sizeof(union timestamp_entry_storage));
|
|
record->v2.auth_uid = orig.v1.auth_uid;
|
|
record->v2.sid = orig.v1.sid;
|
|
sudo_timespecclear(&record->v2.start_time);
|
|
record->v2.ts = orig.v1.ts;
|
|
if (record->common.type == TS_TTY)
|
|
record->v2.u.ttydev = orig.v1.u.ttydev;
|
|
else if (record->common.type == TS_PPID)
|
|
record->v2.u.ppid = orig.v1.u.ppid;
|
|
else
|
|
memset(&record->v2.u, 0, sizeof(record->v2.u));
|
|
}
|
|
|
|
/* On Linux, start time is relative to boot time, adjust to real time. */
|
|
#ifdef __linux__
|
|
if (sudo_timespecisset(&record->v2.start_time))
|
|
sudo_timespecadd(&record->v2.start_time, off, &record->v2.start_time);
|
|
#endif
|
|
|
|
/* Adjust time stamp from mono time to real time. */
|
|
if (sudo_timespecisset(&record->v2.ts))
|
|
sudo_timespecadd(&record->v2.ts, off, &record->v2.ts);
|
|
|
|
debug_return_bool(true);
|
|
}
|
|
|
|
static void
|
|
dump_entry(struct timestamp_entry *entry, off_t pos)
|
|
{
|
|
debug_decl(dump_entry, SUDOERS_DEBUG_UTIL);
|
|
|
|
printf("position: %lld\n", (long long)pos);
|
|
printf("version: %hu\n", entry->version);
|
|
printf("size: %hu\n", entry->size);
|
|
printf("type: %s\n", type2string(entry->type));
|
|
print_flags(entry->flags);
|
|
printf("auth uid: %u\n", (unsigned int)entry->auth_uid);
|
|
printf("session ID: %d\n", (int)entry->sid);
|
|
if (sudo_timespecisset(&entry->start_time))
|
|
printf("start time: %s", ctime(&entry->start_time.tv_sec));
|
|
if (sudo_timespecisset(&entry->ts))
|
|
printf("time stamp: %s", ctime(&entry->ts.tv_sec));
|
|
if (entry->type == TS_TTY) {
|
|
char ttypath[PATH_MAX];
|
|
if (sudo_ttyname_dev(entry->u.ttydev, ttypath, sizeof(ttypath)) == NULL) {
|
|
printf("terminal: %u, %u (0x%x)\n",
|
|
(unsigned int)major(entry->u.ttydev),
|
|
(unsigned int)minor(entry->u.ttydev),
|
|
(unsigned int)entry->u.ttydev);
|
|
} else {
|
|
printf("terminal: %s (0x%x)\n", ttypath,
|
|
(unsigned int)entry->u.ttydev);
|
|
}
|
|
} else if (entry->type == TS_PPID) {
|
|
printf("parent pid: %d\n", (int)entry->u.ppid);
|
|
}
|
|
putchar('\n');
|
|
|
|
debug_return;
|
|
}
|
|
|
|
sudo_noreturn static void
|
|
usage(void)
|
|
{
|
|
fprintf(stderr, "usage: %s [-f timestamp_file] | [-u username]\n",
|
|
getprogname());
|
|
exit(EXIT_FAILURE);
|
|
}
|