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

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

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