Annotation of embedaddon/sudo/plugins/sample/sample_plugin.c, revision 1.1

1.1     ! misho       1: /*
        !             2:  * Copyright (c) 2010-2011 Todd C. Miller <Todd.Miller@courtesan.com>
        !             3:  *
        !             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.
        !             7:  *
        !             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.
        !            15:  */
        !            16: 
        !            17: #include <config.h>
        !            18: 
        !            19: #include <sys/types.h>
        !            20: #include <sys/param.h>
        !            21: #include <sys/stat.h>
        !            22: #include <sys/wait.h>
        !            23: 
        !            24: #include <stdio.h>
        !            25: #ifdef STDC_HEADERS
        !            26: # include <stdlib.h>
        !            27: # include <stddef.h>
        !            28: #else
        !            29: # ifdef HAVE_STDLIB_H
        !            30: #  include <stdlib.h>
        !            31: # endif
        !            32: #endif /* STDC_HEADERS */
        !            33: #ifdef HAVE_STRING_H
        !            34: # if defined(HAVE_MEMORY_H) && !defined(STDC_HEADERS)
        !            35: #  include <memory.h>
        !            36: # endif
        !            37: # include <string.h>
        !            38: #endif /* HAVE_STRING_H */
        !            39: #ifdef HAVE_STRINGS_H
        !            40: # include <strings.h>
        !            41: #endif /* HAVE_STRINGS_H */
        !            42: #ifdef HAVE_UNISTD_H
        !            43: # include <unistd.h>
        !            44: #endif /* HAVE_UNISTD_H */
        !            45: #include <ctype.h>
        !            46: #include <fcntl.h>
        !            47: #include <limits.h>
        !            48: #include <grp.h>
        !            49: #include <pwd.h>
        !            50: #include <stdarg.h>
        !            51: 
        !            52: #include <pathnames.h>
        !            53: #include "sudo_plugin.h"
        !            54: #include "missing.h"
        !            55: 
        !            56: /*
        !            57:  * Sample plugin module that allows any user who knows the password
        !            58:  * ("test") to run any command as root.  Since there is no credential
        !            59:  * caching the validate and invalidate functions are NULL.
        !            60:  */
        !            61: 
        !            62: #ifdef __TANDEM
        !            63: # define ROOT_UID       65535
        !            64: #else
        !            65: # define ROOT_UID       0
        !            66: #endif
        !            67: 
        !            68: #undef TRUE
        !            69: #define TRUE 1
        !            70: #undef FALSE
        !            71: #define FALSE 0
        !            72: #undef ERROR
        !            73: #define ERROR -1
        !            74: 
        !            75: static struct plugin_state {
        !            76:     char **envp;
        !            77:     char * const *settings;
        !            78:     char * const *user_info;
        !            79: } plugin_state;
        !            80: static sudo_conv_t sudo_conv;
        !            81: static sudo_printf_t sudo_log;
        !            82: static FILE *input, *output;
        !            83: static uid_t runas_uid = ROOT_UID;
        !            84: static gid_t runas_gid = -1;
        !            85: static int use_sudoedit = FALSE;
        !            86: 
        !            87: /*
        !            88:  * Allocate storage for a name=value string and return it.
        !            89:  */
        !            90: static char *
        !            91: fmt_string(const char *var, const char *val)
        !            92: {
        !            93:     size_t var_len = strlen(var);
        !            94:     size_t val_len = strlen(val);
        !            95:     char *cp, *str;
        !            96: 
        !            97:     cp = str = malloc(var_len + 1 + val_len + 1);
        !            98:     if (str != NULL) {
        !            99:        memcpy(cp, var, var_len);
        !           100:        cp += var_len;
        !           101:        *cp++ = '=';
        !           102:        memcpy(cp, val, val_len);
        !           103:        cp += val_len;
        !           104:        *cp = '\0';
        !           105:     }
        !           106: 
        !           107:     return str;
        !           108: }
        !           109: 
        !           110: /*
        !           111:  * Plugin policy open function.
        !           112:  */
        !           113: static int
        !           114: policy_open(unsigned int version, sudo_conv_t conversation,
        !           115:     sudo_printf_t sudo_printf, char * const settings[],
        !           116:     char * const user_info[], char * const user_env[])
        !           117: {
        !           118:     char * const *ui;
        !           119:     struct passwd *pw;
        !           120:     const char *runas_user = NULL;
        !           121:     struct group *gr;
        !           122:     const char *runas_group = NULL;
        !           123: 
        !           124:     if (!sudo_conv)
        !           125:        sudo_conv = conversation;
        !           126:     if (!sudo_log)
        !           127:        sudo_log = sudo_printf;
        !           128: 
        !           129:     if (SUDO_API_VERSION_GET_MAJOR(version) != SUDO_API_VERSION_MAJOR) {
        !           130:        sudo_log(SUDO_CONV_ERROR_MSG,
        !           131:            "the sample plugin requires API version %d.x\n",
        !           132:            SUDO_API_VERSION_MAJOR);
        !           133:        return ERROR;
        !           134:     }
        !           135: 
        !           136:     /* Only allow commands to be run as root. */
        !           137:     for (ui = settings; *ui != NULL; ui++) {
        !           138:        if (strncmp(*ui, "runas_user=", sizeof("runas_user=") - 1) == 0) {
        !           139:            runas_user = *ui + sizeof("runas_user=") - 1;
        !           140:        }
        !           141:        if (strncmp(*ui, "runas_group=", sizeof("runas_group=") - 1) == 0) {
        !           142:            runas_group = *ui + sizeof("runas_group=") - 1;
        !           143:        }
        !           144: #if !defined(HAVE_GETPROGNAME) && !defined(HAVE___PROGNAME)
        !           145:        if (strncmp(*ui, "progname=", sizeof("progname=") - 1) == 0) {
        !           146:            setprogname(*ui + sizeof("progname=") - 1);
        !           147:        }
        !           148: #endif
        !           149:        /* Check to see if sudo was called as sudoedit or with -e flag. */
        !           150:        if (strncmp(*ui, "sudoedit=", sizeof("sudoedit=") - 1) == 0) {
        !           151:            if (strcasecmp(*ui + sizeof("sudoedit=") - 1, "true") == 0)
        !           152:                use_sudoedit = TRUE;
        !           153:        }
        !           154:        /* This plugin doesn't support running sudo with no arguments. */
        !           155:        if (strncmp(*ui, "implied_shell=", sizeof("implied_shell=") - 1) == 0) {
        !           156:            if (strcasecmp(*ui + sizeof("implied_shell=") - 1, "true") == 0)
        !           157:                return -2; /* usage error */
        !           158:        }
        !           159:     }
        !           160:     if (runas_user != NULL) {
        !           161:        if ((pw = getpwnam(runas_user)) == NULL) {
        !           162:            sudo_log(SUDO_CONV_ERROR_MSG, "unknown user %s\n", runas_user);
        !           163:            return 0;
        !           164:        }
        !           165:        runas_uid = pw->pw_uid;
        !           166:     }
        !           167:     if (runas_group != NULL) {
        !           168:        if ((gr = getgrnam(runas_group)) == NULL) {
        !           169:            sudo_log(SUDO_CONV_ERROR_MSG, "unknown group %s\n", runas_group);
        !           170:            return 0;
        !           171:        }
        !           172:        runas_gid = gr->gr_gid;
        !           173:     }
        !           174: 
        !           175:     /* Plugin state. */
        !           176:     plugin_state.envp = (char **)user_env;
        !           177:     plugin_state.settings = settings;
        !           178:     plugin_state.user_info = user_info;
        !           179: 
        !           180:     return 1;
        !           181: }
        !           182: 
        !           183: static char *
        !           184: find_in_path(char *command, char **envp)
        !           185: {
        !           186:     struct stat sb;
        !           187:     char *path, *path0, **ep, *cp;
        !           188:     char pathbuf[PATH_MAX], *qualified = NULL;
        !           189: 
        !           190:     if (strchr(command, '/') != NULL)
        !           191:        return command;
        !           192: 
        !           193:     path = _PATH_DEFPATH;
        !           194:     for (ep = plugin_state.envp; *ep != NULL; ep++) {
        !           195:        if (strncmp(*ep, "PATH=", 5) == 0) {
        !           196:            path = *ep + 5;
        !           197:            break;
        !           198:        }
        !           199:     }
        !           200:     path = path0 = strdup(path);
        !           201:     do {
        !           202:        if ((cp = strchr(path, ':')))
        !           203:            *cp = '\0';
        !           204:        snprintf(pathbuf, sizeof(pathbuf), "%s/%s", *path ? path : ".",
        !           205:            command);
        !           206:        if (stat(pathbuf, &sb) == 0) {
        !           207:            if (S_ISREG(sb.st_mode) && (sb.st_mode & 0000111)) {
        !           208:                qualified = pathbuf;
        !           209:                break;
        !           210:            }
        !           211:        }
        !           212:        path = cp + 1;
        !           213:     } while (cp != NULL);
        !           214:     free(path0);
        !           215:     return qualified ? strdup(qualified) : NULL;
        !           216: }
        !           217: 
        !           218: static int
        !           219: check_passwd(void)
        !           220: {
        !           221:     struct sudo_conv_message msg;
        !           222:     struct sudo_conv_reply repl;
        !           223: 
        !           224:     /* Prompt user for password via conversation function. */
        !           225:     memset(&msg, 0, sizeof(msg));
        !           226:     msg.msg_type = SUDO_CONV_PROMPT_ECHO_OFF;
        !           227:     msg.msg = "Password: ";
        !           228:     memset(&repl, 0, sizeof(repl));
        !           229:     sudo_conv(1, &msg, &repl);
        !           230:     if (repl.reply == NULL) {
        !           231:        sudo_log(SUDO_CONV_ERROR_MSG, "missing password\n");
        !           232:        return FALSE;
        !           233:     }
        !           234:     if (strcmp(repl.reply, "test") != 0) {
        !           235:        sudo_log(SUDO_CONV_ERROR_MSG, "incorrect password\n");
        !           236:        return FALSE;
        !           237:     }
        !           238:     return TRUE;
        !           239: }
        !           240: 
        !           241: static char **
        !           242: build_command_info(char *command)
        !           243: {
        !           244:     static char **command_info;
        !           245:     int i = 0;
        !           246: 
        !           247:     /* Setup command info. */
        !           248:     command_info = calloc(32, sizeof(char *));
        !           249:     if (command_info == NULL)
        !           250:        return NULL;
        !           251:     if ((command_info[i++] = fmt_string("command", command)) == NULL ||
        !           252:        asprintf(&command_info[i++], "runas_euid=%ld", (long)runas_uid) == -1 ||
        !           253:        asprintf(&command_info[i++], "runas_uid=%ld", (long)runas_uid) == -1) {
        !           254:        return NULL;
        !           255:     }
        !           256:     if (runas_gid != -1) {
        !           257:        if (asprintf(&command_info[i++], "runas_gid=%ld", (long)runas_gid) == -1 ||
        !           258:            asprintf(&command_info[i++], "runas_egid=%ld", (long)runas_gid) == -1) {
        !           259:            return NULL;
        !           260:        }
        !           261:     }
        !           262:     if (use_sudoedit) {
        !           263:        command_info[i] = strdup("sudoedit=true");
        !           264:        if (command_info[i++] == NULL)
        !           265:                return NULL;
        !           266:     }
        !           267: #ifdef USE_TIMEOUT
        !           268:     command_info[i++] = "timeout=30";
        !           269: #endif
        !           270:     return command_info;
        !           271: }
        !           272: 
        !           273: static char *
        !           274: find_editor(int nfiles, char * const files[], char **argv_out[])
        !           275: {
        !           276:     char *cp, **ep, **nargv, *editor, *editor_path;
        !           277:     int ac, i, nargc, wasblank;
        !           278: 
        !           279:     /* Lookup EDITOR in user's environment. */
        !           280:     editor = _PATH_VI;
        !           281:     for (ep = plugin_state.envp; *ep != NULL; ep++) {
        !           282:        if (strncmp(*ep, "EDITOR=", 7) == 0) {
        !           283:            editor = *ep + 7;
        !           284:            break;
        !           285:        }
        !           286:     }
        !           287:     editor = strdup(editor);
        !           288:     if (editor == NULL) {
        !           289:        sudo_log(SUDO_CONV_ERROR_MSG, "unable to allocate memory\n");
        !           290:        return NULL;
        !           291:     }
        !           292: 
        !           293:     /*
        !           294:      * Split editor into an argument vector; editor is reused (do not free).
        !           295:      * The EDITOR environment variables may contain command
        !           296:      * line args so look for those and alloc space for them too.
        !           297:      */
        !           298:     nargc = 1;
        !           299:     for (wasblank = 0, cp = editor; *cp != '\0'; cp++) {
        !           300:        if (isblank((unsigned char) *cp))
        !           301:            wasblank = 1;
        !           302:        else if (wasblank) {
        !           303:            wasblank = 0;
        !           304:            nargc++;
        !           305:        }
        !           306:     }
        !           307:     /* If we can't find the editor in the user's PATH, give up. */
        !           308:     cp = strtok(editor, " \t");
        !           309:     if (cp == NULL ||
        !           310:        (editor_path = find_in_path(editor, plugin_state.envp)) == NULL) {
        !           311:        return NULL;
        !           312:     }
        !           313:     nargv = (char **) malloc((nargc + 1 + nfiles + 1) * sizeof(char *));
        !           314:     if (nargv == NULL) {
        !           315:        sudo_log(SUDO_CONV_ERROR_MSG, "unable to allocate memory\n");
        !           316:        return NULL;
        !           317:     }
        !           318:     for (ac = 0; cp != NULL && ac < nargc; ac++) {
        !           319:        nargv[ac] = cp;
        !           320:        cp = strtok(NULL, " \t");
        !           321:     }
        !           322:     nargv[ac++] = "--";
        !           323:     for (i = 0; i < nfiles; )
        !           324:        nargv[ac++] = files[i++];
        !           325:     nargv[ac] = NULL;
        !           326: 
        !           327:     *argv_out = nargv;
        !           328:     return editor_path;
        !           329: }
        !           330: 
        !           331: /*
        !           332:  * Plugin policy check function.
        !           333:  * Simple example that prompts for a password, hard-coded to "test".
        !           334:  */
        !           335: static int 
        !           336: policy_check(int argc, char * const argv[],
        !           337:     char *env_add[], char **command_info_out[],
        !           338:     char **argv_out[], char **user_env_out[])
        !           339: {
        !           340:     char *command;
        !           341: 
        !           342:     if (!argc || argv[0] == NULL) {
        !           343:        sudo_log(SUDO_CONV_ERROR_MSG, "no command specified\n");
        !           344:        return FALSE;
        !           345:     }
        !           346: 
        !           347:     if (!check_passwd())
        !           348:        return FALSE;
        !           349: 
        !           350:     command = find_in_path(argv[0], plugin_state.envp);
        !           351:     if (command == NULL) {
        !           352:        sudo_log(SUDO_CONV_ERROR_MSG, "%s: command not found\n", argv[0]);
        !           353:        return FALSE;
        !           354:     }
        !           355: 
        !           356:     /* If "sudo vi" is run, auto-convert to sudoedit.  */
        !           357:     if (strcmp(command, _PATH_VI) == 0)
        !           358:        use_sudoedit = TRUE;
        !           359: 
        !           360:     if (use_sudoedit) {
        !           361:        /* Rebuild argv using editor */
        !           362:        command = find_editor(argc - 1, argv + 1, argv_out);
        !           363:        if (command == NULL) {
        !           364:            sudo_log(SUDO_CONV_ERROR_MSG, "unable to find valid editor\n");
        !           365:            return ERROR;
        !           366:        }
        !           367:        use_sudoedit = TRUE;
        !           368:     } else {
        !           369:        /* No changes needd to argv */
        !           370:        *argv_out = (char **)argv;
        !           371:     }
        !           372: 
        !           373:     /* No changes to envp */
        !           374:     *user_env_out = plugin_state.envp;
        !           375: 
        !           376:     /* Setup command info. */
        !           377:     *command_info_out = build_command_info(command);
        !           378:     if (*command_info_out == NULL) {
        !           379:        sudo_log(SUDO_CONV_ERROR_MSG, "out of memory\n");
        !           380:        return ERROR;
        !           381:     }
        !           382: 
        !           383:     return TRUE;
        !           384: }
        !           385: 
        !           386: static int
        !           387: policy_list(int argc, char * const argv[], int verbose, const char *list_user)
        !           388: {
        !           389:     /*
        !           390:      * List user's capabilities.
        !           391:      */
        !           392:     sudo_log(SUDO_CONV_INFO_MSG, "Validated users may run any command\n");
        !           393:     return TRUE;
        !           394: }
        !           395: 
        !           396: static int
        !           397: policy_version(int verbose)
        !           398: {
        !           399:     sudo_log(SUDO_CONV_INFO_MSG, "Sample policy plugin version %s\n", PACKAGE_VERSION);
        !           400:     return TRUE;
        !           401: }
        !           402: 
        !           403: static void
        !           404: policy_close(int exit_status, int error)
        !           405: {
        !           406:     /*
        !           407:      * The policy might log the command exit status here.
        !           408:      * In this example, we just print a message.
        !           409:      */
        !           410:     if (error) {
        !           411:        sudo_log(SUDO_CONV_ERROR_MSG, "Command error: %s\n", strerror(error));
        !           412:     } else {
        !           413:         if (WIFEXITED(exit_status)) {
        !           414:            sudo_log(SUDO_CONV_INFO_MSG, "Command exited with status %d\n",
        !           415:                WEXITSTATUS(exit_status));
        !           416:         } else if (WIFSIGNALED(exit_status)) {
        !           417:            sudo_log(SUDO_CONV_INFO_MSG, "Command killed by signal %d\n",
        !           418:                WTERMSIG(exit_status));
        !           419:        }
        !           420:     }
        !           421: }
        !           422: 
        !           423: static int
        !           424: io_open(unsigned int version, sudo_conv_t conversation,
        !           425:     sudo_printf_t sudo_printf, char * const settings[],
        !           426:     char * const user_info[], char * const command_info[],
        !           427:     int argc, char * const argv[], char * const user_env[])
        !           428: {
        !           429:     int fd;
        !           430:     char path[PATH_MAX];
        !           431: 
        !           432:     if (!sudo_conv)
        !           433:        sudo_conv = conversation;
        !           434:     if (!sudo_log)
        !           435:        sudo_log = sudo_printf;
        !           436: 
        !           437:     /* Open input and output files. */
        !           438:     snprintf(path, sizeof(path), "/var/tmp/sample-%u.output",
        !           439:        (unsigned int)getpid());
        !           440:     fd = open(path, O_WRONLY|O_CREAT|O_EXCL, 0644);
        !           441:     if (fd == -1)
        !           442:        return FALSE;
        !           443:     output = fdopen(fd, "w");
        !           444: 
        !           445:     snprintf(path, sizeof(path), "/var/tmp/sample-%u.input",
        !           446:        (unsigned int)getpid());
        !           447:     fd = open(path, O_WRONLY|O_CREAT|O_EXCL, 0644);
        !           448:     if (fd == -1)
        !           449:        return FALSE;
        !           450:     input = fdopen(fd, "w");
        !           451: 
        !           452:     return TRUE;
        !           453: }
        !           454: 
        !           455: static void
        !           456: io_close(int exit_status, int error)
        !           457: {
        !           458:     fclose(input);
        !           459:     fclose(output);
        !           460: }
        !           461: 
        !           462: static int
        !           463: io_version(int verbose)
        !           464: {
        !           465:     sudo_log(SUDO_CONV_INFO_MSG, "Sample I/O plugin version %s\n",
        !           466:        PACKAGE_VERSION);
        !           467:     return TRUE;
        !           468: }
        !           469: 
        !           470: static int
        !           471: io_log_input(const char *buf, unsigned int len)
        !           472: {
        !           473:     fwrite(buf, len, 1, input);
        !           474:     return TRUE;
        !           475: }
        !           476: 
        !           477: static int
        !           478: io_log_output(const char *buf, unsigned int len)
        !           479: {
        !           480:     fwrite(buf, len, 1, output);
        !           481:     return TRUE;
        !           482: }
        !           483: 
        !           484: struct policy_plugin sample_policy = {
        !           485:     SUDO_POLICY_PLUGIN,
        !           486:     SUDO_API_VERSION,
        !           487:     policy_open,
        !           488:     policy_close,
        !           489:     policy_version,
        !           490:     policy_check,
        !           491:     policy_list,
        !           492:     NULL, /* validate */
        !           493:     NULL /* invalidate */
        !           494: };
        !           495: 
        !           496: /*
        !           497:  * Note: This plugin does not differentiate between tty and pipe I/O.
        !           498:  *       It all gets logged to the same file.
        !           499:  */
        !           500: struct io_plugin sample_io = {
        !           501:     SUDO_IO_PLUGIN,
        !           502:     SUDO_API_VERSION,
        !           503:     io_open,
        !           504:     io_close,
        !           505:     io_version,
        !           506:     io_log_input,      /* tty input */
        !           507:     io_log_output,     /* tty output */
        !           508:     io_log_input,      /* command stdin if not tty */
        !           509:     io_log_output,     /* command stdout if not tty */
        !           510:     io_log_output      /* command stderr if not tty */
        !           511: };

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>