2 * Copyright (c) 1996, 1998-2005 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 */
67 #if TIME_WITH_SYS_TIME
74 # include <emul/timespec.h>
81 __unused static const char rcsid[] = "$Sudo: visudo.c,v 1.166.2.10 2007/09/01 13:39:13 millert Exp $";
89 struct timespec orig_mtim;
95 static void usage __P((void)) __attribute__((__noreturn__));
96 static char whatnow __P((void));
97 static RETSIGTYPE Exit __P((int));
98 static void edit_sudoers __P((struct sudoersfile *, char *, char *, int));
99 static void visudo __P((struct sudoersfile *, char *, char *));
100 static void setup_signals __P((void));
101 static void install_sudoers __P((struct sudoersfile *, int));
102 static int check_syntax __P(());
103 static int run_command __P((char *, char **));
104 static char *get_args __P((char *));
105 static char *get_editor __P((char **));
106 static FILE *open_sudoers __P((struct sudoersfile *));
108 int command_matches __P((char *, char *));
109 int addr_matches __P((char *));
110 int hostname_matches __P((char *, char *, char *));
111 int netgr_matches __P((char *, char *, char *, char *));
112 int usergr_matches __P((char *, char *, struct passwd *));
113 int userpw_matches __P((char *, char *, struct passwd *));
114 void init_parser __P((void));
115 void yyerror __P((char *));
116 void yyrestart __P((FILE *));
119 * External globals exported by the parser
122 extern int errorlineno;
134 struct sudo_user sudo_user;
135 int Argc, parse_error = FALSE;
136 static struct sudoersfile sudoers;
144 int ch, checkonly, n, oldperms;
146 /* Initialize sudoers struct. */
147 sudoers.path = _PATH_SUDOERS;
148 sudoers.tpath = _PATH_SUDOERS_TMP;
151 /* Warn about aliases that are used before being defined. */
155 if ((Argc = argc) < 1)
161 checkonly = oldperms = FALSE;
162 while ((ch = getopt(argc, argv, "Vcf:sq")) != -1) {
165 (void) printf("%s version %s\n", getprogname(), version);
168 checkonly++; /* check mode */
170 case 'f': /* sudoers file path */
171 sudoers.path = optarg;
172 easprintf(&sudoers.tpath, "%s.tmp", optarg);
176 pedantic++; /* strict mode */
179 quiet++; /* quiet mode */
190 /* Mock up a fake sudo_user struct. */
191 user_host = user_shost = user_cmnd = "";
192 if ((sudo_user.pw = getpwuid(getuid())) == NULL)
193 errx(1, "you don't exist in the passwd database");
195 /* Setup defaults data structures. */
199 exit(check_syntax());
202 * Open and parse the existing sudoers file(s) in quiet mode to highlight
203 * any existing errors and to pull in editor and env_editor conf values.
205 if ((yyin = open_sudoers(&sudoers)) == NULL)
206 err(1, "%s", sudoers.path);
214 /* Edit sudoers, check for parse errors and re-edit on failure. */
215 editor = get_editor(&args);
216 visudo(&sudoers, editor, args);
218 /* Install the new sudoers file. */
219 install_sudoers(&sudoers, oldperms);
225 * Edit the sudoers file.
226 * Returns TRUE on success, else FALSE.
229 edit_sudoers(sp, editor, args, lineno)
230 struct sudoersfile *sp;
234 int ac; /* argument count */
235 char **av; /* argument vector for run_command */
236 char *cp; /* scratch char pointer */
237 char linestr[64]; /* string version of lineno */
238 struct timespec ts1, ts2; /* time before and after edit */
239 struct stat sb; /* stat buffer */
241 /* Make timestamp on temp file match original. */
242 (void) touch(-1, sp->tpath, &sp->orig_mtim);
244 /* Find the length of the argument vector */
245 ac = 3 + (lineno > 0);
250 for (wasblank = FALSE, cp = args; *cp; cp++) {
251 if (isblank((unsigned char) *cp))
260 /* Build up argument vector for the command */
261 av = emalloc2(ac, sizeof(char *));
262 if ((av[0] = strrchr(editor, '/')) != NULL)
268 (void) snprintf(linestr, sizeof(linestr), "+%d", lineno);
272 for ((cp = strtok(args, " \t")); cp; (cp = strtok(NULL, " \t")))
275 av[ac++] = sp->tpath;
280 * We cannot check the editor's exit value against 0 since
281 * XPG4 specifies that vi's exit value is a function of the
282 * number of errors during editing (?!?!).
285 if (run_command(editor, av) != -1) {
290 if (stat(sp->tpath, &sb) < 0) {
291 warnx("cannot stat temporary file (%s), %s unchanged",
292 sp->tpath, sp->path);
295 if (sb.st_size == 0) {
296 warnx("zero length temporary file (%s), %s unchanged",
297 sp->tpath, sp->path);
301 warnx("editor (%s) failed, %s unchanged", editor, sp->path);
305 /* Check to see if the user changed the file. */
306 if (sp->orig_size == sb.st_size &&
307 sp->orig_mtim.tv_sec == mtim_getsec(sb) &&
308 sp->orig_mtim.tv_nsec == mtim_getnsec(sb)) {
310 * If mtime and size match but the user spent no measurable
311 * time in the editor we can't tell if the file was changed.
313 #ifdef HAVE_TIMESPECSUB2
314 timespecsub(&ts1, &ts2);
316 timespecsub(&ts1, &ts2, &ts2);
318 if (timespecisset(&ts2)) {
319 warnx("%s unchanged", sp->tpath);
326 * Parse sudoers after editing and re-edit any ones that caused a parse error.
327 * Returns TRUE on success, else FALSE.
330 visudo(sp, editor, args)
331 struct sudoersfile *sp;
337 * Parse the edited sudoers file and do sanity checking
340 edit_sudoers(sp, editor, args, errorlineno);
342 yyin = fopen(sp->tpath, "r+");
344 warnx("can't re-open temporary file (%s), %s unchanged.",
345 sp->tpath, sp->path);
349 /* Add missing newline at EOF if needed. */
350 if (fseek(yyin, -1, SEEK_END) == 0 && (ch = fgetc(yyin)) != '\n')
354 /* Clean slate for each parse */
359 /* Parse the sudoers temp file */
361 if (yyparse() && parse_error != TRUE) {
362 warnx("unabled to parse temporary file (%s), unknown error",
369 * Got an error, prompt the user for what to do now
373 case 'Q' : parse_error = FALSE; /* ignore parse error */
379 } while (parse_error);
383 * Set the owner and mode on a sudoers temp file and
384 * move it into place. Returns TRUE on success, else FALSE.
387 install_sudoers(sp, oldperms)
388 struct sudoersfile *sp;
394 * Change mode and ownership of temp file so when
395 * we move it to sp->path things are kosher.
398 /* Use perms of the existing file. */
400 if (fstat(sp->fd, &sb) == -1)
402 if (stat(sp->path, &sb) == -1)
404 err(1, "can't stat %s", sp->path);
405 (void) chown(sp->tpath, sb.st_uid, sb.st_gid);
406 (void) chmod(sp->tpath, sb.st_mode & 0777);
408 if (chown(sp->tpath, SUDOERS_UID, SUDOERS_GID) != 0) {
409 warn("unable to set (uid, gid) of %s to (%d, %d)",
410 sp->tpath, SUDOERS_UID, SUDOERS_GID);
413 if (chmod(sp->tpath, SUDOERS_MODE) != 0) {
414 warn("unable to change mode of %s to 0%o", sp->tpath, SUDOERS_MODE);
420 * Now that sp->tpath is sane (parses ok) it needs to be
421 * rename(2)'d to sp->path. If the rename(2) fails we try using
422 * mv(1) in case sp->tpath and sp->path are on different file systems.
424 if (rename(sp->tpath, sp->path) != 0) {
425 if (errno == EXDEV) {
427 warnx("%s and %s not on the same file system, using mv to rename",
428 sp->tpath, sp->path);
430 /* Build up argument vector for the command */
431 if ((av[0] = strrchr(_PATH_MV, '/')) != NULL)
440 if (run_command(_PATH_MV, av)) {
441 warnx("command failed: '%s %s %s', %s unchanged",
442 _PATH_MV, sp->tpath, sp->path, sp->path);
446 warn("error renaming %s, %s unchanged", sp->tpath, sp->path);
453 * Dummy *_matches routines.
454 * These exist to allow us to use the same parser as sudo(8).
457 command_matches(path, sudoers_args)
472 hostname_matches(s, l, p)
479 usergr_matches(g, u, pw)
487 userpw_matches(s, u, pw)
495 netgr_matches(n, h, sh, u)
496 char *n, *h, *sh, *u;
511 extern int sudolineno, used_runas;
514 (void) fprintf(stderr,
515 "%s: runas_default set after old value is in use near line %d\n",
516 pedantic > 1 ? "Error" : "Warning", sudolineno);
536 * Assuming a parse error occurred, prompt the user for what they want
537 * to do now. Returns the first letter of their choice.
545 (void) fputs("What now? ", stdout);
547 for (c = choice; c != '\n' && c != EOF;)
559 (void) puts("Options are:");
560 (void) puts(" (e)dit sudoers file again");
561 (void) puts(" e(x)it without saving changes to sudoers file");
562 (void) puts(" (Q)uit and save changes to sudoers file (DANGER!)\n");
568 * Install signal handlers for visudo.
576 * Setup signal handlers to cleanup nicely.
578 sigemptyset(&sa.sa_mask);
579 sa.sa_flags = SA_RESTART;
580 sa.sa_handler = Exit;
581 (void) sigaction(SIGTERM, &sa, NULL);
582 (void) sigaction(SIGHUP, &sa, NULL);
583 (void) sigaction(SIGINT, &sa, NULL);
584 (void) sigaction(SIGQUIT, &sa, NULL);
588 run_command(path, argv)
596 (void) sigemptyset(&set);
597 (void) sigaddset(&set, SIGCHLD);
598 (void) sigprocmask(SIG_BLOCK, &set, &oset);
600 switch (pid = fork()) {
602 warn("unable to run %s", path);
604 break; /* NOTREACHED */
606 (void) sigprocmask(SIG_SETMASK, &oset, NULL);
608 closefrom(STDERR_FILENO + 1);
610 warn("unable to run %s", path);
612 break; /* NOTREACHED */
616 pid = sudo_waitpid(pid, &status, 0);
621 (void) sigprocmask(SIG_SETMASK, &oset, NULL);
623 if (pid == -1 || !WIFEXITED(status))
625 return(WEXITSTATUS(status));
632 if ((yyin = fopen(sudoers.path, "r")) == NULL) {
634 warn("unable to open %s", sudoers.path);
638 if (yyparse() && parse_error != TRUE) {
640 warnx("failed to parse %s file, unknown error", sudoers.path);
645 (void) printf("parse error in %s near line %d\n", sudoers.path,
648 (void) printf("%s file parsed OK\n", sudoers.path);
651 return(parse_error == TRUE);
656 struct sudoersfile *sp;
661 char buf[PATH_MAX*2];
664 /* Open and lock sudoers. */
665 sp->fd = open(sp->path, O_RDWR | O_CREAT, SUDOERS_MODE);
667 err(1, "%s", sp->path);
668 if (!lock_file(sp->fd, SUDO_TLOCK))
669 errx(1, "%s busy, try again later", sp->path);
670 if ((fp = fdopen(sp->fd, "r")) == NULL)
671 err(1, "%s", sp->path);
673 /* Stash sudoers size and mtime. */
675 if (fstat(sp->fd, &sb) == -1)
677 if (stat(sp->path, &sb) == -1)
679 err(1, "can't stat %s", sp->path);
680 sp->orig_size = sb.st_size;
681 sp->orig_mtim.tv_sec = mtim_getsec(sb);
682 sp->orig_mtim.tv_nsec = mtim_getnsec(sb);
684 /* Create the temp file. */
685 tfd = open(sp->tpath, O_WRONLY | O_CREAT | O_TRUNC, 0600);
687 err(1, "%s", sp->tpath);
689 /* Install signal handlers to clean up temp file if we are killed. */
692 /* Copy sp->path -> sp->tpath. */
693 if (sp->orig_size != 0) {
694 while ((nread = read(sp->fd, buf, sizeof(buf))) > 0)
695 if (write(tfd, buf, nread) != nread) {
700 /* Add missing newline at EOF if needed. */
701 if (nread > 0 && buf[nread - 1] != '\n') {
716 char *Editor, *EditorArgs, *EditorPath, *UserEditor, *UserEditorArgs;
719 * Check VISUAL and EDITOR environment variables to see which editor
720 * the user wants to use (we may not end up using it though).
721 * If the path is not fully-qualified, make it so and check that
722 * the specified executable actually exists.
724 UserEditorArgs = NULL;
725 if ((UserEditor = getenv("VISUAL")) == NULL || *UserEditor == '\0')
726 UserEditor = getenv("EDITOR");
727 if (UserEditor && *UserEditor == '\0')
729 else if (UserEditor) {
730 UserEditorArgs = get_args(UserEditor);
731 if (find_path(UserEditor, &Editor, NULL, getenv("PATH")) == FOUND) {
734 if (def_env_editor) {
735 /* If we are honoring $EDITOR this is a fatal error. */
736 warnx("specified editor (%s) doesn't exist!", UserEditor);
739 /* Otherwise, just ignore $EDITOR. */
746 * See if we can use the user's choice of editors either because
747 * we allow any $EDITOR or because $EDITOR is in the allowable list.
749 Editor = EditorArgs = EditorPath = NULL;
750 if (def_env_editor && UserEditor) {
752 EditorArgs = UserEditorArgs;
753 } else if (UserEditor) {
754 struct stat editor_sb;
755 struct stat user_editor_sb;
756 char *base, *userbase;
758 if (stat(UserEditor, &user_editor_sb) != 0) {
759 /* Should never happen since we already checked above. */
760 warn("unable to stat editor (%s)", UserEditor);
763 EditorPath = estrdup(def_editor);
764 Editor = strtok(EditorPath, ":");
766 EditorArgs = get_args(Editor);
768 * Both Editor and UserEditor should be fully qualified but
771 if ((base = strrchr(Editor, '/')) == NULL)
773 if ((userbase = strrchr(UserEditor, '/')) == NULL) {
780 * We compare the basenames first and then use stat to match
783 if (strcmp(base, userbase) == 0) {
784 if (stat(Editor, &editor_sb) == 0 && S_ISREG(editor_sb.st_mode)
785 && (editor_sb.st_mode & 0000111) &&
786 editor_sb.st_dev == user_editor_sb.st_dev &&
787 editor_sb.st_ino == user_editor_sb.st_ino)
790 } while ((Editor = strtok(NULL, ":")));
794 * Can't use $EDITOR, try each element of def_editor until we
795 * find one that exists, is regular, and is executable.
797 if (Editor == NULL || *Editor == '\0') {
799 EditorPath = estrdup(def_editor);
800 Editor = strtok(EditorPath, ":");
802 EditorArgs = get_args(Editor);
803 if (sudo_goodpath(Editor, NULL))
805 } while ((Editor = strtok(NULL, ":")));
807 /* Bleah, none of the editors existed! */
808 if (Editor == NULL || *Editor == '\0') {
809 warnx("no editor found (editor path = %s)", def_editor);
818 * Split out any command line arguments and return them.
827 while (*args && !isblank((unsigned char) *args))
831 while (*args && isblank((unsigned char) *args))
834 return(*args ? args : NULL);
838 * Unlink the sudoers temp file (if it exists) and exit.
839 * Used in place of a normal exit() and as a signal handler.
840 * A positive parameter indicates we were called as a signal handler.
846 #define emsg " exiting due to signal.\n"
848 (void) unlink(sudoers.tpath);
851 write(STDERR_FILENO, getprogname(), strlen(getprogname()));
852 write(STDERR_FILENO, emsg, sizeof(emsg) - 1);
861 (void) fprintf(stderr, "usage: %s [-c] [-q] [-s] [-V] [-f sudoers]\n",