[BACK]Return to sudo_edit.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / sudo

Annotation of src/usr.bin/sudo/sudo_edit.c, Revision 1.2

1.1       millert     1: /*
                      2:  * Copyright (c) 2004 Todd C. Miller <Todd.Miller@courtesan.com>
                      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/param.h>
                     21: #include <sys/stat.h>
                     22: #include <sys/time.h>
                     23: #include <sys/wait.h>
                     24: #include <sys/socket.h>
                     25: #include <stdio.h>
                     26: #ifdef STDC_HEADERS
                     27: # include <stdlib.h>
                     28: # include <stddef.h>
                     29: #else
                     30: # ifdef HAVE_STDLIB_H
                     31: #  include <stdlib.h>
                     32: # endif
                     33: #endif /* STDC_HEADERS */
                     34: #ifdef HAVE_STRING_H
                     35: # include <string.h>
                     36: #else
                     37: # ifdef HAVE_STRINGS_H
                     38: #  include <strings.h>
                     39: # endif
                     40: #endif /* HAVE_STRING_H */
                     41: #ifdef HAVE_UNISTD_H
                     42: # include <unistd.h>
                     43: #endif /* HAVE_UNISTD_H */
                     44: #ifdef HAVE_ERR_H
                     45: # include <err.h>
                     46: #else
                     47: # include "emul/err.h"
                     48: #endif /* HAVE_ERR_H */
                     49: #include <ctype.h>
                     50: #include <pwd.h>
                     51: #include <signal.h>
                     52: #include <errno.h>
                     53: #include <fcntl.h>
                     54: #include <time.h>
                     55:
                     56: #include "sudo.h"
                     57:
                     58: #ifndef lint
                     59: static const char rcsid[] = "$Sudo: sudo_edit.c,v 1.16 2004/09/15 16:16:20 millert Exp $";
                     60: #endif /* lint */
                     61:
                     62: extern sigaction_t saved_sa_int, saved_sa_quit, saved_sa_tstp, saved_sa_chld;
                     63:
                     64: /*
                     65:  * Wrapper to allow users to edit privileged files with their own uid.
                     66:  */
                     67: int sudo_edit(argc, argv)
                     68:     int argc;
                     69:     char **argv;
                     70: {
                     71:     ssize_t nread, nwritten;
                     72:     pid_t kidpid, pid;
                     73:     const char *tmpdir;
                     74:     char **nargv, **ap, *editor, *cp;
                     75:     char buf[BUFSIZ];
1.2     ! millert    76:     int i, ac, ofd, tfd, nargc, rval, tmplen;
1.1       millert    77:     sigaction_t sa;
                     78:     struct stat sb;
                     79:     struct timespec ts1, ts2;
                     80:     struct tempfile {
                     81:        char *tfile;
                     82:        char *ofile;
                     83:        struct timespec omtim;
                     84:        off_t osize;
                     85:     } *tf;
                     86:
                     87:     /*
                     88:      * Find our temporary directory, one of /var/tmp, /usr/tmp, or /tmp
                     89:      */
                     90:     if (stat(_PATH_VARTMP, &sb) == 0 && S_ISDIR(sb.st_mode))
                     91:        tmpdir = _PATH_VARTMP;
                     92: #ifdef _PATH_USRTMP
                     93:     else if (stat(_PATH_USRTMP, &sb) == 0 && S_ISDIR(sb.st_mode))
                     94:        tmpdir = _PATH_USRTMP;
                     95: #endif
                     96:     else
                     97:        tmpdir = _PATH_TMP;
1.2     ! millert    98:     tmplen = strlen(tmpdir);
        !            99:     while (tmplen > 0 && tmpdir[tmplen - 1] == '/')
        !           100:        tmplen--;
1.1       millert   101:
                    102:     /*
                    103:      * For each file specified by the user, make a temporary version
                    104:      * and copy the contents of the original to it.
                    105:      * XXX - It would be nice to lock the original files but that means
                    106:      *       keeping an extra fd open for each file.
                    107:      */
                    108:     tf = emalloc2(argc - 1, sizeof(*tf));
                    109:     memset(tf, 0, (argc - 1) * sizeof(*tf));
                    110:     for (i = 0, ap = argv + 1; i < argc - 1 && *ap != NULL; i++, ap++) {
                    111:        set_perms(PERM_RUNAS);
                    112:        ofd = open(*ap, O_RDONLY, 0644);
                    113:        if (ofd != -1) {
                    114: #ifdef HAVE_FSTAT
                    115:            if (fstat(ofd, &sb) != 0) {
                    116: #else
                    117:            if (stat(tf[i].ofile, &sb) != 0) {
                    118: #endif
                    119:                close(ofd);     /* XXX - could reset errno */
                    120:                ofd = -1;
                    121:            }
                    122:        }
                    123:        set_perms(PERM_ROOT);
                    124:        if (ofd == -1) {
                    125:            if (errno != ENOENT) {
                    126:                warn("%s", *ap);
                    127:                argc--;
                    128:                i--;
                    129:                continue;
                    130:            }
                    131:            memset(&sb, 0, sizeof(sb));
                    132:        } else if (!S_ISREG(sb.st_mode)) {
                    133:            warnx("%s: not a regular file", *ap);
                    134:            close(ofd);
                    135:            argc--;
                    136:            i--;
                    137:            continue;
                    138:        }
                    139:        tf[i].ofile = *ap;
                    140:        tf[i].omtim.tv_sec = mtim_getsec(sb);
                    141:        tf[i].omtim.tv_nsec = mtim_getnsec(sb);
                    142:        tf[i].osize = sb.st_size;
                    143:        if ((cp = strrchr(tf[i].ofile, '/')) != NULL)
                    144:            cp++;
                    145:        else
                    146:            cp = tf[i].ofile;
1.2     ! millert   147:        easprintf(&tf[i].tfile, "%.*s/%s.XXXXXXXX", tmplen, tmpdir, cp);
1.1       millert   148:        set_perms(PERM_USER);
                    149:        tfd = mkstemp(tf[i].tfile);
                    150:        set_perms(PERM_ROOT);
                    151:        if (tfd == -1) {
                    152:            warn("mkstemp");
                    153:            goto cleanup;
                    154:        }
                    155:        if (ofd != -1) {
                    156:            while ((nread = read(ofd, buf, sizeof(buf))) != 0) {
                    157:                if ((nwritten = write(tfd, buf, nread)) != nread) {
                    158:                    if (nwritten == -1)
                    159:                        warn("%s", tf[i].tfile);
                    160:                    else
                    161:                        warnx("%s: short write", tf[i].tfile);
                    162:                    goto cleanup;
                    163:                }
                    164:            }
                    165:            close(ofd);
                    166:        }
                    167: #ifdef HAVE_FSTAT
                    168:        /*
                    169:         * If we are unable to set the mtime on the temp file to the value
                    170:         * of the original file just make the stashed mtime match the temp
                    171:         * file's mtime.  It is better than nothing and we only use the info
                    172:         * to determine whether or not a file has been modified.
                    173:         */
                    174:        if (touch(tfd, NULL, &tf[i].omtim) == -1) {
                    175:            if (fstat(tfd, &sb) == 0) {
                    176:                tf[i].omtim.tv_sec = mtim_getsec(sb);
                    177:                tf[i].omtim.tv_nsec = mtim_getnsec(sb);
                    178:            }
                    179:            /* XXX - else error? */
                    180:        }
                    181: #endif
                    182:        close(tfd);
                    183:     }
                    184:     if (argc == 1)
                    185:        return(1);                      /* no files readable, you lose */
                    186:
                    187:     /*
                    188:      * Determine which editor to use.  We don't bother restricting this
                    189:      * based on def_env_editor or def_editor since the editor runs with
                    190:      * the uid of the invoking user, not the runas (privileged) user.
                    191:      */
                    192:     if (((editor = getenv("VISUAL")) != NULL && *editor != '\0') ||
                    193:        ((editor = getenv("EDITOR")) != NULL && *editor != '\0')) {
                    194:        editor = estrdup(editor);
                    195:     } else {
                    196:        editor = estrdup(def_editor);
                    197:        if ((cp = strchr(editor, ':')) != NULL)
                    198:            *cp = '\0';                 /* def_editor could be a path */
                    199:     }
                    200:
                    201:     /*
                    202:      * Allocate space for the new argument vector and fill it in.
                    203:      * The EDITOR and VISUAL environment variables may contain command
                    204:      * line args so look for those and alloc space for them too.
                    205:      */
                    206:     nargc = argc;
                    207:     for (cp = editor + 1; *cp != '\0'; cp++) {
                    208:        if (isblank((unsigned char)cp[0]) && !isblank((unsigned char)cp[-1]))
                    209:            nargc++;
                    210:     }
                    211:     nargv = (char **) emalloc2(nargc + 1, sizeof(char *));
                    212:     ac = 0;
                    213:     for ((cp = strtok(editor, " \t")); cp != NULL; (cp = strtok(NULL, " \t")))
                    214:        nargv[ac++] = cp;
                    215:     for (i = 0; i < argc - 1 && ac < nargc; )
                    216:        nargv[ac++] = tf[i++].tfile;
                    217:     nargv[ac] = NULL;
                    218:
                    219:     /* We wait for our own children and can be suspended. */
                    220:     sigemptyset(&sa.sa_mask);
                    221:     sa.sa_flags = SA_RESTART;
                    222:     sa.sa_handler = SIG_DFL;
                    223:     (void) sigaction(SIGCHLD, &sa, NULL);
                    224:     (void) sigaction(SIGTSTP, &saved_sa_tstp, NULL);
                    225:
                    226:     /*
                    227:      * Fork and exec the editor with the invoking user's creds,
                    228:      * keeping track of the time spent in the editor.
                    229:      */
                    230:     gettime(&ts1);
                    231:     kidpid = fork();
                    232:     if (kidpid == -1) {
                    233:        warn("fork");
                    234:        goto cleanup;
                    235:     } else if (kidpid == 0) {
                    236:        /* child */
                    237:        (void) sigaction(SIGINT, &saved_sa_int, NULL);
                    238:        (void) sigaction(SIGQUIT, &saved_sa_quit, NULL);
                    239:        (void) sigaction(SIGCHLD, &saved_sa_chld, NULL);
                    240:        set_perms(PERM_FULL_USER);
                    241:        execvp(nargv[0], nargv);
                    242:        warn("unable to execute %s", nargv[0]);
                    243:        _exit(127);
                    244:     }
                    245:
                    246:     /*
                    247:      * Wait for status from the child.  Most modern kernels
                    248:      * will not let an unprivileged child process send a
                    249:      * signal to its privileged parent to we have to request
                    250:      * status when the child is stopped and then send the
                    251:      * same signal to our own pid.
                    252:      */
                    253:     do {
                    254: #ifdef sudo_waitpid
                    255:         pid = sudo_waitpid(kidpid, &i, WUNTRACED);
                    256: #else
                    257:        pid = wait(&i);
                    258: #endif
                    259:        if (pid == kidpid) {
                    260:            if (WIFSTOPPED(i))
                    261:                kill(getpid(), WSTOPSIG(i));
                    262:            else
                    263:                break;
                    264:        }
                    265:     } while (pid != -1 || errno == EINTR);
                    266:     gettime(&ts2);
                    267:     if (pid == -1 || !WIFEXITED(i))
                    268:        rval = 1;
                    269:     else
                    270:        rval = WEXITSTATUS(i);
                    271:
                    272:     /* Copy contents of temp files to real ones */
                    273:     for (i = 0; i < argc - 1; i++) {
                    274:        set_perms(PERM_USER);
                    275:        tfd = open(tf[i].tfile, O_RDONLY, 0644);
                    276:        set_perms(PERM_ROOT);
                    277:        if (tfd < 0) {
                    278:            warn("unable to read %s", tf[i].tfile);
                    279:            warnx("%s left unmodified", tf[i].ofile);
                    280:            continue;
                    281:        }
                    282: #ifdef HAVE_FSTAT
                    283:        if (fstat(tfd, &sb) == 0) {
                    284:            if (!S_ISREG(sb.st_mode)) {
                    285:                warnx("%s: not a regular file", tf[i].tfile);
                    286:                warnx("%s left unmodified", tf[i].ofile);
                    287:                continue;
                    288:            }
                    289:            if (tf[i].osize == sb.st_size &&
                    290:                tf[i].omtim.tv_sec == mtim_getsec(sb) &&
                    291:                tf[i].omtim.tv_nsec == mtim_getnsec(sb)) {
                    292:                /*
                    293:                 * If mtime and size match but the user spent no measurable
                    294:                 * time in the editor we can't tell if the file was changed.
                    295:                 */
                    296:                timespecsub(&ts1, &ts2, &ts2);
                    297:                if (timespecisset(&ts2)) {
                    298:                    warnx("%s unchanged", tf[i].ofile);
                    299:                    unlink(tf[i].tfile);
                    300:                    close(tfd);
                    301:                    continue;
                    302:                }
                    303:            }
                    304:        }
                    305: #endif
                    306:        set_perms(PERM_RUNAS);
                    307:        ofd = open(tf[i].ofile, O_WRONLY|O_TRUNC|O_CREAT, 0644);
                    308:        set_perms(PERM_ROOT);
                    309:        if (ofd == -1) {
                    310:            warn("unable to write to %s", tf[i].ofile);
                    311:            warnx("contents of edit session left in %s", tf[i].tfile);
                    312:            close(tfd);
                    313:            continue;
                    314:        }
                    315:        while ((nread = read(tfd, buf, sizeof(buf))) > 0) {
                    316:            if ((nwritten = write(ofd, buf, nread)) != nread) {
                    317:                if (nwritten == -1)
                    318:                    warn("%s", tf[i].ofile);
                    319:                else
                    320:                    warnx("%s: short write", tf[i].ofile);
                    321:                break;
                    322:            }
                    323:        }
                    324:        if (nread == 0) {
                    325:            /* success, got EOF */
                    326:            unlink(tf[i].tfile);
                    327:        } else if (nread < 0) {
                    328:            warn("unable to read temporary file");
                    329:            warnx("contents of edit session left in %s", tf[i].tfile);
                    330:        } else {
                    331:            warn("unable to write to %s", tf[i].ofile);
                    332:            warnx("contents of edit session left in %s", tf[i].tfile);
                    333:        }
                    334:        close(ofd);
                    335:     }
                    336:
                    337:     return(rval);
                    338: cleanup:
                    339:     /* Clean up temp files and return. */
                    340:     for (i = 0; i < argc - 1; i++) {
                    341:        if (tf[i].tfile != NULL)
                    342:            unlink(tf[i].tfile);
                    343:     }
                    344:     return(1);
                    345: }