[BACK]Return to visudo.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / sudo

Annotation of src/usr.bin/sudo/visudo.c, Revision 1.20

1.1       millert     1: /*
1.19      millert     2:  * Copyright (c) 1996, 1998-2005 Todd C. Miller <Todd.Miller@courtesan.com>
1.1       millert     3:  *
1.17      millert     4:  * Permission to use, copy, modify, and distribute this software for any
                      5:  * purpose with or without fee is hereby granted, provided that the above
                      6:  * copyright notice and this permission notice appear in all copies.
1.1       millert     7:  *
1.17      millert     8:  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
                      9:  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
                     10:  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
                     11:  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
                     12:  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
                     13:  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
                     14:  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1.15      millert    15:  *
                     16:  * Sponsored in part by the Defense Advanced Research Projects
                     17:  * Agency (DARPA) and Air Force Research Laboratory, Air Force
                     18:  * Materiel Command, USAF, under agreement number F39502-99-1-0512.
1.1       millert    19:  */
                     20:
                     21: /*
                     22:  * Lock the sudoers file for safe editing (ala vipw) and check for parse errors.
                     23:  */
                     24:
1.14      millert    25: #define _SUDO_MAIN
                     26:
1.17      millert    27: #ifdef __TANDEM
                     28: # include <floss.h>
                     29: #endif
                     30:
1.19      millert    31: #include <config.h>
1.1       millert    32:
1.11      millert    33: #include <sys/types.h>
                     34: #include <sys/param.h>
                     35: #include <sys/stat.h>
1.17      millert    36: #include <sys/time.h>
                     37: #ifndef __TANDEM
                     38: # include <sys/file.h>
                     39: #endif
1.11      millert    40: #include <sys/wait.h>
1.1       millert    41: #include <stdio.h>
                     42: #ifdef STDC_HEADERS
1.11      millert    43: # include <stdlib.h>
                     44: # include <stddef.h>
                     45: #else
                     46: # ifdef HAVE_STDLIB_H
                     47: #  include <stdlib.h>
                     48: # endif
1.1       millert    49: #endif /* STDC_HEADERS */
1.11      millert    50: #ifdef HAVE_STRING_H
                     51: # include <string.h>
                     52: #else
                     53: # ifdef HAVE_STRINGS_H
                     54: #  include <strings.h>
                     55: # endif
                     56: #endif /* HAVE_STRING_H */
1.1       millert    57: #ifdef HAVE_UNISTD_H
                     58: #include <unistd.h>
                     59: #endif /* HAVE_UNISTD_H */
1.14      millert    60: #ifdef HAVE_ERR_H
                     61: # include <err.h>
                     62: #else
                     63: # include "emul/err.h"
                     64: #endif /* HAVE_ERR_H */
1.1       millert    65: #include <ctype.h>
                     66: #include <pwd.h>
1.19      millert    67: #if TIME_WITH_SYS_TIME
                     68: # include <time.h>
                     69: #endif
1.1       millert    70: #include <signal.h>
                     71: #include <errno.h>
                     72: #include <fcntl.h>
1.19      millert    73: #ifndef HAVE_TIMESPEC
                     74: # include <emul/timespec.h>
                     75: #endif
1.1       millert    76:
                     77: #include "sudo.h"
                     78: #include "version.h"
                     79:
                     80: #ifndef lint
1.20    ! millert    81: __unused static const char rcsid[] = "$Sudo: visudo.c,v 1.166.2.10 2007/09/01 13:39:13 millert Exp $";
1.1       millert    82: #endif /* lint */
                     83:
1.19      millert    84: struct sudoersfile {
                     85:     char *path;
                     86:     char *tpath;
                     87:     int fd;
                     88:     off_t orig_size;
                     89:     struct timespec orig_mtim;
                     90: };
                     91:
1.1       millert    92: /*
                     93:  * Function prototypes
                     94:  */
1.19      millert    95: static void usage              __P((void)) __attribute__((__noreturn__));
1.1       millert    96: static char whatnow            __P((void));
                     97: static RETSIGTYPE Exit         __P((int));
1.19      millert    98: static void edit_sudoers       __P((struct sudoersfile *, char *, char *, int));
                     99: static void visudo             __P((struct sudoersfile *, char *, char *));
1.1       millert   100: static void setup_signals      __P((void));
1.20    ! millert   101: static void install_sudoers    __P((struct sudoersfile *, int));
1.19      millert   102: static int check_syntax                __P(());
1.11      millert   103: static int run_command         __P((char *, char **));
1.19      millert   104: static char *get_args          __P((char *));
                    105: static char *get_editor                __P((char **));
                    106: static FILE *open_sudoers      __P((struct sudoersfile *));
                    107:
1.17      millert   108: int command_matches            __P((char *, char *));
1.1       millert   109: int addr_matches               __P((char *));
1.4       millert   110: int hostname_matches           __P((char *, char *, char *));
1.3       millert   111: int netgr_matches              __P((char *, char *, char *, char *));
1.17      millert   112: int usergr_matches             __P((char *, char *, struct passwd *));
                    113: int userpw_matches             __P((char *, char *, struct passwd *));
1.1       millert   114: void init_parser               __P((void));
1.17      millert   115: void yyerror                   __P((char *));
1.1       millert   116: void yyrestart                 __P((FILE *));
                    117:
                    118: /*
                    119:  * External globals exported by the parser
                    120:  */
1.19      millert   121: extern FILE *yyin;
1.1       millert   122: extern int errorlineno;
                    123: extern int pedantic;
1.11      millert   124: extern int quiet;
                    125:
                    126: /* For getopt(3) */
                    127: extern char *optarg;
                    128: extern int optind;
1.1       millert   129:
                    130: /*
                    131:  * Globals
                    132:  */
                    133: char **Argv;
                    134: struct sudo_user sudo_user;
1.17      millert   135: int Argc, parse_error = FALSE;
1.19      millert   136: static struct sudoersfile sudoers;
1.1       millert   137:
                    138: int
                    139: main(argc, argv)
                    140:     int argc;
                    141:     char **argv;
                    142: {
1.19      millert   143:     char *args, *editor;
1.20    ! millert   144:     int ch, checkonly, n, oldperms;
1.19      millert   145:
                    146:     /* Initialize sudoers struct. */
                    147:     sudoers.path = _PATH_SUDOERS;
                    148:     sudoers.tpath = _PATH_SUDOERS_TMP;
                    149:     sudoers.fd = -1;
1.1       millert   150:
                    151:     /* Warn about aliases that are used before being defined. */
                    152:     pedantic = 1;
                    153:
1.17      millert   154:     Argv = argv;
                    155:     if ((Argc = argc) < 1)
                    156:        usage();
1.1       millert   157:
                    158:     /*
                    159:      * Arg handling.
                    160:      */
1.20    ! millert   161:     checkonly = oldperms = FALSE;
1.11      millert   162:     while ((ch = getopt(argc, argv, "Vcf:sq")) != -1) {
                    163:        switch (ch) {
                    164:            case 'V':
1.14      millert   165:                (void) printf("%s version %s\n", getprogname(), version);
1.11      millert   166:                exit(0);
                    167:            case 'c':
                    168:                checkonly++;            /* check mode */
                    169:                break;
1.19      millert   170:            case 'f':                   /* sudoers file path */
                    171:                sudoers.path = optarg;
                    172:                easprintf(&sudoers.tpath, "%s.tmp", optarg);
1.20    ! millert   173:                oldperms = TRUE;
1.11      millert   174:                break;
                    175:            case 's':
                    176:                pedantic++;             /* strict mode */
                    177:                break;
                    178:            case 'q':
                    179:                quiet++;                /* quiet mode */
                    180:                break;
                    181:            default:
                    182:                usage();
1.1       millert   183:        }
                    184:     }
1.11      millert   185:     argc -= optind;
                    186:     argv += optind;
                    187:     if (argc)
                    188:        usage();
1.1       millert   189:
                    190:     /* Mock up a fake sudo_user struct. */
                    191:     user_host = user_shost = user_cmnd = "";
1.14      millert   192:     if ((sudo_user.pw = getpwuid(getuid())) == NULL)
                    193:        errx(1, "you don't exist in the passwd database");
1.1       millert   194:
1.4       millert   195:     /* Setup defaults data structures. */
                    196:     init_defaults();
1.1       millert   197:
1.11      millert   198:     if (checkonly)
1.19      millert   199:        exit(check_syntax());
1.11      millert   200:
1.1       millert   201:     /*
1.19      millert   202:      * Open and parse the existing sudoers file(s) in quiet mode to highlight
                    203:      * any existing errors and to pull in editor and env_editor conf values.
                    204:      */
                    205:     if ((yyin = open_sudoers(&sudoers)) == NULL)
                    206:        err(1, "%s", sudoers.path);
                    207:     n = quiet;
                    208:     quiet = 1;
                    209:     init_parser();
                    210:     yyparse();
                    211:     parse_error = FALSE;
                    212:     quiet = n;
                    213:
                    214:     /* Edit sudoers, check for parse errors and re-edit on failure. */
                    215:     editor = get_editor(&args);
                    216:     visudo(&sudoers, editor, args);
1.1       millert   217:
1.19      millert   218:     /* Install the new sudoers file. */
1.20    ! millert   219:     install_sudoers(&sudoers, oldperms);
1.3       millert   220:
1.19      millert   221:     exit(0);
                    222: }
1.1       millert   223:
1.19      millert   224: /*
                    225:  * Edit the sudoers file.
                    226:  * Returns TRUE on success, else FALSE.
                    227:  */
                    228: static void
                    229: edit_sudoers(sp, editor, args, lineno)
                    230:     struct sudoersfile *sp;
                    231:     char *editor, *args;
                    232:     int lineno;
                    233: {
                    234:     int ac;                            /* argument count */
                    235:     char **av;                         /* argument vector for run_command */
                    236:     char *cp;                          /* scratch char pointer */
                    237:     char linestr[64];                  /* string version of lineno */
                    238:     struct timespec ts1, ts2;          /* time before and after edit */
                    239:     struct stat sb;                    /* stat buffer */
1.1       millert   240:
1.19      millert   241:     /* Make timestamp on temp file match original. */
                    242:     (void) touch(-1, sp->tpath, &sp->orig_mtim);
1.13      millert   243:
1.19      millert   244:     /* Find the length of the argument vector */
                    245:     ac = 3 + (lineno > 0);
                    246:     if (args) {
                    247:         int wasblank;
                    248:
                    249:         ac++;
                    250:         for (wasblank = FALSE, cp = args; *cp; cp++) {
                    251:             if (isblank((unsigned char) *cp))
                    252:                 wasblank = TRUE;
                    253:             else if (wasblank) {
                    254:                 wasblank = FALSE;
                    255:                 ac++;
                    256:             }
                    257:         }
                    258:     }
                    259:
                    260:     /* Build up argument vector for the command */
                    261:     av = emalloc2(ac, sizeof(char *));
                    262:     if ((av[0] = strrchr(editor, '/')) != NULL)
                    263:        av[0]++;
                    264:     else
                    265:        av[0] = editor;
                    266:     ac = 1;
                    267:     if (lineno > 0) {
                    268:        (void) snprintf(linestr, sizeof(linestr), "+%d", lineno);
                    269:        av[ac++] = linestr;
                    270:     }
                    271:     if (args) {
                    272:        for ((cp = strtok(args, " \t")); cp; (cp = strtok(NULL, " \t")))
                    273:            av[ac++] = cp;
                    274:     }
                    275:     av[ac++] = sp->tpath;
                    276:     av[ac++] = NULL;
1.1       millert   277:
                    278:     /*
1.19      millert   279:      * Do the edit:
                    280:      *  We cannot check the editor's exit value against 0 since
                    281:      *  XPG4 specifies that vi's exit value is a function of the
                    282:      *  number of errors during editing (?!?!).
1.4       millert   283:      */
1.19      millert   284:     gettime(&ts1);
                    285:     if (run_command(editor, av) != -1) {
                    286:        gettime(&ts2);
                    287:        /*
                    288:         * Sanity checks.
                    289:         */
                    290:        if (stat(sp->tpath, &sb) < 0) {
                    291:            warnx("cannot stat temporary file (%s), %s unchanged",
                    292:                sp->tpath, sp->path);
                    293:            Exit(-1);
1.11      millert   294:        }
1.19      millert   295:        if (sb.st_size == 0) {
                    296:            warnx("zero length temporary file (%s), %s unchanged",
                    297:                sp->tpath, sp->path);
1.11      millert   298:            Exit(-1);
                    299:        }
1.19      millert   300:     } else {
                    301:        warnx("editor (%s) failed, %s unchanged", editor, sp->path);
                    302:        Exit(-1);
1.11      millert   303:     }
                    304:
1.19      millert   305:     /* Check to see if the user changed the file. */
                    306:     if (sp->orig_size == sb.st_size &&
                    307:        sp->orig_mtim.tv_sec == mtim_getsec(sb) &&
                    308:        sp->orig_mtim.tv_nsec == mtim_getnsec(sb)) {
                    309:        /*
                    310:         * If mtime and size match but the user spent no measurable
                    311:         * time in the editor we can't tell if the file was changed.
                    312:         */
                    313: #ifdef HAVE_TIMESPECSUB2
                    314:        timespecsub(&ts1, &ts2);
                    315: #else
                    316:        timespecsub(&ts1, &ts2, &ts2);
                    317: #endif
                    318:        if (timespecisset(&ts2)) {
                    319:            warnx("%s unchanged", sp->tpath);
                    320:            Exit(0);
1.11      millert   321:        }
                    322:     }
1.19      millert   323: }
                    324:
                    325: /*
                    326:  * Parse sudoers after editing and re-edit any ones that caused a parse error.
                    327:  * Returns TRUE on success, else FALSE.
                    328:  */
                    329: static void
                    330: visudo(sp, editor, args)
                    331:     struct sudoersfile *sp;
                    332:     char *editor, *args;
                    333: {
                    334:     int ch;
1.4       millert   335:
                    336:     /*
1.19      millert   337:      * Parse the edited sudoers file and do sanity checking
1.1       millert   338:      */
                    339:     do {
1.19      millert   340:        edit_sudoers(sp, editor, args, errorlineno);
1.11      millert   341:
1.19      millert   342:        yyin = fopen(sp->tpath, "r+");
                    343:        if (yyin == NULL) {
                    344:            warnx("can't re-open temporary file (%s), %s unchanged.",
                    345:                sp->tpath, sp->path);
                    346:            Exit(-1);
1.11      millert   347:        }
                    348:
1.19      millert   349:        /* Add missing newline at EOF if needed. */
                    350:        if (fseek(yyin, -1, SEEK_END) == 0 && (ch = fgetc(yyin)) != '\n')
                    351:            fputc('\n', yyin);
                    352:        rewind(yyin);
                    353:
                    354:        /* Clean slate for each parse */
                    355:        user_runas = NULL;
                    356:        init_defaults();
                    357:        init_parser();
                    358:
                    359:        /* Parse the sudoers temp file */
                    360:        yyrestart(yyin);
                    361:        if (yyparse() && parse_error != TRUE) {
                    362:            warnx("unabled to parse temporary file (%s), unknown error",
                    363:                sp->tpath);
                    364:            parse_error = TRUE;
1.1       millert   365:        }
1.19      millert   366:        fclose(yyin);
1.1       millert   367:
                    368:        /*
                    369:         * Got an error, prompt the user for what to do now
                    370:         */
1.19      millert   371:        if (parse_error) {
1.1       millert   372:            switch (whatnow()) {
1.7       millert   373:                case 'Q' :      parse_error = FALSE;    /* ignore parse error */
1.1       millert   374:                                break;
1.19      millert   375:                case 'x' :      Exit(0);
1.1       millert   376:                                break;
                    377:            }
                    378:        }
1.19      millert   379:     } while (parse_error);
                    380: }
1.1       millert   381:
1.19      millert   382: /*
                    383:  * Set the owner and mode on a sudoers temp file and
                    384:  * move it into place.  Returns TRUE on success, else FALSE.
                    385:  */
                    386: static void
1.20    ! millert   387: install_sudoers(sp, oldperms)
1.19      millert   388:     struct sudoersfile *sp;
1.20    ! millert   389:     int oldperms;
1.19      millert   390: {
1.20    ! millert   391:     struct stat sb;
        !           392:
1.1       millert   393:     /*
                    394:      * Change mode and ownership of temp file so when
1.19      millert   395:      * we move it to sp->path things are kosher.
1.1       millert   396:      */
1.20    ! millert   397:     if (oldperms) {
        !           398:        /* Use perms of the existing file.  */
        !           399: #ifdef HAVE_FSTAT
        !           400:        if (fstat(sp->fd, &sb) == -1)
        !           401: #else
        !           402:        if (stat(sp->path, &sb) == -1)
        !           403: #endif
        !           404:            err(1, "can't stat %s", sp->path);
        !           405:        (void) chown(sp->tpath, sb.st_uid, sb.st_gid);
        !           406:        (void) chmod(sp->tpath, sb.st_mode & 0777);
        !           407:     } else {
        !           408:        if (chown(sp->tpath, SUDOERS_UID, SUDOERS_GID) != 0) {
        !           409:            warn("unable to set (uid, gid) of %s to (%d, %d)",
        !           410:                sp->tpath, SUDOERS_UID, SUDOERS_GID);
        !           411:            Exit(-1);
        !           412:        }
        !           413:        if (chmod(sp->tpath, SUDOERS_MODE) != 0) {
        !           414:            warn("unable to change mode of %s to 0%o", sp->tpath, SUDOERS_MODE);
        !           415:            Exit(-1);
        !           416:        }
1.1       millert   417:     }
                    418:
                    419:     /*
1.19      millert   420:      * Now that sp->tpath is sane (parses ok) it needs to be
                    421:      * rename(2)'d to sp->path.  If the rename(2) fails we try using
                    422:      * mv(1) in case sp->tpath and sp->path are on different file systems.
1.1       millert   423:      */
1.19      millert   424:     if (rename(sp->tpath, sp->path) != 0) {
1.1       millert   425:        if (errno == EXDEV) {
1.19      millert   426:            char *av[4];
1.17      millert   427:            warnx("%s and %s not on the same file system, using mv to rename",
1.19      millert   428:              sp->tpath, sp->path);
1.1       millert   429:
1.11      millert   430:            /* Build up argument vector for the command */
                    431:            if ((av[0] = strrchr(_PATH_MV, '/')) != NULL)
                    432:                av[0]++;
                    433:            else
                    434:                av[0] = _PATH_MV;
1.19      millert   435:            av[1] = sp->tpath;
                    436:            av[2] = sp->path;
1.11      millert   437:            av[3] = NULL;
1.1       millert   438:
1.11      millert   439:            /* And run it... */
                    440:            if (run_command(_PATH_MV, av)) {
1.14      millert   441:                warnx("command failed: '%s %s %s', %s unchanged",
1.19      millert   442:                    _PATH_MV, sp->tpath, sp->path, sp->path);
1.1       millert   443:                Exit(-1);
                    444:            }
                    445:        } else {
1.19      millert   446:            warn("error renaming %s, %s unchanged", sp->tpath, sp->path);
1.1       millert   447:            Exit(-1);
                    448:        }
                    449:     }
                    450: }
                    451:
                    452: /*
                    453:  * Dummy *_matches routines.
                    454:  * These exist to allow us to use the same parser as sudo(8).
                    455:  */
                    456: int
1.17      millert   457: command_matches(path, sudoers_args)
1.1       millert   458:     char *path;
                    459:     char *sudoers_args;
                    460: {
                    461:     return(TRUE);
                    462: }
                    463:
                    464: int
                    465: addr_matches(n)
                    466:     char *n;
1.4       millert   467: {
                    468:     return(TRUE);
                    469: }
                    470:
                    471: int
                    472: hostname_matches(s, l, p)
                    473:     char *s, *l, *p;
1.1       millert   474: {
                    475:     return(TRUE);
                    476: }
                    477:
                    478: int
1.17      millert   479: usergr_matches(g, u, pw)
1.1       millert   480:     char *g, *u;
1.17      millert   481:     struct passwd *pw;
                    482: {
                    483:     return(TRUE);
                    484: }
                    485:
                    486: int
                    487: userpw_matches(s, u, pw)
                    488:     char *s, *u;
                    489:     struct passwd *pw;
1.1       millert   490: {
                    491:     return(TRUE);
                    492: }
                    493:
                    494: int
1.3       millert   495: netgr_matches(n, h, sh, u)
                    496:     char *n, *h, *sh, *u;
1.1       millert   497: {
                    498:     return(TRUE);
1.2       millert   499: }
                    500:
                    501: void
                    502: set_fqdn()
                    503: {
                    504:     return;
1.1       millert   505: }
                    506:
1.11      millert   507: int
1.17      millert   508: set_runaspw(user)
                    509:     char *user;
                    510: {
                    511:     extern int sudolineno, used_runas;
                    512:
                    513:     if (used_runas) {
                    514:        (void) fprintf(stderr,
                    515:            "%s: runas_default set after old value is in use near line %d\n",
                    516:            pedantic > 1 ? "Error" : "Warning", sudolineno);
                    517:        if (pedantic > 1)
                    518:            yyerror(NULL);
                    519:     }
                    520:     return(TRUE);
                    521: }
                    522:
                    523: int
1.11      millert   524: user_is_exempt()
                    525: {
                    526:     return(TRUE);
                    527: }
                    528:
                    529: void
                    530: init_envtables()
                    531: {
                    532:     return;
                    533: }
                    534:
1.1       millert   535: /*
                    536:  * Assuming a parse error occurred, prompt the user for what they want
                    537:  * to do now.  Returns the first letter of their choice.
                    538:  */
                    539: static char
                    540: whatnow()
                    541: {
                    542:     int choice, c;
                    543:
                    544:     for (;;) {
                    545:        (void) fputs("What now? ", stdout);
                    546:        choice = getchar();
                    547:        for (c = choice; c != '\n' && c != EOF;)
                    548:            c = getchar();
                    549:
1.3       millert   550:        switch (choice) {
                    551:            case EOF:
                    552:                choice = 'x';
                    553:                /* FALLTHROUGH */
                    554:            case 'e':
                    555:            case 'x':
                    556:            case 'Q':
                    557:                return(choice);
                    558:            default:
                    559:                (void) puts("Options are:");
                    560:                (void) puts("  (e)dit sudoers file again");
                    561:                (void) puts("  e(x)it without saving changes to sudoers file");
                    562:                (void) puts("  (Q)uit and save changes to sudoers file (DANGER!)\n");
1.1       millert   563:        }
                    564:     }
                    565: }
                    566:
                    567: /*
                    568:  * Install signal handlers for visudo.
                    569:  */
                    570: static void
                    571: setup_signals()
                    572: {
1.11      millert   573:        sigaction_t sa;
1.1       millert   574:
                    575:        /*
                    576:         * Setup signal handlers to cleanup nicely.
                    577:         */
1.11      millert   578:        sigemptyset(&sa.sa_mask);
                    579:        sa.sa_flags = SA_RESTART;
                    580:        sa.sa_handler = Exit;
                    581:        (void) sigaction(SIGTERM, &sa, NULL);
                    582:        (void) sigaction(SIGHUP, &sa, NULL);
                    583:        (void) sigaction(SIGINT, &sa, NULL);
                    584:        (void) sigaction(SIGQUIT, &sa, NULL);
                    585: }
                    586:
                    587: static int
                    588: run_command(path, argv)
                    589:     char *path;
                    590:     char **argv;
                    591: {
                    592:     int status;
                    593:     pid_t pid;
                    594:     sigset_t set, oset;
                    595:
                    596:     (void) sigemptyset(&set);
                    597:     (void) sigaddset(&set, SIGCHLD);
                    598:     (void) sigprocmask(SIG_BLOCK, &set, &oset);
                    599:
                    600:     switch (pid = fork()) {
                    601:        case -1:
1.14      millert   602:            warn("unable to run %s", path);
1.11      millert   603:            Exit(-1);
                    604:            break;      /* NOTREACHED */
                    605:        case 0:
                    606:            (void) sigprocmask(SIG_SETMASK, &oset, NULL);
1.19      millert   607:            endpwent();
                    608:            closefrom(STDERR_FILENO + 1);
1.11      millert   609:            execv(path, argv);
1.14      millert   610:            warn("unable to run %s", path);
1.11      millert   611:            _exit(127);
                    612:            break;      /* NOTREACHED */
                    613:     }
                    614:
                    615: #ifdef sudo_waitpid
                    616:     pid = sudo_waitpid(pid, &status, 0);
1.1       millert   617: #else
1.11      millert   618:     pid = wait(&status);
                    619: #endif
                    620:
                    621:     (void) sigprocmask(SIG_SETMASK, &oset, NULL);
                    622:
1.17      millert   623:     if (pid == -1 || !WIFEXITED(status))
                    624:        return(-1);
                    625:     return(WEXITSTATUS(status));
1.11      millert   626: }
                    627:
                    628: static int
1.19      millert   629: check_syntax()
1.11      millert   630: {
                    631:
1.19      millert   632:     if ((yyin = fopen(sudoers.path, "r")) == NULL) {
1.11      millert   633:        if (!quiet)
1.19      millert   634:            warn("unable to open %s", sudoers.path);
1.11      millert   635:        exit(1);
                    636:     }
                    637:     init_parser();
                    638:     if (yyparse() && parse_error != TRUE) {
                    639:        if (!quiet)
1.19      millert   640:            warnx("failed to parse %s file, unknown error", sudoers.path);
1.11      millert   641:        parse_error = TRUE;
                    642:     }
                    643:     if (!quiet){
                    644:        if (parse_error)
1.19      millert   645:            (void) printf("parse error in %s near line %d\n", sudoers.path,
1.11      millert   646:                errorlineno);
                    647:        else
1.19      millert   648:            (void) printf("%s file parsed OK\n", sudoers.path);
1.11      millert   649:     }
                    650:
                    651:     return(parse_error == TRUE);
1.1       millert   652: }
                    653:
1.19      millert   654: static FILE *
                    655: open_sudoers(sp)
                    656:     struct sudoersfile *sp;
                    657: {
                    658:     struct stat sb;
                    659:     ssize_t nread;
                    660:     FILE *fp;
                    661:     char buf[PATH_MAX*2];
                    662:     int tfd;
                    663:
                    664:     /* Open and lock sudoers. */
                    665:     sp->fd = open(sp->path, O_RDWR | O_CREAT, SUDOERS_MODE);
                    666:     if (sp->fd == -1)
                    667:        err(1, "%s", sp->path);
                    668:     if (!lock_file(sp->fd, SUDO_TLOCK))
                    669:        errx(1, "%s busy, try again later", sp->path);
                    670:     if ((fp = fdopen(sp->fd, "r")) == NULL)
                    671:        err(1, "%s", sp->path);
                    672:
                    673:     /* Stash sudoers size and mtime. */
                    674: #ifdef HAVE_FSTAT
                    675:     if (fstat(sp->fd, &sb) == -1)
                    676: #else
                    677:     if (stat(sp->path, &sb) == -1)
                    678: #endif
                    679:        err(1, "can't stat %s", sp->path);
                    680:     sp->orig_size = sb.st_size;
                    681:     sp->orig_mtim.tv_sec = mtim_getsec(sb);
                    682:     sp->orig_mtim.tv_nsec = mtim_getnsec(sb);
                    683:
                    684:     /* Create the temp file. */
                    685:     tfd = open(sp->tpath, O_WRONLY | O_CREAT | O_TRUNC, 0600);
                    686:     if (tfd < 0)
                    687:        err(1, "%s", sp->tpath);
                    688:
                    689:     /* Install signal handlers to clean up temp file if we are killed. */
                    690:     setup_signals();
                    691:
                    692:     /* Copy sp->path -> sp->tpath. */
                    693:     if (sp->orig_size != 0) {
                    694:        while ((nread = read(sp->fd, buf, sizeof(buf))) > 0)
                    695:            if (write(tfd, buf, nread) != nread) {
                    696:                warn("write error");
                    697:                Exit(-1);
                    698:            }
                    699:
                    700:        /* Add missing newline at EOF if needed. */
                    701:        if (nread > 0 && buf[nread - 1] != '\n') {
                    702:            buf[0] = '\n';
                    703:            write(tfd, buf, 1);
                    704:        }
                    705:     }
                    706:     (void) close(tfd);
                    707:     rewind(fp);
                    708:
                    709:     return(fp);
                    710: }
                    711:
                    712: static char *
                    713: get_editor(args)
                    714:     char **args;
                    715: {
                    716:     char *Editor, *EditorArgs, *EditorPath, *UserEditor, *UserEditorArgs;
                    717:
                    718:     /*
                    719:      * Check VISUAL and EDITOR environment variables to see which editor
                    720:      * the user wants to use (we may not end up using it though).
                    721:      * If the path is not fully-qualified, make it so and check that
                    722:      * the specified executable actually exists.
                    723:      */
                    724:     UserEditorArgs = NULL;
                    725:     if ((UserEditor = getenv("VISUAL")) == NULL || *UserEditor == '\0')
                    726:        UserEditor = getenv("EDITOR");
                    727:     if (UserEditor && *UserEditor == '\0')
                    728:        UserEditor = NULL;
                    729:     else if (UserEditor) {
                    730:        UserEditorArgs = get_args(UserEditor);
                    731:        if (find_path(UserEditor, &Editor, NULL, getenv("PATH")) == FOUND) {
                    732:            UserEditor = Editor;
                    733:        } else {
                    734:            if (def_env_editor) {
                    735:                /* If we are honoring $EDITOR this is a fatal error. */
                    736:                warnx("specified editor (%s) doesn't exist!", UserEditor);
                    737:                Exit(-1);
                    738:            } else {
                    739:                /* Otherwise, just ignore $EDITOR. */
                    740:                UserEditor = NULL;
                    741:            }
                    742:        }
                    743:     }
                    744:
                    745:     /*
                    746:      * See if we can use the user's choice of editors either because
                    747:      * we allow any $EDITOR or because $EDITOR is in the allowable list.
                    748:      */
                    749:     Editor = EditorArgs = EditorPath = NULL;
                    750:     if (def_env_editor && UserEditor) {
                    751:        Editor = UserEditor;
                    752:        EditorArgs = UserEditorArgs;
                    753:     } else if (UserEditor) {
                    754:        struct stat editor_sb;
                    755:        struct stat user_editor_sb;
                    756:        char *base, *userbase;
                    757:
                    758:        if (stat(UserEditor, &user_editor_sb) != 0) {
                    759:            /* Should never happen since we already checked above. */
                    760:            warn("unable to stat editor (%s)", UserEditor);
                    761:            Exit(-1);
                    762:        }
                    763:        EditorPath = estrdup(def_editor);
                    764:        Editor = strtok(EditorPath, ":");
                    765:        do {
                    766:            EditorArgs = get_args(Editor);
                    767:            /*
                    768:             * Both Editor and UserEditor should be fully qualified but
                    769:             * check anyway...
                    770:             */
                    771:            if ((base = strrchr(Editor, '/')) == NULL)
                    772:                continue;
                    773:            if ((userbase = strrchr(UserEditor, '/')) == NULL) {
                    774:                Editor = NULL;
                    775:                break;
                    776:            }
                    777:            base++, userbase++;
                    778:
                    779:            /*
                    780:             * We compare the basenames first and then use stat to match
                    781:             * for sure.
                    782:             */
                    783:            if (strcmp(base, userbase) == 0) {
                    784:                if (stat(Editor, &editor_sb) == 0 && S_ISREG(editor_sb.st_mode)
                    785:                    && (editor_sb.st_mode & 0000111) &&
                    786:                    editor_sb.st_dev == user_editor_sb.st_dev &&
                    787:                    editor_sb.st_ino == user_editor_sb.st_ino)
                    788:                    break;
                    789:            }
                    790:        } while ((Editor = strtok(NULL, ":")));
                    791:     }
                    792:
                    793:     /*
                    794:      * Can't use $EDITOR, try each element of def_editor until we
                    795:      * find one that exists, is regular, and is executable.
                    796:      */
                    797:     if (Editor == NULL || *Editor == '\0') {
                    798:        efree(EditorPath);
                    799:        EditorPath = estrdup(def_editor);
                    800:        Editor = strtok(EditorPath, ":");
                    801:        do {
                    802:            EditorArgs = get_args(Editor);
                    803:            if (sudo_goodpath(Editor, NULL))
                    804:                break;
                    805:        } while ((Editor = strtok(NULL, ":")));
                    806:
                    807:        /* Bleah, none of the editors existed! */
                    808:        if (Editor == NULL || *Editor == '\0') {
                    809:            warnx("no editor found (editor path = %s)", def_editor);
                    810:            Exit(-1);
                    811:        }
                    812:     }
                    813:     *args = EditorArgs;
                    814:     return(Editor);
                    815: }
                    816:
                    817: /*
                    818:  * Split out any command line arguments and return them.
                    819:  */
                    820: static char *
                    821: get_args(cmnd)
                    822:     char *cmnd;
                    823: {
                    824:     char *args;
                    825:
                    826:     args = cmnd;
                    827:     while (*args && !isblank((unsigned char) *args))
                    828:        args++;
                    829:     if (*args) {
                    830:        *args++ = '\0';
                    831:        while (*args && isblank((unsigned char) *args))
                    832:            args++;
                    833:     }
                    834:     return(*args ? args : NULL);
                    835: }
                    836:
1.1       millert   837: /*
                    838:  * Unlink the sudoers temp file (if it exists) and exit.
                    839:  * Used in place of a normal exit() and as a signal handler.
1.10      millert   840:  * A positive parameter indicates we were called as a signal handler.
1.1       millert   841:  */
                    842: static RETSIGTYPE
                    843: Exit(sig)
                    844:     int sig;
                    845: {
1.16      millert   846: #define        emsg     " exiting due to signal.\n"
1.10      millert   847:
1.19      millert   848:     (void) unlink(sudoers.tpath);
1.1       millert   849:
1.10      millert   850:     if (sig > 0) {
1.14      millert   851:        write(STDERR_FILENO, getprogname(), strlen(getprogname()));
1.10      millert   852:        write(STDERR_FILENO, emsg, sizeof(emsg) - 1);
1.16      millert   853:        _exit(sig);
1.10      millert   854:     }
                    855:     exit(-sig);
1.1       millert   856: }
                    857:
                    858: static void
                    859: usage()
                    860: {
1.19      millert   861:     (void) fprintf(stderr, "usage: %s [-c] [-q] [-s] [-V] [-f sudoers]\n",
1.14      millert   862:        getprogname());
1.1       millert   863:     exit(1);
                    864: }