2 * Copyright (c) 1996, 1998-2001 Todd C. Miller <Todd.Miller@courtesan.com>
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
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.
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.
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.
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.
36 * Lock the sudoers file for safe editing (ala vipw) and check for parse errors.
41 #include <sys/types.h>
42 #include <sys/param.h>
54 #endif /* STDC_HEADERS */
58 # ifdef HAVE_STRINGS_H
61 #endif /* HAVE_STRING_H */
64 #endif /* HAVE_UNISTD_H */
76 static const char rcsid[] = "$Sudo: visudo.c,v 1.146 2002/01/17 15:35:54 millert Exp $";
82 static void usage __P((void));
83 static char whatnow __P((void));
84 static RETSIGTYPE Exit __P((int));
85 static void setup_signals __P((void));
86 static int run_command __P((char *, char **));
87 static int check_syntax __P((int));
88 int command_matches __P((char *, char *, char *, char *));
89 int addr_matches __P((char *));
90 int hostname_matches __P((char *, char *, char *));
91 int netgr_matches __P((char *, char *, char *, char *));
92 int usergr_matches __P((char *, char *));
93 void init_parser __P((void));
94 void yyrestart __P((FILE *));
97 * External globals exported by the parser
99 extern FILE *yyin, *yyout;
100 extern int errorlineno;
112 char *sudoers = _PATH_SUDOERS;
113 char *stmp = _PATH_SUDOERS_TMP;
114 struct sudo_user sudo_user;
115 int parse_error = FALSE;
122 char buf[MAXPATHLEN*2]; /* buffer used for copying files */
123 char *Editor; /* editor to use */
124 char *UserEditor; /* editor user wants to use */
125 char *EditorPath; /* colon-separated list of editors */
126 char *av[4]; /* argument vector for run_command */
127 int checkonly; /* only check existing file? */
128 int sudoers_fd; /* sudoers file descriptor */
129 int stmp_fd; /* stmp file descriptor */
130 int n; /* length parameter */
131 int ch; /* getopt char */
132 time_t now; /* time now */
133 struct stat stmp_sb, sudoers_sb; /* to check for changes */
135 /* Warn about aliases that are used before being defined. */
139 * Parse command line options
147 while ((ch = getopt(argc, argv, "Vcf:sq")) != -1) {
150 (void) printf("visudo version %s\n", version);
153 checkonly++; /* check mode */
156 sudoers = optarg; /* sudoers file path */
157 easprintf(&stmp, "%s.tmp", optarg);
160 pedantic++; /* strict mode */
163 quiet++; /* quiet mode */
174 /* Mock up a fake sudo_user struct. */
175 user_host = user_shost = user_cmnd = "";
176 if ((sudo_user.pw = getpwuid(getuid())) == NULL) {
177 (void) fprintf(stderr, "%s: Can't find you in the passwd database.\n",
182 /* Setup defaults data structures. */
186 exit(check_syntax(quiet));
189 * Open sudoers, lock it and stat it.
190 * sudoers_fd must remain open throughout in order to hold the lock.
192 sudoers_fd = open(sudoers, O_RDWR | O_CREAT, SUDOERS_MODE);
193 if (sudoers_fd == -1) {
194 (void) fprintf(stderr, "%s: %s: %s\n", Argv[0], sudoers,
198 if (!lock_file(sudoers_fd, SUDO_TLOCK)) {
199 (void) fprintf(stderr, "%s: sudoers file busy, try again later.\n",
204 if (fstat(sudoers_fd, &sudoers_sb) == -1) {
206 if (stat(sudoers, &sudoers_sb) == -1) {
208 (void) fprintf(stderr, "%s: can't stat %s: %s\n",
209 Argv[0], sudoers, strerror(errno));
214 * Open sudoers temp file.
216 stmp_fd = open(stmp, O_WRONLY | O_CREAT | O_TRUNC, 0600);
218 (void) fprintf(stderr, "%s: %s: %s\n", Argv[0], stmp, strerror(errno));
222 /* Install signal handlers to clean up stmp if we are killed. */
225 /* Copy sudoers -> stmp and reset the mtime */
226 if (sudoers_sb.st_size) {
227 while ((n = read(sudoers_fd, buf, sizeof(buf))) > 0)
228 if (write(stmp_fd, buf, n) != n) {
229 (void) fprintf(stderr, "%s: Write failed: %s\n", Argv[0],
234 (void) close(stmp_fd);
235 (void) touch(stmp, sudoers_sb.st_mtime);
237 /* Parse sudoers to pull in editor and env_editor conf values. */
238 if ((yyin = fopen(stmp, "r"))) {
247 (void) close(stmp_fd);
250 * Check EDITOR and VISUAL environment variables to see which editor
251 * the user wants to use (we may not end up using it though).
252 * If the path is not fully-qualified, make it so and check that
253 * the specified executable actually exists.
255 if ((UserEditor = getenv("EDITOR")) == NULL || *UserEditor == '\0')
256 UserEditor = getenv("VISUAL");
257 if (UserEditor && *UserEditor == '\0')
259 else if (UserEditor) {
260 if (find_path(UserEditor, &Editor, getenv("PATH")) == FOUND) {
263 if (def_flag(I_ENV_EDITOR)) {
264 /* If we are honoring $EDITOR this is a fatal error. */
265 (void) fprintf(stderr,
266 "%s: specified editor (%s) doesn't exist!\n",
267 Argv[0], UserEditor);
270 /* Otherwise, just ignore $EDITOR. */
277 * See if we can use the user's choice of editors either because
278 * we allow any $EDITOR or because $EDITOR is in the allowable list.
280 Editor = EditorPath = NULL;
281 if (def_flag(I_ENV_EDITOR) && UserEditor)
283 else if (UserEditor) {
284 struct stat editor_sb;
285 struct stat user_editor_sb;
286 char *base, *userbase;
288 if (stat(UserEditor, &user_editor_sb) != 0) {
289 /* Should never happen since we already checked above. */
290 (void) fprintf(stderr, "%s: unable to stat editor (%s): %s\n",
291 Argv[0], UserEditor, strerror(errno));
294 EditorPath = estrdup(def_str(I_EDITOR));
295 Editor = strtok(EditorPath, ":");
298 * Both Editor and UserEditor should be fully qualified but
301 if ((base = strrchr(Editor, '/')) == NULL)
303 if ((userbase = strrchr(UserEditor, '/')) == NULL) {
310 * We compare the basenames first and then use stat to match
313 if (strcmp(base, userbase) == 0) {
314 if (stat(Editor, &editor_sb) == 0 && S_ISREG(editor_sb.st_mode)
315 && (editor_sb.st_mode & 0000111) &&
316 editor_sb.st_dev == user_editor_sb.st_dev &&
317 editor_sb.st_ino == user_editor_sb.st_ino)
320 } while ((Editor = strtok(NULL, ":")));
324 * Can't use $EDITOR, try each element of I_EDITOR until we
325 * find one that exists, is regular, and is executable.
327 if (Editor == NULL || *Editor == '\0') {
328 if (EditorPath != NULL)
330 EditorPath = estrdup(def_str(I_EDITOR));
331 Editor = strtok(EditorPath, ":");
333 if (sudo_goodpath(Editor))
335 } while ((Editor = strtok(NULL, ":")));
337 /* Bleah, none of the editors existed! */
338 if (Editor == NULL || *Editor == '\0') {
339 (void) fprintf(stderr, "%s: no editor found (editor path = %s)\n",
340 Argv[0], def_str(I_EDITOR));
346 * Edit the temp file and parse it (for sanity checking)
351 /* Build up argument vector for the command */
352 if ((av[0] = strrchr(Editor, '/')) != NULL)
357 if (parse_error == TRUE) {
358 (void) snprintf(linestr, sizeof(linestr), "+%d", errorlineno);
366 * We cannot check the editor's exit value against 0 since
367 * XPG4 specifies that vi's exit value is a function of the
368 * number of errors during editing (?!?!).
371 if (run_command(Editor, av) != -1) {
375 if (stat(stmp, &stmp_sb) < 0) {
376 (void) fprintf(stderr,
377 "%s: Can't stat temporary file (%s), %s unchanged.\n",
378 Argv[0], stmp, sudoers);
381 if (stmp_sb.st_size == 0) {
382 (void) fprintf(stderr,
383 "%s: Zero length temporary file (%s), %s unchanged.\n",
384 Argv[0], stmp, sudoers);
389 * Passed sanity checks so reopen stmp file and check
394 yyin = freopen(stmp, "r", yyin);
396 yyin = fopen(stmp, "r");
398 (void) fprintf(stderr,
399 "%s: Can't re-open temporary file (%s), %s unchanged.\n",
400 Argv[0], stmp, sudoers);
404 /* Clean slate for each parse */
409 /* Parse the sudoers file */
410 if (yyparse() && parse_error != TRUE) {
411 (void) fprintf(stderr,
412 "%s: Failed to parse temporary file (%s), unknown error.\n",
417 (void) fprintf(stderr,
418 "%s: Editor (%s) failed, %s unchanged.\n", Argv[0],
424 * Got an error, prompt the user for what to do now
426 if (parse_error == TRUE) {
428 case 'Q' : parse_error = FALSE; /* ignore parse error */
430 case 'x' : if (sudoers_sb.st_size == 0)
435 yyrestart(yyin); /* reset lexer */
437 } while (parse_error == TRUE);
440 * If the user didn't change the temp file, just unlink it.
442 if (sudoers_sb.st_mtime != now && sudoers_sb.st_mtime == stmp_sb.st_mtime &&
443 sudoers_sb.st_size == stmp_sb.st_size) {
444 (void) fprintf(stderr, "%s: sudoers file unchanged.\n", Argv[0]);
449 * Change mode and ownership of temp file so when
450 * we move it to sudoers things are kosher.
452 if (chown(stmp, SUDOERS_UID, SUDOERS_GID)) {
453 (void) fprintf(stderr,
454 "%s: Unable to set (uid, gid) of %s to (%d, %d): %s\n",
455 Argv[0], stmp, SUDOERS_UID, SUDOERS_GID, strerror(errno));
458 if (chmod(stmp, SUDOERS_MODE)) {
459 (void) fprintf(stderr,
460 "%s: Unable to change mode of %s to %o: %s\n",
461 Argv[0], stmp, SUDOERS_MODE, strerror(errno));
466 * Now that we have a sane stmp file (parses ok) it needs to be
467 * rename(2)'d to sudoers. If the rename(2) fails we try using
468 * mv(1) in case stmp and sudoers are on different filesystems.
470 if (rename(stmp, sudoers)) {
471 if (errno == EXDEV) {
472 (void) fprintf(stderr,
473 "%s: %s and %s not on the same filesystem, using mv to rename.\n",
474 Argv[0], stmp, sudoers);
476 /* Build up argument vector for the command */
477 if ((av[0] = strrchr(_PATH_MV, '/')) != NULL)
486 if (run_command(_PATH_MV, av)) {
487 (void) fprintf(stderr,
488 "%s: Command failed: '%s %s %s', %s unchanged.\n",
489 Argv[0], _PATH_MV, stmp, sudoers, sudoers);
493 (void) fprintf(stderr, "%s: Error renaming %s, %s unchanged: %s\n",
494 Argv[0], stmp, sudoers, strerror(errno));
503 * Dummy *_matches routines.
504 * These exist to allow us to use the same parser as sudo(8).
507 command_matches(cmnd, cmnd_args, path, sudoers_args)
524 hostname_matches(s, l, p)
538 netgr_matches(n, h, sh, u)
539 char *n, *h, *sh, *u;
563 * Assuming a parse error occurred, prompt the user for what they want
564 * to do now. Returns the first letter of their choice.
572 (void) fputs("What now? ", stdout);
574 for (c = choice; c != '\n' && c != EOF;)
586 (void) puts("Options are:");
587 (void) puts(" (e)dit sudoers file again");
588 (void) puts(" e(x)it without saving changes to sudoers file");
589 (void) puts(" (Q)uit and save changes to sudoers file (DANGER!)\n");
595 * Install signal handlers for visudo.
603 * Setup signal handlers to cleanup nicely.
605 sigemptyset(&sa.sa_mask);
606 sa.sa_flags = SA_RESTART;
607 sa.sa_handler = Exit;
608 (void) sigaction(SIGTERM, &sa, NULL);
609 (void) sigaction(SIGHUP, &sa, NULL);
610 (void) sigaction(SIGINT, &sa, NULL);
611 (void) sigaction(SIGQUIT, &sa, NULL);
615 run_command(path, argv)
623 (void) sigemptyset(&set);
624 (void) sigaddset(&set, SIGCHLD);
625 (void) sigprocmask(SIG_BLOCK, &set, &oset);
627 switch (pid = fork()) {
629 (void) fprintf(stderr,
630 "%s: unable to run %s: %s\n", Argv[0], path, strerror(errno));
632 break; /* NOTREACHED */
634 (void) sigprocmask(SIG_SETMASK, &oset, NULL);
636 (void) fprintf(stderr,
637 "%s: unable to run %s: %s\n", Argv[0], path, strerror(errno));
639 break; /* NOTREACHED */
643 pid = sudo_waitpid(pid, &status, 0);
648 (void) sigprocmask(SIG_SETMASK, &oset, NULL);
650 /* XXX - should use WEXITSTATUS() */
651 return(pid == -1 ? -1 : (status >> 8));
659 if ((yyin = fopen(sudoers, "r")) == NULL) {
661 (void) fprintf(stderr, "%s: unable to open %s: %s\n", Argv[0],
662 sudoers, strerror(errno));
667 if (yyparse() && parse_error != TRUE) {
669 (void) fprintf(stderr,
670 "%s: failed to parse %s file, unknown error.\n",
676 (void) printf("parse error in %s near line %d\n", sudoers,
679 (void) printf("%s file parsed OK\n", sudoers);
682 return(parse_error == TRUE);
686 * Unlink the sudoers temp file (if it exists) and exit.
687 * Used in place of a normal exit() and as a signal handler.
688 * A positive parameter indicates we were called as a signal handler.
694 char *emsg = " exiting due to signal.\n";
699 write(STDERR_FILENO, Argv[0], strlen(Argv[0]));
700 write(STDERR_FILENO, emsg, sizeof(emsg) - 1);
709 (void) fprintf(stderr, "usage: %s [-c] [-f sudoers] [-q] [-s] [-V]\n",