[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.1

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];
        !            76:     int i, ac, ofd, tfd, nargc, rval;
        !            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;
        !            98:
        !            99:     /*
        !           100:      * For each file specified by the user, make a temporary version
        !           101:      * and copy the contents of the original to it.
        !           102:      * XXX - It would be nice to lock the original files but that means
        !           103:      *       keeping an extra fd open for each file.
        !           104:      */
        !           105:     tf = emalloc2(argc - 1, sizeof(*tf));
        !           106:     memset(tf, 0, (argc - 1) * sizeof(*tf));
        !           107:     for (i = 0, ap = argv + 1; i < argc - 1 && *ap != NULL; i++, ap++) {
        !           108:        set_perms(PERM_RUNAS);
        !           109:        ofd = open(*ap, O_RDONLY, 0644);
        !           110:        if (ofd != -1) {
        !           111: #ifdef HAVE_FSTAT
        !           112:            if (fstat(ofd, &sb) != 0) {
        !           113: #else
        !           114:            if (stat(tf[i].ofile, &sb) != 0) {
        !           115: #endif
        !           116:                close(ofd);     /* XXX - could reset errno */
        !           117:                ofd = -1;
        !           118:            }
        !           119:        }
        !           120:        set_perms(PERM_ROOT);
        !           121:        if (ofd == -1) {
        !           122:            if (errno != ENOENT) {
        !           123:                warn("%s", *ap);
        !           124:                argc--;
        !           125:                i--;
        !           126:                continue;
        !           127:            }
        !           128:            memset(&sb, 0, sizeof(sb));
        !           129:        } else if (!S_ISREG(sb.st_mode)) {
        !           130:            warnx("%s: not a regular file", *ap);
        !           131:            close(ofd);
        !           132:            argc--;
        !           133:            i--;
        !           134:            continue;
        !           135:        }
        !           136:        tf[i].ofile = *ap;
        !           137:        tf[i].omtim.tv_sec = mtim_getsec(sb);
        !           138:        tf[i].omtim.tv_nsec = mtim_getnsec(sb);
        !           139:        tf[i].osize = sb.st_size;
        !           140:        if ((cp = strrchr(tf[i].ofile, '/')) != NULL)
        !           141:            cp++;
        !           142:        else
        !           143:            cp = tf[i].ofile;
        !           144:        easprintf(&tf[i].tfile, "%s%s.XXXXXXXX", tmpdir, cp);
        !           145:        set_perms(PERM_USER);
        !           146:        tfd = mkstemp(tf[i].tfile);
        !           147:        set_perms(PERM_ROOT);
        !           148:        if (tfd == -1) {
        !           149:            warn("mkstemp");
        !           150:            goto cleanup;
        !           151:        }
        !           152:        if (ofd != -1) {
        !           153:            while ((nread = read(ofd, buf, sizeof(buf))) != 0) {
        !           154:                if ((nwritten = write(tfd, buf, nread)) != nread) {
        !           155:                    if (nwritten == -1)
        !           156:                        warn("%s", tf[i].tfile);
        !           157:                    else
        !           158:                        warnx("%s: short write", tf[i].tfile);
        !           159:                    goto cleanup;
        !           160:                }
        !           161:            }
        !           162:            close(ofd);
        !           163:        }
        !           164: #ifdef HAVE_FSTAT
        !           165:        /*
        !           166:         * If we are unable to set the mtime on the temp file to the value
        !           167:         * of the original file just make the stashed mtime match the temp
        !           168:         * file's mtime.  It is better than nothing and we only use the info
        !           169:         * to determine whether or not a file has been modified.
        !           170:         */
        !           171:        if (touch(tfd, NULL, &tf[i].omtim) == -1) {
        !           172:            if (fstat(tfd, &sb) == 0) {
        !           173:                tf[i].omtim.tv_sec = mtim_getsec(sb);
        !           174:                tf[i].omtim.tv_nsec = mtim_getnsec(sb);
        !           175:            }
        !           176:            /* XXX - else error? */
        !           177:        }
        !           178: #endif
        !           179:        close(tfd);
        !           180:     }
        !           181:     if (argc == 1)
        !           182:        return(1);                      /* no files readable, you lose */
        !           183:
        !           184:     /*
        !           185:      * Determine which editor to use.  We don't bother restricting this
        !           186:      * based on def_env_editor or def_editor since the editor runs with
        !           187:      * the uid of the invoking user, not the runas (privileged) user.
        !           188:      */
        !           189:     if (((editor = getenv("VISUAL")) != NULL && *editor != '\0') ||
        !           190:        ((editor = getenv("EDITOR")) != NULL && *editor != '\0')) {
        !           191:        editor = estrdup(editor);
        !           192:     } else {
        !           193:        editor = estrdup(def_editor);
        !           194:        if ((cp = strchr(editor, ':')) != NULL)
        !           195:            *cp = '\0';                 /* def_editor could be a path */
        !           196:     }
        !           197:
        !           198:     /*
        !           199:      * Allocate space for the new argument vector and fill it in.
        !           200:      * The EDITOR and VISUAL environment variables may contain command
        !           201:      * line args so look for those and alloc space for them too.
        !           202:      */
        !           203:     nargc = argc;
        !           204:     for (cp = editor + 1; *cp != '\0'; cp++) {
        !           205:        if (isblank((unsigned char)cp[0]) && !isblank((unsigned char)cp[-1]))
        !           206:            nargc++;
        !           207:     }
        !           208:     nargv = (char **) emalloc2(nargc + 1, sizeof(char *));
        !           209:     ac = 0;
        !           210:     for ((cp = strtok(editor, " \t")); cp != NULL; (cp = strtok(NULL, " \t")))
        !           211:        nargv[ac++] = cp;
        !           212:     for (i = 0; i < argc - 1 && ac < nargc; )
        !           213:        nargv[ac++] = tf[i++].tfile;
        !           214:     nargv[ac] = NULL;
        !           215:
        !           216:     /* We wait for our own children and can be suspended. */
        !           217:     sigemptyset(&sa.sa_mask);
        !           218:     sa.sa_flags = SA_RESTART;
        !           219:     sa.sa_handler = SIG_DFL;
        !           220:     (void) sigaction(SIGCHLD, &sa, NULL);
        !           221:     (void) sigaction(SIGTSTP, &saved_sa_tstp, NULL);
        !           222:
        !           223:     /*
        !           224:      * Fork and exec the editor with the invoking user's creds,
        !           225:      * keeping track of the time spent in the editor.
        !           226:      */
        !           227:     gettime(&ts1);
        !           228:     kidpid = fork();
        !           229:     if (kidpid == -1) {
        !           230:        warn("fork");
        !           231:        goto cleanup;
        !           232:     } else if (kidpid == 0) {
        !           233:        /* child */
        !           234:        (void) sigaction(SIGINT, &saved_sa_int, NULL);
        !           235:        (void) sigaction(SIGQUIT, &saved_sa_quit, NULL);
        !           236:        (void) sigaction(SIGCHLD, &saved_sa_chld, NULL);
        !           237:        set_perms(PERM_FULL_USER);
        !           238:        execvp(nargv[0], nargv);
        !           239:        warn("unable to execute %s", nargv[0]);
        !           240:        _exit(127);
        !           241:     }
        !           242:
        !           243:     /*
        !           244:      * Wait for status from the child.  Most modern kernels
        !           245:      * will not let an unprivileged child process send a
        !           246:      * signal to its privileged parent to we have to request
        !           247:      * status when the child is stopped and then send the
        !           248:      * same signal to our own pid.
        !           249:      */
        !           250:     do {
        !           251: #ifdef sudo_waitpid
        !           252:         pid = sudo_waitpid(kidpid, &i, WUNTRACED);
        !           253: #else
        !           254:        pid = wait(&i);
        !           255: #endif
        !           256:        if (pid == kidpid) {
        !           257:            if (WIFSTOPPED(i))
        !           258:                kill(getpid(), WSTOPSIG(i));
        !           259:            else
        !           260:                break;
        !           261:        }
        !           262:     } while (pid != -1 || errno == EINTR);
        !           263:     gettime(&ts2);
        !           264:     if (pid == -1 || !WIFEXITED(i))
        !           265:        rval = 1;
        !           266:     else
        !           267:        rval = WEXITSTATUS(i);
        !           268:
        !           269:     /* Copy contents of temp files to real ones */
        !           270:     for (i = 0; i < argc - 1; i++) {
        !           271:        set_perms(PERM_USER);
        !           272:        tfd = open(tf[i].tfile, O_RDONLY, 0644);
        !           273:        set_perms(PERM_ROOT);
        !           274:        if (tfd < 0) {
        !           275:            warn("unable to read %s", tf[i].tfile);
        !           276:            warnx("%s left unmodified", tf[i].ofile);
        !           277:            continue;
        !           278:        }
        !           279: #ifdef HAVE_FSTAT
        !           280:        if (fstat(tfd, &sb) == 0) {
        !           281:            if (!S_ISREG(sb.st_mode)) {
        !           282:                warnx("%s: not a regular file", tf[i].tfile);
        !           283:                warnx("%s left unmodified", tf[i].ofile);
        !           284:                continue;
        !           285:            }
        !           286:            if (tf[i].osize == sb.st_size &&
        !           287:                tf[i].omtim.tv_sec == mtim_getsec(sb) &&
        !           288:                tf[i].omtim.tv_nsec == mtim_getnsec(sb)) {
        !           289:                /*
        !           290:                 * If mtime and size match but the user spent no measurable
        !           291:                 * time in the editor we can't tell if the file was changed.
        !           292:                 */
        !           293:                timespecsub(&ts1, &ts2, &ts2);
        !           294:                if (timespecisset(&ts2)) {
        !           295:                    warnx("%s unchanged", tf[i].ofile);
        !           296:                    unlink(tf[i].tfile);
        !           297:                    close(tfd);
        !           298:                    continue;
        !           299:                }
        !           300:            }
        !           301:        }
        !           302: #endif
        !           303:        set_perms(PERM_RUNAS);
        !           304:        ofd = open(tf[i].ofile, O_WRONLY|O_TRUNC|O_CREAT, 0644);
        !           305:        set_perms(PERM_ROOT);
        !           306:        if (ofd == -1) {
        !           307:            warn("unable to write to %s", tf[i].ofile);
        !           308:            warnx("contents of edit session left in %s", tf[i].tfile);
        !           309:            close(tfd);
        !           310:            continue;
        !           311:        }
        !           312:        while ((nread = read(tfd, buf, sizeof(buf))) > 0) {
        !           313:            if ((nwritten = write(ofd, buf, nread)) != nread) {
        !           314:                if (nwritten == -1)
        !           315:                    warn("%s", tf[i].ofile);
        !           316:                else
        !           317:                    warnx("%s: short write", tf[i].ofile);
        !           318:                break;
        !           319:            }
        !           320:        }
        !           321:        if (nread == 0) {
        !           322:            /* success, got EOF */
        !           323:            unlink(tf[i].tfile);
        !           324:        } else if (nread < 0) {
        !           325:            warn("unable to read temporary file");
        !           326:            warnx("contents of edit session left in %s", tf[i].tfile);
        !           327:        } else {
        !           328:            warn("unable to write to %s", tf[i].ofile);
        !           329:            warnx("contents of edit session left in %s", tf[i].tfile);
        !           330:        }
        !           331:        close(ofd);
        !           332:     }
        !           333:
        !           334:     return(rval);
        !           335: cleanup:
        !           336:     /* Clean up temp files and return. */
        !           337:     for (i = 0; i < argc - 1; i++) {
        !           338:        if (tf[i].tfile != NULL)
        !           339:            unlink(tf[i].tfile);
        !           340:     }
        !           341:     return(1);
        !           342: }