Annotation of embedaddon/sudo/src/sudo_edit.c, revision 1.1

1.1     ! misho       1: /*
        !             2:  * Copyright (c) 2004-2008, 2010-2011 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: #if defined(HAVE_SETRESUID) || defined(HAVE_SETREUID) || defined(HAVE_SETEUID)
        !            20: 
        !            21: #include <sys/types.h>
        !            22: #include <sys/param.h>
        !            23: #include <sys/stat.h>
        !            24: #include <sys/time.h>
        !            25: #include <sys/wait.h>
        !            26: #include <sys/socket.h>
        !            27: #include <stdio.h>
        !            28: #ifdef STDC_HEADERS
        !            29: # include <stdlib.h>
        !            30: # include <stddef.h>
        !            31: #else
        !            32: # ifdef HAVE_STDLIB_H
        !            33: #  include <stdlib.h>
        !            34: # endif
        !            35: #endif /* STDC_HEADERS */
        !            36: #ifdef HAVE_STRING_H
        !            37: # include <string.h>
        !            38: #endif /* HAVE_STRING_H */
        !            39: #ifdef HAVE_STRINGS_H
        !            40: # include <strings.h>
        !            41: #endif /* HAVE_STRINGS_H */
        !            42: #ifdef HAVE_UNISTD_H
        !            43: # include <unistd.h>
        !            44: #endif /* HAVE_UNISTD_H */
        !            45: #include <ctype.h>
        !            46: #include <grp.h>
        !            47: #include <pwd.h>
        !            48: #include <signal.h>
        !            49: #include <errno.h>
        !            50: #include <fcntl.h>
        !            51: #if TIME_WITH_SYS_TIME
        !            52: # include <time.h>
        !            53: #endif
        !            54: 
        !            55: #include "sudo.h"
        !            56: 
        !            57: static void
        !            58: switch_user(uid_t euid, gid_t egid, int ngroups, GETGROUPS_T *groups)
        !            59: {
        !            60:     int serrno = errno;
        !            61: 
        !            62:     /* When restoring root, change euid first; otherwise change it last. */
        !            63:     if (euid == ROOT_UID) {
        !            64:        if (seteuid(ROOT_UID) != 0)
        !            65:            error(1, "seteuid(ROOT_UID)");
        !            66:     }
        !            67:     if (setegid(egid) != 0)
        !            68:        error(1, "setegid(%d)", (int)egid);
        !            69:     if (ngroups != -1) {
        !            70:        if (sudo_setgroups(ngroups, groups) != 0)
        !            71:            error(1, "setgroups");
        !            72:     }
        !            73:     if (euid != ROOT_UID) {
        !            74:        if (seteuid(euid) != 0)
        !            75:            error(1, "seteuid(%d)", (int)euid);
        !            76:     }
        !            77: 
        !            78:     errno = serrno;
        !            79: }
        !            80: 
        !            81: /*
        !            82:  * Wrapper to allow users to edit privileged files with their own uid.
        !            83:  */
        !            84: int
        !            85: sudo_edit(struct command_details *command_details)
        !            86: {
        !            87:     struct command_details editor_details;
        !            88:     ssize_t nread, nwritten;
        !            89:     const char *tmpdir;
        !            90:     char *cp, *suff, **nargv, **ap, **files = NULL;
        !            91:     char buf[BUFSIZ];
        !            92:     int rc, i, j, ac, ofd, tfd, nargc, rval, tmplen;
        !            93:     int editor_argc = 0, nfiles = 0;
        !            94:     struct stat sb;
        !            95:     struct timeval tv, tv1, tv2;
        !            96:     struct tempfile {
        !            97:        char *tfile;
        !            98:        char *ofile;
        !            99:        struct timeval omtim;
        !           100:        off_t osize;
        !           101:     } *tf;
        !           102: 
        !           103:     /*
        !           104:      * Set real, effective and saved uids to root.
        !           105:      * We will change the euid as needed below.
        !           106:      */
        !           107:     if (setuid(ROOT_UID) != 0) {
        !           108:        warning(_("unable to change uid to root (%u)"), ROOT_UID);
        !           109:        return 1;
        !           110:     }
        !           111: 
        !           112:     /*
        !           113:      * Find our temporary directory, one of /var/tmp, /usr/tmp, or /tmp
        !           114:      */
        !           115:     if (stat(_PATH_VARTMP, &sb) == 0 && S_ISDIR(sb.st_mode))
        !           116:        tmpdir = _PATH_VARTMP;
        !           117: #ifdef _PATH_USRTMP
        !           118:     else if (stat(_PATH_USRTMP, &sb) == 0 && S_ISDIR(sb.st_mode))
        !           119:        tmpdir = _PATH_USRTMP;
        !           120: #endif
        !           121:     else
        !           122:        tmpdir = _PATH_TMP;
        !           123:     tmplen = strlen(tmpdir);
        !           124:     while (tmplen > 0 && tmpdir[tmplen - 1] == '/')
        !           125:        tmplen--;
        !           126: 
        !           127:     /*
        !           128:      * The user's editor must be separated from the files to be
        !           129:      * edited by a "--" option.
        !           130:      */
        !           131:     for (ap = command_details->argv; *ap != NULL; ap++) {
        !           132:        if (files)
        !           133:            nfiles++;
        !           134:        else if (strcmp(*ap, "--") == 0)
        !           135:            files = ap + 1;
        !           136:        else
        !           137:            editor_argc++;
        !           138:     }
        !           139:     if (nfiles == 0) {
        !           140:        warningx(_("plugin error: missing file list for sudoedit"));
        !           141:        return 1;
        !           142:     }
        !           143: 
        !           144:     /*
        !           145:      * For each file specified by the user, make a temporary version
        !           146:      * and copy the contents of the original to it.
        !           147:      */
        !           148:     tf = emalloc2(nfiles, sizeof(*tf));
        !           149:     zero_bytes(tf, nfiles * sizeof(*tf));
        !           150:     for (i = 0, j = 0; i < nfiles; i++) {
        !           151:        rc = -1;
        !           152:        switch_user(command_details->euid, command_details->egid,
        !           153:            command_details->ngroups, command_details->groups);
        !           154:        if ((ofd = open(files[i], O_RDONLY, 0644)) != -1 || errno == ENOENT) {
        !           155:            if (ofd == -1) {
        !           156:                zero_bytes(&sb, sizeof(sb));            /* new file */
        !           157:                rc = 0;
        !           158:            } else {
        !           159:                rc = fstat(ofd, &sb);
        !           160:            }
        !           161:        }
        !           162:        switch_user(ROOT_UID, user_details.egid,
        !           163:            user_details.ngroups, user_details.groups);
        !           164:        if (rc || (ofd != -1 && !S_ISREG(sb.st_mode))) {
        !           165:            if (rc)
        !           166:                warning("%s", files[i]);
        !           167:            else
        !           168:                warningx(_("%s: not a regular file"), files[i]);
        !           169:            if (ofd != -1)
        !           170:                close(ofd);
        !           171:            continue;
        !           172:        }
        !           173:        tf[j].ofile = files[i];
        !           174:        tf[j].osize = sb.st_size;
        !           175:        mtim_get(&sb, &tf[j].omtim);
        !           176:        if ((cp = strrchr(tf[j].ofile, '/')) != NULL)
        !           177:            cp++;
        !           178:        else
        !           179:            cp = tf[j].ofile;
        !           180:        suff = strrchr(cp, '.');
        !           181:        if (suff != NULL) {
        !           182:            easprintf(&tf[j].tfile, "%.*s/%.*sXXXXXXXX%s", tmplen, tmpdir,
        !           183:                (int)(size_t)(suff - cp), cp, suff);
        !           184:        } else {
        !           185:            easprintf(&tf[j].tfile, "%.*s/%s.XXXXXXXX", tmplen, tmpdir, cp);
        !           186:        }
        !           187:        if (seteuid(user_details.uid) != 0)
        !           188:            error(1, "seteuid(%d)", (int)user_details.uid);
        !           189:        tfd = mkstemps(tf[j].tfile, suff ? strlen(suff) : 0);
        !           190:        if (seteuid(ROOT_UID) != 0)
        !           191:            error(1, "seteuid(ROOT_UID)");
        !           192:        if (tfd == -1) {
        !           193:            warning("mkstemps");
        !           194:            goto cleanup;
        !           195:        }
        !           196:        if (ofd != -1) {
        !           197:            while ((nread = read(ofd, buf, sizeof(buf))) != 0) {
        !           198:                if ((nwritten = write(tfd, buf, nread)) != nread) {
        !           199:                    if (nwritten == -1)
        !           200:                        warning("%s", tf[j].tfile);
        !           201:                    else
        !           202:                        warningx(_("%s: short write"), tf[j].tfile);
        !           203:                    goto cleanup;
        !           204:                }
        !           205:            }
        !           206:            close(ofd);
        !           207:        }
        !           208:        /*
        !           209:         * We always update the stashed mtime because the time
        !           210:         * resolution of the filesystem the temporary file is on may
        !           211:         * not match that of the filesystem where the file to be edited
        !           212:         * resides.  It is OK if touch() fails since we only use the info
        !           213:         * to determine whether or not a file has been modified.
        !           214:         */
        !           215:        (void) touch(tfd, NULL, &tf[j].omtim);
        !           216:        rc = fstat(tfd, &sb);
        !           217:        if (!rc)
        !           218:            mtim_get(&sb, &tf[j].omtim);
        !           219:        close(tfd);
        !           220:        j++;
        !           221:     }
        !           222:     if ((nfiles = j) == 0)
        !           223:        return 1;                       /* no files readable, you lose */
        !           224: 
        !           225:     /*
        !           226:      * Allocate space for the new argument vector and fill it in.
        !           227:      * We concatenate the editor with its args and the file list
        !           228:      * to create a new argv.
        !           229:      */
        !           230:     nargc = editor_argc + nfiles;
        !           231:     nargv = (char **) emalloc2(nargc + 1, sizeof(char *));
        !           232:     for (ac = 0; ac < editor_argc; ac++)
        !           233:        nargv[ac] = command_details->argv[ac];
        !           234:     for (i = 0; i < nfiles && ac < nargc; )
        !           235:        nargv[ac++] = tf[i++].tfile;
        !           236:     nargv[ac] = NULL;
        !           237: 
        !           238:     /*
        !           239:      * Run the editor with the invoking user's creds,
        !           240:      * keeping track of the time spent in the editor.
        !           241:      */
        !           242:     gettimeofday(&tv1, NULL);
        !           243:     memcpy(&editor_details, command_details, sizeof(editor_details));
        !           244:     editor_details.uid = user_details.uid;
        !           245:     editor_details.euid = user_details.uid;
        !           246:     editor_details.gid = user_details.gid;
        !           247:     editor_details.egid = user_details.gid;
        !           248:     editor_details.ngroups = user_details.ngroups;
        !           249:     editor_details.groups = user_details.groups;
        !           250:     editor_details.argv = nargv;
        !           251:     rval = run_command(&editor_details);
        !           252:     gettimeofday(&tv2, NULL);
        !           253: 
        !           254:     /* Copy contents of temp files to real ones */
        !           255:     for (i = 0; i < nfiles; i++) {
        !           256:        rc = -1;
        !           257:        if (seteuid(user_details.uid) != 0)
        !           258:            error(1, "seteuid(%d)", (int)user_details.uid);
        !           259:        if ((tfd = open(tf[i].tfile, O_RDONLY, 0644)) != -1) {
        !           260:            rc = fstat(tfd, &sb);
        !           261:        }
        !           262:        if (seteuid(ROOT_UID) != 0)
        !           263:            error(1, "seteuid(ROOT_UID)");
        !           264:        if (rc || !S_ISREG(sb.st_mode)) {
        !           265:            if (rc)
        !           266:                warning("%s", tf[i].tfile);
        !           267:            else
        !           268:                warningx(_("%s: not a regular file"), tf[i].tfile);
        !           269:            warningx(_("%s left unmodified"), tf[i].ofile);
        !           270:            if (tfd != -1)
        !           271:                close(tfd);
        !           272:            continue;
        !           273:        }
        !           274:        mtim_get(&sb, &tv);
        !           275:        if (tf[i].osize == sb.st_size && timevalcmp(&tf[i].omtim, &tv, ==)) {
        !           276:            /*
        !           277:             * If mtime and size match but the user spent no measurable
        !           278:             * time in the editor we can't tell if the file was changed.
        !           279:             */
        !           280:            timevalsub(&tv1, &tv2);
        !           281:            if (timevalisset(&tv2)) {
        !           282:                warningx(_("%s unchanged"), tf[i].ofile);
        !           283:                unlink(tf[i].tfile);
        !           284:                close(tfd);
        !           285:                continue;
        !           286:            }
        !           287:        }
        !           288:        switch_user(command_details->euid, command_details->egid,
        !           289:            command_details->ngroups, command_details->groups);
        !           290:        ofd = open(tf[i].ofile, O_WRONLY|O_TRUNC|O_CREAT, 0644);
        !           291:        switch_user(ROOT_UID, user_details.egid,
        !           292:            user_details.ngroups, user_details.groups);
        !           293:        if (ofd == -1) {
        !           294:            warning(_("unable to write to %s"), tf[i].ofile);
        !           295:            warningx(_("contents of edit session left in %s"), tf[i].tfile);
        !           296:            close(tfd);
        !           297:            continue;
        !           298:        }
        !           299:        while ((nread = read(tfd, buf, sizeof(buf))) > 0) {
        !           300:            if ((nwritten = write(ofd, buf, nread)) != nread) {
        !           301:                if (nwritten == -1)
        !           302:                    warning("%s", tf[i].ofile);
        !           303:                else
        !           304:                    warningx(_("%s: short write"), tf[i].ofile);
        !           305:                break;
        !           306:            }
        !           307:        }
        !           308:        if (nread == 0) {
        !           309:            /* success, got EOF */
        !           310:            unlink(tf[i].tfile);
        !           311:        } else if (nread < 0) {
        !           312:            warning(_("unable to read temporary file"));
        !           313:            warningx(_("contents of edit session left in %s"), tf[i].tfile);
        !           314:        } else {
        !           315:            warning(_("unable to write to %s"), tf[i].ofile);
        !           316:            warningx(_("contents of edit session left in %s"), tf[i].tfile);
        !           317:        }
        !           318:        close(ofd);
        !           319:     }
        !           320: 
        !           321:     return rval;
        !           322: cleanup:
        !           323:     /* Clean up temp files and return. */
        !           324:     for (i = 0; i < nfiles; i++) {
        !           325:        if (tf[i].tfile != NULL)
        !           326:            unlink(tf[i].tfile);
        !           327:     }
        !           328:     return 1;
        !           329: }
        !           330: 
        !           331: #else /* HAVE_SETRESUID || HAVE_SETREUID || HAVE_SETEUID */
        !           332: 
        !           333: /*
        !           334:  * Must have the ability to change the effective uid to use sudoedit.
        !           335:  */
        !           336: int
        !           337: sudo_edit(struct command_details *command_details)
        !           338: {
        !           339:     return 1;
        !           340: }
        !           341: 
        !           342: #endif /* HAVE_SETRESUID || HAVE_SETREUID || HAVE_SETEUID */

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