File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / sudo / plugins / sample / sample_plugin.c
Revision 1.1: download - view: text, annotated - select for diffs - revision graph
Tue Feb 21 16:23:02 2012 UTC (12 years, 5 months ago) by misho
CVS tags: MAIN, HEAD
Initial revision

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

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