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

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 */
1.1.1.2   misho      33: #ifdef HAVE_STDBOOL_H
                     34: # include <stdbool.h>
                     35: #else
                     36: # include "compat/stdbool.h"
                     37: #endif /* HAVE_STDBOOL_H */
1.1       misho      38: #ifdef HAVE_STRING_H
                     39: # if defined(HAVE_MEMORY_H) && !defined(STDC_HEADERS)
                     40: #  include <memory.h>
                     41: # endif
                     42: # include <string.h>
                     43: #endif /* HAVE_STRING_H */
                     44: #ifdef HAVE_STRINGS_H
                     45: # include <strings.h>
                     46: #endif /* HAVE_STRINGS_H */
                     47: #ifdef HAVE_UNISTD_H
                     48: # include <unistd.h>
                     49: #endif /* HAVE_UNISTD_H */
                     50: #include <ctype.h>
                     51: #include <fcntl.h>
                     52: #include <limits.h>
                     53: #include <grp.h>
                     54: #include <pwd.h>
                     55: #include <stdarg.h>
                     56: 
                     57: #include <pathnames.h>
                     58: #include "sudo_plugin.h"
                     59: #include "missing.h"
                     60: 
                     61: /*
                     62:  * Sample plugin module that allows any user who knows the password
                     63:  * ("test") to run any command as root.  Since there is no credential
                     64:  * caching the validate and invalidate functions are NULL.
                     65:  */
                     66: 
                     67: #ifdef __TANDEM
                     68: # define ROOT_UID       65535
                     69: #else
                     70: # define ROOT_UID       0
                     71: #endif
                     72: 
                     73: static struct plugin_state {
                     74:     char **envp;
                     75:     char * const *settings;
                     76:     char * const *user_info;
                     77: } plugin_state;
                     78: static sudo_conv_t sudo_conv;
                     79: static sudo_printf_t sudo_log;
                     80: static FILE *input, *output;
                     81: static uid_t runas_uid = ROOT_UID;
                     82: static gid_t runas_gid = -1;
1.1.1.2   misho      83: static int use_sudoedit = false;
1.1       misho      84: 
                     85: /*
                     86:  * Allocate storage for a name=value string and return it.
                     87:  */
                     88: static char *
                     89: fmt_string(const char *var, const char *val)
                     90: {
                     91:     size_t var_len = strlen(var);
                     92:     size_t val_len = strlen(val);
                     93:     char *cp, *str;
                     94: 
                     95:     cp = str = malloc(var_len + 1 + val_len + 1);
                     96:     if (str != NULL) {
                     97:        memcpy(cp, var, var_len);
                     98:        cp += var_len;
                     99:        *cp++ = '=';
                    100:        memcpy(cp, val, val_len);
                    101:        cp += val_len;
                    102:        *cp = '\0';
                    103:     }
                    104: 
                    105:     return str;
                    106: }
                    107: 
                    108: /*
                    109:  * Plugin policy open function.
                    110:  */
                    111: static int
                    112: policy_open(unsigned int version, sudo_conv_t conversation,
                    113:     sudo_printf_t sudo_printf, char * const settings[],
1.1.1.2   misho     114:     char * const user_info[], char * const user_env[], char * const args[])
1.1       misho     115: {
                    116:     char * const *ui;
                    117:     struct passwd *pw;
                    118:     const char *runas_user = NULL;
                    119:     struct group *gr;
                    120:     const char *runas_group = NULL;
                    121: 
                    122:     if (!sudo_conv)
                    123:        sudo_conv = conversation;
                    124:     if (!sudo_log)
                    125:        sudo_log = sudo_printf;
                    126: 
                    127:     if (SUDO_API_VERSION_GET_MAJOR(version) != SUDO_API_VERSION_MAJOR) {
                    128:        sudo_log(SUDO_CONV_ERROR_MSG,
                    129:            "the sample plugin requires API version %d.x\n",
                    130:            SUDO_API_VERSION_MAJOR);
1.1.1.2   misho     131:        return -1;
1.1       misho     132:     }
                    133: 
                    134:     /* Only allow commands to be run as root. */
                    135:     for (ui = settings; *ui != NULL; ui++) {
                    136:        if (strncmp(*ui, "runas_user=", sizeof("runas_user=") - 1) == 0) {
                    137:            runas_user = *ui + sizeof("runas_user=") - 1;
                    138:        }
                    139:        if (strncmp(*ui, "runas_group=", sizeof("runas_group=") - 1) == 0) {
                    140:            runas_group = *ui + sizeof("runas_group=") - 1;
                    141:        }
                    142: #if !defined(HAVE_GETPROGNAME) && !defined(HAVE___PROGNAME)
                    143:        if (strncmp(*ui, "progname=", sizeof("progname=") - 1) == 0) {
                    144:            setprogname(*ui + sizeof("progname=") - 1);
                    145:        }
                    146: #endif
                    147:        /* Check to see if sudo was called as sudoedit or with -e flag. */
                    148:        if (strncmp(*ui, "sudoedit=", sizeof("sudoedit=") - 1) == 0) {
                    149:            if (strcasecmp(*ui + sizeof("sudoedit=") - 1, "true") == 0)
1.1.1.2   misho     150:                use_sudoedit = true;
1.1       misho     151:        }
                    152:        /* This plugin doesn't support running sudo with no arguments. */
                    153:        if (strncmp(*ui, "implied_shell=", sizeof("implied_shell=") - 1) == 0) {
                    154:            if (strcasecmp(*ui + sizeof("implied_shell=") - 1, "true") == 0)
                    155:                return -2; /* usage error */
                    156:        }
                    157:     }
                    158:     if (runas_user != NULL) {
                    159:        if ((pw = getpwnam(runas_user)) == NULL) {
                    160:            sudo_log(SUDO_CONV_ERROR_MSG, "unknown user %s\n", runas_user);
                    161:            return 0;
                    162:        }
                    163:        runas_uid = pw->pw_uid;
                    164:     }
                    165:     if (runas_group != NULL) {
                    166:        if ((gr = getgrnam(runas_group)) == NULL) {
                    167:            sudo_log(SUDO_CONV_ERROR_MSG, "unknown group %s\n", runas_group);
                    168:            return 0;
                    169:        }
                    170:        runas_gid = gr->gr_gid;
                    171:     }
                    172: 
                    173:     /* Plugin state. */
                    174:     plugin_state.envp = (char **)user_env;
                    175:     plugin_state.settings = settings;
                    176:     plugin_state.user_info = user_info;
                    177: 
                    178:     return 1;
                    179: }
                    180: 
                    181: static char *
                    182: find_in_path(char *command, char **envp)
                    183: {
                    184:     struct stat sb;
                    185:     char *path, *path0, **ep, *cp;
                    186:     char pathbuf[PATH_MAX], *qualified = NULL;
                    187: 
                    188:     if (strchr(command, '/') != NULL)
                    189:        return command;
                    190: 
                    191:     path = _PATH_DEFPATH;
                    192:     for (ep = plugin_state.envp; *ep != NULL; ep++) {
                    193:        if (strncmp(*ep, "PATH=", 5) == 0) {
                    194:            path = *ep + 5;
                    195:            break;
                    196:        }
                    197:     }
                    198:     path = path0 = strdup(path);
                    199:     do {
                    200:        if ((cp = strchr(path, ':')))
                    201:            *cp = '\0';
                    202:        snprintf(pathbuf, sizeof(pathbuf), "%s/%s", *path ? path : ".",
                    203:            command);
                    204:        if (stat(pathbuf, &sb) == 0) {
                    205:            if (S_ISREG(sb.st_mode) && (sb.st_mode & 0000111)) {
                    206:                qualified = pathbuf;
                    207:                break;
                    208:            }
                    209:        }
                    210:        path = cp + 1;
                    211:     } while (cp != NULL);
                    212:     free(path0);
                    213:     return qualified ? strdup(qualified) : NULL;
                    214: }
                    215: 
                    216: static int
                    217: check_passwd(void)
                    218: {
                    219:     struct sudo_conv_message msg;
                    220:     struct sudo_conv_reply repl;
                    221: 
                    222:     /* Prompt user for password via conversation function. */
                    223:     memset(&msg, 0, sizeof(msg));
                    224:     msg.msg_type = SUDO_CONV_PROMPT_ECHO_OFF;
                    225:     msg.msg = "Password: ";
                    226:     memset(&repl, 0, sizeof(repl));
                    227:     sudo_conv(1, &msg, &repl);
                    228:     if (repl.reply == NULL) {
                    229:        sudo_log(SUDO_CONV_ERROR_MSG, "missing password\n");
1.1.1.2   misho     230:        return false;
1.1       misho     231:     }
                    232:     if (strcmp(repl.reply, "test") != 0) {
                    233:        sudo_log(SUDO_CONV_ERROR_MSG, "incorrect password\n");
1.1.1.2   misho     234:        return false;
1.1       misho     235:     }
1.1.1.2   misho     236:     return true;
1.1       misho     237: }
                    238: 
                    239: static char **
1.1.1.3 ! misho     240: build_command_info(const char *command)
1.1       misho     241: {
                    242:     static char **command_info;
                    243:     int i = 0;
                    244: 
                    245:     /* Setup command info. */
                    246:     command_info = calloc(32, sizeof(char *));
                    247:     if (command_info == NULL)
                    248:        return NULL;
                    249:     if ((command_info[i++] = fmt_string("command", command)) == NULL ||
                    250:        asprintf(&command_info[i++], "runas_euid=%ld", (long)runas_uid) == -1 ||
                    251:        asprintf(&command_info[i++], "runas_uid=%ld", (long)runas_uid) == -1) {
                    252:        return NULL;
                    253:     }
                    254:     if (runas_gid != -1) {
                    255:        if (asprintf(&command_info[i++], "runas_gid=%ld", (long)runas_gid) == -1 ||
                    256:            asprintf(&command_info[i++], "runas_egid=%ld", (long)runas_gid) == -1) {
                    257:            return NULL;
                    258:        }
                    259:     }
                    260:     if (use_sudoedit) {
                    261:        command_info[i] = strdup("sudoedit=true");
                    262:        if (command_info[i++] == NULL)
                    263:                return NULL;
                    264:     }
                    265: #ifdef USE_TIMEOUT
                    266:     command_info[i++] = "timeout=30";
                    267: #endif
                    268:     return command_info;
                    269: }
                    270: 
                    271: static char *
                    272: find_editor(int nfiles, char * const files[], char **argv_out[])
                    273: {
                    274:     char *cp, **ep, **nargv, *editor, *editor_path;
                    275:     int ac, i, nargc, wasblank;
                    276: 
                    277:     /* Lookup EDITOR in user's environment. */
                    278:     editor = _PATH_VI;
                    279:     for (ep = plugin_state.envp; *ep != NULL; ep++) {
                    280:        if (strncmp(*ep, "EDITOR=", 7) == 0) {
                    281:            editor = *ep + 7;
                    282:            break;
                    283:        }
                    284:     }
                    285:     editor = strdup(editor);
                    286:     if (editor == NULL) {
                    287:        sudo_log(SUDO_CONV_ERROR_MSG, "unable to allocate memory\n");
                    288:        return NULL;
                    289:     }
                    290: 
                    291:     /*
                    292:      * Split editor into an argument vector; editor is reused (do not free).
                    293:      * The EDITOR environment variables may contain command
                    294:      * line args so look for those and alloc space for them too.
                    295:      */
                    296:     nargc = 1;
                    297:     for (wasblank = 0, cp = editor; *cp != '\0'; cp++) {
                    298:        if (isblank((unsigned char) *cp))
                    299:            wasblank = 1;
                    300:        else if (wasblank) {
                    301:            wasblank = 0;
                    302:            nargc++;
                    303:        }
                    304:     }
                    305:     /* If we can't find the editor in the user's PATH, give up. */
                    306:     cp = strtok(editor, " \t");
                    307:     if (cp == NULL ||
                    308:        (editor_path = find_in_path(editor, plugin_state.envp)) == NULL) {
                    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:  */
                    506: struct io_plugin sample_io = {
                    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>