Annotation of embedaddon/sudo/plugins/sudoers/iolog.c, revision 1.1.1.6

1.1       misho       1: /*
1.1.1.6 ! misho       2:  * Copyright (c) 2009-2014 Todd C. Miller <Todd.Miller@courtesan.com>
1.1       misho       3:  *
                      4:  * Permission to use, copy, modify, and distribute this software for any
                      5:  * purpose with or without fee is hereby granted, provided that the above
                      6:  * copyright notice and this permission notice appear in all copies.
                      7:  *
                      8:  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
                      9:  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
                     10:  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
                     11:  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
                     12:  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
                     13:  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
                     14:  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
                     15:  */
                     16: 
                     17: #include <config.h>
                     18: 
                     19: #include <sys/types.h>
                     20: #include <sys/stat.h>
                     21: #include <sys/time.h>
                     22: #include <stdio.h>
                     23: #ifdef STDC_HEADERS
                     24: # include <stdlib.h>
                     25: # include <stddef.h>
                     26: #else
                     27: # ifdef HAVE_STDLIB_H
                     28: #  include <stdlib.h>
                     29: # endif
                     30: #endif /* STDC_HEADERS */
                     31: #ifdef HAVE_STRING_H
                     32: # include <string.h>
                     33: #endif /* HAVE_STRING_H */
                     34: #ifdef HAVE_STRINGS_H
                     35: # include <strings.h>
                     36: #endif /* HAVE_STRINGS_H */
                     37: #ifdef HAVE_UNISTD_H
                     38: # include <unistd.h>
                     39: #endif /* HAVE_UNISTD_H */
1.1.1.6 ! misho      40: #ifdef TIME_WITH_SYS_TIME
1.1       misho      41: # include <time.h>
                     42: #endif
                     43: #include <errno.h>
                     44: #include <fcntl.h>
                     45: #include <signal.h>
                     46: #include <pwd.h>
                     47: #include <grp.h>
                     48: #ifdef HAVE_ZLIB_H
                     49: # include <zlib.h>
                     50: #endif
                     51: 
                     52: #include "sudoers.h"
1.1.1.5   misho      53: #include "iolog.h"
1.1       misho      54: 
                     55: struct script_buf {
                     56:     int len; /* buffer length (how much read in) */
                     57:     int off; /* write position (how much already consumed) */
                     58:     char buf[16 * 1024];
                     59: };
                     60: 
                     61: /* XXX - separate sudoers.h and iolog.h? */
                     62: #undef runas_pw
                     63: #undef runas_gr
                     64: 
                     65: struct iolog_details {
                     66:     const char *cwd;
                     67:     const char *tty;
                     68:     const char *user;
                     69:     const char *command;
                     70:     const char *iolog_path;
                     71:     struct passwd *runas_pw;
                     72:     struct group *runas_gr;
1.1.1.4   misho      73:     int lines;
                     74:     int cols;
1.1       misho      75: };
                     76: 
                     77: static int iolog_compress;
                     78: static struct timeval last_time;
1.1.1.4   misho      79: static unsigned int sessid_max = SESSID_MAX;
                     80: 
                     81: /* sudoers_io is declared at the end of this file. */
                     82: extern __dso_public struct io_plugin sudoers_io;
1.1       misho      83: 
                     84: /*
1.1.1.4   misho      85:  * Create path and any parent directories as needed.
                     86:  * If is_temp is set, use mkdtemp() for the final directory.
1.1       misho      87:  */
                     88: static void
1.1.1.4   misho      89: io_mkdirs(char *path, mode_t mode, bool is_temp)
1.1       misho      90: {
                     91:     struct stat sb;
1.1.1.4   misho      92:     gid_t parent_gid = 0;
1.1       misho      93:     char *slash = path;
1.1.1.4   misho      94:     debug_decl(io_mkdirs, SUDO_DEBUG_UTIL)
1.1       misho      95: 
1.1.1.4   misho      96:     /* Fast path: not a temporary and already exists. */
                     97:     if (!is_temp && stat(path, &sb) == 0) {
                     98:        if (!S_ISDIR(sb.st_mode)) {
                     99:            log_fatal(0, N_("%s exists but is not a directory (0%o)"),
                    100:                path, (unsigned int) sb.st_mode);
                    101:        }
                    102:        debug_return;
                    103:     }
                    104: 
                    105:     while ((slash = strchr(slash + 1, '/')) != NULL) {
1.1       misho     106:        *slash = '\0';
                    107:        if (stat(path, &sb) != 0) {
1.1.1.4   misho     108:            if (mkdir(path, mode) != 0)
                    109:                log_fatal(USE_ERRNO, N_("unable to mkdir %s"), path);
                    110:            ignore_result(chown(path, (uid_t)-1, parent_gid));
1.1       misho     111:        } else if (!S_ISDIR(sb.st_mode)) {
1.1.1.4   misho     112:            log_fatal(0, N_("%s exists but is not a directory (0%o)"),
                    113:                path, (unsigned int) sb.st_mode);
                    114:        } else {
                    115:            /* Inherit gid of parent dir for ownership. */
                    116:            parent_gid = sb.st_gid;
1.1       misho     117:        }
                    118:        *slash = '/';
                    119:     }
1.1.1.4   misho     120:     /* Create final path component. */
                    121:     if (is_temp) {
                    122:        if (mkdtemp(path) == NULL)
                    123:            log_fatal(USE_ERRNO, N_("unable to mkdir %s"), path);
                    124:        ignore_result(chown(path, (uid_t)-1, parent_gid));
                    125:     } else {
                    126:        if (mkdir(path, mode) != 0 && errno != EEXIST)
                    127:            log_fatal(USE_ERRNO, N_("unable to mkdir %s"), path);
                    128:        ignore_result(chown(path, (uid_t)-1, parent_gid));
                    129:     }
1.1.1.2   misho     130:     debug_return;
1.1       misho     131: }
                    132: 
                    133: /*
1.1.1.4   misho     134:  * Set max session ID (aka sequence number)
                    135:  */
                    136: int
                    137: io_set_max_sessid(const char *maxval)
                    138: {
1.1.1.6 ! misho     139:     const char *errstr;
        !           140:     unsigned int value;
        !           141:     debug_decl(io_set_max_sessid, SUDO_DEBUG_UTIL)
1.1.1.4   misho     142: 
1.1.1.6 ! misho     143:     value = strtonum(maxval, 0, SESSID_MAX, &errstr);
        !           144:     if (errstr != NULL) {
        !           145:        if (errno != ERANGE) {
        !           146:            sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
        !           147:                "bad maxseq: %s: %s", maxval, errstr);
        !           148:            debug_return_bool(false);
        !           149:        }
        !           150:        /* Out of range, clamp to SESSID_MAX as documented. */
        !           151:        value = SESSID_MAX;
1.1.1.4   misho     152:     }
1.1.1.6 ! misho     153:     sessid_max = value;
        !           154:     debug_return_bool(true);
1.1.1.4   misho     155: }
                    156: 
                    157: /*
1.1       misho     158:  * Read the on-disk sequence number, set sessid to the next
                    159:  * number, and update the on-disk copy.
                    160:  * Uses file locking to avoid sequence number collisions.
                    161:  */
                    162: void
1.1.1.3   misho     163: io_nextid(char *iolog_dir, char *iolog_dir_fallback, char sessid[7])
1.1       misho     164: {
                    165:     struct stat sb;
                    166:     char buf[32], *ep;
                    167:     int fd, i;
                    168:     unsigned long id = 0;
                    169:     int len;
                    170:     ssize_t nread;
                    171:     char pathbuf[PATH_MAX];
                    172:     static const char b36char[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1.1.1.2   misho     173:     debug_decl(io_nextid, SUDO_DEBUG_UTIL)
1.1       misho     174: 
                    175:     /*
                    176:      * Create I/O log directory if it doesn't already exist.
                    177:      */
1.1.1.4   misho     178:     io_mkdirs(iolog_dir, S_IRWXU, false);
1.1       misho     179: 
                    180:     /*
                    181:      * Open sequence file
                    182:      */
                    183:     len = snprintf(pathbuf, sizeof(pathbuf), "%s/seq", iolog_dir);
1.1.1.6 ! misho     184:     if (len <= 0 || (size_t)len >= sizeof(pathbuf)) {
1.1       misho     185:        errno = ENAMETOOLONG;
1.1.1.2   misho     186:        log_fatal(USE_ERRNO, "%s/seq", pathbuf);
1.1       misho     187:     }
                    188:     fd = open(pathbuf, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR);
                    189:     if (fd == -1)
1.1.1.4   misho     190:        log_fatal(USE_ERRNO, N_("unable to open %s"), pathbuf);
1.1       misho     191:     lock_file(fd, SUDO_LOCK);
                    192: 
1.1.1.3   misho     193:     /*
                    194:      * If there is no seq file in iolog_dir and a fallback dir was
                    195:      * specified, look for seq in the fallback dir.  This is to work
                    196:      * around a bug in sudo 1.8.5 and older where iolog_dir was not
                    197:      * expanded before the sequence number was updated.
                    198:      */
                    199:     if (iolog_dir_fallback != NULL && fstat(fd, &sb) == 0 && sb.st_size == 0) {
                    200:        char fallback[PATH_MAX];
                    201: 
                    202:        len = snprintf(fallback, sizeof(fallback), "%s/seq",
                    203:            iolog_dir_fallback);
1.1.1.6 ! misho     204:        if (len > 0 && (size_t)len < sizeof(fallback)) {
1.1.1.3   misho     205:            int fd2 = open(fallback, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR);
                    206:            if (fd2 != -1) {
1.1.1.6 ! misho     207:                nread = read(fd2, buf, sizeof(buf) - 1);
1.1.1.3   misho     208:                if (nread > 0) {
1.1.1.6 ! misho     209:                    if (buf[nread - 1] == '\n')
        !           210:                        nread--;
        !           211:                    buf[nread] = '\0';
1.1.1.3   misho     212:                    id = strtoul(buf, &ep, 36);
1.1.1.6 ! misho     213:                    if (ep == buf || *ep != '\0' || id >= sessid_max) {
        !           214:                        sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
        !           215:                            "%s: bad sequence number: %s", fallback, buf);
1.1.1.3   misho     216:                        id = 0;
1.1.1.6 ! misho     217:                    }
1.1.1.3   misho     218:                }
                    219:                close(fd2);
                    220:            }
                    221:        }
                    222:     }
                    223: 
                    224:     /* Read current seq number (base 36). */
                    225:     if (id == 0) {
1.1.1.6 ! misho     226:        nread = read(fd, buf, sizeof(buf) - 1);
1.1.1.3   misho     227:        if (nread != 0) {
                    228:            if (nread == -1)
1.1.1.4   misho     229:                log_fatal(USE_ERRNO, N_("unable to read %s"), pathbuf);
1.1.1.6 ! misho     230:            if (buf[nread - 1] == '\n')
        !           231:                nread--;
        !           232:            buf[nread] = '\0';
1.1.1.3   misho     233:            id = strtoul(buf, &ep, 36);
1.1.1.6 ! misho     234:            if (ep == buf || *ep != '\0' || id >= sessid_max) {
        !           235:                sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
        !           236:                    "%s: bad sequence number: %s", pathbuf, buf);
1.1.1.4   misho     237:                id = 0;
1.1.1.6 ! misho     238:            }
1.1.1.3   misho     239:        }
1.1       misho     240:     }
                    241:     id++;
                    242: 
                    243:     /*
                    244:      * Convert id to a string and stash in sessid.
                    245:      * Note that that least significant digits go at the end of the string.
                    246:      */
                    247:     for (i = 5; i >= 0; i--) {
                    248:        buf[i] = b36char[id % 36];
                    249:        id /= 36;
                    250:     }
                    251:     buf[6] = '\n';
                    252: 
1.1.1.2   misho     253:     /* Stash id for logging purposes. */
1.1       misho     254:     memcpy(sessid, buf, 6);
                    255:     sessid[6] = '\0';
                    256: 
1.1.1.6 ! misho     257:     /* Rewind and overwrite old seq file, including the NUL byte. */
1.1.1.3   misho     258:     if (lseek(fd, (off_t)0, SEEK_SET) == (off_t)-1 || write(fd, buf, 7) != 7)
1.1.1.4   misho     259:        log_fatal(USE_ERRNO, N_("unable to write to %s"), pathbuf);
1.1       misho     260:     close(fd);
1.1.1.2   misho     261: 
                    262:     debug_return;
1.1       misho     263: }
                    264: 
                    265: /*
                    266:  * Copy iolog_path to pathbuf and create the directory and any intermediate
                    267:  * directories.  If iolog_path ends in 'XXXXXX', use mkdtemp().
                    268:  */
                    269: static size_t
                    270: mkdir_iopath(const char *iolog_path, char *pathbuf, size_t pathsize)
                    271: {
                    272:     size_t len;
1.1.1.4   misho     273:     bool is_temp = false;
1.1.1.2   misho     274:     debug_decl(mkdir_iopath, SUDO_DEBUG_UTIL)
1.1       misho     275: 
                    276:     len = strlcpy(pathbuf, iolog_path, pathsize);
                    277:     if (len >= pathsize) {
                    278:        errno = ENAMETOOLONG;
1.1.1.2   misho     279:        log_fatal(USE_ERRNO, "%s", iolog_path);
1.1       misho     280:     }
                    281: 
                    282:     /*
                    283:      * Create path and intermediate subdirs as needed.
                    284:      * If path ends in at least 6 Xs (ala POSIX mktemp), use mkdtemp().
                    285:      */
1.1.1.4   misho     286:     if (len >= 6 && strcmp(&pathbuf[len - 6], "XXXXXX") == 0)
                    287:        is_temp = true;
                    288:     io_mkdirs(pathbuf, S_IRWXU, is_temp);
1.1       misho     289: 
1.1.1.2   misho     290:     debug_return_size_t(len);
1.1       misho     291: }
                    292: 
                    293: /*
                    294:  * Append suffix to pathbuf after len chars and open the resulting file.
                    295:  * Note that the size of pathbuf is assumed to be PATH_MAX.
1.1.1.2   misho     296:  * Uses zlib if docompress is true.
1.1.1.4   misho     297:  * Stores the open file handle which has the close-on-exec flag set.
1.1       misho     298:  */
1.1.1.4   misho     299: static void
                    300: open_io_fd(char *pathbuf, size_t len, struct io_log_file *iol, bool docompress)
1.1       misho     301: {
                    302:     int fd;
1.1.1.2   misho     303:     debug_decl(open_io_fd, SUDO_DEBUG_UTIL)
1.1       misho     304: 
                    305:     pathbuf[len] = '\0';
1.1.1.4   misho     306:     strlcat(pathbuf, iol->suffix, PATH_MAX);
                    307:     if (iol->enabled) {
1.1.1.6 ! misho     308:        fd = open(pathbuf, O_CREAT|O_TRUNC|O_WRONLY, S_IRUSR|S_IWUSR);
1.1.1.4   misho     309:        if (fd != -1) {
                    310:            fcntl(fd, F_SETFD, FD_CLOEXEC);
1.1       misho     311: #ifdef HAVE_ZLIB_H
1.1.1.4   misho     312:            if (docompress)
                    313:                iol->fd.g = gzdopen(fd, "w");
                    314:            else
1.1       misho     315: #endif
1.1.1.4   misho     316:                iol->fd.f = fdopen(fd, "w");
                    317:        }
1.1.1.5   misho     318:        if (fd == -1 || iol->fd.v == NULL)
1.1.1.4   misho     319:            log_fatal(USE_ERRNO, N_("unable to create %s"), pathbuf);
                    320:     } else {
                    321:        /* Remove old log file if we recycled sequence numbers. */
                    322:        unlink(pathbuf);
1.1       misho     323:     }
1.1.1.4   misho     324:     debug_return;
1.1       misho     325: }
                    326: 
                    327: /*
                    328:  * Pull out I/O log related data from user_info and command_info arrays.
1.1.1.4   misho     329:  * Returns true if I/O logging is enabled, else false.
1.1       misho     330:  */
1.1.1.4   misho     331: static bool
1.1       misho     332: iolog_deserialize_info(struct iolog_details *details, char * const user_info[],
                    333:     char * const command_info[])
                    334: {
                    335:     const char *runas_uid_str = "0", *runas_euid_str = NULL;
                    336:     const char *runas_gid_str = "0", *runas_egid_str = NULL;
1.1.1.5   misho     337:     const char *errstr;
                    338:     char idbuf[MAX_UID_T_LEN + 2];
1.1       misho     339:     char * const *cur;
1.1.1.5   misho     340:     id_t id;
1.1       misho     341:     uid_t runas_uid = 0;
                    342:     gid_t runas_gid = 0;
1.1.1.2   misho     343:     debug_decl(iolog_deserialize_info, SUDO_DEBUG_UTIL)
1.1       misho     344: 
1.1.1.4   misho     345:     details->lines = 24;
                    346:     details->cols = 80;
1.1       misho     347: 
                    348:     for (cur = user_info; *cur != NULL; cur++) {
                    349:        switch (**cur) {
                    350:        case 'c':
1.1.1.4   misho     351:            if (strncmp(*cur, "cols=", sizeof("cols=") - 1) == 0) {
1.1.1.6 ! misho     352:                int n = strtonum(*cur + sizeof("cols=") - 1, 1, INT_MAX, NULL);
        !           353:                if (n > 0)
        !           354:                    details->cols = n;
1.1.1.4   misho     355:                continue;
                    356:            }
1.1       misho     357:            if (strncmp(*cur, "cwd=", sizeof("cwd=") - 1) == 0) {
                    358:                details->cwd = *cur + sizeof("cwd=") - 1;
                    359:                continue;
                    360:            }
                    361:            break;
1.1.1.4   misho     362:        case 'l':
                    363:            if (strncmp(*cur, "lines=", sizeof("lines=") - 1) == 0) {
1.1.1.6 ! misho     364:                int n = strtonum(*cur + sizeof("lines=") - 1, 1, INT_MAX, NULL);
        !           365:                if (n > 0)
        !           366:                    details->lines = n;
1.1.1.4   misho     367:                continue;
                    368:            }
                    369:            break;
1.1       misho     370:        case 't':
                    371:            if (strncmp(*cur, "tty=", sizeof("tty=") - 1) == 0) {
                    372:                details->tty = *cur + sizeof("tty=") - 1;
                    373:                continue;
                    374:            }
                    375:            break;
                    376:        case 'u':
                    377:            if (strncmp(*cur, "user=", sizeof("user=") - 1) == 0) {
                    378:                details->user = *cur + sizeof("user=") - 1;
                    379:                continue;
                    380:            }
                    381:            break;
                    382:        }
                    383:     }
                    384: 
                    385:     for (cur = command_info; *cur != NULL; cur++) {
                    386:        switch (**cur) {
                    387:        case 'c':
                    388:            if (strncmp(*cur, "command=", sizeof("command=") - 1) == 0) {
                    389:                details->command = *cur + sizeof("command=") - 1;
                    390:                continue;
                    391:            }
                    392:            break;
                    393:        case 'i':
                    394:            if (strncmp(*cur, "iolog_path=", sizeof("iolog_path=") - 1) == 0) {
                    395:                details->iolog_path = *cur + sizeof("iolog_path=") - 1;
                    396:                continue;
                    397:            }
                    398:            if (strncmp(*cur, "iolog_stdin=", sizeof("iolog_stdin=") - 1) == 0) {
1.1.1.2   misho     399:                if (atobool(*cur + sizeof("iolog_stdin=") - 1) == true)
1.1.1.4   misho     400:                    io_log_files[IOFD_STDIN].enabled = true;
1.1       misho     401:                continue;
                    402:            }
                    403:            if (strncmp(*cur, "iolog_stdout=", sizeof("iolog_stdout=") - 1) == 0) {
1.1.1.2   misho     404:                if (atobool(*cur + sizeof("iolog_stdout=") - 1) == true)
1.1.1.4   misho     405:                    io_log_files[IOFD_STDOUT].enabled = true;
1.1       misho     406:                continue;
                    407:            }
                    408:            if (strncmp(*cur, "iolog_stderr=", sizeof("iolog_stderr=") - 1) == 0) {
1.1.1.2   misho     409:                if (atobool(*cur + sizeof("iolog_stderr=") - 1) == true)
1.1.1.4   misho     410:                    io_log_files[IOFD_STDERR].enabled = true;
1.1       misho     411:                continue;
                    412:            }
                    413:            if (strncmp(*cur, "iolog_ttyin=", sizeof("iolog_ttyin=") - 1) == 0) {
1.1.1.2   misho     414:                if (atobool(*cur + sizeof("iolog_ttyin=") - 1) == true)
1.1.1.4   misho     415:                    io_log_files[IOFD_TTYIN].enabled = true;
1.1       misho     416:                continue;
                    417:            }
                    418:            if (strncmp(*cur, "iolog_ttyout=", sizeof("iolog_ttyout=") - 1) == 0) {
1.1.1.2   misho     419:                if (atobool(*cur + sizeof("iolog_ttyout=") - 1) == true)
1.1.1.4   misho     420:                    io_log_files[IOFD_TTYOUT].enabled = true;
1.1       misho     421:                continue;
                    422:            }
                    423:            if (strncmp(*cur, "iolog_compress=", sizeof("iolog_compress=") - 1) == 0) {
1.1.1.2   misho     424:                if (atobool(*cur + sizeof("iolog_compress=") - 1) == true)
                    425:                    iolog_compress = true; /* must be global */
1.1       misho     426:                continue;
                    427:            }
                    428:            break;
1.1.1.4   misho     429:        case 'm':
                    430:            if (strncmp(*cur, "maxseq=", sizeof("maxseq=") - 1) == 0)
                    431:                io_set_max_sessid(*cur + sizeof("maxseq=") - 1);
                    432:            break;
1.1       misho     433:        case 'r':
                    434:            if (strncmp(*cur, "runas_gid=", sizeof("runas_gid=") - 1) == 0) {
                    435:                runas_gid_str = *cur + sizeof("runas_gid=") - 1;
                    436:                continue;
                    437:            }
                    438:            if (strncmp(*cur, "runas_egid=", sizeof("runas_egid=") - 1) == 0) {
                    439:                runas_egid_str = *cur + sizeof("runas_egid=") - 1;
                    440:                continue;
                    441:            }
                    442:            if (strncmp(*cur, "runas_uid=", sizeof("runas_uid=") - 1) == 0) {
                    443:                runas_uid_str = *cur + sizeof("runas_uid=") - 1;
                    444:                continue;
                    445:            }
                    446:            if (strncmp(*cur, "runas_euid=", sizeof("runas_euid=") - 1) == 0) {
                    447:                runas_euid_str = *cur + sizeof("runas_euid=") - 1;
                    448:                continue;
                    449:            }
                    450:            break;
                    451:        }
                    452:     }
                    453: 
                    454:     /*
                    455:      * Lookup runas user and group, preferring effective over real uid/gid.
                    456:      */
                    457:     if (runas_euid_str != NULL)
                    458:        runas_uid_str = runas_euid_str;
                    459:     if (runas_uid_str != NULL) {
1.1.1.5   misho     460:        id = atoid(runas_uid_str, NULL, NULL, &errstr);
                    461:        if (errstr != NULL)
1.1.1.6 ! misho     462:            warningx("runas uid %s: %s", runas_uid_str, U_(errstr));
1.1.1.5   misho     463:        else
                    464:            runas_uid = (uid_t)id;
1.1       misho     465:     }
                    466:     if (runas_egid_str != NULL)
                    467:        runas_gid_str = runas_egid_str;
                    468:     if (runas_gid_str != NULL) {
1.1.1.5   misho     469:        id = atoid(runas_gid_str, NULL, NULL, &errstr);
                    470:        if (errstr != NULL)
1.1.1.6 ! misho     471:            warningx("runas gid %s: %s", runas_gid_str, U_(errstr));
1.1.1.5   misho     472:        else
                    473:            runas_gid = (gid_t)id;
1.1       misho     474:     }
                    475: 
                    476:     details->runas_pw = sudo_getpwuid(runas_uid);
                    477:     if (details->runas_pw == NULL) {
1.1.1.5   misho     478:        idbuf[0] = '#';
                    479:        strlcpy(&idbuf[1], runas_uid_str, sizeof(idbuf) - 1);
                    480:        details->runas_pw = sudo_fakepwnam(idbuf, runas_gid);
1.1       misho     481:     }
                    482: 
                    483:     if (runas_gid != details->runas_pw->pw_gid) {
                    484:        details->runas_gr = sudo_getgrgid(runas_gid);
                    485:        if (details->runas_gr == NULL) {
1.1.1.5   misho     486:            idbuf[0] = '#';
                    487:            strlcpy(&idbuf[1], runas_gid_str, sizeof(idbuf) - 1);
                    488:            details->runas_gr = sudo_fakegrnam(idbuf);
1.1       misho     489:        }
                    490:     }
1.1.1.4   misho     491:     debug_return_bool(
                    492:        io_log_files[IOFD_STDIN].enabled || io_log_files[IOFD_STDOUT].enabled ||
                    493:        io_log_files[IOFD_STDERR].enabled || io_log_files[IOFD_TTYIN].enabled ||
                    494:        io_log_files[IOFD_TTYOUT].enabled);
1.1       misho     495: }
                    496: 
1.1.1.5   misho     497: /*
                    498:  * Write the "/log" file that contains the user and command info.
                    499:  */
                    500: void
                    501: write_info_log(char *pathbuf, size_t len, struct iolog_details *details,
                    502:     char * const argv[], struct timeval *now)
                    503: {
                    504:     char * const *av;
                    505:     FILE *fp;
                    506:     int fd;
                    507: 
                    508:     pathbuf[len] = '\0';
                    509:     strlcat(pathbuf, "/log", PATH_MAX);
1.1.1.6 ! misho     510:     fd = open(pathbuf, O_CREAT|O_TRUNC|O_WRONLY, S_IRUSR|S_IWUSR);
1.1.1.5   misho     511:     if (fd == -1 || (fp = fdopen(fd, "w")) == NULL)
                    512:        log_fatal(USE_ERRNO, N_("unable to create %s"), pathbuf);
                    513: 
                    514:     fprintf(fp, "%lld:%s:%s:%s:%s:%d:%d\n%s\n%s", (long long)now->tv_sec,
                    515:        details->user ? details->user : "unknown", details->runas_pw->pw_name,
                    516:        details->runas_gr ? details->runas_gr->gr_name : "",
                    517:        details->tty ? details->tty : "unknown", details->lines, details->cols,
                    518:        details->cwd ? details->cwd : "unknown",
                    519:        details->command ? details->command : "unknown");
                    520:     for (av = argv + 1; *av != NULL; av++) {
                    521:        fputc(' ', fp);
                    522:        fputs(*av, fp);
                    523:     }
                    524:     fputc('\n', fp);
                    525:     fclose(fp);
                    526: }
                    527: 
1.1       misho     528: static int
                    529: sudoers_io_open(unsigned int version, sudo_conv_t conversation,
                    530:     sudo_printf_t plugin_printf, char * const settings[],
                    531:     char * const user_info[], char * const command_info[],
1.1.1.2   misho     532:     int argc, char * const argv[], char * const user_env[], char * const args[])
1.1       misho     533: {
                    534:     struct iolog_details details;
                    535:     char pathbuf[PATH_MAX], sessid[7];
                    536:     char *tofree = NULL;
                    537:     char * const *cur;
1.1.1.2   misho     538:     const char *debug_flags = NULL;
1.1       misho     539:     size_t len;
1.1.1.4   misho     540:     int i, rval = -1;
1.1.1.2   misho     541:     debug_decl(sudoers_io_open, SUDO_DEBUG_PLUGIN)
1.1       misho     542: 
1.1.1.4   misho     543:     sudo_conv = conversation;
                    544:     sudo_printf = plugin_printf;
1.1       misho     545: 
                    546:     /* If we have no command (because -V was specified) just return. */
                    547:     if (argc == 0)
1.1.1.2   misho     548:        debug_return_bool(true);
1.1       misho     549: 
1.1.1.4   misho     550:     memset(&details, 0, sizeof(details));
                    551: 
                    552:     if (fatal_setjmp() != 0) {
                    553:        /* called via fatal(), fatalx() or log_fatal() */
1.1       misho     554:        rval = -1;
                    555:        goto done;
                    556:     }
                    557: 
                    558:     bindtextdomain("sudoers", LOCALEDIR);
                    559: 
                    560:     sudo_setpwent();
                    561:     sudo_setgrent();
                    562: 
                    563:     /*
1.1.1.2   misho     564:      * Check for debug flags in settings list.
                    565:      */
                    566:     for (cur = settings; *cur != NULL; cur++) {
                    567:        if (strncmp(*cur, "debug_flags=", sizeof("debug_flags=") - 1) == 0)
                    568:            debug_flags = *cur + sizeof("debug_flags=") - 1;
                    569:     }
                    570:     if (debug_flags != NULL)
                    571:        sudo_debug_init(NULL, debug_flags);
                    572: 
                    573:     /*
1.1.1.4   misho     574:      * Pull iolog settings out of command_info.
1.1       misho     575:      */
1.1.1.4   misho     576:     if (!iolog_deserialize_info(&details, user_info, command_info)) {
1.1.1.2   misho     577:        rval = false;
1.1       misho     578:        goto done;
                    579:     }
                    580: 
                    581:     /* If no I/O log path defined we need to figure it out ourselves. */
                    582:     if (details.iolog_path == NULL) {
                    583:        /* Get next session ID and convert it into a path. */
                    584:        tofree = emalloc(sizeof(_PATH_SUDO_IO_LOGDIR) + sizeof(sessid) + 2);
                    585:        memcpy(tofree, _PATH_SUDO_IO_LOGDIR, sizeof(_PATH_SUDO_IO_LOGDIR));
1.1.1.3   misho     586:        io_nextid(tofree, NULL, sessid);
1.1       misho     587:        snprintf(tofree + sizeof(_PATH_SUDO_IO_LOGDIR), sizeof(sessid) + 2,
                    588:            "%c%c/%c%c/%c%c", sessid[0], sessid[1], sessid[2], sessid[3],
                    589:            sessid[4], sessid[5]);
                    590:        details.iolog_path = tofree;
                    591:     }
                    592: 
                    593:     /*
                    594:      * Make local copy of I/O log path and create it, along with any
                    595:      * intermediate subdirs.  Calls mkdtemp() if iolog_path ends in XXXXXX.
                    596:      */
                    597:     len = mkdir_iopath(details.iolog_path, pathbuf, sizeof(pathbuf));
                    598:     if (len >= sizeof(pathbuf))
                    599:        goto done;
                    600: 
1.1.1.5   misho     601:     /* Write log file with user and command details. */
1.1       misho     602:     gettimeofday(&last_time, NULL);
1.1.1.5   misho     603:     write_info_log(pathbuf, len, &details, argv, &last_time);
                    604: 
1.1.1.6 ! misho     605:     /* Create the timing and I/O log files. */
1.1.1.5   misho     606:     for (i = 0; i < IOFD_MAX; i++)
                    607:        open_io_fd(pathbuf, len, &io_log_files[i], iolog_compress);
1.1.1.4   misho     608: 
                    609:     /*
                    610:      * Clear I/O log function pointers for disabled log functions.
                    611:      */
                    612:     if (!io_log_files[IOFD_STDIN].enabled)
                    613:        sudoers_io.log_stdin = NULL;
                    614:     if (!io_log_files[IOFD_STDOUT].enabled)
                    615:        sudoers_io.log_stdout = NULL;
                    616:     if (!io_log_files[IOFD_STDERR].enabled)
                    617:        sudoers_io.log_stderr = NULL;
                    618:     if (!io_log_files[IOFD_TTYIN].enabled)
                    619:        sudoers_io.log_ttyin = NULL;
                    620:     if (!io_log_files[IOFD_TTYOUT].enabled)
                    621:        sudoers_io.log_ttyout = NULL;
1.1       misho     622: 
1.1.1.2   misho     623:     rval = true;
1.1       misho     624: 
                    625: done:
1.1.1.4   misho     626:     fatal_disable_setjmp();
1.1       misho     627:     efree(tofree);
                    628:     if (details.runas_pw)
1.1.1.3   misho     629:        sudo_pw_delref(details.runas_pw);
1.1       misho     630:     sudo_endpwent();
                    631:     if (details.runas_gr)
1.1.1.3   misho     632:        sudo_gr_delref(details.runas_gr);
1.1       misho     633:     sudo_endgrent();
                    634: 
1.1.1.2   misho     635:     debug_return_bool(rval);
1.1       misho     636: }
                    637: 
                    638: static void
                    639: sudoers_io_close(int exit_status, int error)
                    640: {
                    641:     int i;
1.1.1.2   misho     642:     debug_decl(sudoers_io_close, SUDO_DEBUG_PLUGIN)
1.1       misho     643: 
1.1.1.4   misho     644:     if (fatal_setjmp() != 0) {
                    645:        /* called via fatal(), fatalx() or log_fatal() */
                    646:        fatal_disable_setjmp();
1.1.1.2   misho     647:        debug_return;
1.1       misho     648:     }
                    649: 
                    650:     for (i = 0; i < IOFD_MAX; i++) {
1.1.1.4   misho     651:        if (io_log_files[i].fd.v == NULL)
1.1       misho     652:            continue;
                    653: #ifdef HAVE_ZLIB_H
                    654:        if (iolog_compress)
1.1.1.4   misho     655:            gzclose(io_log_files[i].fd.g);
1.1       misho     656:        else
                    657: #endif
1.1.1.4   misho     658:            fclose(io_log_files[i].fd.f);
1.1       misho     659:     }
1.1.1.2   misho     660:     debug_return;
1.1       misho     661: }
                    662: 
                    663: static int
                    664: sudoers_io_version(int verbose)
                    665: {
1.1.1.2   misho     666:     debug_decl(sudoers_io_version, SUDO_DEBUG_PLUGIN)
                    667: 
1.1.1.4   misho     668:     if (fatal_setjmp() != 0) {
                    669:        /* called via fatal(), fatalx() or log_fatal() */
                    670:        fatal_disable_setjmp();
1.1.1.2   misho     671:        debug_return_bool(-1);
1.1       misho     672:     }
                    673: 
                    674:     sudo_printf(SUDO_CONV_INFO_MSG, "Sudoers I/O plugin version %s\n",
                    675:        PACKAGE_VERSION);
                    676: 
1.1.1.2   misho     677:     debug_return_bool(true);
1.1       misho     678: }
                    679: 
                    680: /*
                    681:  * Generic I/O logging function.  Called by the I/O logging entry points.
                    682:  */
                    683: static int
                    684: sudoers_io_log(const char *buf, unsigned int len, int idx)
                    685: {
                    686:     struct timeval now, delay;
1.1.1.2   misho     687:     debug_decl(sudoers_io_version, SUDO_DEBUG_PLUGIN)
1.1       misho     688: 
                    689:     gettimeofday(&now, NULL);
                    690: 
1.1.1.4   misho     691:     if (fatal_setjmp() != 0) {
                    692:        /* called via fatal(), fatalx() or log_fatal() */
                    693:        fatal_disable_setjmp();
1.1.1.2   misho     694:        debug_return_bool(-1);
1.1       misho     695:     }
                    696: 
                    697: #ifdef HAVE_ZLIB_H
                    698:     if (iolog_compress)
1.1.1.4   misho     699:        ignore_result(gzwrite(io_log_files[idx].fd.g, (const voidp)buf, len));
1.1       misho     700:     else
                    701: #endif
1.1.1.4   misho     702:        ignore_result(fwrite(buf, 1, len, io_log_files[idx].fd.f));
1.1.1.6 ! misho     703:     sudo_timevalsub(&now, &last_time, &delay);
1.1       misho     704: #ifdef HAVE_ZLIB_H
                    705:     if (iolog_compress)
1.1.1.6 ! misho     706:        gzprintf(io_log_files[IOFD_TIMING].fd.g, "%d %f %u\n", idx,
1.1       misho     707:            delay.tv_sec + ((double)delay.tv_usec / 1000000), len);
                    708:     else
                    709: #endif
1.1.1.6 ! misho     710:        fprintf(io_log_files[IOFD_TIMING].fd.f, "%d %f %u\n", idx,
1.1       misho     711:            delay.tv_sec + ((double)delay.tv_usec / 1000000), len);
                    712:     last_time.tv_sec = now.tv_sec;
                    713:     last_time.tv_usec = now.tv_usec;
                    714: 
1.1.1.2   misho     715:     debug_return_bool(true);
1.1       misho     716: }
                    717: 
                    718: static int
                    719: sudoers_io_log_ttyin(const char *buf, unsigned int len)
                    720: {
                    721:     return sudoers_io_log(buf, len, IOFD_TTYIN);
                    722: }
                    723: 
                    724: static int
                    725: sudoers_io_log_ttyout(const char *buf, unsigned int len)
                    726: {
                    727:     return sudoers_io_log(buf, len, IOFD_TTYOUT);
                    728: }
                    729: 
                    730: static int
                    731: sudoers_io_log_stdin(const char *buf, unsigned int len)
                    732: {
                    733:     return sudoers_io_log(buf, len, IOFD_STDIN);
                    734: }
                    735: 
                    736: static int
                    737: sudoers_io_log_stdout(const char *buf, unsigned int len)
                    738: {
                    739:     return sudoers_io_log(buf, len, IOFD_STDOUT);
                    740: }
                    741: 
                    742: static int
                    743: sudoers_io_log_stderr(const char *buf, unsigned int len)
                    744: {
                    745:     return sudoers_io_log(buf, len, IOFD_STDERR);
                    746: }
                    747: 
1.1.1.3   misho     748: __dso_public struct io_plugin sudoers_io = {
1.1       misho     749:     SUDO_IO_PLUGIN,
                    750:     SUDO_API_VERSION,
                    751:     sudoers_io_open,
                    752:     sudoers_io_close,
                    753:     sudoers_io_version,
                    754:     sudoers_io_log_ttyin,
                    755:     sudoers_io_log_ttyout,
                    756:     sudoers_io_log_stdin,
                    757:     sudoers_io_log_stdout,
                    758:     sudoers_io_log_stderr
                    759: };

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>