Annotation of src/usr.bin/sudo/sudo_edit.c, Revision 1.8
1.1 millert 1: /*
1.7 millert 2: * Copyright (c) 2004-2008 Todd C. Miller <Todd.Miller@courtesan.com>
1.1 millert 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:
1.4 millert 17: #include <config.h>
1.1 millert 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: #include <ctype.h>
45: #include <pwd.h>
46: #include <signal.h>
47: #include <errno.h>
48: #include <fcntl.h>
1.4 millert 49: #if TIME_WITH_SYS_TIME
50: # include <time.h>
51: #endif
52: #ifndef HAVE_TIMESPEC
53: # include <emul/timespec.h>
54: #endif
1.1 millert 55:
56: #include "sudo.h"
57:
58: #ifndef lint
1.8 ! millert 59: __unused static const char rcsid[] = "$Sudo: sudo_edit.c,v 1.39 2009/09/30 13:50:58 millert Exp $";
1.1 millert 60: #endif /* lint */
61:
1.6 millert 62: extern sigaction_t saved_sa_int, saved_sa_quit, saved_sa_tstp;
1.4 millert 63: extern char **environ;
1.1 millert 64:
1.7 millert 65: static char *find_editor();
66:
1.1 millert 67: /*
68: * Wrapper to allow users to edit privileged files with their own uid.
69: */
1.7 millert 70: int
71: sudo_edit(argc, argv, envp)
1.1 millert 72: int argc;
73: char **argv;
1.4 millert 74: char **envp;
1.1 millert 75: {
76: ssize_t nread, nwritten;
77: pid_t kidpid, pid;
78: const char *tmpdir;
79: char **nargv, **ap, *editor, *cp;
80: char buf[BUFSIZ];
1.4 millert 81: int error, i, ac, ofd, tfd, nargc, rval, tmplen, wasblank;
1.1 millert 82: struct stat sb;
83: struct timespec ts1, ts2;
84: struct tempfile {
85: char *tfile;
86: char *ofile;
87: struct timespec omtim;
88: off_t osize;
89: } *tf;
90:
91: /*
92: * Find our temporary directory, one of /var/tmp, /usr/tmp, or /tmp
93: */
94: if (stat(_PATH_VARTMP, &sb) == 0 && S_ISDIR(sb.st_mode))
95: tmpdir = _PATH_VARTMP;
96: #ifdef _PATH_USRTMP
97: else if (stat(_PATH_USRTMP, &sb) == 0 && S_ISDIR(sb.st_mode))
98: tmpdir = _PATH_USRTMP;
99: #endif
100: else
101: tmpdir = _PATH_TMP;
1.2 millert 102: tmplen = strlen(tmpdir);
103: while (tmplen > 0 && tmpdir[tmplen - 1] == '/')
104: tmplen--;
1.1 millert 105:
106: /*
1.7 millert 107: * Close password, shadow, and group files before we try to open
108: * user-specified files to prevent the opening of things like /dev/fd/4
109: */
110: sudo_endpwent();
111: sudo_endgrent();
112:
113: /*
1.1 millert 114: * For each file specified by the user, make a temporary version
115: * and copy the contents of the original to it.
116: */
117: tf = emalloc2(argc - 1, sizeof(*tf));
1.7 millert 118: zero_bytes(tf, (argc - 1) * sizeof(*tf));
1.1 millert 119: for (i = 0, ap = argv + 1; i < argc - 1 && *ap != NULL; i++, ap++) {
1.4 millert 120: error = -1;
1.1 millert 121: set_perms(PERM_RUNAS);
1.4 millert 122: if ((ofd = open(*ap, O_RDONLY, 0644)) != -1 || errno == ENOENT) {
123: if (ofd == -1) {
1.7 millert 124: zero_bytes(&sb, sizeof(sb)); /* new file */
1.4 millert 125: error = 0;
126: } else {
1.1 millert 127: #ifdef HAVE_FSTAT
1.4 millert 128: error = fstat(ofd, &sb);
1.1 millert 129: #else
1.4 millert 130: error = stat(tf[i].ofile, &sb);
1.1 millert 131: #endif
132: }
133: }
134: set_perms(PERM_ROOT);
1.5 millert 135: if (error || (ofd != -1 && !S_ISREG(sb.st_mode))) {
1.4 millert 136: if (error)
1.7 millert 137: warning("%s", *ap);
1.4 millert 138: else
1.7 millert 139: warningx("%s: not a regular file", *ap);
1.4 millert 140: if (ofd != -1)
141: close(ofd);
1.1 millert 142: argc--;
143: i--;
144: continue;
145: }
146: tf[i].ofile = *ap;
147: tf[i].omtim.tv_sec = mtim_getsec(sb);
148: tf[i].omtim.tv_nsec = mtim_getnsec(sb);
149: tf[i].osize = sb.st_size;
150: if ((cp = strrchr(tf[i].ofile, '/')) != NULL)
151: cp++;
152: else
153: cp = tf[i].ofile;
1.2 millert 154: easprintf(&tf[i].tfile, "%.*s/%s.XXXXXXXX", tmplen, tmpdir, cp);
1.1 millert 155: set_perms(PERM_USER);
156: tfd = mkstemp(tf[i].tfile);
157: set_perms(PERM_ROOT);
158: if (tfd == -1) {
1.7 millert 159: warning("mkstemp");
1.1 millert 160: goto cleanup;
161: }
162: if (ofd != -1) {
163: while ((nread = read(ofd, buf, sizeof(buf))) != 0) {
164: if ((nwritten = write(tfd, buf, nread)) != nread) {
165: if (nwritten == -1)
1.7 millert 166: warning("%s", tf[i].tfile);
1.1 millert 167: else
1.7 millert 168: warningx("%s: short write", tf[i].tfile);
1.1 millert 169: goto cleanup;
170: }
171: }
172: close(ofd);
173: }
174: /*
1.8 ! millert 175: * We always update the stashed mtime because the time
! 176: * resolution of the filesystem the temporary file is on may
! 177: * not match that of the filesystem where the file to be edited
! 178: * resides. It is OK if touch() fails since we only use the info
1.1 millert 179: * to determine whether or not a file has been modified.
180: */
1.8 ! millert 181: (void) touch(tfd, NULL, &tf[i].omtim);
! 182: #ifdef HAVE_FSTAT
! 183: error = fstat(tfd, &sb);
! 184: #else
! 185: error = stat(tf[i].tfile, &sb);
! 186: #endif
! 187: if (!error) {
! 188: tf[i].omtim.tv_sec = mtim_getsec(sb);
! 189: tf[i].omtim.tv_nsec = mtim_getnsec(sb);
1.1 millert 190: }
191: close(tfd);
192: }
193: if (argc == 1)
194: return(1); /* no files readable, you lose */
195:
1.4 millert 196: environ = envp;
1.7 millert 197: editor = find_editor();
1.1 millert 198:
199: /*
200: * Allocate space for the new argument vector and fill it in.
201: * The EDITOR and VISUAL environment variables may contain command
202: * line args so look for those and alloc space for them too.
203: */
204: nargc = argc;
1.4 millert 205: for (wasblank = FALSE, cp = editor; *cp != '\0'; cp++) {
206: if (isblank((unsigned char) *cp))
207: wasblank = TRUE;
208: else if (wasblank) {
209: wasblank = FALSE;
1.1 millert 210: nargc++;
1.4 millert 211: }
1.1 millert 212: }
213: nargv = (char **) emalloc2(nargc + 1, sizeof(char *));
214: ac = 0;
215: for ((cp = strtok(editor, " \t")); cp != NULL; (cp = strtok(NULL, " \t")))
216: nargv[ac++] = cp;
217: for (i = 0; i < argc - 1 && ac < nargc; )
218: nargv[ac++] = tf[i++].tfile;
219: nargv[ac] = NULL;
220:
1.6 millert 221: /* Allow the editor to be suspended. */
1.1 millert 222: (void) sigaction(SIGTSTP, &saved_sa_tstp, NULL);
223:
224: /*
225: * Fork and exec the editor with the invoking user's creds,
226: * keeping track of the time spent in the editor.
227: */
228: gettime(&ts1);
229: kidpid = fork();
230: if (kidpid == -1) {
1.7 millert 231: warning("fork");
1.1 millert 232: goto cleanup;
233: } else if (kidpid == 0) {
234: /* child */
235: (void) sigaction(SIGINT, &saved_sa_int, NULL);
236: (void) sigaction(SIGQUIT, &saved_sa_quit, NULL);
237: set_perms(PERM_FULL_USER);
1.8 ! millert 238: closefrom(def_closefrom);
1.1 millert 239: execvp(nargv[0], nargv);
1.7 millert 240: warning("unable to execute %s", nargv[0]);
1.1 millert 241: _exit(127);
242: }
243:
244: /*
245: * Wait for status from the child. Most modern kernels
246: * will not let an unprivileged child process send a
1.4 millert 247: * signal to its privileged parent so we have to request
1.1 millert 248: * status when the child is stopped and then send the
249: * same signal to our own pid.
250: */
251: do {
252: #ifdef sudo_waitpid
253: pid = sudo_waitpid(kidpid, &i, WUNTRACED);
254: #else
255: pid = wait(&i);
256: #endif
257: if (pid == kidpid) {
258: if (WIFSTOPPED(i))
259: kill(getpid(), WSTOPSIG(i));
260: else
261: break;
262: }
263: } while (pid != -1 || errno == EINTR);
264: gettime(&ts2);
265: if (pid == -1 || !WIFEXITED(i))
266: rval = 1;
267: else
268: rval = WEXITSTATUS(i);
269:
270: /* Copy contents of temp files to real ones */
271: for (i = 0; i < argc - 1; i++) {
1.4 millert 272: error = -1;
1.1 millert 273: set_perms(PERM_USER);
1.4 millert 274: if ((tfd = open(tf[i].tfile, O_RDONLY, 0644)) != -1) {
275: #ifdef HAVE_FSTAT
276: error = fstat(tfd, &sb);
277: #else
278: error = stat(tf[i].tfile, &sb);
279: #endif
280: }
1.1 millert 281: set_perms(PERM_ROOT);
1.4 millert 282: if (error || !S_ISREG(sb.st_mode)) {
283: if (error)
1.7 millert 284: warning("%s", tf[i].tfile);
1.4 millert 285: else
1.7 millert 286: warningx("%s: not a regular file", tf[i].tfile);
287: warningx("%s left unmodified", tf[i].ofile);
1.4 millert 288: if (tfd != -1)
289: close(tfd);
1.1 millert 290: continue;
291: }
1.4 millert 292: if (tf[i].osize == sb.st_size && tf[i].omtim.tv_sec == mtim_getsec(sb)
293: && tf[i].omtim.tv_nsec == mtim_getnsec(sb)) {
294: /*
295: * If mtime and size match but the user spent no measurable
296: * time in the editor we can't tell if the file was changed.
297: */
1.3 millert 298: #ifdef HAVE_TIMESPECSUB2
1.4 millert 299: timespecsub(&ts1, &ts2);
1.3 millert 300: #else
1.4 millert 301: timespecsub(&ts1, &ts2, &ts2);
1.3 millert 302: #endif
1.4 millert 303: if (timespecisset(&ts2)) {
1.7 millert 304: warningx("%s unchanged", tf[i].ofile);
1.4 millert 305: unlink(tf[i].tfile);
306: close(tfd);
307: continue;
1.1 millert 308: }
309: }
310: set_perms(PERM_RUNAS);
311: ofd = open(tf[i].ofile, O_WRONLY|O_TRUNC|O_CREAT, 0644);
312: set_perms(PERM_ROOT);
313: if (ofd == -1) {
1.7 millert 314: warning("unable to write to %s", tf[i].ofile);
315: warningx("contents of edit session left in %s", tf[i].tfile);
1.1 millert 316: close(tfd);
317: continue;
318: }
319: while ((nread = read(tfd, buf, sizeof(buf))) > 0) {
320: if ((nwritten = write(ofd, buf, nread)) != nread) {
321: if (nwritten == -1)
1.7 millert 322: warning("%s", tf[i].ofile);
1.1 millert 323: else
1.7 millert 324: warningx("%s: short write", tf[i].ofile);
1.1 millert 325: break;
326: }
327: }
328: if (nread == 0) {
329: /* success, got EOF */
330: unlink(tf[i].tfile);
331: } else if (nread < 0) {
1.7 millert 332: warning("unable to read temporary file");
333: warningx("contents of edit session left in %s", tf[i].tfile);
1.1 millert 334: } else {
1.7 millert 335: warning("unable to write to %s", tf[i].ofile);
336: warningx("contents of edit session left in %s", tf[i].tfile);
1.1 millert 337: }
338: close(ofd);
339: }
340:
341: return(rval);
342: cleanup:
343: /* Clean up temp files and return. */
344: for (i = 0; i < argc - 1; i++) {
345: if (tf[i].tfile != NULL)
346: unlink(tf[i].tfile);
347: }
348: return(1);
1.7 millert 349: }
350:
351: /*
352: * Determine which editor to use. We don't bother restricting this
353: * based on def_env_editor or def_editor since the editor runs with
354: * the uid of the invoking user, not the runas (privileged) user.
355: */
356: static char *
357: find_editor()
358: {
359: char *cp, *editor = NULL, **ev, *ev0[4];
360:
361: ev0[0] = "SUDO_EDITOR";
362: ev0[1] = "VISUAL";
363: ev0[2] = "EDITOR";
364: ev0[3] = NULL;
365: for (ev = ev0; *ev != NULL; ev++) {
366: if ((editor = getenv(*ev)) != NULL && *editor != '\0') {
367: if ((cp = strrchr(editor, '/')) != NULL)
368: cp++;
369: else
370: cp = editor;
371: /* Ignore "sudoedit" and "sudo" to avoid an endless loop. */
372: if (strncmp(cp, "sudo", 4) != 0 ||
373: (cp[4] != ' ' && cp[4] != '\0' && strcmp(cp + 4, "edit") != 0)) {
374: editor = estrdup(editor);
375: break;
376: }
377: }
378: editor = NULL;
379: }
380: if (editor == NULL) {
381: editor = estrdup(def_editor);
382: if ((cp = strchr(editor, ':')) != NULL)
383: *cp = '\0'; /* def_editor could be a path */
384: }
385: return(editor);
1.1 millert 386: }