Imported Upstream version 1.6.8p5
[debian/sudo] / visudo.c
1 /*
2  * Copyright (c) 1996, 1998-2004 Todd C. Miller <Todd.Miller@courtesan.com>
3  *
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.
7  *
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.
15  *
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.
19  */
20
21 /*
22  * Lock the sudoers file for safe editing (ala vipw) and check for parse errors.
23  */
24
25 #define _SUDO_MAIN
26
27 #ifdef __TANDEM
28 # include <floss.h>
29 #endif
30
31 #include "config.h"
32
33 #include <sys/types.h>
34 #include <sys/param.h>
35 #include <sys/stat.h>
36 #include <sys/time.h>
37 #ifndef __TANDEM
38 # include <sys/file.h>
39 #endif
40 #include <sys/wait.h>
41 #include <stdio.h>
42 #ifdef STDC_HEADERS
43 # include <stdlib.h>
44 # include <stddef.h>
45 #else
46 # ifdef HAVE_STDLIB_H
47 #  include <stdlib.h>
48 # endif
49 #endif /* STDC_HEADERS */
50 #ifdef HAVE_STRING_H
51 # include <string.h>
52 #else
53 # ifdef HAVE_STRINGS_H
54 #  include <strings.h>
55 # endif
56 #endif /* HAVE_STRING_H */
57 #ifdef HAVE_UNISTD_H
58 #include <unistd.h>
59 #endif /* HAVE_UNISTD_H */
60 #ifdef HAVE_ERR_H
61 # include <err.h>
62 #else
63 # include "emul/err.h"
64 #endif /* HAVE_ERR_H */
65 #include <ctype.h>
66 #include <pwd.h>
67 #include <time.h>
68 #include <signal.h>
69 #include <errno.h>
70 #include <fcntl.h>
71
72 #include "sudo.h"
73 #include "version.h"
74
75 #ifndef lint
76 static const char rcsid[] = "$Sudo: visudo.c,v 1.170 2004/09/08 15:48:23 millert Exp $";
77 #endif /* lint */
78
79 /*
80  * Function prototypes
81  */
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 *));
97
98 /*
99  * External globals exported by the parser
100  */
101 extern FILE *yyin, *yyout;
102 extern int errorlineno;
103 extern int pedantic;
104 extern int quiet;
105
106 /* For getopt(3) */
107 extern char *optarg;
108 extern int optind;
109
110 /*
111  * Globals
112  */
113 char **Argv;
114 char *sudoers = _PATH_SUDOERS;
115 char *stmp = _PATH_SUDOERS_TMP;
116 struct sudo_user sudo_user;
117 int Argc, parse_error = FALSE;
118
119 int
120 main(argc, argv)
121     int argc;
122     char **argv;
123 {
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 */
138
139     /* Warn about aliases that are used before being defined. */
140     pedantic = 1;
141
142     Argv = argv;
143     if ((Argc = argc) < 1)
144         usage();
145
146     /*
147      * Arg handling.
148      */
149     checkonly = 0;
150     while ((ch = getopt(argc, argv, "Vcf:sq")) != -1) {
151         switch (ch) {
152             case 'V':
153                 (void) printf("%s version %s\n", getprogname(), version);
154                 exit(0);
155             case 'c':
156                 checkonly++;            /* check mode */
157                 break;
158             case 'f':
159                 sudoers = optarg;       /* sudoers file path */
160                 easprintf(&stmp, "%s.tmp", optarg);
161                 break;
162             case 's':
163                 pedantic++;             /* strict mode */
164                 break;
165             case 'q':
166                 quiet++;                /* quiet mode */
167                 break;
168             default:
169                 usage();
170         }
171     }
172     argc -= optind;
173     argv += optind;
174     if (argc)
175         usage();
176
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");
181
182     /* Setup defaults data structures. */
183     init_defaults();
184
185     if (checkonly)
186         exit(check_syntax(quiet));
187
188     /*
189      * Open sudoers, lock it and stat it.
190      * sudoers_fd must remain open throughout in order to hold the lock.
191      */
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");
197 #ifdef HAVE_FSTAT
198     if (fstat(sudoers_fd, &sb) == -1)
199 #else
200     if (stat(sudoers, &sb) == -1)
201 #endif
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);
206
207     /*
208      * Open sudoers temp file.
209      */
210     stmp_fd = open(stmp, O_WRONLY | O_CREAT | O_TRUNC, 0600);
211     if (stmp_fd < 0)
212         err(1, "%s", stmp);
213
214     /* Install signal handlers to clean up stmp if we are killed. */
215     setup_signals();
216
217     /* Copy sudoers -> stmp and reset the mtime */
218     if (sudoers_size) {
219         while ((n = read(sudoers_fd, buf, sizeof(buf))) > 0)
220             if (write(stmp_fd, buf, n) != n)
221                 err(1, "write error");
222
223         /* Add missing newline at EOF if needed. */
224         if (n > 0 && buf[n - 1] != '\n') {
225             buf[0] = '\n';
226             write(stmp_fd, buf, 1);
227         }
228
229         (void) touch(stmp_fd, stmp, &sudoers_mtim);
230         (void) close(stmp_fd);
231
232         /* Parse sudoers to pull in editor and env_editor conf values. */
233         if ((yyin = fopen(stmp, "r"))) {
234             yyout = stdout;
235             n = quiet;
236             quiet = 1;
237             init_parser();
238             yyparse();
239             parse_error = FALSE;
240             quiet = n;
241             fclose(yyin);
242         }
243     } else
244         (void) close(stmp_fd);
245
246     /*
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.
251      */
252     if ((UserEditor = getenv("VISUAL")) == NULL || *UserEditor == '\0')
253         UserEditor = getenv("EDITOR");
254     if (UserEditor && *UserEditor == '\0')
255         UserEditor = NULL;
256     else if (UserEditor) {
257         if (find_path(UserEditor, &Editor, NULL, getenv("PATH")) == FOUND) {
258             UserEditor = Editor;
259         } else {
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);
263                 Exit(-1);
264             } else {
265                 /* Otherwise, just ignore $EDITOR. */
266                 UserEditor = NULL;
267             }
268         }
269     }
270
271     /*
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.
274      */
275     Editor = EditorPath = NULL;
276     if (def_env_editor && UserEditor)
277         Editor = UserEditor;
278     else if (UserEditor) {
279         struct stat editor_sb;
280         struct stat user_editor_sb;
281         char *base, *userbase;
282
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);
286             Exit(-1);
287         }
288         EditorPath = estrdup(def_editor);
289         Editor = strtok(EditorPath, ":");
290         do {
291             /*
292              * Both Editor and UserEditor should be fully qualified but
293              * check anyway...
294              */
295             if ((base = strrchr(Editor, '/')) == NULL)
296                 continue;
297             if ((userbase = strrchr(UserEditor, '/')) == NULL) {
298                 Editor = NULL;
299                 break;
300             }
301             base++, userbase++;
302
303             /*
304              * We compare the basenames first and then use stat to match
305              * for sure.
306              */
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)
312                     break;
313             }
314         } while ((Editor = strtok(NULL, ":")));
315     }
316
317     /*
318      * Can't use $EDITOR, try each element of def_editor until we
319      * find one that exists, is regular, and is executable.
320      */
321     if (Editor == NULL || *Editor == '\0') {
322         if (EditorPath != NULL)
323             free(EditorPath);
324         EditorPath = estrdup(def_editor);
325         Editor = strtok(EditorPath, ":");
326         do {
327             if (sudo_goodpath(Editor, NULL))
328                 break;
329         } while ((Editor = strtok(NULL, ":")));
330
331         /* Bleah, none of the editors existed! */
332         if (Editor == NULL || *Editor == '\0') {
333             warnx("no editor found (editor path = %s)", def_editor);
334             Exit(-1);
335         }
336     }
337
338     /*
339      * Edit the temp file and parse it (for sanity checking)
340      */
341     do {
342         char linestr[64];
343
344         /* Build up argument vector for the command */
345         if ((av[0] = strrchr(Editor, '/')) != NULL)
346             av[0]++;
347         else
348             av[0] = Editor;
349         n = 1;
350         if (parse_error == TRUE) {
351             (void) snprintf(linestr, sizeof(linestr), "+%d", errorlineno);
352             av[n++] = linestr;
353         }
354         av[n++] = stmp;
355         av[n++] = NULL;
356
357         /*
358          * Do the edit:
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 (?!?!).
362          */
363         gettime(&ts1);
364         if (run_command(Editor, av) != -1) {
365             gettime(&ts2);
366             /*
367              * Sanity checks.
368              */
369             if (stat(stmp, &sb) < 0) {
370                 warnx("cannot stat temporary file (%s), %s unchanged",
371                     stmp, sudoers);
372                 Exit(-1);
373             }
374             if (sb.st_size == 0) {
375                 warnx("zero length temporary file (%s), %s unchanged",
376                     stmp, sudoers);
377                 Exit(-1);
378             }
379
380             /*
381              * Passed sanity checks so reopen stmp file and check
382              * for parse errors.
383              */
384             yyout = stdout;
385             yyin = fopen(stmp, "r+");
386             if (yyin == NULL) {
387                 warnx("can't re-open temporary file (%s), %s unchanged.",
388                     stmp, sudoers);
389                 Exit(-1);
390             }
391
392             /* Add missing newline at EOF if needed. */
393             if (fseek(yyin, -1, SEEK_END) == 0 && (ch = fgetc(yyin)) != '\n')
394                 fputc('\n', yyin);
395             rewind(yyin);
396
397             /* Clean slate for each parse */
398             user_runas = NULL;
399             init_defaults();
400             init_parser();
401
402             /* Parse the sudoers temp file */
403             yyrestart(yyin);
404             if (yyparse() && parse_error != TRUE) {
405                 warnx("unabled to parse temporary file (%s), unknown error",
406                     stmp);
407                 parse_error = TRUE;
408             }
409             fclose(yyin);
410         } else {
411             warnx("editor (%s) failed, %s unchanged", Editor, sudoers);
412             Exit(-1);
413         }
414
415         /*
416          * Got an error, prompt the user for what to do now
417          */
418         if (parse_error == TRUE) {
419             switch (whatnow()) {
420                 case 'Q' :      parse_error = FALSE;    /* ignore parse error */
421                                 break;
422                 case 'x' :      if (sudoers_size == 0)
423                                     unlink(sudoers);
424                                 Exit(0);
425                                 break;
426             }
427         }
428     } while (parse_error == TRUE);
429
430     /*
431      * If the user didn't change the temp file, just unlink it.
432      */
433     if (sudoers_size == sb.st_size &&
434         sudoers_mtim.tv_sec == mtim_getsec(sb) &&
435         sudoers_mtim.tv_nsec == mtim_getnsec(sb)) {
436         /*
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.
439          */
440 #ifdef HAVE_TIMESPECSUB2
441         timespecsub(&ts1, &ts2);
442 #else
443         timespecsub(&ts1, &ts2, &ts2);
444 #endif
445         if (timespecisset(&ts2)) {
446             warnx("sudoers file unchanged");
447             Exit(0);
448         }
449     }
450
451     /*
452      * Change mode and ownership of temp file so when
453      * we move it to sudoers things are kosher.
454      */
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);
458         Exit(-1);
459     }
460     if (chmod(stmp, SUDOERS_MODE)) {
461         warn("unable to change mode of %s to 0%o", stmp, SUDOERS_MODE);
462         Exit(-1);
463     }
464
465     /*
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.
469      */
470     if (rename(stmp, sudoers)) {
471         if (errno == EXDEV) {
472             warnx("%s and %s not on the same file system, using mv to rename",
473               stmp, sudoers);
474
475             /* Build up argument vector for the command */
476             if ((av[0] = strrchr(_PATH_MV, '/')) != NULL)
477                 av[0]++;
478             else
479                 av[0] = _PATH_MV;
480             av[1] = stmp;
481             av[2] = sudoers;
482             av[3] = NULL;
483
484             /* And run it... */
485             if (run_command(_PATH_MV, av)) {
486                 warnx("command failed: '%s %s %s', %s unchanged",
487                     _PATH_MV, stmp, sudoers, sudoers);
488                 Exit(-1);
489             }
490         } else {
491             warn("error renaming %s, %s unchanged", stmp, sudoers);
492             Exit(-1);
493         }
494     }
495
496     exit(0);
497 }
498
499 /*
500  * Dummy *_matches routines.
501  * These exist to allow us to use the same parser as sudo(8).
502  */
503 int
504 command_matches(path, sudoers_args)
505     char *path;
506     char *sudoers_args;
507 {
508     return(TRUE);
509 }
510
511 int
512 addr_matches(n)
513     char *n;
514 {
515     return(TRUE);
516 }
517
518 int
519 hostname_matches(s, l, p)
520     char *s, *l, *p;
521 {
522     return(TRUE);
523 }
524
525 int
526 usergr_matches(g, u, pw)
527     char *g, *u;
528     struct passwd *pw;
529 {
530     return(TRUE);
531 }
532
533 int
534 userpw_matches(s, u, pw)
535     char *s, *u;
536     struct passwd *pw;
537 {
538     return(TRUE);
539 }
540
541 int
542 netgr_matches(n, h, sh, u)
543     char *n, *h, *sh, *u;
544 {
545     return(TRUE);
546 }
547
548 void
549 set_fqdn()
550 {
551     return;
552 }
553
554 int
555 set_runaspw(user)
556     char *user;
557 {
558     extern int sudolineno, used_runas;
559
560     if (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);
564         if (pedantic > 1)
565             yyerror(NULL);
566     }
567     return(TRUE);
568 }
569
570 int
571 user_is_exempt()
572 {
573     return(TRUE);
574 }
575
576 void
577 init_envtables()
578 {
579     return;
580 }
581
582 /*
583  * Assuming a parse error occurred, prompt the user for what they want
584  * to do now.  Returns the first letter of their choice.
585  */
586 static char
587 whatnow()
588 {
589     int choice, c;
590
591     for (;;) {
592         (void) fputs("What now? ", stdout);
593         choice = getchar();
594         for (c = choice; c != '\n' && c != EOF;)
595             c = getchar();
596
597         switch (choice) {
598             case EOF:
599                 choice = 'x';
600                 /* FALLTHROUGH */
601             case 'e':
602             case 'x':
603             case 'Q':
604                 return(choice);
605             default:
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");
610         }
611     }
612 }
613
614 /*
615  * Install signal handlers for visudo.
616  */
617 static void
618 setup_signals()
619 {
620         sigaction_t sa;
621
622         /*
623          * Setup signal handlers to cleanup nicely.
624          */
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);
632 }
633
634 static int
635 run_command(path, argv)
636     char *path;
637     char **argv;
638 {
639     int status;
640     pid_t pid;
641     sigset_t set, oset;
642
643     (void) sigemptyset(&set);
644     (void) sigaddset(&set, SIGCHLD);
645     (void) sigprocmask(SIG_BLOCK, &set, &oset);
646
647     switch (pid = fork()) {
648         case -1:
649             warn("unable to run %s", path);
650             Exit(-1);
651             break;      /* NOTREACHED */
652         case 0:
653             (void) sigprocmask(SIG_SETMASK, &oset, NULL);
654             execv(path, argv);
655             warn("unable to run %s", path);
656             _exit(127);
657             break;      /* NOTREACHED */
658     }
659
660 #ifdef sudo_waitpid
661     pid = sudo_waitpid(pid, &status, 0);
662 #else
663     pid = wait(&status);
664 #endif
665
666     (void) sigprocmask(SIG_SETMASK, &oset, NULL);
667
668     if (pid == -1 || !WIFEXITED(status))
669         return(-1);
670     return(WEXITSTATUS(status));
671 }
672
673 static int
674 check_syntax(quiet)
675     int quiet;
676 {
677
678     if ((yyin = fopen(sudoers, "r")) == NULL) {
679         if (!quiet)
680             warn("unable to open %s", sudoers);
681         exit(1);
682     }
683     yyout = stdout;
684     init_parser();
685     if (yyparse() && parse_error != TRUE) {
686         if (!quiet)
687             warnx("failed to parse %s file, unknown error", sudoers);
688         parse_error = TRUE;
689     }
690     if (!quiet){
691         if (parse_error)
692             (void) printf("parse error in %s near line %d\n", sudoers,
693                 errorlineno);
694         else
695             (void) printf("%s file parsed OK\n", sudoers);
696     }
697
698     return(parse_error == TRUE);
699 }
700
701 /*
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.
705  */
706 static RETSIGTYPE
707 Exit(sig)
708     int sig;
709 {
710 #define emsg     " exiting due to signal.\n"
711
712     (void) unlink(stmp);
713
714     if (sig > 0) {
715         write(STDERR_FILENO, getprogname(), strlen(getprogname()));
716         write(STDERR_FILENO, emsg, sizeof(emsg) - 1);
717         _exit(sig);
718     }
719     exit(-sig);
720 }
721
722 static void
723 usage()
724 {
725     (void) fprintf(stderr, "usage: %s [-c] [-f sudoers] [-q] [-s] [-V]\n",
726         getprogname());
727     exit(1);
728 }