File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / sudo / src / sudo_edit.c
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Tue Feb 21 16:23:02 2012 UTC (12 years, 4 months ago) by misho
Branches: sudo, MAIN
CVS tags: v1_8_3p2, HEAD
sudo

    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>