Annotation of src/usr.bin/sudo/visudo.c, Revision 1.1.1.1
1.1 millert 1: /*
2: * Copyright (c) 1996, 1998, 1999 Todd C. Miller <Todd.Miller@courtesan.com>
3: * All rights reserved.
4: *
5: * Redistribution and use in source and binary forms, with or without
6: * modification, are permitted provided that the following conditions
7: * are met:
8: *
9: * 1. Redistributions of source code must retain the above copyright
10: * notice, this list of conditions and the following disclaimer.
11: *
12: * 2. Redistributions in binary form must reproduce the above copyright
13: * notice, this list of conditions and the following disclaimer in the
14: * documentation and/or other materials provided with the distribution.
15: *
16: * 3. The name of the author may not be used to endorse or promote products
17: * derived from this software without specific prior written permission.
18: *
19: * 4. Products derived from this software may not be called "Sudo" nor
20: * may "Sudo" appear in their names without specific prior written
21: * permission from the author.
22: *
23: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
24: * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
25: * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
26: * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
27: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
28: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
29: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
30: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
31: * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
32: * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33: */
34:
35: /*
36: * Lock the sudoers file for safe editing (ala vipw) and check for parse errors.
37: */
38:
39: #include "config.h"
40:
41: #include <stdio.h>
42: #ifdef STDC_HEADERS
43: #include <stdlib.h>
44: #endif /* STDC_HEADERS */
45: #ifdef HAVE_UNISTD_H
46: #include <unistd.h>
47: #endif /* HAVE_UNISTD_H */
48: #ifdef HAVE_STRING_H
49: #include <string.h>
50: #endif /* HAVE_STRING_H */
51: #ifdef HAVE_STRINGS_H
52: #include <strings.h>
53: #endif /* HAVE_STRINGS_H */
54: #if defined(HAVE_MALLOC_H) && !defined(STDC_HEADERS)
55: #include <malloc.h>
56: #endif /* HAVE_MALLOC_H && !STDC_HEADERS */
57: #include <ctype.h>
58: #include <pwd.h>
59: #include <time.h>
60: #include <signal.h>
61: #include <errno.h>
62: #include <fcntl.h>
63: #include <sys/types.h>
64: #include <sys/param.h>
65: #include <sys/stat.h>
66: #include <sys/file.h>
67:
68: #include "sudo.h"
69: #include "version.h"
70:
71: #ifndef STDC_HEADERS
72: #ifndef __GNUC__ /* gcc has its own malloc */
73: extern char *malloc __P((size_t));
74: #endif /* __GNUC__ */
75: extern char *getenv __P((const char *));
76: extern int stat __P((const char *, struct stat *));
77: #endif /* !STDC_HEADERS */
78:
79: #if defined(POSIX_SIGNALS) && !defined(SA_RESETHAND)
80: #define SA_RESETHAND 0
81: #endif /* POSIX_SIGNALS && !SA_RESETHAND */
82:
83: #ifndef lint
84: static const char rcsid[] = "$Sudo: visudo.c,v 1.116 1999/11/09 20:12:20 millert Exp $";
85: #endif /* lint */
86:
87: /*
88: * Function prototypes
89: */
90: static void usage __P((void));
91: static char whatnow __P((void));
92: static RETSIGTYPE Exit __P((int));
93: static void setup_signals __P((void));
94: int command_matches __P((char *, char *, char *, char *));
95: int addr_matches __P((char *));
96: int netgr_matches __P((char *, char *, char *));
97: int usergr_matches __P((char *, char *));
98: void init_parser __P((void));
99: void yyrestart __P((FILE *));
100:
101: /*
102: * External globals exported by the parser
103: */
104: extern FILE *yyin, *yyout;
105: extern int errorlineno;
106: extern int pedantic;
107:
108: /*
109: * Globals
110: */
111: char **Argv;
112: char *sudoers = _PATH_SUDOERS;
113: char *stmp = _PATH_SUDOERS_TMP;
114: struct sudo_user sudo_user;
115: int parse_error = FALSE;
116:
117:
118: int
119: main(argc, argv)
120: int argc;
121: char **argv;
122: {
123: char buf[MAXPATHLEN*2]; /* buffer used for copying files */
124: char *Editor = EDITOR; /* editor to use (default is EDITOR */
125: int sudoers_fd; /* sudoers file descriptor */
126: int stmp_fd; /* stmp file descriptor */
127: int n; /* length parameter */
128: time_t now; /* time now */
129: struct stat stmp_sb, sudoers_sb; /* to check for changes */
130:
131: /* Warn about aliases that are used before being defined. */
132: pedantic = 1;
133:
134: /*
135: * Parse command line options
136: */
137: Argv = argv;
138:
139: /*
140: * Arg handling.
141: */
142: while (--argc) {
143: if (!strcmp(argv[argc], "-V")) {
144: (void) printf("visudo version %s\n", version);
145: exit(0);
146: } else if (!strcmp(argv[argc], "-s")) {
147: pedantic++; /* strict mode */
148: } else {
149: usage();
150: }
151: }
152:
153: /* Mock up a fake sudo_user struct. */
154: user_host = user_shost = user_cmnd = "";
155: if ((sudo_user.pw = getpwuid(getuid())) == NULL) {
156: (void) fprintf(stderr, "%s: Can't find you in the passwd database.\n",
157: Argv[0]);
158: exit(1);
159: }
160:
161: #ifdef ENV_EDITOR
162: /*
163: * If we are allowing EDITOR and VISUAL envariables set Editor
164: * base on whichever exists...
165: */
166: if (!(Editor = getenv("EDITOR")))
167: if (!(Editor = getenv("VISUAL")))
168: Editor = EDITOR;
169: #endif /* ENV_EDITOR */
170:
171: /*
172: * Open sudoers temp file and grab a lock.
173: */
174: stmp_fd = open(stmp, O_WRONLY | O_CREAT, 0600);
175: if (stmp_fd < 0) {
176: (void) fprintf(stderr, "%s: %s\n", Argv[0], strerror(errno));
177: exit(1);
178: }
179: if (!lock_file(stmp_fd, SUDO_TLOCK)) {
180: (void) fprintf(stderr, "%s: sudoers file busy, try again later.\n",
181: Argv[0]);
182: exit(1);
183: }
184: #ifdef HAVE_FTRUNCATE
185: if (ftruncate(stmp_fd, 0) == -1) {
186: #else
187: if (truncate(stmp, 0) == -1) {
188: #endif
189: (void) fprintf(stderr, "%s: can't truncate %s: %s\n", Argv[0],
190: stmp, strerror(errno));
191: Exit(-1);
192: }
193:
194: /* Install signal handlers to clean up stmp if we are killed. */
195: setup_signals();
196:
197: (void) memset(&sudoers_sb, 0, sizeof(sudoers_sb));
198: if (stat(sudoers, &sudoers_sb) == -1 && errno != ENOENT) {
199: (void) fprintf(stderr, "%s: %s\n", Argv[0], strerror(errno));
200: Exit(-1);
201: }
202: sudoers_fd = open(sudoers, O_RDONLY);
203: if (sudoers_fd == -1 && errno != ENOENT) {
204: (void) fprintf(stderr, "%s: %s\n", Argv[0], strerror(errno));
205: Exit(-1);
206: }
207:
208: /* Copy sudoers -> stmp and reset the mtime */
209: if (sudoers_fd != -1) {
210: while ((n = read(sudoers_fd, buf, sizeof(buf))) > 0)
211: if (write(stmp_fd, buf, n) != n) {
212: (void) fprintf(stderr, "%s: Write failed: %s\n", Argv[0],
213: strerror(errno));
214: Exit(-1);
215: }
216:
217: (void) close(sudoers_fd);
218: }
219: (void) close(stmp_fd);
220: (void) touch(stmp, sudoers_sb.st_mtime);
221:
222: /*
223: * Edit the temp file and parse it (for sanity checking)
224: */
225: do {
226: /*
227: * Build up a buffer to execute
228: */
229: if (strlen(Editor) + strlen(stmp) + 30 > sizeof(buf)) {
230: (void) fprintf(stderr, "%s: Buffer too short (line %d).\n",
231: Argv[0], __LINE__);
232: Exit(-1);
233: }
234: if (parse_error == TRUE)
235: (void) sprintf(buf, "%s +%d %s", Editor, errorlineno, stmp);
236: else
237: (void) sprintf(buf, "%s %s", Editor, stmp);
238:
239: /* Do the edit -- some SYSV editors exit with 1 instead of 0 */
240: now = time(NULL);
241: n = system(buf);
242: if (n != -1 && ((n >> 8) == 0 || (n >> 8) == 1)) {
243: /*
244: * Sanity checks.
245: */
246: if (stat(stmp, &stmp_sb) < 0) {
247: (void) fprintf(stderr,
248: "%s: Can't stat temporary file (%s), %s unchanged.\n",
249: Argv[0], stmp, sudoers);
250: Exit(-1);
251: }
252: if (stmp_sb.st_size == 0) {
253: (void) fprintf(stderr,
254: "%s: Zero length temporary file (%s), %s unchanged.\n",
255: Argv[0], stmp, sudoers);
256: Exit(-1);
257: }
258:
259: /*
260: * Passed sanity checks so reopen stmp file and check
261: * for parse errors.
262: */
263: yyout = stdout;
264: if (parse_error)
265: yyin = freopen(stmp, "r", yyin);
266: else
267: yyin = fopen(stmp, "r");
268: if (yyin == NULL) {
269: (void) fprintf(stderr,
270: "%s: Can't re-open temporary file (%s), %s unchanged.\n",
271: Argv[0], stmp, sudoers);
272: Exit(-1);
273: }
274:
275: /* Clean slate for each parse */
276: init_defaults();
277: init_parser();
278:
279: /* Parse the sudoers file */
280: if (yyparse() && parse_error != TRUE) {
281: (void) fprintf(stderr,
282: "%s: Failed to parse temporary file (%s), unknown error.\n",
283: Argv[0], stmp);
284: parse_error = TRUE;
285: }
286: } else {
287: (void) fprintf(stderr,
288: "%s: Editor (%s) failed with exit status %d, %s unchanged.\n",
289: Argv[0], Editor, n, sudoers);
290: Exit(-1);
291: }
292:
293: /*
294: * Got an error, prompt the user for what to do now
295: */
296: if (parse_error == TRUE) {
297: switch (whatnow()) {
298: case 'q' : parse_error = FALSE; /* ignore parse error */
299: break;
300: case 'x' : Exit(0);
301: break;
302: }
303: yyrestart(yyin); /* reset lexer */
304: }
305: } while (parse_error == TRUE);
306:
307: /*
308: * If the user didn't change the temp file, just unlink it.
309: */
310: if (sudoers_sb.st_mtime != now && sudoers_sb.st_mtime == stmp_sb.st_mtime &&
311: sudoers_sb.st_size == stmp_sb.st_size) {
312: (void) fprintf(stderr, "%s: sudoers file unchanged.\n", Argv[0]);
313: Exit(0);
314: }
315:
316: /*
317: * Change mode and ownership of temp file so when
318: * we move it to sudoers things are kosher.
319: */
320: if (chown(stmp, SUDOERS_UID, SUDOERS_GID)) {
321: (void) fprintf(stderr,
322: "%s: Unable to set (uid, gid) of %s to (%d, %d): %s\n",
323: Argv[0], stmp, SUDOERS_UID, SUDOERS_GID, strerror(errno));
324: Exit(-1);
325: }
326: if (chmod(stmp, SUDOERS_MODE)) {
327: (void) fprintf(stderr,
328: "%s: Unable to change mode of %s to %o: %s\n",
329: Argv[0], stmp, SUDOERS_MODE, strerror(errno));
330: Exit(-1);
331: }
332:
333: /*
334: * Now that we have a sane stmp file (parses ok) it needs to be
335: * rename(2)'d to sudoers. If the rename(2) fails we try using
336: * mv(1) in case stmp and sudoers are on different filesystems.
337: */
338: if (rename(stmp, sudoers)) {
339: if (errno == EXDEV) {
340: char *tmpbuf;
341:
342: (void) fprintf(stderr,
343: "%s: %s and %s not on the same filesystem, using mv to rename.\n",
344: Argv[0], stmp, sudoers);
345:
346: /* Allocate just enough space for tmpbuf */
347: n = sizeof(char) * (strlen(_PATH_MV) + strlen(stmp) +
348: strlen(sudoers) + 4);
349: if ((tmpbuf = (char *) malloc(n)) == NULL) {
350: (void) fprintf(stderr,
351: "%s: Cannot alocate memory, %s unchanged: %s\n",
352: Argv[0], sudoers, strerror(errno));
353: Exit(-1);
354: }
355:
356: /* Build up command and execute it */
357: (void) sprintf(tmpbuf, "%s %s %s", _PATH_MV, stmp, sudoers);
358: if (system(tmpbuf)) {
359: (void) fprintf(stderr,
360: "%s: Command failed: '%s', %s unchanged.\n",
361: Argv[0], tmpbuf, sudoers);
362: Exit(-1);
363: }
364: free(tmpbuf);
365: } else {
366: (void) fprintf(stderr, "%s: Error renaming %s, %s unchanged: %s\n",
367: Argv[0], stmp, sudoers, strerror(errno));
368: Exit(-1);
369: }
370: }
371:
372: return(0);
373: }
374:
375: /*
376: * Dummy *_matches routines.
377: * These exist to allow us to use the same parser as sudo(8).
378: */
379: int
380: command_matches(cmnd, cmnd_args, path, sudoers_args)
381: char *cmnd;
382: char *cmnd_args;
383: char *path;
384: char *sudoers_args;
385: {
386: return(TRUE);
387: }
388:
389: int
390: addr_matches(n)
391: char *n;
392: {
393: return(TRUE);
394: }
395:
396: int
397: usergr_matches(g, u)
398: char *g, *u;
399: {
400: return(TRUE);
401: }
402:
403: int
404: netgr_matches(n, h, u)
405: char *n, *h, *u;
406: {
407: return(TRUE);
408: }
409:
410: /*
411: * Assuming a parse error occurred, prompt the user for what they want
412: * to do now. Returns the first letter of their choice.
413: */
414: static char
415: whatnow()
416: {
417: int choice, c;
418:
419: for (;;) {
420: (void) fputs("What now? ", stdout);
421: choice = getchar();
422: for (c = choice; c != '\n' && c != EOF;)
423: c = getchar();
424:
425: if (choice == 'e' || choice == 'x' || choice == 'Q')
426: break;
427: else {
428: (void) puts("Options are:");
429: (void) puts(" (e)dit sudoers file again");
430: (void) puts(" e(x)it without saving changes to sudoers file");
431: (void) puts(" (Q)uit and save changes to sudoers file (DANGER!)\n");
432: }
433: }
434:
435: return(choice);
436: }
437:
438: /*
439: * Install signal handlers for visudo.
440: */
441: static void
442: setup_signals()
443: {
444: #ifdef POSIX_SIGNALS
445: struct sigaction action; /* POSIX signal structure */
446: #endif /* POSIX_SIGNALS */
447:
448: /*
449: * Setup signal handlers to cleanup nicely.
450: */
451: #ifdef POSIX_SIGNALS
452: (void) memset((VOID *)&action, 0, sizeof(action));
453: action.sa_handler = Exit;
454: action.sa_flags = SA_RESETHAND;
455: (void) sigaction(SIGTERM, &action, NULL);
456: (void) sigaction(SIGHUP, &action, NULL);
457: (void) sigaction(SIGINT, &action, NULL);
458: (void) sigaction(SIGQUIT, &action, NULL);
459: #else
460: (void) signal(SIGTERM, Exit);
461: (void) signal(SIGHUP, Exit);
462: (void) signal(SIGINT, Exit);
463: (void) signal(SIGQUIT, Exit);
464: #endif /* POSIX_SIGNALS */
465: }
466:
467: /*
468: * Unlink the sudoers temp file (if it exists) and exit.
469: * Used in place of a normal exit() and as a signal handler.
470: * A positive parameter is considered to be a signal and is reported.
471: */
472: static RETSIGTYPE
473: Exit(sig)
474: int sig;
475: {
476: (void) unlink(stmp);
477:
478: if (sig > 0)
479: (void) fprintf(stderr, "%s exiting, caught signal %d.\n", Argv[0], sig);
480:
481: exit(-sig);
482: }
483:
484: static void
485: usage()
486: {
487: (void) fprintf(stderr, "usage: %s [-s] [-V]\n", Argv[0]);
488: exit(1);
489: }