File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / sudo / src / sudo_edit.c
Revision 1.1.1.5 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Sun Jun 15 16:12:55 2014 UTC (10 years ago) by misho
Branches: sudo, MAIN
CVS tags: v1_8_10p3_0, v1_8_10p3, HEAD
sudo v 1.8.10p3

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

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