[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.7

1.1       millert     1: /*
1.3       millert     2:  * Copyright (c) 1996, 1998-2000 Todd C. Miller <Todd.Miller@courtesan.com>
1.1       millert     3:  * All rights reserved.
                      4:  *
                      5:  * Redistribution and use in source and binary forms, with or without
                      6:  * modification, are permitted provided that the following conditions
                      7:  * are met:
                      8:  *
                      9:  * 1. Redistributions of source code must retain the above copyright
                     10:  *    notice, this list of conditions and the following disclaimer.
                     11:  *
                     12:  * 2. Redistributions in binary form must reproduce the above copyright
                     13:  *    notice, this list of conditions and the following disclaimer in the
                     14:  *    documentation and/or other materials provided with the distribution.
                     15:  *
                     16:  * 3. The name of the author may not be used to endorse or promote products
                     17:  *    derived from this software without specific prior written permission.
                     18:  *
                     19:  * 4. Products derived from this software may not be called "Sudo" nor
                     20:  *    may "Sudo" appear in their names without specific prior written
                     21:  *    permission from the author.
                     22:  *
                     23:  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
                     24:  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
                     25:  * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
                     26:  * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
                     27:  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
                     28:  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
                     29:  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
                     30:  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
                     31:  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
                     32:  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
                     33:  */
                     34:
                     35: /*
                     36:  * Lock the sudoers file for safe editing (ala vipw) and check for parse errors.
                     37:  */
                     38:
                     39: #include "config.h"
                     40:
                     41: #include <stdio.h>
                     42: #ifdef STDC_HEADERS
                     43: #include <stdlib.h>
                     44: #endif /* STDC_HEADERS */
                     45: #ifdef HAVE_UNISTD_H
                     46: #include <unistd.h>
                     47: #endif /* HAVE_UNISTD_H */
                     48: #ifdef HAVE_STRING_H
                     49: #include <string.h>
                     50: #endif /* HAVE_STRING_H */
                     51: #ifdef HAVE_STRINGS_H
                     52: #include <strings.h>
                     53: #endif /* HAVE_STRINGS_H */
                     54: #if defined(HAVE_MALLOC_H) && !defined(STDC_HEADERS)
                     55: #include <malloc.h>
                     56: #endif /* HAVE_MALLOC_H && !STDC_HEADERS */
                     57: #include <ctype.h>
                     58: #include <pwd.h>
                     59: #include <time.h>
                     60: #include <signal.h>
                     61: #include <errno.h>
                     62: #include <fcntl.h>
                     63: #include <sys/types.h>
                     64: #include <sys/param.h>
                     65: #include <sys/stat.h>
                     66: #include <sys/file.h>
                     67:
                     68: #include "sudo.h"
                     69: #include "version.h"
                     70:
                     71: #ifndef STDC_HEADERS
                     72: #ifndef __GNUC__       /* gcc has its own malloc */
                     73: extern char *malloc    __P((size_t));
                     74: #endif /* __GNUC__ */
                     75: extern char *getenv    __P((const char *));
                     76: extern int stat                __P((const char *, struct stat *));
                     77: #endif /* !STDC_HEADERS */
                     78:
                     79: #if defined(POSIX_SIGNALS) && !defined(SA_RESETHAND)
                     80: #define SA_RESETHAND    0
                     81: #endif /* POSIX_SIGNALS && !SA_RESETHAND */
                     82:
                     83: #ifndef lint
1.4       millert    84: static const char rcsid[] = "$Sudo: visudo.c,v 1.126 2000/03/23 04:38:22 millert Exp $";
1.1       millert    85: #endif /* lint */
                     86:
                     87: /*
                     88:  * Function prototypes
                     89:  */
                     90: static void usage              __P((void));
                     91: static char whatnow            __P((void));
                     92: static RETSIGTYPE Exit         __P((int));
                     93: static void setup_signals      __P((void));
                     94: int command_matches            __P((char *, char *, char *, char *));
                     95: int addr_matches               __P((char *));
1.4       millert    96: int hostname_matches           __P((char *, char *, char *));
1.3       millert    97: int netgr_matches              __P((char *, char *, char *, char *));
1.1       millert    98: int usergr_matches             __P((char *, char *));
                     99: void init_parser               __P((void));
                    100: void yyrestart                 __P((FILE *));
                    101:
                    102: /*
                    103:  * External globals exported by the parser
                    104:  */
                    105: extern FILE *yyin, *yyout;
                    106: extern int errorlineno;
                    107: extern int pedantic;
                    108:
                    109: /*
                    110:  * Globals
                    111:  */
                    112: char **Argv;
                    113: char *sudoers = _PATH_SUDOERS;
                    114: char *stmp = _PATH_SUDOERS_TMP;
                    115: struct sudo_user sudo_user;
                    116: int parse_error = FALSE;
                    117:
                    118:
                    119: int
                    120: main(argc, argv)
                    121:     int argc;
                    122:     char **argv;
                    123: {
                    124:     char buf[MAXPATHLEN*2];            /* buffer used for copying files */
1.4       millert   125:     char *Editor;                      /* editor to use */
1.1       millert   126:     int sudoers_fd;                    /* sudoers file descriptor */
                    127:     int stmp_fd;                       /* stmp file descriptor */
                    128:     int n;                             /* length parameter */
                    129:     time_t now;                                /* time now */
                    130:     struct stat stmp_sb, sudoers_sb;   /* to check for changes */
                    131:
                    132:     /* Warn about aliases that are used before being defined. */
                    133:     pedantic = 1;
                    134:
                    135:     /*
                    136:      * Parse command line options
                    137:      */
                    138:     Argv = argv;
                    139:
                    140:     /*
                    141:      * Arg handling.
                    142:      */
                    143:     while (--argc) {
                    144:        if (!strcmp(argv[argc], "-V")) {
                    145:            (void) printf("visudo version %s\n", version);
                    146:            exit(0);
                    147:        } else if (!strcmp(argv[argc], "-s")) {
                    148:            pedantic++;                 /* strict mode */
                    149:        } else {
                    150:            usage();
                    151:        }
                    152:     }
                    153:
                    154:     /* Mock up a fake sudo_user struct. */
                    155:     user_host = user_shost = user_cmnd = "";
                    156:     if ((sudo_user.pw = getpwuid(getuid())) == NULL) {
                    157:        (void) fprintf(stderr, "%s: Can't find you in the passwd database.\n",
                    158:            Argv[0]);
                    159:        exit(1);
                    160:     }
                    161:
1.4       millert   162:     /* Setup defaults data structures. */
                    163:     init_defaults();
1.1       millert   164:
                    165:     /*
1.3       millert   166:      * Open sudoers, lock it and stat it.
                    167:      * sudoers_fd must remain open throughout in order to hold the lock.
1.1       millert   168:      */
1.6       millert   169:     sudoers_fd = open(sudoers, O_RDWR | O_CREAT, SUDOERS_MODE);
1.3       millert   170:     if (sudoers_fd == -1) {
                    171:        (void) fprintf(stderr, "%s: %s: %s\n", Argv[0], sudoers,
                    172:            strerror(errno));
                    173:        Exit(-1);
1.1       millert   174:     }
1.3       millert   175:     if (!lock_file(sudoers_fd, SUDO_TLOCK)) {
1.1       millert   176:        (void) fprintf(stderr, "%s: sudoers file busy, try again later.\n",
                    177:            Argv[0]);
                    178:        exit(1);
                    179:     }
1.3       millert   180: #ifdef HAVE_FSTAT
                    181:     if (fstat(sudoers_fd, &sudoers_sb) == -1) {
1.1       millert   182: #else
1.3       millert   183:     if (stat(sudoers, &sudoers_sb) == -1) {
1.1       millert   184: #endif
1.3       millert   185:        (void) fprintf(stderr, "%s: can't stat %s: %s\n",
                    186:            Argv[0], sudoers, strerror(errno));
1.1       millert   187:        Exit(-1);
                    188:     }
                    189:
1.3       millert   190:     /*
                    191:      * Open sudoers temp file.
                    192:      */
                    193:     stmp_fd = open(stmp, O_WRONLY | O_CREAT | O_TRUNC, 0600);
                    194:     if (stmp_fd < 0) {
                    195:        (void) fprintf(stderr, "%s: %s: %s\n", Argv[0], stmp, strerror(errno));
                    196:        exit(1);
                    197:     }
                    198:
1.1       millert   199:     /* Install signal handlers to clean up stmp if we are killed. */
                    200:     setup_signals();
                    201:
                    202:     /* Copy sudoers -> stmp and reset the mtime */
1.3       millert   203:     if (sudoers_sb.st_size) {
1.1       millert   204:        while ((n = read(sudoers_fd, buf, sizeof(buf))) > 0)
                    205:            if (write(stmp_fd, buf, n) != n) {
                    206:                (void) fprintf(stderr, "%s: Write failed: %s\n", Argv[0],
                    207:                strerror(errno));
                    208:                Exit(-1);
                    209:            }
                    210:
1.3       millert   211:        (void) close(stmp_fd);
                    212:        (void) touch(stmp, sudoers_sb.st_mtime);
1.4       millert   213:
                    214:        /* Parse sudoers to pull in editor and env_editor conf values. */
                    215:        if ((yyin = fopen(stmp, "r"))) {
                    216:            yyout = stdout;
                    217:            init_parser();
                    218:            yyparse();
                    219:            parse_error = FALSE;
                    220:            yyrestart(yyin);
                    221:            fclose(yyin);
                    222:        }
1.3       millert   223:     } else
                    224:        (void) close(stmp_fd);
1.1       millert   225:
                    226:     /*
1.4       millert   227:      * If we are allowing EDITOR and VISUAL envariables set Editor
                    228:      * base on whichever exists...
                    229:      */
                    230:     if (!def_flag(I_ENV_EDITOR) ||
                    231:        (!(Editor = getenv("EDITOR")) && !(Editor = getenv("VISUAL"))))
1.5       millert   232:        Editor = estrdup(def_str(I_EDITOR));
1.4       millert   233:
                    234:     /*
1.1       millert   235:      * Edit the temp file and parse it (for sanity checking)
                    236:      */
                    237:     do {
                    238:        /*
                    239:         * Build up a buffer to execute
                    240:         */
                    241:        if (strlen(Editor) + strlen(stmp) + 30 > sizeof(buf)) {
                    242:            (void) fprintf(stderr, "%s: Buffer too short (line %d).\n",
                    243:                           Argv[0], __LINE__);
                    244:            Exit(-1);
                    245:        }
                    246:        if (parse_error == TRUE)
                    247:            (void) sprintf(buf, "%s +%d %s", Editor, errorlineno, stmp);
                    248:        else
                    249:            (void) sprintf(buf, "%s %s", Editor, stmp);
                    250:
                    251:        /* Do the edit -- some SYSV editors exit with 1 instead of 0 */
                    252:        now = time(NULL);
                    253:        n = system(buf);
                    254:        if (n != -1 && ((n >> 8) == 0 || (n >> 8) == 1)) {
                    255:            /*
                    256:             * Sanity checks.
                    257:             */
                    258:            if (stat(stmp, &stmp_sb) < 0) {
                    259:                (void) fprintf(stderr,
                    260:                    "%s: Can't stat temporary file (%s), %s unchanged.\n",
                    261:                    Argv[0], stmp, sudoers);
                    262:                Exit(-1);
                    263:            }
                    264:            if (stmp_sb.st_size == 0) {
                    265:                (void) fprintf(stderr,
                    266:                    "%s: Zero length temporary file (%s), %s unchanged.\n",
                    267:                    Argv[0], stmp, sudoers);
                    268:                Exit(-1);
                    269:            }
                    270:
                    271:            /*
                    272:             * Passed sanity checks so reopen stmp file and check
                    273:             * for parse errors.
                    274:             */
                    275:            yyout = stdout;
                    276:            if (parse_error)
                    277:                yyin = freopen(stmp, "r", yyin);
                    278:            else
                    279:                yyin = fopen(stmp, "r");
                    280:            if (yyin == NULL) {
                    281:                (void) fprintf(stderr,
                    282:                    "%s: Can't re-open temporary file (%s), %s unchanged.\n",
                    283:                    Argv[0], stmp, sudoers);
                    284:                Exit(-1);
                    285:            }
                    286:
                    287:            /* Clean slate for each parse */
1.5       millert   288:            user_runas = NULL;
1.1       millert   289:            init_defaults();
                    290:            init_parser();
                    291:
                    292:            /* Parse the sudoers file */
                    293:            if (yyparse() && parse_error != TRUE) {
                    294:                (void) fprintf(stderr,
                    295:                    "%s: Failed to parse temporary file (%s), unknown error.\n",
                    296:                    Argv[0], stmp);
                    297:                parse_error = TRUE;
                    298:            }
                    299:        } else {
                    300:            (void) fprintf(stderr,
                    301:                "%s: Editor (%s) failed with exit status %d, %s unchanged.\n",
                    302:                Argv[0], Editor, n, sudoers);
                    303:            Exit(-1);
                    304:        }
                    305:
                    306:        /*
                    307:         * Got an error, prompt the user for what to do now
                    308:         */
                    309:        if (parse_error == TRUE) {
                    310:            switch (whatnow()) {
1.7     ! millert   311:                case 'Q' :      parse_error = FALSE;    /* ignore parse error */
1.1       millert   312:                                break;
                    313:                case 'x' :      Exit(0);
                    314:                                break;
                    315:            }
                    316:            yyrestart(yyin);    /* reset lexer */
                    317:        }
                    318:     } while (parse_error == TRUE);
                    319:
                    320:     /*
                    321:      * If the user didn't change the temp file, just unlink it.
                    322:      */
                    323:     if (sudoers_sb.st_mtime != now && sudoers_sb.st_mtime == stmp_sb.st_mtime &&
                    324:        sudoers_sb.st_size == stmp_sb.st_size) {
                    325:        (void) fprintf(stderr, "%s: sudoers file unchanged.\n", Argv[0]);
                    326:        Exit(0);
                    327:     }
                    328:
                    329:     /*
                    330:      * Change mode and ownership of temp file so when
                    331:      * we move it to sudoers things are kosher.
                    332:      */
                    333:     if (chown(stmp, SUDOERS_UID, SUDOERS_GID)) {
                    334:        (void) fprintf(stderr,
                    335:            "%s: Unable to set (uid, gid) of %s to (%d, %d): %s\n",
                    336:            Argv[0], stmp, SUDOERS_UID, SUDOERS_GID, strerror(errno));
                    337:        Exit(-1);
                    338:     }
                    339:     if (chmod(stmp, SUDOERS_MODE)) {
                    340:        (void) fprintf(stderr,
                    341:            "%s: Unable to change mode of %s to %o: %s\n",
                    342:            Argv[0], stmp, SUDOERS_MODE, strerror(errno));
                    343:        Exit(-1);
                    344:     }
                    345:
                    346:     /*
                    347:      * Now that we have a sane stmp file (parses ok) it needs to be
                    348:      * rename(2)'d to sudoers.  If the rename(2) fails we try using
                    349:      * mv(1) in case stmp and sudoers are on different filesystems.
                    350:      */
                    351:     if (rename(stmp, sudoers)) {
                    352:        if (errno == EXDEV) {
                    353:            char *tmpbuf;
                    354:
                    355:            (void) fprintf(stderr,
                    356:              "%s: %s and %s not on the same filesystem, using mv to rename.\n",
                    357:              Argv[0], stmp, sudoers);
                    358:
                    359:            /* Allocate just enough space for tmpbuf */
                    360:            n = sizeof(char) * (strlen(_PATH_MV) + strlen(stmp) +
                    361:                  strlen(sudoers) + 4);
                    362:            if ((tmpbuf = (char *) malloc(n)) == NULL) {
                    363:                (void) fprintf(stderr,
                    364:                              "%s: Cannot alocate memory, %s unchanged: %s\n",
                    365:                              Argv[0], sudoers, strerror(errno));
                    366:                Exit(-1);
                    367:            }
                    368:
                    369:            /* Build up command and execute it */
                    370:            (void) sprintf(tmpbuf, "%s %s %s", _PATH_MV, stmp, sudoers);
                    371:            if (system(tmpbuf)) {
                    372:                (void) fprintf(stderr,
                    373:                               "%s: Command failed: '%s', %s unchanged.\n",
                    374:                               Argv[0], tmpbuf, sudoers);
                    375:                Exit(-1);
                    376:            }
                    377:            free(tmpbuf);
                    378:        } else {
                    379:            (void) fprintf(stderr, "%s: Error renaming %s, %s unchanged: %s\n",
                    380:                                   Argv[0], stmp, sudoers, strerror(errno));
                    381:            Exit(-1);
                    382:        }
                    383:     }
                    384:
1.3       millert   385:     exit(0);
1.1       millert   386: }
                    387:
                    388: /*
                    389:  * Dummy *_matches routines.
                    390:  * These exist to allow us to use the same parser as sudo(8).
                    391:  */
                    392: int
                    393: command_matches(cmnd, cmnd_args, path, sudoers_args)
                    394:     char *cmnd;
                    395:     char *cmnd_args;
                    396:     char *path;
                    397:     char *sudoers_args;
                    398: {
                    399:     return(TRUE);
                    400: }
                    401:
                    402: int
                    403: addr_matches(n)
                    404:     char *n;
1.4       millert   405: {
                    406:     return(TRUE);
                    407: }
                    408:
                    409: int
                    410: hostname_matches(s, l, p)
                    411:     char *s, *l, *p;
1.1       millert   412: {
                    413:     return(TRUE);
                    414: }
                    415:
                    416: int
                    417: usergr_matches(g, u)
                    418:     char *g, *u;
                    419: {
                    420:     return(TRUE);
                    421: }
                    422:
                    423: int
1.3       millert   424: netgr_matches(n, h, sh, u)
                    425:     char *n, *h, *sh, *u;
1.1       millert   426: {
                    427:     return(TRUE);
1.2       millert   428: }
                    429:
                    430: void
                    431: set_fqdn()
                    432: {
                    433:     return;
1.1       millert   434: }
                    435:
                    436: /*
                    437:  * Assuming a parse error occurred, prompt the user for what they want
                    438:  * to do now.  Returns the first letter of their choice.
                    439:  */
                    440: static char
                    441: whatnow()
                    442: {
                    443:     int choice, c;
                    444:
                    445:     for (;;) {
                    446:        (void) fputs("What now? ", stdout);
                    447:        choice = getchar();
                    448:        for (c = choice; c != '\n' && c != EOF;)
                    449:            c = getchar();
                    450:
1.3       millert   451:        switch (choice) {
                    452:            case EOF:
                    453:                choice = 'x';
                    454:                /* FALLTHROUGH */
                    455:            case 'e':
                    456:            case 'x':
                    457:            case 'Q':
                    458:                return(choice);
                    459:            default:
                    460:                (void) puts("Options are:");
                    461:                (void) puts("  (e)dit sudoers file again");
                    462:                (void) puts("  e(x)it without saving changes to sudoers file");
                    463:                (void) puts("  (Q)uit and save changes to sudoers file (DANGER!)\n");
1.1       millert   464:        }
                    465:     }
                    466: }
                    467:
                    468: /*
                    469:  * Install signal handlers for visudo.
                    470:  */
                    471: static void
                    472: setup_signals()
                    473: {
                    474: #ifdef POSIX_SIGNALS
                    475:        struct sigaction action;                /* POSIX signal structure */
                    476: #endif /* POSIX_SIGNALS */
                    477:
                    478:        /*
                    479:         * Setup signal handlers to cleanup nicely.
                    480:         */
                    481: #ifdef POSIX_SIGNALS
                    482:        (void) memset((VOID *)&action, 0, sizeof(action));
                    483:        action.sa_handler = Exit;
                    484:        action.sa_flags = SA_RESETHAND;
                    485:        (void) sigaction(SIGTERM, &action, NULL);
                    486:        (void) sigaction(SIGHUP, &action, NULL);
                    487:        (void) sigaction(SIGINT, &action, NULL);
                    488:        (void) sigaction(SIGQUIT, &action, NULL);
                    489: #else
                    490:        (void) signal(SIGTERM, Exit);
                    491:        (void) signal(SIGHUP, Exit);
                    492:        (void) signal(SIGINT, Exit);
                    493:        (void) signal(SIGQUIT, Exit);
                    494: #endif /* POSIX_SIGNALS */
                    495: }
                    496:
                    497: /*
                    498:  * Unlink the sudoers temp file (if it exists) and exit.
                    499:  * Used in place of a normal exit() and as a signal handler.
                    500:  * A positive parameter is considered to be a signal and is reported.
                    501:  */
                    502: static RETSIGTYPE
                    503: Exit(sig)
                    504:     int sig;
                    505: {
                    506:     (void) unlink(stmp);
                    507:
                    508:     if (sig > 0)
                    509:        (void) fprintf(stderr, "%s exiting, caught signal %d.\n", Argv[0], sig);
                    510:
                    511:     exit(-sig);
                    512: }
                    513:
                    514: static void
                    515: usage()
                    516: {
                    517:     (void) fprintf(stderr, "usage: %s [-s] [-V]\n", Argv[0]);
                    518:     exit(1);
                    519: }