File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / sudo / plugins / sample / sample_plugin.c
Revision 1.1.1.4 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Mon Jul 22 10:46:12 2013 UTC (11 years ago) by misho
Branches: sudo, MAIN
CVS tags: v1_8_8p0, v1_8_8, v1_8_7p0, v1_8_7, HEAD
1.8.7

    1: /*
    2:  * Copyright (c) 2010-2013 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/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 */
   32: #ifdef HAVE_STDBOOL_H
   33: # include <stdbool.h>
   34: #else
   35: # include "compat/stdbool.h"
   36: #endif /* HAVE_STDBOOL_H */
   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;
   82: static int use_sudoedit = false;
   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[],
  113:     char * const user_info[], char * const user_env[], char * const args[])
  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);
  130: 	return -1;
  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)
  149: 		use_sudoedit = true;
  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");
  229: 	return false;
  230:     }
  231:     if (strcmp(repl.reply, "test") != 0) {
  232: 	sudo_log(SUDO_CONV_ERROR_MSG, "incorrect password\n");
  233: 	return false;
  234:     }
  235:     return true;
  236: }
  237: 
  238: static char **
  239: build_command_info(const char *command)
  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) {
  308: 	free(editor);
  309: 	return NULL;
  310:     }
  311:     if (editor_path != editor)
  312: 	free(editor);
  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: 	free(editor_path);
  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");
  345: 	return false;
  346:     }
  347: 
  348:     if (!check_passwd())
  349: 	return false;
  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]);
  354: 	return false;
  355:     }
  356: 
  357:     /* If "sudo vi" is run, auto-convert to sudoedit.  */
  358:     if (strcmp(command, _PATH_VI) == 0)
  359: 	use_sudoedit = true;
  360: 
  361:     if (use_sudoedit) {
  362: 	/* Rebuild argv using editor */
  363: 	free(command);
  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");
  367: 	    return -1;
  368: 	}
  369: 	use_sudoedit = true;
  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);
  380:     free(command);
  381:     if (*command_info_out == NULL) {
  382: 	sudo_log(SUDO_CONV_ERROR_MSG, "out of memory\n");
  383: 	return -1;
  384:     }
  385: 
  386:     return true;
  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");
  396:     return true;
  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);
  403:     return true;
  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[],
  430:     int argc, char * const argv[], char * const user_env[], char * const args[])
  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)
  445: 	return false;
  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)
  452: 	return false;
  453:     input = fdopen(fd, "w");
  454: 
  455:     return true;
  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);
  470:     return true;
  471: }
  472: 
  473: static int
  474: io_log_input(const char *buf, unsigned int len)
  475: {
  476:     ignore_result(fwrite(buf, len, 1, input));
  477:     return true;
  478: }
  479: 
  480: static int
  481: io_log_output(const char *buf, unsigned int len)
  482: {
  483:     ignore_result(fwrite(buf, len, 1, output));
  484:     return true;
  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 */
  496:     NULL, /* invalidate */
  497:     NULL, /* init_session */
  498:     NULL, /* register_hooks */
  499:     NULL /* deregister_hooks */
  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: __dso_public 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>