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