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

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 **
                    240: build_command_info(char *command)
                    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:     }
                    311:     nargv = (char **) malloc((nargc + 1 + nfiles + 1) * sizeof(char *));
                    312:     if (nargv == NULL) {
                    313:        sudo_log(SUDO_CONV_ERROR_MSG, "unable to allocate memory\n");
                    314:        return NULL;
                    315:     }
                    316:     for (ac = 0; cp != NULL && ac < nargc; ac++) {
                    317:        nargv[ac] = cp;
                    318:        cp = strtok(NULL, " \t");
                    319:     }
                    320:     nargv[ac++] = "--";
                    321:     for (i = 0; i < nfiles; )
                    322:        nargv[ac++] = files[i++];
                    323:     nargv[ac] = NULL;
                    324: 
                    325:     *argv_out = nargv;
                    326:     return editor_path;
                    327: }
                    328: 
                    329: /*
                    330:  * Plugin policy check function.
                    331:  * Simple example that prompts for a password, hard-coded to "test".
                    332:  */
                    333: static int 
                    334: policy_check(int argc, char * const argv[],
                    335:     char *env_add[], char **command_info_out[],
                    336:     char **argv_out[], char **user_env_out[])
                    337: {
                    338:     char *command;
                    339: 
                    340:     if (!argc || argv[0] == NULL) {
                    341:        sudo_log(SUDO_CONV_ERROR_MSG, "no command specified\n");
1.1.1.2 ! misho     342:        return false;
1.1       misho     343:     }
                    344: 
                    345:     if (!check_passwd())
1.1.1.2 ! misho     346:        return false;
1.1       misho     347: 
                    348:     command = find_in_path(argv[0], plugin_state.envp);
                    349:     if (command == NULL) {
                    350:        sudo_log(SUDO_CONV_ERROR_MSG, "%s: command not found\n", argv[0]);
1.1.1.2 ! misho     351:        return false;
1.1       misho     352:     }
                    353: 
                    354:     /* If "sudo vi" is run, auto-convert to sudoedit.  */
                    355:     if (strcmp(command, _PATH_VI) == 0)
1.1.1.2 ! misho     356:        use_sudoedit = true;
1.1       misho     357: 
                    358:     if (use_sudoedit) {
                    359:        /* Rebuild argv using editor */
                    360:        command = find_editor(argc - 1, argv + 1, argv_out);
                    361:        if (command == NULL) {
                    362:            sudo_log(SUDO_CONV_ERROR_MSG, "unable to find valid editor\n");
1.1.1.2 ! misho     363:            return -1;
1.1       misho     364:        }
1.1.1.2 ! misho     365:        use_sudoedit = true;
1.1       misho     366:     } else {
                    367:        /* No changes needd to argv */
                    368:        *argv_out = (char **)argv;
                    369:     }
                    370: 
                    371:     /* No changes to envp */
                    372:     *user_env_out = plugin_state.envp;
                    373: 
                    374:     /* Setup command info. */
                    375:     *command_info_out = build_command_info(command);
                    376:     if (*command_info_out == NULL) {
                    377:        sudo_log(SUDO_CONV_ERROR_MSG, "out of memory\n");
1.1.1.2 ! misho     378:        return -1;
1.1       misho     379:     }
                    380: 
1.1.1.2 ! misho     381:     return true;
1.1       misho     382: }
                    383: 
                    384: static int
                    385: policy_list(int argc, char * const argv[], int verbose, const char *list_user)
                    386: {
                    387:     /*
                    388:      * List user's capabilities.
                    389:      */
                    390:     sudo_log(SUDO_CONV_INFO_MSG, "Validated users may run any command\n");
1.1.1.2 ! misho     391:     return true;
1.1       misho     392: }
                    393: 
                    394: static int
                    395: policy_version(int verbose)
                    396: {
                    397:     sudo_log(SUDO_CONV_INFO_MSG, "Sample policy plugin version %s\n", PACKAGE_VERSION);
1.1.1.2 ! misho     398:     return true;
1.1       misho     399: }
                    400: 
                    401: static void
                    402: policy_close(int exit_status, int error)
                    403: {
                    404:     /*
                    405:      * The policy might log the command exit status here.
                    406:      * In this example, we just print a message.
                    407:      */
                    408:     if (error) {
                    409:        sudo_log(SUDO_CONV_ERROR_MSG, "Command error: %s\n", strerror(error));
                    410:     } else {
                    411:         if (WIFEXITED(exit_status)) {
                    412:            sudo_log(SUDO_CONV_INFO_MSG, "Command exited with status %d\n",
                    413:                WEXITSTATUS(exit_status));
                    414:         } else if (WIFSIGNALED(exit_status)) {
                    415:            sudo_log(SUDO_CONV_INFO_MSG, "Command killed by signal %d\n",
                    416:                WTERMSIG(exit_status));
                    417:        }
                    418:     }
                    419: }
                    420: 
                    421: static int
                    422: io_open(unsigned int version, sudo_conv_t conversation,
                    423:     sudo_printf_t sudo_printf, char * const settings[],
                    424:     char * const user_info[], char * const command_info[],
1.1.1.2 ! misho     425:     int argc, char * const argv[], char * const user_env[], char * const args[])
1.1       misho     426: {
                    427:     int fd;
                    428:     char path[PATH_MAX];
                    429: 
                    430:     if (!sudo_conv)
                    431:        sudo_conv = conversation;
                    432:     if (!sudo_log)
                    433:        sudo_log = sudo_printf;
                    434: 
                    435:     /* Open input and output files. */
                    436:     snprintf(path, sizeof(path), "/var/tmp/sample-%u.output",
                    437:        (unsigned int)getpid());
                    438:     fd = open(path, O_WRONLY|O_CREAT|O_EXCL, 0644);
                    439:     if (fd == -1)
1.1.1.2 ! misho     440:        return false;
1.1       misho     441:     output = fdopen(fd, "w");
                    442: 
                    443:     snprintf(path, sizeof(path), "/var/tmp/sample-%u.input",
                    444:        (unsigned int)getpid());
                    445:     fd = open(path, O_WRONLY|O_CREAT|O_EXCL, 0644);
                    446:     if (fd == -1)
1.1.1.2 ! misho     447:        return false;
1.1       misho     448:     input = fdopen(fd, "w");
                    449: 
1.1.1.2 ! misho     450:     return true;
1.1       misho     451: }
                    452: 
                    453: static void
                    454: io_close(int exit_status, int error)
                    455: {
                    456:     fclose(input);
                    457:     fclose(output);
                    458: }
                    459: 
                    460: static int
                    461: io_version(int verbose)
                    462: {
                    463:     sudo_log(SUDO_CONV_INFO_MSG, "Sample I/O plugin version %s\n",
                    464:        PACKAGE_VERSION);
1.1.1.2 ! misho     465:     return true;
1.1       misho     466: }
                    467: 
                    468: static int
                    469: io_log_input(const char *buf, unsigned int len)
                    470: {
1.1.1.2 ! misho     471:     ignore_result(fwrite(buf, len, 1, input));
        !           472:     return true;
1.1       misho     473: }
                    474: 
                    475: static int
                    476: io_log_output(const char *buf, unsigned int len)
                    477: {
1.1.1.2 ! misho     478:     ignore_result(fwrite(buf, len, 1, output));
        !           479:     return true;
1.1       misho     480: }
                    481: 
                    482: struct policy_plugin sample_policy = {
                    483:     SUDO_POLICY_PLUGIN,
                    484:     SUDO_API_VERSION,
                    485:     policy_open,
                    486:     policy_close,
                    487:     policy_version,
                    488:     policy_check,
                    489:     policy_list,
                    490:     NULL, /* validate */
1.1.1.2 ! misho     491:     NULL, /* invalidate */
        !           492:     NULL, /* init_session */
        !           493:     NULL, /* register_hooks */
        !           494:     NULL /* deregister_hooks */
1.1       misho     495: };
                    496: 
                    497: /*
                    498:  * Note: This plugin does not differentiate between tty and pipe I/O.
                    499:  *       It all gets logged to the same file.
                    500:  */
                    501: struct io_plugin sample_io = {
                    502:     SUDO_IO_PLUGIN,
                    503:     SUDO_API_VERSION,
                    504:     io_open,
                    505:     io_close,
                    506:     io_version,
                    507:     io_log_input,      /* tty input */
                    508:     io_log_output,     /* tty output */
                    509:     io_log_input,      /* command stdin if not tty */
                    510:     io_log_output,     /* command stdout if not tty */
                    511:     io_log_output      /* command stderr if not tty */
                    512: };

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