Annotation of src/usr.bin/sudo/visudo.c, Revision 1.8
1.1 millert 1: /*
1.3 millert 2: * Copyright (c) 1996, 1998-2000 Todd C. Miller <Todd.Miller@courtesan.com>
1.1 millert 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
1.4 millert 84: static const char rcsid[] = "$Sudo: visudo.c,v 1.126 2000/03/23 04:38:22 millert Exp $";
1.1 millert 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 *));
1.4 millert 96: int hostname_matches __P((char *, char *, char *));
1.3 millert 97: int netgr_matches __P((char *, char *, char *, char *));
1.1 millert 98: int usergr_matches __P((char *, char *));
99: void init_parser __P((void));
100: void yyrestart __P((FILE *));
101:
102: /*
103: * External globals exported by the parser
104: */
105: extern FILE *yyin, *yyout;
106: extern int errorlineno;
107: extern int pedantic;
108:
109: /*
110: * Globals
111: */
112: char **Argv;
113: char *sudoers = _PATH_SUDOERS;
114: char *stmp = _PATH_SUDOERS_TMP;
115: struct sudo_user sudo_user;
116: int parse_error = FALSE;
117:
118:
119: int
120: main(argc, argv)
121: int argc;
122: char **argv;
123: {
124: char buf[MAXPATHLEN*2]; /* buffer used for copying files */
1.4 millert 125: char *Editor; /* editor to use */
1.1 millert 126: int sudoers_fd; /* sudoers file descriptor */
127: int stmp_fd; /* stmp file descriptor */
128: int n; /* length parameter */
129: time_t now; /* time now */
130: struct stat stmp_sb, sudoers_sb; /* to check for changes */
131:
132: /* Warn about aliases that are used before being defined. */
133: pedantic = 1;
134:
135: /*
136: * Parse command line options
137: */
138: Argv = argv;
139:
140: /*
141: * Arg handling.
142: */
143: while (--argc) {
144: if (!strcmp(argv[argc], "-V")) {
145: (void) printf("visudo version %s\n", version);
146: exit(0);
147: } else if (!strcmp(argv[argc], "-s")) {
148: pedantic++; /* strict mode */
149: } else {
150: usage();
151: }
152: }
153:
154: /* Mock up a fake sudo_user struct. */
155: user_host = user_shost = user_cmnd = "";
156: if ((sudo_user.pw = getpwuid(getuid())) == NULL) {
157: (void) fprintf(stderr, "%s: Can't find you in the passwd database.\n",
158: Argv[0]);
159: exit(1);
160: }
161:
1.4 millert 162: /* Setup defaults data structures. */
163: init_defaults();
1.1 millert 164:
165: /*
1.3 millert 166: * Open sudoers, lock it and stat it.
167: * sudoers_fd must remain open throughout in order to hold the lock.
1.1 millert 168: */
1.6 millert 169: sudoers_fd = open(sudoers, O_RDWR | O_CREAT, SUDOERS_MODE);
1.3 millert 170: if (sudoers_fd == -1) {
171: (void) fprintf(stderr, "%s: %s: %s\n", Argv[0], sudoers,
172: strerror(errno));
173: Exit(-1);
1.1 millert 174: }
1.3 millert 175: if (!lock_file(sudoers_fd, SUDO_TLOCK)) {
1.1 millert 176: (void) fprintf(stderr, "%s: sudoers file busy, try again later.\n",
177: Argv[0]);
178: exit(1);
179: }
1.3 millert 180: #ifdef HAVE_FSTAT
181: if (fstat(sudoers_fd, &sudoers_sb) == -1) {
1.1 millert 182: #else
1.3 millert 183: if (stat(sudoers, &sudoers_sb) == -1) {
1.1 millert 184: #endif
1.3 millert 185: (void) fprintf(stderr, "%s: can't stat %s: %s\n",
186: Argv[0], sudoers, strerror(errno));
1.1 millert 187: Exit(-1);
188: }
189:
1.3 millert 190: /*
191: * Open sudoers temp file.
192: */
193: stmp_fd = open(stmp, O_WRONLY | O_CREAT | O_TRUNC, 0600);
194: if (stmp_fd < 0) {
195: (void) fprintf(stderr, "%s: %s: %s\n", Argv[0], stmp, strerror(errno));
196: exit(1);
197: }
198:
1.1 millert 199: /* Install signal handlers to clean up stmp if we are killed. */
200: setup_signals();
201:
202: /* Copy sudoers -> stmp and reset the mtime */
1.3 millert 203: if (sudoers_sb.st_size) {
1.1 millert 204: while ((n = read(sudoers_fd, buf, sizeof(buf))) > 0)
205: if (write(stmp_fd, buf, n) != n) {
206: (void) fprintf(stderr, "%s: Write failed: %s\n", Argv[0],
207: strerror(errno));
208: Exit(-1);
209: }
210:
1.3 millert 211: (void) close(stmp_fd);
212: (void) touch(stmp, sudoers_sb.st_mtime);
1.4 millert 213:
214: /* Parse sudoers to pull in editor and env_editor conf values. */
215: if ((yyin = fopen(stmp, "r"))) {
216: yyout = stdout;
217: init_parser();
218: yyparse();
219: parse_error = FALSE;
220: yyrestart(yyin);
221: fclose(yyin);
222: }
1.3 millert 223: } else
224: (void) close(stmp_fd);
1.1 millert 225:
226: /*
1.4 millert 227: * If we are allowing EDITOR and VISUAL envariables set Editor
228: * base on whichever exists...
229: */
230: if (!def_flag(I_ENV_EDITOR) ||
231: (!(Editor = getenv("EDITOR")) && !(Editor = getenv("VISUAL"))))
1.5 millert 232: Editor = estrdup(def_str(I_EDITOR));
1.4 millert 233:
234: /*
1.1 millert 235: * Edit the temp file and parse it (for sanity checking)
236: */
237: do {
238: /*
239: * Build up a buffer to execute
240: */
241: if (strlen(Editor) + strlen(stmp) + 30 > sizeof(buf)) {
242: (void) fprintf(stderr, "%s: Buffer too short (line %d).\n",
243: Argv[0], __LINE__);
244: Exit(-1);
245: }
246: if (parse_error == TRUE)
247: (void) sprintf(buf, "%s +%d %s", Editor, errorlineno, stmp);
248: else
249: (void) sprintf(buf, "%s %s", Editor, stmp);
250:
251: /* Do the edit -- some SYSV editors exit with 1 instead of 0 */
252: now = time(NULL);
253: n = system(buf);
254: if (n != -1 && ((n >> 8) == 0 || (n >> 8) == 1)) {
255: /*
256: * Sanity checks.
257: */
258: if (stat(stmp, &stmp_sb) < 0) {
259: (void) fprintf(stderr,
260: "%s: Can't stat temporary file (%s), %s unchanged.\n",
261: Argv[0], stmp, sudoers);
262: Exit(-1);
263: }
264: if (stmp_sb.st_size == 0) {
265: (void) fprintf(stderr,
266: "%s: Zero length temporary file (%s), %s unchanged.\n",
267: Argv[0], stmp, sudoers);
268: Exit(-1);
269: }
270:
271: /*
272: * Passed sanity checks so reopen stmp file and check
273: * for parse errors.
274: */
275: yyout = stdout;
276: if (parse_error)
277: yyin = freopen(stmp, "r", yyin);
278: else
279: yyin = fopen(stmp, "r");
280: if (yyin == NULL) {
281: (void) fprintf(stderr,
282: "%s: Can't re-open temporary file (%s), %s unchanged.\n",
283: Argv[0], stmp, sudoers);
284: Exit(-1);
285: }
286:
287: /* Clean slate for each parse */
1.5 millert 288: user_runas = NULL;
1.1 millert 289: init_defaults();
290: init_parser();
291:
292: /* Parse the sudoers file */
293: if (yyparse() && parse_error != TRUE) {
294: (void) fprintf(stderr,
295: "%s: Failed to parse temporary file (%s), unknown error.\n",
296: Argv[0], stmp);
297: parse_error = TRUE;
298: }
299: } else {
300: (void) fprintf(stderr,
301: "%s: Editor (%s) failed with exit status %d, %s unchanged.\n",
302: Argv[0], Editor, n, sudoers);
303: Exit(-1);
304: }
305:
306: /*
307: * Got an error, prompt the user for what to do now
308: */
309: if (parse_error == TRUE) {
310: switch (whatnow()) {
1.7 millert 311: case 'Q' : parse_error = FALSE; /* ignore parse error */
1.1 millert 312: break;
1.8 ! millert 313: case 'x' : if (sudoers_sb.st_size == 0)
! 314: unlink(sudoers);
! 315: Exit(0);
1.1 millert 316: break;
317: }
318: yyrestart(yyin); /* reset lexer */
319: }
320: } while (parse_error == TRUE);
321:
322: /*
323: * If the user didn't change the temp file, just unlink it.
324: */
325: if (sudoers_sb.st_mtime != now && sudoers_sb.st_mtime == stmp_sb.st_mtime &&
326: sudoers_sb.st_size == stmp_sb.st_size) {
327: (void) fprintf(stderr, "%s: sudoers file unchanged.\n", Argv[0]);
328: Exit(0);
329: }
330:
331: /*
332: * Change mode and ownership of temp file so when
333: * we move it to sudoers things are kosher.
334: */
335: if (chown(stmp, SUDOERS_UID, SUDOERS_GID)) {
336: (void) fprintf(stderr,
337: "%s: Unable to set (uid, gid) of %s to (%d, %d): %s\n",
338: Argv[0], stmp, SUDOERS_UID, SUDOERS_GID, strerror(errno));
339: Exit(-1);
340: }
341: if (chmod(stmp, SUDOERS_MODE)) {
342: (void) fprintf(stderr,
343: "%s: Unable to change mode of %s to %o: %s\n",
344: Argv[0], stmp, SUDOERS_MODE, strerror(errno));
345: Exit(-1);
346: }
347:
348: /*
349: * Now that we have a sane stmp file (parses ok) it needs to be
350: * rename(2)'d to sudoers. If the rename(2) fails we try using
351: * mv(1) in case stmp and sudoers are on different filesystems.
352: */
353: if (rename(stmp, sudoers)) {
354: if (errno == EXDEV) {
355: char *tmpbuf;
356:
357: (void) fprintf(stderr,
358: "%s: %s and %s not on the same filesystem, using mv to rename.\n",
359: Argv[0], stmp, sudoers);
360:
361: /* Allocate just enough space for tmpbuf */
362: n = sizeof(char) * (strlen(_PATH_MV) + strlen(stmp) +
363: strlen(sudoers) + 4);
364: if ((tmpbuf = (char *) malloc(n)) == NULL) {
365: (void) fprintf(stderr,
366: "%s: Cannot alocate memory, %s unchanged: %s\n",
367: Argv[0], sudoers, strerror(errno));
368: Exit(-1);
369: }
370:
371: /* Build up command and execute it */
372: (void) sprintf(tmpbuf, "%s %s %s", _PATH_MV, stmp, sudoers);
373: if (system(tmpbuf)) {
374: (void) fprintf(stderr,
375: "%s: Command failed: '%s', %s unchanged.\n",
376: Argv[0], tmpbuf, sudoers);
377: Exit(-1);
378: }
379: free(tmpbuf);
380: } else {
381: (void) fprintf(stderr, "%s: Error renaming %s, %s unchanged: %s\n",
382: Argv[0], stmp, sudoers, strerror(errno));
383: Exit(-1);
384: }
385: }
386:
1.3 millert 387: exit(0);
1.1 millert 388: }
389:
390: /*
391: * Dummy *_matches routines.
392: * These exist to allow us to use the same parser as sudo(8).
393: */
394: int
395: command_matches(cmnd, cmnd_args, path, sudoers_args)
396: char *cmnd;
397: char *cmnd_args;
398: char *path;
399: char *sudoers_args;
400: {
401: return(TRUE);
402: }
403:
404: int
405: addr_matches(n)
406: char *n;
1.4 millert 407: {
408: return(TRUE);
409: }
410:
411: int
412: hostname_matches(s, l, p)
413: char *s, *l, *p;
1.1 millert 414: {
415: return(TRUE);
416: }
417:
418: int
419: usergr_matches(g, u)
420: char *g, *u;
421: {
422: return(TRUE);
423: }
424:
425: int
1.3 millert 426: netgr_matches(n, h, sh, u)
427: char *n, *h, *sh, *u;
1.1 millert 428: {
429: return(TRUE);
1.2 millert 430: }
431:
432: void
433: set_fqdn()
434: {
435: return;
1.1 millert 436: }
437:
438: /*
439: * Assuming a parse error occurred, prompt the user for what they want
440: * to do now. Returns the first letter of their choice.
441: */
442: static char
443: whatnow()
444: {
445: int choice, c;
446:
447: for (;;) {
448: (void) fputs("What now? ", stdout);
449: choice = getchar();
450: for (c = choice; c != '\n' && c != EOF;)
451: c = getchar();
452:
1.3 millert 453: switch (choice) {
454: case EOF:
455: choice = 'x';
456: /* FALLTHROUGH */
457: case 'e':
458: case 'x':
459: case 'Q':
460: return(choice);
461: default:
462: (void) puts("Options are:");
463: (void) puts(" (e)dit sudoers file again");
464: (void) puts(" e(x)it without saving changes to sudoers file");
465: (void) puts(" (Q)uit and save changes to sudoers file (DANGER!)\n");
1.1 millert 466: }
467: }
468: }
469:
470: /*
471: * Install signal handlers for visudo.
472: */
473: static void
474: setup_signals()
475: {
476: #ifdef POSIX_SIGNALS
477: struct sigaction action; /* POSIX signal structure */
478: #endif /* POSIX_SIGNALS */
479:
480: /*
481: * Setup signal handlers to cleanup nicely.
482: */
483: #ifdef POSIX_SIGNALS
484: (void) memset((VOID *)&action, 0, sizeof(action));
485: action.sa_handler = Exit;
486: action.sa_flags = SA_RESETHAND;
487: (void) sigaction(SIGTERM, &action, NULL);
488: (void) sigaction(SIGHUP, &action, NULL);
489: (void) sigaction(SIGINT, &action, NULL);
490: (void) sigaction(SIGQUIT, &action, NULL);
491: #else
492: (void) signal(SIGTERM, Exit);
493: (void) signal(SIGHUP, Exit);
494: (void) signal(SIGINT, Exit);
495: (void) signal(SIGQUIT, Exit);
496: #endif /* POSIX_SIGNALS */
497: }
498:
499: /*
500: * Unlink the sudoers temp file (if it exists) and exit.
501: * Used in place of a normal exit() and as a signal handler.
502: * A positive parameter is considered to be a signal and is reported.
503: */
504: static RETSIGTYPE
505: Exit(sig)
506: int sig;
507: {
508: (void) unlink(stmp);
509:
510: if (sig > 0)
511: (void) fprintf(stderr, "%s exiting, caught signal %d.\n", Argv[0], sig);
512:
513: exit(-sig);
514: }
515:
516: static void
517: usage()
518: {
519: (void) fprintf(stderr, "usage: %s [-s] [-V]\n", Argv[0]);
520: exit(1);
521: }