diff --git a/plugins/sudoers/Makefile.in b/plugins/sudoers/Makefile.in index 4c89af187..b19f1d5f7 100644 --- a/plugins/sudoers/Makefile.in +++ b/plugins/sudoers/Makefile.in @@ -103,8 +103,9 @@ LIBSUDOERS_OBJS = alias.lo audit.lo defaults.lo gram.lo match.lo pwutil.lo \ timestr.lo toke.lo redblack.lo SUDOERS_OBJS = $(AUTH_OBJS) boottime.lo check.lo env.lo goodpath.lo \ - group_plugin.lo find_path.lo interfaces.lo logging.lo parse.lo \ - set_perms.lo sudoers.lo sudo_nss.lo iolog.lo @SUDOERS_OBJS@ + group_plugin.lo find_path.lo interfaces.lo logging.lo \ + parse.lo set_perms.lo sudoers.lo sudo_nss.lo iolog.lo \ + iolog_path.lo @SUDOERS_OBJS@ VISUDO_OBJS = visudo.o goodpath.o find_path.o error.o @@ -221,6 +222,8 @@ interfaces.lo: $(srcdir)/interfaces.c $(SUDODEP) $(srcdir)/interfaces.h $(LIBTOOL) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(srcdir)/interfaces.c iolog.lo: $(srcdir)/iolog.c $(SUDODEP) $(LIBTOOL) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(srcdir)/iolog.c +iolog_path.lo: $(srcdir)/iolog_path.c $(SUDODEP) + $(LIBTOOL) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(srcdir)/iolog_path.c ldap.lo: $(srcdir)/ldap.c $(SUDODEP) $(srcdir)/parse.h $(incdir)/list.h $(LIBTOOL) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(srcdir)/ldap.c linux_audit.lo: $(srcdir)/linux_audit.c $(SUDODEP) $(srcdir)/linux_audit.h diff --git a/plugins/sudoers/def_data.c b/plugins/sudoers/def_data.c index b440c54c1..5be92807b 100644 --- a/plugins/sudoers/def_data.c +++ b/plugins/sudoers/def_data.c @@ -334,6 +334,10 @@ struct sudo_defs_types sudo_defs_table[] = { "iolog_dir", T_STR|T_PATH, "Directory in which to store input/output logs", NULL, + }, { + "iolog_file", T_STR, + "File in which to store the input/output log", + NULL, }, { NULL, 0, NULL } diff --git a/plugins/sudoers/def_data.h b/plugins/sudoers/def_data.h index cb2b9ac3c..b510b21db 100644 --- a/plugins/sudoers/def_data.h +++ b/plugins/sudoers/def_data.h @@ -154,6 +154,8 @@ #define I_GROUP_PLUGIN 76 #define def_iolog_dir (sudo_defs_table[77].sd_un.str) #define I_IOLOG_DIR 77 +#define def_iolog_file (sudo_defs_table[78].sd_un.str) +#define I_IOLOG_FILE 78 enum def_tupple { never, diff --git a/plugins/sudoers/def_data.in b/plugins/sudoers/def_data.in index d19579053..655b3470d 100644 --- a/plugins/sudoers/def_data.in +++ b/plugins/sudoers/def_data.in @@ -247,3 +247,6 @@ group_plugin iolog_dir T_STR|T_PATH "Directory in which to store input/output logs" +iolog_file + T_STR + "File in which to store the input/output log" diff --git a/plugins/sudoers/defaults.c b/plugins/sudoers/defaults.c index 60c1104b9..4d64c9bc0 100644 --- a/plugins/sudoers/defaults.c +++ b/plugins/sudoers/defaults.c @@ -443,7 +443,8 @@ init_defaults(void) #ifdef UMASK_OVERRIDE def_umask_override = TRUE; #endif - def_iolog_dir = _PATH_SUDO_IO_LOGDIR; + def_iolog_file = estrdup("%{seq}"); + def_iolog_dir = estrdup(_PATH_SUDO_IO_LOGDIR); def_sudoers_locale = estrdup("C"); def_env_reset = TRUE; def_set_logname = TRUE; diff --git a/plugins/sudoers/iolog.c b/plugins/sudoers/iolog.c index 554fef9fa..7ba618bb6 100644 --- a/plugins/sudoers/iolog.c +++ b/plugins/sudoers/iolog.c @@ -84,6 +84,26 @@ static struct timeval last_time; static union io_fd io_fds[IOFD_MAX]; extern struct io_plugin sudoers_io; +static void +mkdir_parents(char *path) +{ + struct stat sb; + char *slash = path; + + for (;;) { + if ((slash = strchr(slash + 1, '/')) == NULL) + break; + *slash = '\0'; + if (stat(path, &sb) != 0) { + if (mkdir(path, S_IRWXU) != 0) + log_error(USE_ERRNO, "Can't mkdir %s", path); + } else if (!S_ISDIR(sb.st_mode)) { + log_error(0, "%s: %s", path, strerror(ENOTDIR)); + } + *slash = '/'; + } +} + void io_nextid(void) { @@ -98,6 +118,7 @@ io_nextid(void) /* * Create I/O log directory if it doesn't already exist. */ + mkdir_parents(def_iolog_dir); if (stat(def_iolog_dir, &sb) != 0) { if (mkdir(def_iolog_dir, S_IRWXU) != 0) log_error(USE_ERRNO, "Can't mkdir %s", def_iolog_dir); @@ -152,72 +173,36 @@ io_nextid(void) } static int -build_idpath(const char *iolog_dir, const char *sessid, char *pathbuf, size_t pathsize) +build_iopath(const char *iolog_dir, const char *iolog_file, char *pathbuf, + size_t pathsize) { - struct stat sb; - char *cp; - int i, len; + int dirlen, filelen, len; - if (sessid[0] == '\0') - log_error(0, "tried to build a session id path without a session id"); + /* Trim extraneous slashes. */ + dirlen = strlen(iolog_dir); + while (dirlen > 1 && iolog_dir[dirlen - 1] == '/') + dirlen--; + while (*iolog_file == '/') + iolog_file++; + filelen = strlen(iolog_file); + while (filelen > 1 && iolog_file[filelen - 1] == '/') + filelen--; - /* Check whether or not we have a real session ID. */ - for (i = 0; i < 6; i++) { - switch (sessid[i]) { - case '0': case '1': case '2': case '3': case '4': case '5': - case '6': case '7': case '8': case '9': case 'A': case 'B': - case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': - case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': - case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': - case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': - break; - default: - goto checked; - } + if (*iolog_dir != '/' || *iolog_file == '\0') + log_error(0, "invalid I/O log path: %s/%s", iolog_dir, iolog_file); + + len = snprintf(pathbuf, pathsize, "%.*s/%.*s", dirlen, iolog_dir, + filelen, iolog_file); + if (len <= 0 && len >= pathsize) { + errno = ENAMETOOLONG; + log_error(USE_ERRNO, "%.*s/%.*s", dirlen, iolog_dir, + filelen, iolog_file); } -checked: - if (i == 6 && sessid[6] == '\0') { - /* Path is of the form /var/log/sudo-io/00/00/01. */ - len = snprintf(pathbuf, pathsize, "%s/%c%c/%c%c/%c%c", iolog_dir, - sessid[0], sessid[1], sessid[2], sessid[3], sessid[4], sessid[5]); - if (len <= 0 && len >= pathsize) { - errno = ENAMETOOLONG; - log_error(USE_ERRNO, "%s/%s", iolog_dir, sessid); - } - /* Create the intermediate subdirs as needed. */ - for (i = 6; i > 0; i -= 3) { - pathbuf[len - i] = '\0'; - if (stat(pathbuf, &sb) != 0) { - if (mkdir(pathbuf, S_IRWXU) != 0) - log_error(USE_ERRNO, "Can't mkdir %s", pathbuf); - } else if (!S_ISDIR(sb.st_mode)) { - log_error(0, "%s: %s", pathbuf, strerror(ENOTDIR)); - } - pathbuf[len - i] = '/'; - } - } else { - /* Not a session ID, just append dir + file. */ - len = snprintf(pathbuf, pathsize, "%s/%s", iolog_dir, sessid); - if (len <= 0 && len >= pathsize) { - errno = ENAMETOOLONG; - log_error(USE_ERRNO, "%s/%s", iolog_dir, sessid); - } - - /* Create the intermediate subdirs as needed. */ - cp = &pathbuf[strlen(iolog_dir)]; - do { - *cp = '\0'; - if (stat(pathbuf, &sb) != 0) { - if (mkdir(pathbuf, S_IRWXU) != 0) - log_error(USE_ERRNO, "Can't mkdir %s", pathbuf); - } else if (!S_ISDIR(sb.st_mode)) { - log_error(0, "%s: %s", pathbuf, strerror(ENOTDIR)); - } - *cp++ = '/'; - cp = strchr(cp, '/'); - } while (cp != NULL); - } + /* Create path and intermediate subdirs as needed. */ + mkdir_parents(pathbuf); + if (mkdtemp(pathbuf) == NULL) + log_error(USE_ERRNO, "Can't create %s", pathbuf); return(len); } @@ -272,8 +257,8 @@ sudoers_io_open(unsigned int version, sudo_conv_t conversation, /* * Pull iolog settings out of command_info, if any. */ - iolog_dir = def_iolog_dir; - iolog_file = sudo_user.sessid; + iolog_dir = _PATH_SUDO_IO_LOGDIR; /* XXX */ + iolog_file = sudo_user.sessid; /* XXX */ iolog_stdin = iolog_ttyin = def_log_input; iolog_stdout = iolog_stderr = iolog_ttyout = def_log_output; for (cur = command_info; *cur != NULL; cur++) { @@ -319,20 +304,17 @@ sudoers_io_open(unsigned int version, sudo_conv_t conversation, return FALSE; /* If no I/O log file defined there is nothing to do. */ - if (iolog_file == NULL) + if (iolog_file == NULL || iolog_dir == NULL) return FALSE; /* - * Build a path containing the session ID split into two-digit subdirs, - * so ID 000001 becomes /var/log/sudo-io/00/00/01. + * Build a path from I/O file and dir, creating intermediate subdirs + * and calling mkdtemp() on the result. */ - len = build_idpath(iolog_dir, iolog_file, pathbuf, sizeof(pathbuf)); - if (len == -1) + len = build_iopath(iolog_dir, iolog_file, pathbuf, sizeof(pathbuf)); + if (len < 0 || len >= sizeof(pathbuf)) return -1; - if (mkdir(pathbuf, S_IRUSR|S_IWUSR|S_IXUSR) != 0) - log_error(USE_ERRNO, "Can't mkdir %s", pathbuf); - /* * We create 7 files: a log file, a timing file and 5 for input/output. */ diff --git a/plugins/sudoers/iolog_path.c b/plugins/sudoers/iolog_path.c new file mode 100644 index 000000000..0ea707bc3 --- /dev/null +++ b/plugins/sudoers/iolog_path.c @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2010 Todd C. Miller + * + * 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. + */ + +#include + +#include +#include +#ifdef STDC_HEADERS +# include +# include +#else +# ifdef HAVE_STDLIB_H +# include +# endif +#endif /* STDC_HEADERS */ +#ifdef HAVE_STRING_H +# if defined(HAVE_MEMORY_H) && !defined(STDC_HEADERS) +# include +# endif +# include +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include +#endif /* HAVE_STRINGS_H */ +#ifdef HAVE_SETLOCALE +# include +#endif +#include +#include +#include + +#include "sudoers.h" + +struct path_escape { + const char *name; + size_t (*copy_fn)(char *, size_t); +}; + +static size_t fill_seq(char *, size_t); +static size_t fill_user(char *, size_t); +static size_t fill_group(char *, size_t); +static size_t fill_runas_user(char *, size_t); +static size_t fill_runas_group(char *, size_t); +static size_t fill_hostname(char *, size_t); +static size_t fill_command(char *, size_t); + +static struct path_escape escapes[] = { + { "seq", fill_seq }, + { "user", fill_user }, + { "group", fill_group }, + { "runas_user", fill_runas_user }, + { "runas_group", fill_runas_group }, + { "hostname", fill_hostname }, + { "command", fill_command }, + { NULL, NULL } +}; + +static size_t +fill_seq(char *str, size_t strsize) +{ + int len; + + /* Path is of the form /var/log/sudo-io/00/00/01. */ + len = snprintf(str, strsize, "%c%c/%c%c/%c%c", sudo_user.sessid[0], + sudo_user.sessid[1], sudo_user.sessid[2], sudo_user.sessid[3], + sudo_user.sessid[4], sudo_user.sessid[5]); + if (len < 0) + return strsize; /* handle non-standard snprintf() */ + return (size_t)len; +} + +static size_t +fill_user(char *str, size_t strsize) +{ + return strlcpy(str, user_name, strsize); +} + +static size_t +fill_group(char *str, size_t strsize) +{ + struct group *grp; + size_t len; + + if ((grp = sudo_getgrgid(user_gid)) != NULL) { + len = strlcpy(str, grp->gr_name, strsize); + gr_delref(grp); + } else { + len = strlen(str); + len = snprintf(str + len, strsize - len, "#%u", + (unsigned int) user_gid); + } + return len; +} + +static size_t +fill_runas_user(char *str, size_t strsize) +{ + return strlcpy(str, runas_pw->pw_name, strsize); +} + +static size_t +fill_runas_group(char *str, size_t strsize) +{ + struct group *grp; + size_t len; + + if (runas_gr != NULL) { + len = strlcpy(str, runas_gr->gr_name, strsize); + } else { + if ((grp = sudo_getgrgid(runas_pw->pw_gid)) != NULL) { + len = strlcpy(str, grp->gr_name, strsize); + gr_delref(grp); + } else { + len = strlen(str); + len = snprintf(str + len, strsize - len, "#%u", + (unsigned int) runas_pw->pw_gid); + } + } + return len; +} + +static size_t +fill_hostname(char *str, size_t strsize) +{ + return strlcpy(str, user_shost, strsize); +} + +static size_t +fill_command(char *str, size_t strsize) +{ + return strlcpy(str, user_base, strsize); +} + +char * +expand_iolog_path(const char *prefix, const char *opath) +{ + size_t plen = 0, psize = 1024; + char *path, *dst; + const char *src, *ep; + int strfit = FALSE; + + /* Copy opath -> path, expanding any escape sequences. */ + dst = path = emalloc(psize); + *path = '\0'; + + if (prefix != NULL) { + plen = strlcpy(path, prefix, psize); + dst += plen; + } + for (src = opath; *src != '\0'; src++) { + if (src[0] == '%') { + if (src[1] == '{') { + ep = strchr(src + 2, '}'); + if (ep != NULL) { + struct path_escape *esc; + size_t len = (size_t)(ep - src - 2); + for (esc = escapes; esc->name != NULL; esc++) { + if (strncmp(src + 2, esc->name, len) == 0 && + esc->name[len] == '\0') + break; + } + for (;;) { + len = esc->copy_fn(dst, psize - (dst - path)); + if (len < psize - (dst - path)) + break; + path = erealloc3(path, 2, psize); + psize *= 2; + dst = path + plen; + } + dst += len; + src = ep; + continue; + } + } else { + /* May need strftime() */ + strfit = 1; + } + } + /* Need at least 2 chars, including the NUL terminator. */ + if (plen + 2 >= psize) { + path = erealloc3(path, 2, psize); + psize *= 2; + dst = path + plen; + } + *dst++ = *src; + } + *dst = '\0'; + + if (strfit) { + time_t now; + struct tm *timeptr; + char *buf = NULL; + + time(&now); + timeptr = localtime(&now); + +#ifdef HAVE_SETLOCALE + if (!setlocale(LC_ALL, def_sudoers_locale)) { + warningx("unable to set locale to \"%s\", using \"C\"", + def_sudoers_locale); + setlocale(LC_ALL, "C"); + } +#endif + /* Double the size of the buffer until it is big enough to expand. */ + do { + psize * 2; + buf = erealloc(buf, psize); + buf[psize - 1] = '\0'; + } while (!strftime(buf, psize, path, timeptr) || buf[psize - 1] != '\0'); + setlocale(LC_ALL, ""); + efree(path); + path = buf; + } + + return path; +} diff --git a/plugins/sudoers/plugin_error.c b/plugins/sudoers/plugin_error.c index c4a6a996e..2d42871b7 100644 --- a/plugins/sudoers/plugin_error.c +++ b/plugins/sudoers/plugin_error.c @@ -32,7 +32,7 @@ static void _warning(int, const char *, va_list); void plugin_cleanup(int); -extern sigjmp_buf error_jmp; +sigjmp_buf error_jmp; extern sudo_conv_t sudo_conv; diff --git a/plugins/sudoers/sudoers.c b/plugins/sudoers/sudoers.c index 08933fca4..021fe677e 100644 --- a/plugins/sudoers/sudoers.c +++ b/plugins/sudoers/sudoers.c @@ -135,8 +135,8 @@ static struct sudo_nss_list *snl; int NewArgc; char **NewArgv; -/* error.c */ -sigjmp_buf error_jmp; +/* plugin_error.c */ +extern sigjmp_buf error_jmp; static int sudoers_policy_open(unsigned int version, sudo_conv_t conversation, @@ -500,9 +500,13 @@ sudoers_policy_main(int argc, char * const argv[], int pwflag, char *env_add[], } if (ISSET(sudo_mode, (MODE_RUN | MODE_EDIT)) && (def_log_input || def_log_output)) { - io_nextid(); - command_info[info_len++] = fmt_string("iolog_dir", def_iolog_dir); - command_info[info_len++] = fmt_string("iolog_file", sudo_user.sessid); + if (def_iolog_file) { + if (strstr(def_iolog_file, "%{seq}") != NULL) /* XXX - inline? */ + io_nextid(); + command_info[info_len++] = expand_iolog_path("iolog_file=", def_iolog_file); + } + if (def_iolog_dir) + command_info[info_len++] = expand_iolog_path("iolog_dir=", def_iolog_dir); if (def_log_input) { command_info[info_len++] = estrdup("iolog_stdin=true"); command_info[info_len++] = estrdup("iolog_ttyin=true"); diff --git a/plugins/sudoers/sudoers.h b/plugins/sudoers/sudoers.h index 44826ae65..e9a125250 100644 --- a/plugins/sudoers/sudoers.h +++ b/plugins/sudoers/sudoers.h @@ -285,6 +285,9 @@ int get_boottime(struct timeval *); /* iolog.c */ void io_nextid(void); +/* iolog_path.c */ +char *expand_iolog_path(const char *prefix, const char *opath); + /* env.c */ char **env_get(void); void env_init(char * const envp[]);