1: /*
2: * Copyright (c) 1996, 1998-2005, 2007-2013
3: * Todd C. Miller <Todd.Miller@courtesan.com>
4: *
5: * Permission to use, copy, modify, and distribute this software for any
6: * purpose with or without fee is hereby granted, provided that the above
7: * copyright notice and this permission notice appear in all copies.
8: *
9: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16: *
17: * Sponsored in part by the Defense Advanced Research Projects
18: * Agency (DARPA) and Air Force Research Laboratory, Air Force
19: * Materiel Command, USAF, under agreement number F39502-99-1-0512.
20: */
21:
22: /*
23: * Lock the sudoers file for safe editing (ala vipw) and check for parse errors.
24: */
25:
26: #define _SUDO_MAIN
27:
28: #ifdef __TANDEM
29: # include <floss.h>
30: #endif
31:
32: #include <config.h>
33:
34: #include <sys/types.h>
35: #include <sys/stat.h>
36: #include <sys/socket.h>
37: #include <sys/time.h>
38: #include <sys/uio.h>
39: #ifndef __TANDEM
40: # include <sys/file.h>
41: #endif
42: #include <sys/wait.h>
43: #include <stdio.h>
44: #ifdef STDC_HEADERS
45: # include <stdlib.h>
46: # include <stddef.h>
47: #else
48: # ifdef HAVE_STDLIB_H
49: # include <stdlib.h>
50: # endif
51: #endif /* STDC_HEADERS */
52: #ifdef HAVE_STRING_H
53: # include <string.h>
54: #endif /* HAVE_STRING_H */
55: #ifdef HAVE_STRINGS_H
56: # include <strings.h>
57: #endif /* HAVE_STRINGS_H */
58: #ifdef HAVE_UNISTD_H
59: #include <unistd.h>
60: #endif /* HAVE_UNISTD_H */
61: #include <stdarg.h>
62: #include <ctype.h>
63: #include <pwd.h>
64: #include <grp.h>
65: #include <signal.h>
66: #include <errno.h>
67: #include <fcntl.h>
68: #include <netinet/in.h>
69: #include <arpa/inet.h>
70: #ifdef TIME_WITH_SYS_TIME
71: # include <time.h>
72: #endif
73: #ifdef HAVE_GETOPT_LONG
74: # include <getopt.h>
75: # else
76: # include "compat/getopt.h"
77: #endif /* HAVE_GETOPT_LONG */
78:
79: #include "sudoers.h"
80: #include "parse.h"
81: #include "redblack.h"
82: #include "sudoers_version.h"
83: #include "sudo_conf.h"
84: #include <gram.h>
85:
86: struct sudoersfile {
87: TAILQ_ENTRY(sudoersfile) entries;
88: char *path;
89: char *tpath;
90: int fd;
91: int modified;
92: int doedit;
93: };
94: TAILQ_HEAD(sudoersfile_list, sudoersfile);
95:
96: /*
97: * Function prototypes
98: */
99: static void quit(int);
100: static char *get_args(char *);
101: static char *get_editor(char **);
102: static void get_hostname(void);
103: static int whatnow(void);
104: static int check_aliases(bool, bool);
105: static bool check_syntax(char *, bool, bool, bool);
106: static bool edit_sudoers(struct sudoersfile *, char *, char *, int);
107: static bool install_sudoers(struct sudoersfile *, bool);
108: static int print_unused(void *, void *);
109: static bool reparse_sudoers(char *, char *, bool, bool);
110: static int run_command(char *, char **);
111: static void setup_signals(void);
112: static void help(void) __attribute__((__noreturn__));
113: static void usage(int);
114: static void visudo_cleanup(void);
115:
116: extern bool export_sudoers(const char *, const char *, bool, bool);
117:
118: extern void sudoerserror(const char *);
119: extern void sudoersrestart(FILE *);
120:
121: /*
122: * Globals
123: */
124: struct sudo_user sudo_user;
125: struct passwd *list_pw;
126: static struct sudoersfile_list sudoerslist = TAILQ_HEAD_INITIALIZER(sudoerslist);
127: static struct rbtree *alias_freelist;
128: static bool checkonly;
129: static const char short_opts[] = "cf:hqsVx:";
130: static struct option long_opts[] = {
131: { "check", no_argument, NULL, 'c' },
132: { "export", required_argument, NULL, 'x' },
133: { "file", required_argument, NULL, 'f' },
134: { "help", no_argument, NULL, 'h' },
135: { "quiet", no_argument, NULL, 'q' },
136: { "strict", no_argument, NULL, 's' },
137: { "version", no_argument, NULL, 'V' },
138: { NULL, no_argument, NULL, '\0' },
139: };
140:
141: __dso_public int main(int argc, char *argv[]);
142:
143: int
144: main(int argc, char *argv[])
145: {
146: struct sudoersfile *sp;
147: char *args, *editor, *sudoers_path;
148: int ch, exitcode = 0;
149: bool quiet, strict, oldperms;
150: const char *export_path;
151: debug_decl(main, SUDO_DEBUG_MAIN)
152:
153: #if defined(SUDO_DEVEL) && defined(__OpenBSD__)
154: {
155: extern char *malloc_options;
156: malloc_options = "AFGJPR";
157: }
158: #endif
159:
160: initprogname(argc > 0 ? argv[0] : "visudo");
161: sudoers_initlocale(setlocale(LC_ALL, ""), def_sudoers_locale);
162: bindtextdomain("sudoers", LOCALEDIR); /* XXX - should have visudo domain */
163: textdomain("sudoers");
164:
165: if (argc < 1)
166: usage(1);
167:
168: /* Register fatal/fatalx callback. */
169: fatal_callback_register(visudo_cleanup);
170:
171: /* Read sudo.conf. */
172: sudo_conf_read(NULL);
173:
174: /*
175: * Arg handling.
176: */
177: checkonly = oldperms = quiet = strict = false;
178: export_path = NULL;
179: sudoers_path = _PATH_SUDOERS;
180: while ((ch = getopt_long(argc, argv, short_opts, long_opts, NULL)) != -1) {
181: switch (ch) {
182: case 'V':
183: (void) printf(_("%s version %s\n"), getprogname(),
184: PACKAGE_VERSION);
185: (void) printf(_("%s grammar version %d\n"), getprogname(),
186: SUDOERS_GRAMMAR_VERSION);
187: goto done;
188: case 'c':
189: checkonly = true; /* check mode */
190: break;
191: case 'f':
192: sudoers_path = optarg; /* sudoers file path */
193: oldperms = true;
194: break;
195: case 'h':
196: help();
197: break;
198: case 's':
199: strict = true; /* strict mode */
200: break;
201: case 'q':
202: quiet = true; /* quiet mode */
203: break;
204: case 'x':
205: export_path = optarg; /* export mode */
206: break;
207: default:
208: usage(1);
209: }
210: }
211: /* There should be no other command line arguments. */
212: if (argc - optind != 0)
213: usage(1);
214:
215: sudo_setpwent();
216: sudo_setgrent();
217:
218: /* Mock up a fake sudo_user struct. */
219: user_cmnd = user_base = "";
220: if ((sudo_user.pw = sudo_getpwuid(getuid())) == NULL)
221: fatalx(U_("you do not exist in the %s database"), "passwd");
222: get_hostname();
223:
224: /* Setup defaults data structures. */
225: init_defaults();
226:
227: if (checkonly) {
228: exitcode = check_syntax(sudoers_path, quiet, strict, oldperms) ? 0 : 1;
229: goto done;
230: }
231: if (export_path != NULL) {
232: exitcode = export_sudoers(sudoers_path, export_path, quiet, strict) ? 0 : 1;
233: goto done;
234: }
235:
236: /*
237: * Parse the existing sudoers file(s) to highlight any existing
238: * errors and to pull in editor and env_editor conf values.
239: */
240: if ((sudoersin = open_sudoers(sudoers_path, true, NULL)) == NULL)
241: exit(1);
242: init_parser(sudoers_path, false);
243: sudoersparse();
244: (void) update_defaults(SETDEF_GENERIC|SETDEF_HOST|SETDEF_USER);
245:
246: editor = get_editor(&args);
247:
248: /* Install signal handlers to clean up temp files if we are killed. */
249: setup_signals();
250:
251: /* Edit the sudoers file(s) */
252: TAILQ_FOREACH(sp, &sudoerslist, entries) {
253: if (!sp->doedit)
254: continue;
255: if (sp != TAILQ_FIRST(&sudoerslist)) {
256: printf(_("press return to edit %s: "), sp->path);
257: while ((ch = getchar()) != EOF && ch != '\n')
258: continue;
259: }
260: edit_sudoers(sp, editor, args, -1);
261: }
262:
263: /*
264: * Check edited files for a parse error, re-edit any that fail
265: * and install the edited files as needed.
266: */
267: if (reparse_sudoers(editor, args, strict, quiet)) {
268: TAILQ_FOREACH(sp, &sudoerslist, entries) {
269: (void) install_sudoers(sp, oldperms);
270: }
271: }
272:
273: done:
274: sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys, exitcode);
275: exit(exitcode);
276: }
277:
278: /*
279: * List of editors that support the "+lineno" command line syntax.
280: * If an entry starts with '*' the tail end of the string is matched.
281: * No other wild cards are supported.
282: */
283: static char *lineno_editors[] = {
284: "ex",
285: "nex",
286: "vi",
287: "nvi",
288: "vim",
289: "elvis",
290: "*macs",
291: "mg",
292: "vile",
293: "jove",
294: "pico",
295: "nano",
296: "ee",
297: "joe",
298: "zile",
299: NULL
300: };
301:
302: /*
303: * Edit each sudoers file.
304: * Returns true on success, else false.
305: */
306: static bool
307: edit_sudoers(struct sudoersfile *sp, char *editor, char *args, int lineno)
308: {
309: int tfd; /* sudoers temp file descriptor */
310: bool modified; /* was the file modified? */
311: int ac; /* argument count */
312: char **av; /* argument vector for run_command */
313: char *cp; /* scratch char pointer */
314: char buf[PATH_MAX*2]; /* buffer used for copying files */
315: char linestr[64]; /* string version of lineno */
316: struct timeval tv, tv1, tv2; /* time before and after edit */
317: struct timeval orig_mtim; /* starting mtime of sudoers file */
318: off_t orig_size; /* starting size of sudoers file */
319: ssize_t nread; /* number of bytes read */
320: struct stat sb; /* stat buffer */
321: bool rval = false; /* return value */
322: debug_decl(edit_sudoers, SUDO_DEBUG_UTIL)
323:
324: if (fstat(sp->fd, &sb) == -1)
325: fatal(U_("unable to stat %s"), sp->path);
326: orig_size = sb.st_size;
327: mtim_get(&sb, &orig_mtim);
328:
329: /* Create the temp file if needed and set timestamp. */
330: if (sp->tpath == NULL) {
331: easprintf(&sp->tpath, "%s.tmp", sp->path);
332: tfd = open(sp->tpath, O_WRONLY | O_CREAT | O_TRUNC, 0600);
333: if (tfd < 0)
334: fatal("%s", sp->tpath);
335:
336: /* Copy sp->path -> sp->tpath and reset the mtime. */
337: if (orig_size != 0) {
338: (void) lseek(sp->fd, (off_t)0, SEEK_SET);
339: while ((nread = read(sp->fd, buf, sizeof(buf))) > 0)
340: if (write(tfd, buf, nread) != nread)
341: fatal(U_("write error"));
342:
343: /* Add missing newline at EOF if needed. */
344: if (nread > 0 && buf[nread - 1] != '\n') {
345: buf[0] = '\n';
346: if (write(tfd, buf, 1) != 1)
347: fatal(U_("write error"));
348: }
349: }
350: (void) close(tfd);
351: }
352: (void) touch(-1, sp->tpath, &orig_mtim);
353:
354: /* Does the editor support +lineno? */
355: if (lineno > 0)
356: {
357: char *editor_base = strrchr(editor, '/');
358: if (editor_base != NULL)
359: editor_base++;
360: else
361: editor_base = editor;
362: if (*editor_base == 'r')
363: editor_base++;
364:
365: for (av = lineno_editors; (cp = *av) != NULL; av++) {
366: /* We only handle a leading '*' wildcard. */
367: if (*cp == '*') {
368: size_t blen = strlen(editor_base);
369: size_t clen = strlen(++cp);
370: if (blen >= clen) {
371: if (strcmp(cp, editor_base + blen - clen) == 0)
372: break;
373: }
374: } else if (strcmp(cp, editor_base) == 0)
375: break;
376: }
377: /* Disable +lineno if editor doesn't support it. */
378: if (cp == NULL)
379: lineno = -1;
380: }
381:
382: /* Find the length of the argument vector */
383: ac = 3 + (lineno > 0);
384: if (args) {
385: bool wasblank;
386:
387: ac++;
388: for (wasblank = false, cp = args; *cp; cp++) {
389: if (isblank((unsigned char) *cp))
390: wasblank = true;
391: else if (wasblank) {
392: wasblank = false;
393: ac++;
394: }
395: }
396: }
397:
398: /* Build up argument vector for the command */
399: av = emalloc2(ac, sizeof(char *));
400: if ((av[0] = strrchr(editor, '/')) != NULL)
401: av[0]++;
402: else
403: av[0] = editor;
404: ac = 1;
405: if (lineno > 0) {
406: (void) snprintf(linestr, sizeof(linestr), "+%d", lineno);
407: av[ac++] = linestr;
408: }
409: if (args) {
410: for ((cp = strtok(args, " \t")); cp; (cp = strtok(NULL, " \t")))
411: av[ac++] = cp;
412: }
413: av[ac++] = sp->tpath;
414: av[ac++] = NULL;
415:
416: /*
417: * Do the edit:
418: * We cannot check the editor's exit value against 0 since
419: * XPG4 specifies that vi's exit value is a function of the
420: * number of errors during editing (?!?!).
421: */
422: gettimeofday(&tv1, NULL);
423: if (run_command(editor, av) != -1) {
424: gettimeofday(&tv2, NULL);
425: /*
426: * Sanity checks.
427: */
428: if (stat(sp->tpath, &sb) < 0) {
429: warningx(U_("unable to stat temporary file (%s), %s unchanged"),
430: sp->tpath, sp->path);
431: goto done;
432: }
433: if (sb.st_size == 0 && orig_size != 0) {
434: warningx(U_("zero length temporary file (%s), %s unchanged"),
435: sp->tpath, sp->path);
436: sp->modified = true;
437: goto done;
438: }
439: } else {
440: warningx(U_("editor (%s) failed, %s unchanged"), editor, sp->path);
441: goto done;
442: }
443:
444: /* Set modified bit if the user changed the file. */
445: modified = true;
446: mtim_get(&sb, &tv);
447: if (orig_size == sb.st_size && sudo_timevalcmp(&orig_mtim, &tv, ==)) {
448: /*
449: * If mtime and size match but the user spent no measurable
450: * time in the editor we can't tell if the file was changed.
451: */
452: if (sudo_timevalcmp(&tv1, &tv2, !=))
453: modified = false;
454: }
455:
456: /*
457: * If modified in this edit session, mark as modified.
458: */
459: if (modified)
460: sp->modified = modified;
461: else
462: warningx(U_("%s unchanged"), sp->tpath);
463:
464: rval = true;
465: done:
466: debug_return_bool(rval);
467: }
468:
469: /*
470: * Parse sudoers after editing and re-edit any ones that caused a parse error.
471: */
472: static bool
473: reparse_sudoers(char *editor, char *args, bool strict, bool quiet)
474: {
475: struct sudoersfile *sp, *last;
476: FILE *fp;
477: int ch;
478: debug_decl(reparse_sudoers, SUDO_DEBUG_UTIL)
479:
480: /*
481: * Parse the edited sudoers files and do sanity checking
482: */
483: while ((sp = TAILQ_FIRST(&sudoerslist)) != NULL) {
484: last = TAILQ_LAST(&sudoerslist, sudoersfile_list);
485: fp = fopen(sp->tpath, "r+");
486: if (fp == NULL)
487: fatalx(U_("unable to re-open temporary file (%s), %s unchanged."),
488: sp->tpath, sp->path);
489:
490: /* Clean slate for each parse */
491: init_defaults();
492: init_parser(sp->path, quiet);
493:
494: /* Parse the sudoers temp file(s) */
495: sudoersrestart(fp);
496: if (sudoersparse() && !parse_error) {
497: warningx(U_("unabled to parse temporary file (%s), unknown error"),
498: sp->tpath);
499: parse_error = true;
500: errorfile = sp->path;
501: }
502: fclose(sudoersin);
503: if (!parse_error) {
504: if (!check_defaults(SETDEF_ALL, quiet) ||
505: check_aliases(strict, quiet) != 0) {
506: parse_error = true;
507: errorfile = NULL;
508: }
509: }
510:
511: /*
512: * Got an error, prompt the user for what to do now.
513: */
514: if (parse_error) {
515: switch (whatnow()) {
516: case 'Q':
517: parse_error = false; /* ignore parse error */
518: break;
519: case 'x':
520: visudo_cleanup(); /* discard changes */
521: debug_return_bool(false);
522: case 'e':
523: default:
524: /* Edit file with the parse error */
525: TAILQ_FOREACH(sp, &sudoerslist, entries) {
526: if (errorfile == NULL || strcmp(sp->path, errorfile) == 0) {
527: edit_sudoers(sp, editor, args, errorlineno);
528: if (errorfile != NULL)
529: break;
530: }
531: }
532: if (errorfile != NULL && sp == NULL) {
533: fatalx(U_("internal error, unable to find %s in list!"),
534: sudoers);
535: }
536: break;
537: }
538: }
539:
540: /* If any new #include directives were added, edit them too. */
541: for (sp = TAILQ_NEXT(last, entries); sp != NULL; sp = TAILQ_NEXT(sp, entries)) {
542: printf(_("press return to edit %s: "), sp->path);
543: while ((ch = getchar()) != EOF && ch != '\n')
544: continue;
545: edit_sudoers(sp, editor, args, errorlineno);
546: }
547:
548: /* If all sudoers files parsed OK we are done. */
549: if (!parse_error)
550: break;
551: }
552:
553: debug_return_bool(true);
554: }
555:
556: /*
557: * Set the owner and mode on a sudoers temp file and
558: * move it into place. Returns true on success, else false.
559: */
560: static bool
561: install_sudoers(struct sudoersfile *sp, bool oldperms)
562: {
563: struct stat sb;
564: bool rval = false;
565: debug_decl(install_sudoers, SUDO_DEBUG_UTIL)
566:
567: if (!sp->modified) {
568: /*
569: * No changes but fix owner/mode if needed.
570: */
571: (void) unlink(sp->tpath);
572: if (!oldperms && fstat(sp->fd, &sb) != -1) {
573: if (sb.st_uid != SUDOERS_UID || sb.st_gid != SUDOERS_GID)
574: ignore_result(chown(sp->path, SUDOERS_UID, SUDOERS_GID));
575: if ((sb.st_mode & 0777) != SUDOERS_MODE)
576: ignore_result(chmod(sp->path, SUDOERS_MODE));
577: }
578: rval = true;
579: goto done;
580: }
581:
582: /*
583: * Change mode and ownership of temp file so when
584: * we move it to sp->path things are kosher.
585: */
586: if (oldperms) {
587: /* Use perms of the existing file. */
588: if (fstat(sp->fd, &sb) == -1)
589: fatal(U_("unable to stat %s"), sp->path);
590: if (chown(sp->tpath, sb.st_uid, sb.st_gid) != 0) {
591: warning(U_("unable to set (uid, gid) of %s to (%u, %u)"),
592: sp->tpath, (unsigned int)sb.st_uid, (unsigned int)sb.st_gid);
593: }
594: if (chmod(sp->tpath, sb.st_mode & 0777) != 0) {
595: warning(U_("unable to change mode of %s to 0%o"), sp->tpath,
596: (unsigned int)(sb.st_mode & 0777));
597: }
598: } else {
599: if (chown(sp->tpath, SUDOERS_UID, SUDOERS_GID) != 0) {
600: warning(U_("unable to set (uid, gid) of %s to (%u, %u)"),
601: sp->tpath, SUDOERS_UID, SUDOERS_GID);
602: goto done;
603: }
604: if (chmod(sp->tpath, SUDOERS_MODE) != 0) {
605: warning(U_("unable to change mode of %s to 0%o"), sp->tpath,
606: SUDOERS_MODE);
607: goto done;
608: }
609: }
610:
611: /*
612: * Now that sp->tpath is sane (parses ok) it needs to be
613: * rename(2)'d to sp->path. If the rename(2) fails we try using
614: * mv(1) in case sp->tpath and sp->path are on different file systems.
615: */
616: if (rename(sp->tpath, sp->path) == 0) {
617: efree(sp->tpath);
618: sp->tpath = NULL;
619: } else {
620: if (errno == EXDEV) {
621: char *av[4];
622: warningx(U_("%s and %s not on the same file system, using mv to rename"),
623: sp->tpath, sp->path);
624:
625: /* Build up argument vector for the command */
626: if ((av[0] = strrchr(_PATH_MV, '/')) != NULL)
627: av[0]++;
628: else
629: av[0] = _PATH_MV;
630: av[1] = sp->tpath;
631: av[2] = sp->path;
632: av[3] = NULL;
633:
634: /* And run it... */
635: if (run_command(_PATH_MV, av)) {
636: warningx(U_("command failed: '%s %s %s', %s unchanged"),
637: _PATH_MV, sp->tpath, sp->path, sp->path);
638: (void) unlink(sp->tpath);
639: efree(sp->tpath);
640: sp->tpath = NULL;
641: goto done;
642: }
643: efree(sp->tpath);
644: sp->tpath = NULL;
645: } else {
646: warning(U_("error renaming %s, %s unchanged"), sp->tpath, sp->path);
647: (void) unlink(sp->tpath);
648: goto done;
649: }
650: }
651: rval = true;
652: done:
653: debug_return_bool(rval);
654: }
655:
656: /* STUB */
657: void
658: init_envtables(void)
659: {
660: return;
661: }
662:
663: /* STUB */
664: bool
665: user_is_exempt(void)
666: {
667: return false;
668: }
669:
670: /* STUB */
671: void
672: sudo_setspent(void)
673: {
674: return;
675: }
676:
677: /* STUB */
678: void
679: sudo_endspent(void)
680: {
681: return;
682: }
683:
684: /* STUB */
685: int
686: group_plugin_query(const char *user, const char *group, const struct passwd *pw)
687: {
688: return false;
689: }
690:
691: /* STUB */
692: struct interface *get_interfaces(void)
693: {
694: return NULL;
695: }
696:
697: /*
698: * Assuming a parse error occurred, prompt the user for what they want
699: * to do now. Returns the first letter of their choice.
700: */
701: static int
702: whatnow(void)
703: {
704: int choice, c;
705: debug_decl(whatnow, SUDO_DEBUG_UTIL)
706:
707: for (;;) {
708: (void) fputs(_("What now? "), stdout);
709: choice = getchar();
710: for (c = choice; c != '\n' && c != EOF;)
711: c = getchar();
712:
713: switch (choice) {
714: case EOF:
715: choice = 'x';
716: /* FALLTHROUGH */
717: case 'e':
718: case 'x':
719: case 'Q':
720: debug_return_int(choice);
721: default:
722: (void) puts(_("Options are:\n"
723: " (e)dit sudoers file again\n"
724: " e(x)it without saving changes to sudoers file\n"
725: " (Q)uit and save changes to sudoers file (DANGER!)\n"));
726: }
727: }
728: }
729:
730: /*
731: * Install signal handlers for visudo.
732: */
733: static void
734: setup_signals(void)
735: {
736: sigaction_t sa;
737: debug_decl(setup_signals, SUDO_DEBUG_UTIL)
738:
739: /*
740: * Setup signal handlers to cleanup nicely.
741: */
742: memset(&sa, 0, sizeof(sa));
743: sigemptyset(&sa.sa_mask);
744: sa.sa_flags = SA_RESTART;
745: sa.sa_handler = quit;
746: (void) sigaction(SIGTERM, &sa, NULL);
747: (void) sigaction(SIGHUP, &sa, NULL);
748: (void) sigaction(SIGINT, &sa, NULL);
749: (void) sigaction(SIGQUIT, &sa, NULL);
750:
751: debug_return;
752: }
753:
754: static int
755: run_command(char *path, char **argv)
756: {
757: int status;
758: pid_t pid, rv;
759: debug_decl(run_command, SUDO_DEBUG_UTIL)
760:
761: switch (pid = sudo_debug_fork()) {
762: case -1:
763: fatal(U_("unable to execute %s"), path);
764: break; /* NOTREACHED */
765: case 0:
766: sudo_endpwent();
767: sudo_endgrent();
768: closefrom(STDERR_FILENO + 1);
769: execv(path, argv);
770: warning(U_("unable to run %s"), path);
771: _exit(127);
772: break; /* NOTREACHED */
773: }
774:
775: do {
776: rv = waitpid(pid, &status, 0);
777: } while (rv == -1 && errno == EINTR);
778:
779: if (rv != -1)
780: rv = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
781: debug_return_int(rv);
782: }
783:
784: static bool
785: check_owner(const char *path, bool quiet)
786: {
787: struct stat sb;
788: bool ok = true;
789: debug_decl(check_owner, SUDO_DEBUG_UTIL)
790:
791: if (stat(path, &sb) == 0) {
792: if (sb.st_uid != SUDOERS_UID || sb.st_gid != SUDOERS_GID) {
793: ok = false;
794: if (!quiet) {
795: fprintf(stderr,
796: _("%s: wrong owner (uid, gid) should be (%u, %u)\n"),
797: path, SUDOERS_UID, SUDOERS_GID);
798: }
799: }
800: if ((sb.st_mode & 07777) != SUDOERS_MODE) {
801: ok = false;
802: if (!quiet) {
803: fprintf(stderr, _("%s: bad permissions, should be mode 0%o\n"),
804: path, SUDOERS_MODE);
805: }
806: }
807: }
808: debug_return_bool(ok);
809: }
810:
811: static bool
812: check_syntax(char *sudoers_path, bool quiet, bool strict, bool oldperms)
813: {
814: bool ok = false;
815: debug_decl(check_syntax, SUDO_DEBUG_UTIL)
816:
817: if (strcmp(sudoers_path, "-") == 0) {
818: sudoersin = stdin;
819: sudoers_path = "stdin";
820: } else if ((sudoersin = fopen(sudoers_path, "r")) == NULL) {
821: if (!quiet)
822: warning(U_("unable to open %s"), sudoers_path);
823: goto done;
824: }
825: init_parser(sudoers_path, quiet);
826: if (sudoersparse() && !parse_error) {
827: if (!quiet)
828: warningx(U_("failed to parse %s file, unknown error"), sudoers_path);
829: parse_error = true;
830: errorfile = sudoers_path;
831: }
832: if (!parse_error) {
833: if (!check_defaults(SETDEF_ALL, quiet) ||
834: check_aliases(strict, quiet) != 0) {
835: parse_error = true;
836: errorfile = NULL;
837: }
838: }
839: ok = !parse_error;
840:
841: if (parse_error) {
842: if (!quiet) {
843: if (errorlineno != -1)
844: (void) printf(_("parse error in %s near line %d\n"),
845: errorfile, errorlineno);
846: else if (errorfile != NULL)
847: (void) printf(_("parse error in %s\n"), errorfile);
848: }
849: } else {
850: struct sudoersfile *sp;
851:
852: /* Parsed OK, check mode and owner. */
853: if (oldperms || check_owner(sudoers_path, quiet)) {
854: if (!quiet)
855: (void) printf(_("%s: parsed OK\n"), sudoers_path);
856: } else {
857: ok = false;
858: }
859: TAILQ_FOREACH(sp, &sudoerslist, entries) {
860: if (oldperms || check_owner(sp->path, quiet)) {
861: if (!quiet)
862: (void) printf(_("%s: parsed OK\n"), sp->path);
863: } else {
864: ok = false;
865: }
866: }
867: }
868:
869: done:
870: debug_return_bool(ok);
871: }
872:
873: /*
874: * Used to open (and lock) the initial sudoers file and to also open
875: * any subsequent files #included via a callback from the parser.
876: */
877: FILE *
878: open_sudoers(const char *path, bool doedit, bool *keepopen)
879: {
880: struct sudoersfile *entry;
881: FILE *fp;
882: int open_flags;
883: debug_decl(open_sudoers, SUDO_DEBUG_UTIL)
884:
885: if (checkonly)
886: open_flags = O_RDONLY;
887: else
888: open_flags = O_RDWR | O_CREAT;
889:
890: /* Check for existing entry */
891: TAILQ_FOREACH(entry, &sudoerslist, entries) {
892: if (strcmp(path, entry->path) == 0)
893: break;
894: }
895: if (entry == NULL) {
896: entry = ecalloc(1, sizeof(*entry));
897: entry->path = estrdup(path);
898: /* entry->modified = 0; */
899: entry->fd = open(entry->path, open_flags, SUDOERS_MODE);
900: /* entry->tpath = NULL; */
901: entry->doedit = doedit;
902: if (entry->fd == -1) {
903: warning("%s", entry->path);
904: efree(entry);
905: debug_return_ptr(NULL);
906: }
907: if (!checkonly && !lock_file(entry->fd, SUDO_TLOCK))
908: fatalx(U_("%s busy, try again later"), entry->path);
909: if ((fp = fdopen(entry->fd, "r")) == NULL)
910: fatal("%s", entry->path);
911: TAILQ_INSERT_TAIL(&sudoerslist, entry, entries);
912: } else {
913: /* Already exists, open .tmp version if there is one. */
914: if (entry->tpath != NULL) {
915: if ((fp = fopen(entry->tpath, "r")) == NULL)
916: fatal("%s", entry->tpath);
917: } else {
918: if ((fp = fdopen(entry->fd, "r")) == NULL)
919: fatal("%s", entry->path);
920: rewind(fp);
921: }
922: }
923: if (keepopen != NULL)
924: *keepopen = true;
925: debug_return_ptr(fp);
926: }
927:
928: static char *
929: get_editor(char **args)
930: {
931: char *Editor, *EditorArgs, *EditorPath, *UserEditor, *UserEditorArgs;
932: debug_decl(get_editor, SUDO_DEBUG_UTIL)
933:
934: /*
935: * Check VISUAL and EDITOR environment variables to see which editor
936: * the user wants to use (we may not end up using it though).
937: * If the path is not fully-qualified, make it so and check that
938: * the specified executable actually exists.
939: */
940: UserEditorArgs = NULL;
941: if ((UserEditor = getenv("VISUAL")) == NULL || *UserEditor == '\0')
942: UserEditor = getenv("EDITOR");
943: if (UserEditor && *UserEditor == '\0')
944: UserEditor = NULL;
945: else if (UserEditor) {
946: UserEditorArgs = get_args(UserEditor);
947: if (find_path(UserEditor, &Editor, NULL, getenv("PATH"), 0) == FOUND) {
948: UserEditor = Editor;
949: } else {
950: if (def_env_editor) {
951: /* If we are honoring $EDITOR this is a fatal error. */
952: fatalx(U_("specified editor (%s) doesn't exist"), UserEditor);
953: } else {
954: /* Otherwise, just ignore $EDITOR. */
955: UserEditor = NULL;
956: }
957: }
958: }
959:
960: /*
961: * See if we can use the user's choice of editors either because
962: * we allow any $EDITOR or because $EDITOR is in the allowable list.
963: */
964: Editor = EditorArgs = EditorPath = NULL;
965: if (def_env_editor && UserEditor) {
966: Editor = UserEditor;
967: EditorArgs = UserEditorArgs;
968: } else if (UserEditor) {
969: struct stat editor_sb;
970: struct stat user_editor_sb;
971: char *base, *userbase;
972:
973: if (stat(UserEditor, &user_editor_sb) != 0) {
974: /* Should never happen since we already checked above. */
975: fatal(U_("unable to stat editor (%s)"), UserEditor);
976: }
977: EditorPath = estrdup(def_editor);
978: Editor = strtok(EditorPath, ":");
979: do {
980: EditorArgs = get_args(Editor);
981: /*
982: * Both Editor and UserEditor should be fully qualified but
983: * check anyway...
984: */
985: if ((base = strrchr(Editor, '/')) == NULL)
986: continue;
987: if ((userbase = strrchr(UserEditor, '/')) == NULL) {
988: Editor = NULL;
989: break;
990: }
991: base++, userbase++;
992:
993: /*
994: * We compare the basenames first and then use stat to match
995: * for sure.
996: */
997: if (strcmp(base, userbase) == 0) {
998: if (stat(Editor, &editor_sb) == 0 && S_ISREG(editor_sb.st_mode)
999: && (editor_sb.st_mode & 0000111) &&
1000: editor_sb.st_dev == user_editor_sb.st_dev &&
1001: editor_sb.st_ino == user_editor_sb.st_ino)
1002: break;
1003: }
1004: } while ((Editor = strtok(NULL, ":")));
1005: }
1006:
1007: /*
1008: * Can't use $EDITOR, try each element of def_editor until we
1009: * find one that exists, is regular, and is executable.
1010: */
1011: if (Editor == NULL || *Editor == '\0') {
1012: efree(EditorPath);
1013: EditorPath = estrdup(def_editor);
1014: Editor = strtok(EditorPath, ":");
1015: do {
1016: EditorArgs = get_args(Editor);
1017: if (sudo_goodpath(Editor, NULL))
1018: break;
1019: } while ((Editor = strtok(NULL, ":")));
1020:
1021: /* Bleah, none of the editors existed! */
1022: if (Editor == NULL || *Editor == '\0')
1023: fatalx(U_("no editor found (editor path = %s)"), def_editor);
1024: }
1025: *args = EditorArgs;
1026: debug_return_str(Editor);
1027: }
1028:
1029: /*
1030: * Split out any command line arguments and return them.
1031: */
1032: static char *
1033: get_args(char *cmnd)
1034: {
1035: char *args;
1036: debug_decl(get_args, SUDO_DEBUG_UTIL)
1037:
1038: args = cmnd;
1039: while (*args && !isblank((unsigned char) *args))
1040: args++;
1041: if (*args) {
1042: *args++ = '\0';
1043: while (*args && isblank((unsigned char) *args))
1044: args++;
1045: }
1046: debug_return_str(*args ? args : NULL);
1047: }
1048:
1049: /*
1050: * Look up the hostname and set user_host and user_shost.
1051: */
1052: static void
1053: get_hostname(void)
1054: {
1055: char *p, thost[HOST_NAME_MAX + 1];
1056: debug_decl(get_hostname, SUDO_DEBUG_UTIL)
1057:
1058: if (gethostname(thost, sizeof(thost)) != -1) {
1059: thost[sizeof(thost) - 1] = '\0';
1060: user_host = estrdup(thost);
1061:
1062: if ((p = strchr(user_host, '.'))) {
1063: *p = '\0';
1064: user_shost = estrdup(user_host);
1065: *p = '.';
1066: } else {
1067: user_shost = user_host;
1068: }
1069: } else {
1070: user_host = user_shost = "localhost";
1071: }
1072: user_runhost = user_host;
1073: user_srunhost = user_shost;
1074: debug_return;
1075: }
1076:
1077: static bool
1078: alias_remove_recursive(char *name, int type)
1079: {
1080: struct member *m;
1081: struct alias *a;
1082: bool rval = true;
1083: debug_decl(alias_remove_recursive, SUDO_DEBUG_ALIAS)
1084:
1085: if ((a = alias_remove(name, type)) != NULL) {
1086: TAILQ_FOREACH(m, &a->members, entries) {
1087: if (m->type == ALIAS) {
1088: if (!alias_remove_recursive(m->name, type))
1089: rval = false;
1090: }
1091: }
1092: rbinsert(alias_freelist, a);
1093: }
1094: debug_return_bool(rval);
1095: }
1096:
1097: static int
1098: check_alias(char *name, int type, int strict, int quiet)
1099: {
1100: struct member *m;
1101: struct alias *a;
1102: int errors = 0;
1103: debug_decl(check_alias, SUDO_DEBUG_ALIAS)
1104:
1105: if ((a = alias_get(name, type)) != NULL) {
1106: /* check alias contents */
1107: TAILQ_FOREACH(m, &a->members, entries) {
1108: if (m->type == ALIAS)
1109: errors += check_alias(m->name, type, strict, quiet);
1110: }
1111: alias_put(a);
1112: } else {
1113: if (!quiet) {
1114: if (errno == ELOOP) {
1115: warningx(strict ?
1116: U_("Error: cycle in %s_Alias `%s'") :
1117: U_("Warning: cycle in %s_Alias `%s'"),
1118: type == HOSTALIAS ? "Host" : type == CMNDALIAS ? "Cmnd" :
1119: type == USERALIAS ? "User" : type == RUNASALIAS ? "Runas" :
1120: "Unknown", name);
1121: } else {
1122: warningx(strict ?
1123: U_("Error: %s_Alias `%s' referenced but not defined") :
1124: U_("Warning: %s_Alias `%s' referenced but not defined"),
1125: type == HOSTALIAS ? "Host" : type == CMNDALIAS ? "Cmnd" :
1126: type == USERALIAS ? "User" : type == RUNASALIAS ? "Runas" :
1127: "Unknown", name);
1128: }
1129: }
1130: errors++;
1131: }
1132:
1133: debug_return_int(errors);
1134: }
1135:
1136: /*
1137: * Iterate through the sudoers datastructures looking for undefined
1138: * aliases or unused aliases.
1139: */
1140: static int
1141: check_aliases(bool strict, bool quiet)
1142: {
1143: struct cmndspec *cs;
1144: struct member *m;
1145: struct privilege *priv;
1146: struct userspec *us;
1147: struct defaults *d;
1148: int atype, errors = 0;
1149: debug_decl(check_aliases, SUDO_DEBUG_ALIAS)
1150:
1151: alias_freelist = rbcreate(alias_compare);
1152:
1153: /* Forward check. */
1154: TAILQ_FOREACH(us, &userspecs, entries) {
1155: TAILQ_FOREACH(m, &us->users, entries) {
1156: if (m->type == ALIAS) {
1157: errors += check_alias(m->name, USERALIAS, strict, quiet);
1158: }
1159: }
1160: TAILQ_FOREACH(priv, &us->privileges, entries) {
1161: TAILQ_FOREACH(m, &priv->hostlist, entries) {
1162: if (m->type == ALIAS) {
1163: errors += check_alias(m->name, HOSTALIAS, strict, quiet);
1164: }
1165: }
1166: TAILQ_FOREACH(cs, &priv->cmndlist, entries) {
1167: if (cs->runasuserlist != NULL) {
1168: TAILQ_FOREACH(m, cs->runasuserlist, entries) {
1169: if (m->type == ALIAS) {
1170: errors += check_alias(m->name, RUNASALIAS, strict, quiet);
1171: }
1172: }
1173: }
1174: if (cs->runasgrouplist != NULL) {
1175: TAILQ_FOREACH(m, cs->runasgrouplist, entries) {
1176: if (m->type == ALIAS) {
1177: errors += check_alias(m->name, RUNASALIAS, strict, quiet);
1178: }
1179: }
1180: }
1181: if ((m = cs->cmnd)->type == ALIAS) {
1182: errors += check_alias(m->name, CMNDALIAS, strict, quiet);
1183: }
1184: }
1185: }
1186: }
1187:
1188: /* Reverse check (destructive) */
1189: TAILQ_FOREACH(us, &userspecs, entries) {
1190: TAILQ_FOREACH(m, &us->users, entries) {
1191: if (m->type == ALIAS) {
1192: if (!alias_remove_recursive(m->name, USERALIAS))
1193: errors++;
1194: }
1195: }
1196: TAILQ_FOREACH(priv, &us->privileges, entries) {
1197: TAILQ_FOREACH(m, &priv->hostlist, entries) {
1198: if (m->type == ALIAS) {
1199: if (!alias_remove_recursive(m->name, HOSTALIAS))
1200: errors++;
1201: }
1202: }
1203: TAILQ_FOREACH(cs, &priv->cmndlist, entries) {
1204: if (cs->runasuserlist != NULL) {
1205: TAILQ_FOREACH(m, cs->runasuserlist, entries) {
1206: if (m->type == ALIAS) {
1207: if (!alias_remove_recursive(m->name, RUNASALIAS))
1208: errors++;
1209: }
1210: }
1211: }
1212: if (cs->runasgrouplist != NULL) {
1213: TAILQ_FOREACH(m, cs->runasgrouplist, entries) {
1214: if (m->type == ALIAS) {
1215: if (!alias_remove_recursive(m->name, RUNASALIAS))
1216: errors++;
1217: }
1218: }
1219: }
1220: if ((m = cs->cmnd)->type == ALIAS) {
1221: if (!alias_remove_recursive(m->name, CMNDALIAS))
1222: errors++;
1223: }
1224: }
1225: }
1226: }
1227: TAILQ_FOREACH(d, &defaults, entries) {
1228: switch (d->type) {
1229: case DEFAULTS_HOST:
1230: atype = HOSTALIAS;
1231: break;
1232: case DEFAULTS_USER:
1233: atype = USERALIAS;
1234: break;
1235: case DEFAULTS_RUNAS:
1236: atype = RUNASALIAS;
1237: break;
1238: case DEFAULTS_CMND:
1239: atype = CMNDALIAS;
1240: break;
1241: default:
1242: continue; /* not an alias */
1243: }
1244: TAILQ_FOREACH(m, d->binding, entries) {
1245: if (m->type == ALIAS) {
1246: if (!alias_remove_recursive(m->name, atype))
1247: errors++;
1248: }
1249: }
1250: }
1251: rbdestroy(alias_freelist, alias_free);
1252:
1253: /* If all aliases were referenced we will have an empty tree. */
1254: if (!no_aliases() && !quiet)
1255: alias_apply(print_unused, strict ? "Error" : "Warning");
1256:
1257: debug_return_int(strict ? errors : 0);
1258: }
1259:
1260: static int
1261: print_unused(void *v1, void *v2)
1262: {
1263: struct alias *a = (struct alias *)v1;
1264: char *prefix = (char *)v2;
1265:
1266: warningx_nodebug(U_("%s: unused %s_Alias %s"), prefix,
1267: a->type == HOSTALIAS ? "Host" : a->type == CMNDALIAS ? "Cmnd" :
1268: a->type == USERALIAS ? "User" : a->type == RUNASALIAS ? "Runas" :
1269: "Unknown", a->name);
1270: return 0;
1271: }
1272:
1273: /*
1274: * Unlink any sudoers temp files that remain.
1275: */
1276: static void
1277: visudo_cleanup(void)
1278: {
1279: struct sudoersfile *sp;
1280:
1281: TAILQ_FOREACH(sp, &sudoerslist, entries) {
1282: if (sp->tpath != NULL)
1283: (void) unlink(sp->tpath);
1284: }
1285: sudo_endpwent();
1286: sudo_endgrent();
1287: }
1288:
1289: /*
1290: * Unlink sudoers temp files (if any) and exit.
1291: */
1292: static void
1293: quit(int signo)
1294: {
1295: struct sudoersfile *sp;
1296: struct iovec iov[4];
1297:
1298: TAILQ_FOREACH(sp, &sudoerslist, entries) {
1299: if (sp->tpath != NULL)
1300: (void) unlink(sp->tpath);
1301: }
1302:
1303: #define emsg " exiting due to signal: "
1304: iov[0].iov_base = (char *)getprogname();
1305: iov[0].iov_len = strlen(iov[0].iov_base);
1306: iov[1].iov_base = emsg;
1307: iov[1].iov_len = sizeof(emsg) - 1;
1308: iov[2].iov_base = strsignal(signo);
1309: iov[2].iov_len = strlen(iov[2].iov_base);
1310: iov[3].iov_base = "\n";
1311: iov[3].iov_len = 1;
1312: ignore_result(writev(STDERR_FILENO, iov, 4));
1313: _exit(signo);
1314: }
1315:
1316: static void
1317: usage(int fatal)
1318: {
1319: (void) fprintf(fatal ? stderr : stdout,
1320: "usage: %s [-chqsV] [-f sudoers] [-x file]\n", getprogname());
1321: if (fatal)
1322: exit(1);
1323: }
1324:
1325: static void
1326: help(void)
1327: {
1328: (void) printf(_("%s - safely edit the sudoers file\n\n"), getprogname());
1329: usage(0);
1330: (void) puts(_("\nOptions:\n"
1331: " -c, --check check-only mode\n"
1332: " -f, --file=file specify sudoers file location\n"
1333: " -h, --help display help message and exit\n"
1334: " -q, --quiet less verbose (quiet) syntax error messages\n"
1335: " -s, --strict strict syntax checking\n"
1336: " -V, --version display version information and exit\n"
1337: " -x, --export=file export sudoers in JSON format"));
1338: exit(0);
1339: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>