Allow sudoers to specify the iolog file in addition to the iolog dir.
authorTodd C. Miller <Todd.Miller@courtesan.com>
Mon, 27 Dec 2010 17:18:32 +0000 (12:18 -0500)
committerTodd C. Miller <Todd.Miller@courtesan.com>
Mon, 27 Dec 2010 17:18:32 +0000 (12:18 -0500)
Add escape sequence support to iolog file and dir: sequence number,
    user, group, runas_user, runas_group, hostname and command in
    addition to any escape sequence recognized by strftime(3).

plugins/sudoers/Makefile.in
plugins/sudoers/def_data.c
plugins/sudoers/def_data.h
plugins/sudoers/def_data.in
plugins/sudoers/defaults.c
plugins/sudoers/iolog.c
plugins/sudoers/iolog_path.c [new file with mode: 0644]
plugins/sudoers/plugin_error.c
plugins/sudoers/sudoers.c
plugins/sudoers/sudoers.h

index 4c89af187297300a74153ed44e84bc1d8ab55d80..b19f1d5f7a9c74f966a511ac75962f3a2ce79b02 100644 (file)
@@ -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
index b440c54c1b8ab1d7fa69e6444fb3ae614321e6a6..5be92807bfadfc6383cbf941b2b919002767a3f9 100644 (file)
@@ -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
     }
index cb2b9ac3c7f799642affe2c41e447f6d262d4404..b510b21db83cf761d2335f027ed8bd1b27abcf62 100644 (file)
 #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,
index d19579053de5d39e986bde479d7830f3db6963af..655b3470d38cce7e0a999094163977394f02e58f 100644 (file)
@@ -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"
index 60c1104b99cc1d0ba52cda385478a58814461142..4d64c9bc0355923f044ea04c49f056b9dcece0f5 100644 (file)
@@ -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;
index 554fef9fa56245a31b2c92402bf9b8866d6f88f0..7ba618bb683ea8c63d9344ada13cbbaac246b346 100644 (file)
@@ -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;
-
-    if (sessid[0] == '\0')
-       log_error(0, "tried to build a session id path without a session id");
-
-    /* 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;
-       }
+    int dirlen, filelen, len;
+
+    /* 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--;
+
+    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 (file)
index 0000000..0ea707b
--- /dev/null
@@ -0,0 +1,229 @@
+/*
+ * Copyright (c) 2010 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * 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 <config.h>
+
+#include <sys/types.h>
+#include <stdio.h>
+#ifdef STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#else
+# ifdef HAVE_STDLIB_H
+#  include <stdlib.h>
+# endif
+#endif /* STDC_HEADERS */
+#ifdef HAVE_STRING_H
+# if defined(HAVE_MEMORY_H) && !defined(STDC_HEADERS)
+#  include <memory.h>
+# endif
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+#ifdef HAVE_SETLOCALE
+# include <locale.h>
+#endif
+#include <pwd.h>
+#include <grp.h>
+#include <time.h>
+
+#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;
+}
index c4a6a996ef03bc3d9e4a7bca5530662b5741d235..2d42871b7d887be14f1b7a896c6c7de1c8babc40 100644 (file)
@@ -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;
 
index 08933fca44362bd183542474b522b477f49b13c8..021fe677e6acc3dbd8df0ad09c341fd293ddf34f 100644 (file)
@@ -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");
index 44826ae6517e8c4e11cdbda67bf4a7da3b705359..e9a125250170e1812d266389805fdaffc114112c 100644 (file)
@@ -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[]);