Annotation of src/usr.bin/sudo/sudo_edit.c, Revision 1.2
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];
1.2 ! millert 76: int i, ac, ofd, tfd, nargc, rval, tmplen;
1.1 millert 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;
1.2 ! millert 98: tmplen = strlen(tmpdir);
! 99: while (tmplen > 0 && tmpdir[tmplen - 1] == '/')
! 100: tmplen--;
1.1 millert 101:
102: /*
103: * For each file specified by the user, make a temporary version
104: * and copy the contents of the original to it.
105: * XXX - It would be nice to lock the original files but that means
106: * keeping an extra fd open for each file.
107: */
108: tf = emalloc2(argc - 1, sizeof(*tf));
109: memset(tf, 0, (argc - 1) * sizeof(*tf));
110: for (i = 0, ap = argv + 1; i < argc - 1 && *ap != NULL; i++, ap++) {
111: set_perms(PERM_RUNAS);
112: ofd = open(*ap, O_RDONLY, 0644);
113: if (ofd != -1) {
114: #ifdef HAVE_FSTAT
115: if (fstat(ofd, &sb) != 0) {
116: #else
117: if (stat(tf[i].ofile, &sb) != 0) {
118: #endif
119: close(ofd); /* XXX - could reset errno */
120: ofd = -1;
121: }
122: }
123: set_perms(PERM_ROOT);
124: if (ofd == -1) {
125: if (errno != ENOENT) {
126: warn("%s", *ap);
127: argc--;
128: i--;
129: continue;
130: }
131: memset(&sb, 0, sizeof(sb));
132: } else if (!S_ISREG(sb.st_mode)) {
133: warnx("%s: not a regular file", *ap);
134: close(ofd);
135: argc--;
136: i--;
137: continue;
138: }
139: tf[i].ofile = *ap;
140: tf[i].omtim.tv_sec = mtim_getsec(sb);
141: tf[i].omtim.tv_nsec = mtim_getnsec(sb);
142: tf[i].osize = sb.st_size;
143: if ((cp = strrchr(tf[i].ofile, '/')) != NULL)
144: cp++;
145: else
146: cp = tf[i].ofile;
1.2 ! millert 147: easprintf(&tf[i].tfile, "%.*s/%s.XXXXXXXX", tmplen, tmpdir, cp);
1.1 millert 148: set_perms(PERM_USER);
149: tfd = mkstemp(tf[i].tfile);
150: set_perms(PERM_ROOT);
151: if (tfd == -1) {
152: warn("mkstemp");
153: goto cleanup;
154: }
155: if (ofd != -1) {
156: while ((nread = read(ofd, buf, sizeof(buf))) != 0) {
157: if ((nwritten = write(tfd, buf, nread)) != nread) {
158: if (nwritten == -1)
159: warn("%s", tf[i].tfile);
160: else
161: warnx("%s: short write", tf[i].tfile);
162: goto cleanup;
163: }
164: }
165: close(ofd);
166: }
167: #ifdef HAVE_FSTAT
168: /*
169: * If we are unable to set the mtime on the temp file to the value
170: * of the original file just make the stashed mtime match the temp
171: * file's mtime. It is better than nothing and we only use the info
172: * to determine whether or not a file has been modified.
173: */
174: if (touch(tfd, NULL, &tf[i].omtim) == -1) {
175: if (fstat(tfd, &sb) == 0) {
176: tf[i].omtim.tv_sec = mtim_getsec(sb);
177: tf[i].omtim.tv_nsec = mtim_getnsec(sb);
178: }
179: /* XXX - else error? */
180: }
181: #endif
182: close(tfd);
183: }
184: if (argc == 1)
185: return(1); /* no files readable, you lose */
186:
187: /*
188: * Determine which editor to use. We don't bother restricting this
189: * based on def_env_editor or def_editor since the editor runs with
190: * the uid of the invoking user, not the runas (privileged) user.
191: */
192: if (((editor = getenv("VISUAL")) != NULL && *editor != '\0') ||
193: ((editor = getenv("EDITOR")) != NULL && *editor != '\0')) {
194: editor = estrdup(editor);
195: } else {
196: editor = estrdup(def_editor);
197: if ((cp = strchr(editor, ':')) != NULL)
198: *cp = '\0'; /* def_editor could be a path */
199: }
200:
201: /*
202: * Allocate space for the new argument vector and fill it in.
203: * The EDITOR and VISUAL environment variables may contain command
204: * line args so look for those and alloc space for them too.
205: */
206: nargc = argc;
207: for (cp = editor + 1; *cp != '\0'; cp++) {
208: if (isblank((unsigned char)cp[0]) && !isblank((unsigned char)cp[-1]))
209: nargc++;
210: }
211: nargv = (char **) emalloc2(nargc + 1, sizeof(char *));
212: ac = 0;
213: for ((cp = strtok(editor, " \t")); cp != NULL; (cp = strtok(NULL, " \t")))
214: nargv[ac++] = cp;
215: for (i = 0; i < argc - 1 && ac < nargc; )
216: nargv[ac++] = tf[i++].tfile;
217: nargv[ac] = NULL;
218:
219: /* We wait for our own children and can be suspended. */
220: sigemptyset(&sa.sa_mask);
221: sa.sa_flags = SA_RESTART;
222: sa.sa_handler = SIG_DFL;
223: (void) sigaction(SIGCHLD, &sa, NULL);
224: (void) sigaction(SIGTSTP, &saved_sa_tstp, NULL);
225:
226: /*
227: * Fork and exec the editor with the invoking user's creds,
228: * keeping track of the time spent in the editor.
229: */
230: gettime(&ts1);
231: kidpid = fork();
232: if (kidpid == -1) {
233: warn("fork");
234: goto cleanup;
235: } else if (kidpid == 0) {
236: /* child */
237: (void) sigaction(SIGINT, &saved_sa_int, NULL);
238: (void) sigaction(SIGQUIT, &saved_sa_quit, NULL);
239: (void) sigaction(SIGCHLD, &saved_sa_chld, NULL);
240: set_perms(PERM_FULL_USER);
241: execvp(nargv[0], nargv);
242: warn("unable to execute %s", nargv[0]);
243: _exit(127);
244: }
245:
246: /*
247: * Wait for status from the child. Most modern kernels
248: * will not let an unprivileged child process send a
249: * signal to its privileged parent to we have to request
250: * status when the child is stopped and then send the
251: * same signal to our own pid.
252: */
253: do {
254: #ifdef sudo_waitpid
255: pid = sudo_waitpid(kidpid, &i, WUNTRACED);
256: #else
257: pid = wait(&i);
258: #endif
259: if (pid == kidpid) {
260: if (WIFSTOPPED(i))
261: kill(getpid(), WSTOPSIG(i));
262: else
263: break;
264: }
265: } while (pid != -1 || errno == EINTR);
266: gettime(&ts2);
267: if (pid == -1 || !WIFEXITED(i))
268: rval = 1;
269: else
270: rval = WEXITSTATUS(i);
271:
272: /* Copy contents of temp files to real ones */
273: for (i = 0; i < argc - 1; i++) {
274: set_perms(PERM_USER);
275: tfd = open(tf[i].tfile, O_RDONLY, 0644);
276: set_perms(PERM_ROOT);
277: if (tfd < 0) {
278: warn("unable to read %s", tf[i].tfile);
279: warnx("%s left unmodified", tf[i].ofile);
280: continue;
281: }
282: #ifdef HAVE_FSTAT
283: if (fstat(tfd, &sb) == 0) {
284: if (!S_ISREG(sb.st_mode)) {
285: warnx("%s: not a regular file", tf[i].tfile);
286: warnx("%s left unmodified", tf[i].ofile);
287: continue;
288: }
289: if (tf[i].osize == sb.st_size &&
290: tf[i].omtim.tv_sec == mtim_getsec(sb) &&
291: tf[i].omtim.tv_nsec == mtim_getnsec(sb)) {
292: /*
293: * If mtime and size match but the user spent no measurable
294: * time in the editor we can't tell if the file was changed.
295: */
296: timespecsub(&ts1, &ts2, &ts2);
297: if (timespecisset(&ts2)) {
298: warnx("%s unchanged", tf[i].ofile);
299: unlink(tf[i].tfile);
300: close(tfd);
301: continue;
302: }
303: }
304: }
305: #endif
306: set_perms(PERM_RUNAS);
307: ofd = open(tf[i].ofile, O_WRONLY|O_TRUNC|O_CREAT, 0644);
308: set_perms(PERM_ROOT);
309: if (ofd == -1) {
310: warn("unable to write to %s", tf[i].ofile);
311: warnx("contents of edit session left in %s", tf[i].tfile);
312: close(tfd);
313: continue;
314: }
315: while ((nread = read(tfd, buf, sizeof(buf))) > 0) {
316: if ((nwritten = write(ofd, buf, nread)) != nread) {
317: if (nwritten == -1)
318: warn("%s", tf[i].ofile);
319: else
320: warnx("%s: short write", tf[i].ofile);
321: break;
322: }
323: }
324: if (nread == 0) {
325: /* success, got EOF */
326: unlink(tf[i].tfile);
327: } else if (nread < 0) {
328: warn("unable to read temporary file");
329: warnx("contents of edit session left in %s", tf[i].tfile);
330: } else {
331: warn("unable to write to %s", tf[i].ofile);
332: warnx("contents of edit session left in %s", tf[i].tfile);
333: }
334: close(ofd);
335: }
336:
337: return(rval);
338: cleanup:
339: /* Clean up temp files and return. */
340: for (i = 0; i < argc - 1; i++) {
341: if (tf[i].tfile != NULL)
342: unlink(tf[i].tfile);
343: }
344: return(1);
345: }