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: }