Annotation of src/usr.bin/sudo/visudo.c, Revision 1.7
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;
313: case 'x' : Exit(0);
314: break;
315: }
316: yyrestart(yyin); /* reset lexer */
317: }
318: } while (parse_error == TRUE);
319:
320: /*
321: * If the user didn't change the temp file, just unlink it.
322: */
323: if (sudoers_sb.st_mtime != now && sudoers_sb.st_mtime == stmp_sb.st_mtime &&
324: sudoers_sb.st_size == stmp_sb.st_size) {
325: (void) fprintf(stderr, "%s: sudoers file unchanged.\n", Argv[0]);
326: Exit(0);
327: }
328:
329: /*
330: * Change mode and ownership of temp file so when
331: * we move it to sudoers things are kosher.
332: */
333: if (chown(stmp, SUDOERS_UID, SUDOERS_GID)) {
334: (void) fprintf(stderr,
335: "%s: Unable to set (uid, gid) of %s to (%d, %d): %s\n",
336: Argv[0], stmp, SUDOERS_UID, SUDOERS_GID, strerror(errno));
337: Exit(-1);
338: }
339: if (chmod(stmp, SUDOERS_MODE)) {
340: (void) fprintf(stderr,
341: "%s: Unable to change mode of %s to %o: %s\n",
342: Argv[0], stmp, SUDOERS_MODE, strerror(errno));
343: Exit(-1);
344: }
345:
346: /*
347: * Now that we have a sane stmp file (parses ok) it needs to be
348: * rename(2)'d to sudoers. If the rename(2) fails we try using
349: * mv(1) in case stmp and sudoers are on different filesystems.
350: */
351: if (rename(stmp, sudoers)) {
352: if (errno == EXDEV) {
353: char *tmpbuf;
354:
355: (void) fprintf(stderr,
356: "%s: %s and %s not on the same filesystem, using mv to rename.\n",
357: Argv[0], stmp, sudoers);
358:
359: /* Allocate just enough space for tmpbuf */
360: n = sizeof(char) * (strlen(_PATH_MV) + strlen(stmp) +
361: strlen(sudoers) + 4);
362: if ((tmpbuf = (char *) malloc(n)) == NULL) {
363: (void) fprintf(stderr,
364: "%s: Cannot alocate memory, %s unchanged: %s\n",
365: Argv[0], sudoers, strerror(errno));
366: Exit(-1);
367: }
368:
369: /* Build up command and execute it */
370: (void) sprintf(tmpbuf, "%s %s %s", _PATH_MV, stmp, sudoers);
371: if (system(tmpbuf)) {
372: (void) fprintf(stderr,
373: "%s: Command failed: '%s', %s unchanged.\n",
374: Argv[0], tmpbuf, sudoers);
375: Exit(-1);
376: }
377: free(tmpbuf);
378: } else {
379: (void) fprintf(stderr, "%s: Error renaming %s, %s unchanged: %s\n",
380: Argv[0], stmp, sudoers, strerror(errno));
381: Exit(-1);
382: }
383: }
384:
1.3 millert 385: exit(0);
1.1 millert 386: }
387:
388: /*
389: * Dummy *_matches routines.
390: * These exist to allow us to use the same parser as sudo(8).
391: */
392: int
393: command_matches(cmnd, cmnd_args, path, sudoers_args)
394: char *cmnd;
395: char *cmnd_args;
396: char *path;
397: char *sudoers_args;
398: {
399: return(TRUE);
400: }
401:
402: int
403: addr_matches(n)
404: char *n;
1.4 millert 405: {
406: return(TRUE);
407: }
408:
409: int
410: hostname_matches(s, l, p)
411: char *s, *l, *p;
1.1 millert 412: {
413: return(TRUE);
414: }
415:
416: int
417: usergr_matches(g, u)
418: char *g, *u;
419: {
420: return(TRUE);
421: }
422:
423: int
1.3 millert 424: netgr_matches(n, h, sh, u)
425: char *n, *h, *sh, *u;
1.1 millert 426: {
427: return(TRUE);
1.2 millert 428: }
429:
430: void
431: set_fqdn()
432: {
433: return;
1.1 millert 434: }
435:
436: /*
437: * Assuming a parse error occurred, prompt the user for what they want
438: * to do now. Returns the first letter of their choice.
439: */
440: static char
441: whatnow()
442: {
443: int choice, c;
444:
445: for (;;) {
446: (void) fputs("What now? ", stdout);
447: choice = getchar();
448: for (c = choice; c != '\n' && c != EOF;)
449: c = getchar();
450:
1.3 millert 451: switch (choice) {
452: case EOF:
453: choice = 'x';
454: /* FALLTHROUGH */
455: case 'e':
456: case 'x':
457: case 'Q':
458: return(choice);
459: default:
460: (void) puts("Options are:");
461: (void) puts(" (e)dit sudoers file again");
462: (void) puts(" e(x)it without saving changes to sudoers file");
463: (void) puts(" (Q)uit and save changes to sudoers file (DANGER!)\n");
1.1 millert 464: }
465: }
466: }
467:
468: /*
469: * Install signal handlers for visudo.
470: */
471: static void
472: setup_signals()
473: {
474: #ifdef POSIX_SIGNALS
475: struct sigaction action; /* POSIX signal structure */
476: #endif /* POSIX_SIGNALS */
477:
478: /*
479: * Setup signal handlers to cleanup nicely.
480: */
481: #ifdef POSIX_SIGNALS
482: (void) memset((VOID *)&action, 0, sizeof(action));
483: action.sa_handler = Exit;
484: action.sa_flags = SA_RESETHAND;
485: (void) sigaction(SIGTERM, &action, NULL);
486: (void) sigaction(SIGHUP, &action, NULL);
487: (void) sigaction(SIGINT, &action, NULL);
488: (void) sigaction(SIGQUIT, &action, NULL);
489: #else
490: (void) signal(SIGTERM, Exit);
491: (void) signal(SIGHUP, Exit);
492: (void) signal(SIGINT, Exit);
493: (void) signal(SIGQUIT, Exit);
494: #endif /* POSIX_SIGNALS */
495: }
496:
497: /*
498: * Unlink the sudoers temp file (if it exists) and exit.
499: * Used in place of a normal exit() and as a signal handler.
500: * A positive parameter is considered to be a signal and is reported.
501: */
502: static RETSIGTYPE
503: Exit(sig)
504: int sig;
505: {
506: (void) unlink(stmp);
507:
508: if (sig > 0)
509: (void) fprintf(stderr, "%s exiting, caught signal %d.\n", Argv[0], sig);
510:
511: exit(-sig);
512: }
513:
514: static void
515: usage()
516: {
517: (void) fprintf(stderr, "usage: %s [-s] [-V]\n", Argv[0]);
518: exit(1);
519: }