Imported Upstream version 1.6.6
[debian/sudo] / visudo.c
1 /*
2  * Copyright (c) 1996, 1998-2001 Todd C. Miller <Todd.Miller@courtesan.com>
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 <sys/types.h>
42 #include <sys/param.h>
43 #include <sys/stat.h>
44 #include <sys/file.h>
45 #include <sys/wait.h>
46 #include <stdio.h>
47 #ifdef STDC_HEADERS
48 # include <stdlib.h>
49 # include <stddef.h>
50 #else
51 # ifdef HAVE_STDLIB_H
52 #  include <stdlib.h>
53 # endif
54 #endif /* STDC_HEADERS */
55 #ifdef HAVE_STRING_H
56 # include <string.h>
57 #else
58 # ifdef HAVE_STRINGS_H
59 #  include <strings.h>
60 # endif
61 #endif /* HAVE_STRING_H */
62 #ifdef HAVE_UNISTD_H
63 #include <unistd.h>
64 #endif /* HAVE_UNISTD_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.146 2002/01/17 15:35:54 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 *, 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 *));
95
96 /*
97  * External globals exported by the parser
98  */
99 extern FILE *yyin, *yyout;
100 extern int errorlineno;
101 extern int pedantic;
102 extern int quiet;
103
104 /* For getopt(3) */
105 extern char *optarg;
106 extern int optind;
107
108 /*
109  * Globals
110  */
111 char **Argv;
112 char *sudoers = _PATH_SUDOERS;
113 char *stmp = _PATH_SUDOERS_TMP;
114 struct sudo_user sudo_user;
115 int parse_error = FALSE;
116
117 int
118 main(argc, argv)
119     int argc;
120     char **argv;
121 {
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 */
134
135     /* Warn about aliases that are used before being defined. */
136     pedantic = 1;
137
138     /*
139      * Parse command line options
140      */
141     Argv = argv;
142
143     /*
144      * Arg handling.
145      */
146     checkonly = 0;
147     while ((ch = getopt(argc, argv, "Vcf:sq")) != -1) {
148         switch (ch) {
149             case 'V':
150                 (void) printf("visudo version %s\n", version);
151                 exit(0);
152             case 'c':
153                 checkonly++;            /* check mode */
154                 break;
155             case 'f':
156                 sudoers = optarg;       /* sudoers file path */
157                 easprintf(&stmp, "%s.tmp", optarg);
158                 break;
159             case 's':
160                 pedantic++;             /* strict mode */
161                 break;
162             case 'q':
163                 quiet++;                /* quiet mode */
164                 break;
165             default:
166                 usage();
167         }
168     }
169     argc -= optind;
170     argv += optind;
171     if (argc)
172         usage();
173
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",
178             Argv[0]);
179         exit(1);
180     }
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         (void) fprintf(stderr, "%s: %s: %s\n", Argv[0], sudoers,
195             strerror(errno));
196         exit(1);
197     }
198     if (!lock_file(sudoers_fd, SUDO_TLOCK)) {
199         (void) fprintf(stderr, "%s: sudoers file busy, try again later.\n",
200             Argv[0]);
201         exit(1);
202     }
203 #ifdef HAVE_FSTAT
204     if (fstat(sudoers_fd, &sudoers_sb) == -1) {
205 #else
206     if (stat(sudoers, &sudoers_sb) == -1) {
207 #endif
208         (void) fprintf(stderr, "%s: can't stat %s: %s\n",
209             Argv[0], sudoers, strerror(errno));
210         exit(1);
211     }
212
213     /*
214      * Open sudoers temp file.
215      */
216     stmp_fd = open(stmp, O_WRONLY | O_CREAT | O_TRUNC, 0600);
217     if (stmp_fd < 0) {
218         (void) fprintf(stderr, "%s: %s: %s\n", Argv[0], stmp, strerror(errno));
219         exit(1);
220     }
221
222     /* Install signal handlers to clean up stmp if we are killed. */
223     setup_signals();
224
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],
230                 strerror(errno));
231                 Exit(-1);
232             }
233
234         (void) close(stmp_fd);
235         (void) touch(stmp, sudoers_sb.st_mtime);
236
237         /* Parse sudoers to pull in editor and env_editor conf values. */
238         if ((yyin = fopen(stmp, "r"))) {
239             yyout = stdout;
240             init_parser();
241             yyparse();
242             parse_error = FALSE;
243             yyrestart(yyin);
244             fclose(yyin);
245         }
246     } else
247         (void) close(stmp_fd);
248
249     /*
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.
254      */
255     if ((UserEditor = getenv("EDITOR")) == NULL || *UserEditor == '\0')
256         UserEditor = getenv("VISUAL");
257     if (UserEditor && *UserEditor == '\0')
258         UserEditor = NULL;
259     else if (UserEditor) {
260         if (find_path(UserEditor, &Editor, getenv("PATH")) == FOUND) {
261             UserEditor = Editor;
262         } else {
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);
268                 Exit(-1);
269             } else {
270                 /* Otherwise, just ignore $EDITOR. */
271                 UserEditor = NULL;
272             }
273         }
274     }
275
276     /*
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.
279      */
280     Editor = EditorPath = NULL;
281     if (def_flag(I_ENV_EDITOR) && UserEditor)
282         Editor = UserEditor;
283     else if (UserEditor) {
284         struct stat editor_sb;
285         struct stat user_editor_sb;
286         char *base, *userbase;
287
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));
292             Exit(-1);
293         }
294         EditorPath = estrdup(def_str(I_EDITOR));
295         Editor = strtok(EditorPath, ":");
296         do {
297             /*
298              * Both Editor and UserEditor should be fully qualified but
299              * check anyway...
300              */
301             if ((base = strrchr(Editor, '/')) == NULL)
302                 continue;
303             if ((userbase = strrchr(UserEditor, '/')) == NULL) {
304                 Editor = NULL;
305                 break;
306             }
307             base++, userbase++;
308
309             /*
310              * We compare the basenames first and then use stat to match
311              * for sure.
312              */
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)
318                     break;
319             }
320         } while ((Editor = strtok(NULL, ":")));
321     }
322
323     /*
324      * Can't use $EDITOR, try each element of I_EDITOR until we
325      * find one that exists, is regular, and is executable.
326      */
327     if (Editor == NULL || *Editor == '\0') {
328         if (EditorPath != NULL)
329             free(EditorPath);
330         EditorPath = estrdup(def_str(I_EDITOR));
331         Editor = strtok(EditorPath, ":");
332         do {
333             if (sudo_goodpath(Editor))
334                 break;
335         } while ((Editor = strtok(NULL, ":")));
336
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));
341             Exit(-1);
342         }
343     }
344
345     /*
346      * Edit the temp file and parse it (for sanity checking)
347      */
348     do {
349         char linestr[64];
350
351         /* Build up argument vector for the command */
352         if ((av[0] = strrchr(Editor, '/')) != NULL)
353             av[0]++;
354         else
355             av[0] = Editor;
356         n = 1;
357         if (parse_error == TRUE) {
358             (void) snprintf(linestr, sizeof(linestr), "+%d", errorlineno);
359             av[n++] = linestr;
360         }
361         av[n++] = stmp;
362         av[n++] = NULL;
363
364         /*
365          * Do the edit:
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 (?!?!).
369          */
370         now = time(NULL);
371         if (run_command(Editor, av) != -1) {
372             /*
373              * Sanity checks.
374              */
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);
379                 Exit(-1);
380             }
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);
385                 Exit(-1);
386             }
387
388             /*
389              * Passed sanity checks so reopen stmp file and check
390              * for parse errors.
391              */
392             yyout = stdout;
393             if (parse_error)
394                 yyin = freopen(stmp, "r", yyin);
395             else
396                 yyin = fopen(stmp, "r");
397             if (yyin == NULL) {
398                 (void) fprintf(stderr,
399                     "%s: Can't re-open temporary file (%s), %s unchanged.\n",
400                     Argv[0], stmp, sudoers);
401                 Exit(-1);
402             }
403
404             /* Clean slate for each parse */
405             user_runas = NULL;
406             init_defaults();
407             init_parser();
408
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",
413                     Argv[0], stmp);
414                 parse_error = TRUE;
415             }
416         } else {
417             (void) fprintf(stderr,
418                 "%s: Editor (%s) failed, %s unchanged.\n", Argv[0],
419                     Editor, sudoers);
420             Exit(-1);
421         }
422
423         /*
424          * Got an error, prompt the user for what to do now
425          */
426         if (parse_error == TRUE) {
427             switch (whatnow()) {
428                 case 'Q' :      parse_error = FALSE;    /* ignore parse error */
429                                 break;
430                 case 'x' :      if (sudoers_sb.st_size == 0)
431                                     unlink(sudoers);
432                                 Exit(0);
433                                 break;
434             }
435             yyrestart(yyin);    /* reset lexer */
436         }
437     } while (parse_error == TRUE);
438
439     /*
440      * If the user didn't change the temp file, just unlink it.
441      */
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]);
445         Exit(0);
446     }
447
448     /*
449      * Change mode and ownership of temp file so when
450      * we move it to sudoers things are kosher.
451      */
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));
456         Exit(-1);
457     }
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));
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 filesystems.
469      */
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);
475
476             /* Build up argument vector for the command */
477             if ((av[0] = strrchr(_PATH_MV, '/')) != NULL)
478                 av[0]++;
479             else
480                 av[0] = _PATH_MV;
481             av[1] = stmp;
482             av[2] = sudoers;
483             av[3] = NULL;
484
485             /* And run it... */
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);
490                 Exit(-1);
491             }
492         } else {
493             (void) fprintf(stderr, "%s: Error renaming %s, %s unchanged: %s\n",
494                                    Argv[0], stmp, sudoers, strerror(errno));
495             Exit(-1);
496         }
497     }
498
499     exit(0);
500 }
501
502 /*
503  * Dummy *_matches routines.
504  * These exist to allow us to use the same parser as sudo(8).
505  */
506 int
507 command_matches(cmnd, cmnd_args, path, sudoers_args)
508     char *cmnd;
509     char *cmnd_args;
510     char *path;
511     char *sudoers_args;
512 {
513     return(TRUE);
514 }
515
516 int
517 addr_matches(n)
518     char *n;
519 {
520     return(TRUE);
521 }
522
523 int
524 hostname_matches(s, l, p)
525     char *s, *l, *p;
526 {
527     return(TRUE);
528 }
529
530 int
531 usergr_matches(g, u)
532     char *g, *u;
533 {
534     return(TRUE);
535 }
536
537 int
538 netgr_matches(n, h, sh, u)
539     char *n, *h, *sh, *u;
540 {
541     return(TRUE);
542 }
543
544 void
545 set_fqdn()
546 {
547     return;
548 }
549
550 int
551 user_is_exempt()
552 {
553     return(TRUE);
554 }
555
556 void
557 init_envtables()
558 {
559     return;
560 }
561
562 /*
563  * Assuming a parse error occurred, prompt the user for what they want
564  * to do now.  Returns the first letter of their choice.
565  */
566 static char
567 whatnow()
568 {
569     int choice, c;
570
571     for (;;) {
572         (void) fputs("What now? ", stdout);
573         choice = getchar();
574         for (c = choice; c != '\n' && c != EOF;)
575             c = getchar();
576
577         switch (choice) {
578             case EOF:
579                 choice = 'x';
580                 /* FALLTHROUGH */
581             case 'e':
582             case 'x':
583             case 'Q':
584                 return(choice);
585             default:
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");
590         }
591     }
592 }
593
594 /*
595  * Install signal handlers for visudo.
596  */
597 static void
598 setup_signals()
599 {
600         sigaction_t sa;
601
602         /*
603          * Setup signal handlers to cleanup nicely.
604          */
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);
612 }
613
614 static int
615 run_command(path, argv)
616     char *path;
617     char **argv;
618 {
619     int status;
620     pid_t pid;
621     sigset_t set, oset;
622
623     (void) sigemptyset(&set);
624     (void) sigaddset(&set, SIGCHLD);
625     (void) sigprocmask(SIG_BLOCK, &set, &oset);
626
627     switch (pid = fork()) {
628         case -1:
629             (void) fprintf(stderr,
630                 "%s: unable to run %s: %s\n", Argv[0], path, strerror(errno));
631             Exit(-1);
632             break;      /* NOTREACHED */
633         case 0:
634             (void) sigprocmask(SIG_SETMASK, &oset, NULL);
635             execv(path, argv);
636             (void) fprintf(stderr,
637                 "%s: unable to run %s: %s\n", Argv[0], path, strerror(errno));
638             _exit(127);
639             break;      /* NOTREACHED */
640     }
641
642 #ifdef sudo_waitpid
643     pid = sudo_waitpid(pid, &status, 0);
644 #else
645     pid = wait(&status);
646 #endif
647
648     (void) sigprocmask(SIG_SETMASK, &oset, NULL);
649
650     /* XXX - should use WEXITSTATUS() */
651     return(pid == -1 ? -1 : (status >> 8));
652 }
653
654 static int
655 check_syntax(quiet)
656     int quiet;
657 {
658
659     if ((yyin = fopen(sudoers, "r")) == NULL) {
660         if (!quiet)
661             (void) fprintf(stderr, "%s: unable to open %s: %s\n", Argv[0],
662                 sudoers, strerror(errno));
663         exit(1);
664     }
665     yyout = stdout;
666     init_parser();
667     if (yyparse() && parse_error != TRUE) {
668         if (!quiet)
669             (void) fprintf(stderr,
670                 "%s: failed to parse %s file, unknown error.\n",
671                 Argv[0], sudoers);
672         parse_error = TRUE;
673     }
674     if (!quiet){
675         if (parse_error)
676             (void) printf("parse error in %s near line %d\n", sudoers,
677                 errorlineno);
678         else
679             (void) printf("%s file parsed OK\n", sudoers);
680     }
681
682     return(parse_error == TRUE);
683 }
684
685 /*
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.
689  */
690 static RETSIGTYPE
691 Exit(sig)
692     int sig;
693 {
694     char *emsg = " exiting due to signal.\n";
695
696     (void) unlink(stmp);
697
698     if (sig > 0) {
699         write(STDERR_FILENO, Argv[0], strlen(Argv[0]));
700         write(STDERR_FILENO, emsg, sizeof(emsg) - 1);
701         _exit(-sig);
702     }
703     exit(-sig);
704 }
705
706 static void
707 usage()
708 {
709     (void) fprintf(stderr, "usage: %s [-c] [-f sudoers] [-q] [-s] [-V]\n",
710         Argv[0]);
711     exit(1);
712 }