2 * Copyright (c) 1996, 1998-2004 Todd C. Miller <Todd.Miller@courtesan.com>
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.
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.
16 * Sponsored in part by the Defense Advanced Research Projects
17 * Agency (DARPA) and Air Force Research Laboratory, Air Force
18 * Materiel Command, USAF, under agreement number F39502-99-1-0512.
22 * Lock the sudoers file for safe editing (ala vipw) and check for parse errors.
33 #include <sys/types.h>
34 #include <sys/param.h>
38 # include <sys/file.h>
49 #endif /* STDC_HEADERS */
53 # ifdef HAVE_STRINGS_H
56 #endif /* HAVE_STRING_H */
59 #endif /* HAVE_UNISTD_H */
63 # include "emul/err.h"
64 #endif /* HAVE_ERR_H */
76 static const char rcsid[] = "$Sudo: visudo.c,v 1.170 2004/09/08 15:48:23 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 *));
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 *, struct passwd *));
93 int userpw_matches __P((char *, char *, struct passwd *));
94 void init_parser __P((void));
95 void yyerror __P((char *));
96 void yyrestart __P((FILE *));
99 * External globals exported by the parser
101 extern FILE *yyin, *yyout;
102 extern int errorlineno;
114 char *sudoers = _PATH_SUDOERS;
115 char *stmp = _PATH_SUDOERS_TMP;
116 struct sudo_user sudo_user;
117 int Argc, parse_error = FALSE;
124 char buf[PATH_MAX*2]; /* buffer used for copying files */
125 char *Editor; /* editor to use */
126 char *UserEditor; /* editor user wants to use */
127 char *EditorPath; /* colon-separated list of editors */
128 char *av[4]; /* argument vector for run_command */
129 int checkonly; /* only check existing file? */
130 int sudoers_fd; /* sudoers file descriptor */
131 int stmp_fd; /* stmp file descriptor */
132 int n; /* length parameter */
133 int ch; /* getopt char */
134 struct timespec ts1, ts2; /* time before and after edit */
135 struct timespec sudoers_mtim; /* starting mtime of sudoers file */
136 off_t sudoers_size; /* starting size of sudoers file */
137 struct stat sb; /* stat buffer */
139 /* Warn about aliases that are used before being defined. */
143 if ((Argc = argc) < 1)
150 while ((ch = getopt(argc, argv, "Vcf:sq")) != -1) {
153 (void) printf("%s version %s\n", getprogname(), version);
156 checkonly++; /* check mode */
159 sudoers = optarg; /* sudoers file path */
160 easprintf(&stmp, "%s.tmp", optarg);
163 pedantic++; /* strict mode */
166 quiet++; /* quiet mode */
177 /* Mock up a fake sudo_user struct. */
178 user_host = user_shost = user_cmnd = "";
179 if ((sudo_user.pw = getpwuid(getuid())) == NULL)
180 errx(1, "you don't exist in the passwd database");
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 err(1, "%s", sudoers);
195 if (!lock_file(sudoers_fd, SUDO_TLOCK))
196 errx(1, "sudoers file busy, try again later");
198 if (fstat(sudoers_fd, &sb) == -1)
200 if (stat(sudoers, &sb) == -1)
202 err(1, "can't stat %s", sudoers);
203 sudoers_size = sb.st_size;
204 sudoers_mtim.tv_sec = mtim_getsec(sb);
205 sudoers_mtim.tv_nsec = mtim_getnsec(sb);
208 * Open sudoers temp file.
210 stmp_fd = open(stmp, O_WRONLY | O_CREAT | O_TRUNC, 0600);
214 /* Install signal handlers to clean up stmp if we are killed. */
217 /* Copy sudoers -> stmp and reset the mtime */
219 while ((n = read(sudoers_fd, buf, sizeof(buf))) > 0)
220 if (write(stmp_fd, buf, n) != n)
221 err(1, "write error");
223 /* Add missing newline at EOF if needed. */
224 if (n > 0 && buf[n - 1] != '\n') {
226 write(stmp_fd, buf, 1);
229 (void) touch(stmp_fd, stmp, &sudoers_mtim);
230 (void) close(stmp_fd);
232 /* Parse sudoers to pull in editor and env_editor conf values. */
233 if ((yyin = fopen(stmp, "r"))) {
244 (void) close(stmp_fd);
247 * Check VISUAL and EDITOR environment variables to see which editor
248 * the user wants to use (we may not end up using it though).
249 * If the path is not fully-qualified, make it so and check that
250 * the specified executable actually exists.
252 if ((UserEditor = getenv("VISUAL")) == NULL || *UserEditor == '\0')
253 UserEditor = getenv("EDITOR");
254 if (UserEditor && *UserEditor == '\0')
256 else if (UserEditor) {
257 if (find_path(UserEditor, &Editor, NULL, getenv("PATH")) == FOUND) {
260 if (def_env_editor) {
261 /* If we are honoring $EDITOR this is a fatal error. */
262 warnx("specified editor (%s) doesn't exist!", UserEditor);
265 /* Otherwise, just ignore $EDITOR. */
272 * See if we can use the user's choice of editors either because
273 * we allow any $EDITOR or because $EDITOR is in the allowable list.
275 Editor = EditorPath = NULL;
276 if (def_env_editor && UserEditor)
278 else if (UserEditor) {
279 struct stat editor_sb;
280 struct stat user_editor_sb;
281 char *base, *userbase;
283 if (stat(UserEditor, &user_editor_sb) != 0) {
284 /* Should never happen since we already checked above. */
285 warn("unable to stat editor (%s)", UserEditor);
288 EditorPath = estrdup(def_editor);
289 Editor = strtok(EditorPath, ":");
292 * Both Editor and UserEditor should be fully qualified but
295 if ((base = strrchr(Editor, '/')) == NULL)
297 if ((userbase = strrchr(UserEditor, '/')) == NULL) {
304 * We compare the basenames first and then use stat to match
307 if (strcmp(base, userbase) == 0) {
308 if (stat(Editor, &editor_sb) == 0 && S_ISREG(editor_sb.st_mode)
309 && (editor_sb.st_mode & 0000111) &&
310 editor_sb.st_dev == user_editor_sb.st_dev &&
311 editor_sb.st_ino == user_editor_sb.st_ino)
314 } while ((Editor = strtok(NULL, ":")));
318 * Can't use $EDITOR, try each element of def_editor until we
319 * find one that exists, is regular, and is executable.
321 if (Editor == NULL || *Editor == '\0') {
322 if (EditorPath != NULL)
324 EditorPath = estrdup(def_editor);
325 Editor = strtok(EditorPath, ":");
327 if (sudo_goodpath(Editor, NULL))
329 } while ((Editor = strtok(NULL, ":")));
331 /* Bleah, none of the editors existed! */
332 if (Editor == NULL || *Editor == '\0') {
333 warnx("no editor found (editor path = %s)", def_editor);
339 * Edit the temp file and parse it (for sanity checking)
344 /* Build up argument vector for the command */
345 if ((av[0] = strrchr(Editor, '/')) != NULL)
350 if (parse_error == TRUE) {
351 (void) snprintf(linestr, sizeof(linestr), "+%d", errorlineno);
359 * We cannot check the editor's exit value against 0 since
360 * XPG4 specifies that vi's exit value is a function of the
361 * number of errors during editing (?!?!).
364 if (run_command(Editor, av) != -1) {
369 if (stat(stmp, &sb) < 0) {
370 warnx("cannot stat temporary file (%s), %s unchanged",
374 if (sb.st_size == 0) {
375 warnx("zero length temporary file (%s), %s unchanged",
381 * Passed sanity checks so reopen stmp file and check
385 yyin = fopen(stmp, "r+");
387 warnx("can't re-open temporary file (%s), %s unchanged.",
392 /* Add missing newline at EOF if needed. */
393 if (fseek(yyin, -1, SEEK_END) == 0 && (ch = fgetc(yyin)) != '\n')
397 /* Clean slate for each parse */
402 /* Parse the sudoers temp file */
404 if (yyparse() && parse_error != TRUE) {
405 warnx("unabled to parse temporary file (%s), unknown error",
411 warnx("editor (%s) failed, %s unchanged", Editor, sudoers);
416 * Got an error, prompt the user for what to do now
418 if (parse_error == TRUE) {
420 case 'Q' : parse_error = FALSE; /* ignore parse error */
422 case 'x' : if (sudoers_size == 0)
428 } while (parse_error == TRUE);
431 * If the user didn't change the temp file, just unlink it.
433 if (sudoers_size == sb.st_size &&
434 sudoers_mtim.tv_sec == mtim_getsec(sb) &&
435 sudoers_mtim.tv_nsec == mtim_getnsec(sb)) {
437 * If mtime and size match but the user spent no measurable
438 * time in the editor we can't tell if the file was changed.
440 #ifdef HAVE_TIMESPECSUB2
441 timespecsub(&ts1, &ts2);
443 timespecsub(&ts1, &ts2, &ts2);
445 if (timespecisset(&ts2)) {
446 warnx("sudoers file unchanged");
452 * Change mode and ownership of temp file so when
453 * we move it to sudoers things are kosher.
455 if (chown(stmp, SUDOERS_UID, SUDOERS_GID)) {
456 warn("unable to set (uid, gid) of %s to (%d, %d)",
457 stmp, SUDOERS_UID, SUDOERS_GID);
460 if (chmod(stmp, SUDOERS_MODE)) {
461 warn("unable to change mode of %s to 0%o", stmp, SUDOERS_MODE);
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 file systems.
470 if (rename(stmp, sudoers)) {
471 if (errno == EXDEV) {
472 warnx("%s and %s not on the same file system, using mv to rename",
475 /* Build up argument vector for the command */
476 if ((av[0] = strrchr(_PATH_MV, '/')) != NULL)
485 if (run_command(_PATH_MV, av)) {
486 warnx("command failed: '%s %s %s', %s unchanged",
487 _PATH_MV, stmp, sudoers, sudoers);
491 warn("error renaming %s, %s unchanged", stmp, sudoers);
500 * Dummy *_matches routines.
501 * These exist to allow us to use the same parser as sudo(8).
504 command_matches(path, sudoers_args)
519 hostname_matches(s, l, p)
526 usergr_matches(g, u, pw)
534 userpw_matches(s, u, pw)
542 netgr_matches(n, h, sh, u)
543 char *n, *h, *sh, *u;
558 extern int sudolineno, used_runas;
561 (void) fprintf(stderr,
562 "%s: runas_default set after old value is in use near line %d\n",
563 pedantic > 1 ? "Error" : "Warning", sudolineno);
583 * Assuming a parse error occurred, prompt the user for what they want
584 * to do now. Returns the first letter of their choice.
592 (void) fputs("What now? ", stdout);
594 for (c = choice; c != '\n' && c != EOF;)
606 (void) puts("Options are:");
607 (void) puts(" (e)dit sudoers file again");
608 (void) puts(" e(x)it without saving changes to sudoers file");
609 (void) puts(" (Q)uit and save changes to sudoers file (DANGER!)\n");
615 * Install signal handlers for visudo.
623 * Setup signal handlers to cleanup nicely.
625 sigemptyset(&sa.sa_mask);
626 sa.sa_flags = SA_RESTART;
627 sa.sa_handler = Exit;
628 (void) sigaction(SIGTERM, &sa, NULL);
629 (void) sigaction(SIGHUP, &sa, NULL);
630 (void) sigaction(SIGINT, &sa, NULL);
631 (void) sigaction(SIGQUIT, &sa, NULL);
635 run_command(path, argv)
643 (void) sigemptyset(&set);
644 (void) sigaddset(&set, SIGCHLD);
645 (void) sigprocmask(SIG_BLOCK, &set, &oset);
647 switch (pid = fork()) {
649 warn("unable to run %s", path);
651 break; /* NOTREACHED */
653 (void) sigprocmask(SIG_SETMASK, &oset, NULL);
655 warn("unable to run %s", path);
657 break; /* NOTREACHED */
661 pid = sudo_waitpid(pid, &status, 0);
666 (void) sigprocmask(SIG_SETMASK, &oset, NULL);
668 if (pid == -1 || !WIFEXITED(status))
670 return(WEXITSTATUS(status));
678 if ((yyin = fopen(sudoers, "r")) == NULL) {
680 warn("unable to open %s", sudoers);
685 if (yyparse() && parse_error != TRUE) {
687 warnx("failed to parse %s file, unknown error", sudoers);
692 (void) printf("parse error in %s near line %d\n", sudoers,
695 (void) printf("%s file parsed OK\n", sudoers);
698 return(parse_error == TRUE);
702 * Unlink the sudoers temp file (if it exists) and exit.
703 * Used in place of a normal exit() and as a signal handler.
704 * A positive parameter indicates we were called as a signal handler.
710 #define emsg " exiting due to signal.\n"
715 write(STDERR_FILENO, getprogname(), strlen(getprogname()));
716 write(STDERR_FILENO, emsg, sizeof(emsg) - 1);
725 (void) fprintf(stderr, "usage: %s [-c] [-f sudoers] [-q] [-s] [-V]\n",