From: Todd C. Miller Date: Sat, 20 Feb 2010 14:42:16 +0000 (-0500) Subject: Sample plugin demonstrating the sudo plugin API X-Git-Tag: SUDO_1_8_0~890 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=3b354e36f33226d5f62c503578a5e010d9ca709e;p=sudo Sample plugin demonstrating the sudo plugin API --- diff --git a/plugins/sample/Makefile.in b/plugins/sample/Makefile.in new file mode 100644 index 000000000..c34db932d --- /dev/null +++ b/plugins/sample/Makefile.in @@ -0,0 +1,115 @@ +# +# Copyright (c) 2010 Todd C. Miller +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# @configure_input@ +# + +#### Start of system configuration section. #### + +srcdir = @srcdir@ +devdir = @devdir@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +incdir = $(top_srcdir)/include + +# Compiler & tools to use +CC = @CC@ +LIBTOOL = @LIBTOOL@ + +# Our install program supports extra flags... +INSTALL = $(SHELL) $(top_srcdir)/install-sh -c + +# Libraries +LIBS = + +# C preprocessor flags +CPPFLAGS = -I$(incdir) -I$(top_builddir) @CPPFLAGS@ + +# Usually -O and/or -g +CFLAGS = @CFLAGS@ + +# Flags to pass to the link stage +LDFLAGS = + +# Where to install things... +prefix = @prefix@ +exec_prefix = @exec_prefix@ +bindir = @bindir@ +sbindir = @sbindir@ +sysconfdir = @sysconfdir@ +libexecdir = @libexecdir@ +datarootdir = @datarootdir@ +plugindir = @PLUGINDIR@ + +# Pass in paths and OS dependent defines +DEFS = @OSDEFS@ + +#### End of system configuration section. #### + +SHELL = /bin/sh + +OBJS = sample_plugin.lo + +# XXX - need to support linking in compat bits +LIB_OBJS = @LIBOBJS@ + +VERSION = @PACKAGE_VERSION@ + +all: sample_plugin.la + +.SUFFIXES: .o .c .h .lo + +.c.o: + $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $< + +.c.lo: + $(LIBTOOL) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $< + +sample_plugin.lo: $(srcdir)/sample_plugin.c + $(LIBTOOL) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(OPTIONS) $(srcdir)/sample_plugin.c + +sample_plugin.la: sample_plugin.lo + $(LIBTOOL) --mode=link $(CC) $(LDFLAGS) -o $@ sample_plugin.lo -module -avoid-version -rpath $(plugindir) + +install: install-dirs install-plugin + +install-dirs: + $(SHELL) $(top_srcdir)/mkinstalldirs $(DESTDIR)$(plugindir) + +install-binaries: + +install-man: + +install-plugin: install-dirs sample_plugin.la + $(LIBTOOL) --mode=install $(INSTALL) sample_plugin.la $(plugindir) + +check: + @echo nothing to check + +clean: + -rm -f *.a *.o *.lo *.la stamp-* core *.core core.* + +mostlyclean: clean + +distclean: clean + -rm -rf Makefile sample_plugin.lo .libs + +clobber: distclean + +realclean: distclean + rm -f TAGS tags + +cleandir: realclean diff --git a/plugins/sample/sample_plugin.c b/plugins/sample/sample_plugin.c new file mode 100644 index 000000000..c1d0d5d92 --- /dev/null +++ b/plugins/sample/sample_plugin.c @@ -0,0 +1,301 @@ +/* + * Copyright (c) 2010 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include +#include + +#include +#ifdef STDC_HEADERS +# include +# include +#else +# ifdef HAVE_STDLIB_H +# include +# endif +#endif /* STDC_HEADERS */ +#ifdef HAVE_STRING_H +# if defined(HAVE_MEMORY_H) && !defined(STDC_HEADERS) +# include +# endif +# include +#else +# ifdef HAVE_STRINGS_H +# include +# endif +#endif /* HAVE_STRING_H */ +#include + +#include + +/* + * Sample plugin module that allows any user who knows the password + * ("test") to run any command as root. Since there is no credential + * caching the validate and invalidate functions are NULL. + */ + +static struct plugin_state { + char **envp; + char * const *settings; + char * const *user_info; +} plugin_state; +static sudo_conv_t sudo_conv; + +#undef TRUE +#define TRUE 1 +#undef FALSE +#define FALSE 0 +#undef ERROR +#define ERROR -1 + +/* + * Allocate storage for a name=value string and return it. + */ +static char * +fmt_string(const char *var, const char *val) +{ + size_t var_len = strlen(var); + size_t val_len = strlen(val); + char *cp, *str; + + cp = str = malloc(var_len + 1 + val_len + 1); + if (!str) + return NULL; + memcpy(cp, var, var_len); + cp += var_len; + *cp++ = '='; + memcpy(cp, val, val_len); + cp += val_len; + *cp = '\0'; + + return(str); +} + +/* + * Display warning via conversation function. + */ +static void +sudo_log(int type, const char *fmt, ...) +{ + struct sudo_conv_message msg; + struct sudo_conv_reply repl; + va_list ap; + char *str; + int rc; + + va_start(ap, fmt); + rc = vasprintf(&str, fmt, ap); + va_end(ap); + if (rc == -1) + return; + + /* Call conversation function */ + memset(&msg, 0, sizeof(msg)); + msg.msg_type = type; + msg.msg = str; + memset(&repl, 0, sizeof(repl)); + sudo_conv(1, &msg, &repl); +} + +/* + * Plugin policy open function. + */ +static int +policy_open(unsigned int version, sudo_conv_t conversation, + char * const settings[], char * const user_info[], + char * const user_env[]) +{ + char * const *ui; + const char *runas_user = NULL; + + sudo_conv = conversation; + + if (SUDO_API_VERSION_GET_MAJOR(version) != SUDO_API_VERSION_MAJOR) { + sudo_log(SUDO_CONV_ERROR_MSG, + "the sample plugin requires API version %d.x", + SUDO_API_VERSION_MAJOR); + return ERROR; + } + + /* Only allow commands to be run as root. */ + for (ui = user_info; *ui != NULL; ui++) { + if (strncmp(*ui, "runas_user=", sizeof("runas_user=") - 1) == 0) { + runas_user = *ui + sizeof("runas_user=") - 1; + } + } + if (runas_user && strcmp(runas_user, "root") != 0) { + sudo_log(SUDO_CONV_ERROR_MSG, "commands may only be run as root."); + return 0; + } + + /* Plugin state. */ + plugin_state.envp = (char **)user_env; + plugin_state.settings = settings; + plugin_state.user_info = user_info; + + return 1; +} + +/* + * Plugin policy check function. + * Simple example that prompts for a password, hard-coded to "test". + */ +static int +policy_check(int argc, char * const argv[], + char *env_add[], char **command_info_out[], + char **argv_out[], char **user_env_out[]) +{ + struct sudo_conv_message msg; + struct sudo_conv_reply repl; + char **command_info; + int i = 0; + + if (!argc || argv[0] == NULL) { + sudo_log(SUDO_CONV_ERROR_MSG, "no command specified"); + return FALSE; + } + /* Only allow fully qualified paths to keep things simple. */ + if (argv[0][0] != '/') { + sudo_log(SUDO_CONV_ERROR_MSG, + "only fully qualified pathnames may be specified"); + return FALSE; + } + + /* Prompt user for password via conversation function. */ + memset(&msg, 0, sizeof(msg)); + msg.msg_type = SUDO_CONV_PROMPT_ECHO_OFF; + msg.msg = "Password: "; + memset(&repl, 0, sizeof(repl)); + sudo_conv(1, &msg, &repl); + if (strcmp(repl.reply, "test") != 0) { + sudo_log(SUDO_CONV_ERROR_MSG, "incorrect password"); + return FALSE; + } + + /* No changes to argv or envp */ + *argv_out = (char **)argv; + *user_env_out = plugin_state.envp; + + /* Setup command info. */ + command_info = calloc(32, sizeof(char *)); + if (command_info == NULL) { + sudo_log(SUDO_CONV_ERROR_MSG, "out of memory"); + return ERROR; + } + command_info[i++] = fmt_string("command", argv[0]); + command_info[i++] = "runas_uid=0"; + command_info[i++] = "runas_euid=0"; + *command_info_out = command_info; + + return TRUE; +} + +static int +policy_list(int argc, char * const argv[], int verbose, const char *list_user) +{ + /* + * List user's capabilities. + */ + sudo_log(SUDO_CONV_INFO_MSG, "Validated users may run any command"); + return TRUE; +} + +static int +policy_version(int verbose) +{ + sudo_log(SUDO_CONV_INFO_MSG, "Sample policy plugin version %s", PACKAGE_VERSION); + return TRUE; +} + +static void +policy_close(int exit_status, int error) +{ + /* + * The policy might log the command exit status here. + * In this example, we just print a message. + */ + if (error) { + sudo_log(SUDO_CONV_ERROR_MSG, "Command error: %s", strerror(error)); + } else { + if (WIFEXITED(exit_status)) { + sudo_log(SUDO_CONV_INFO_MSG, "Command exited with status %d", + WEXITSTATUS(exit_status)); + } else if (WIFSIGNALED(exit_status)) { + sudo_log(SUDO_CONV_INFO_MSG, "Command killed by signal %d", + WTERMSIG(exit_status)); + } + } +} + +static int +io_open(unsigned int version, sudo_conv_t conversation, + char * const settings[], char * const user_info[], + char * const user_env[]) +{ + /* TODO: something here */ + return TRUE; +} + +static void +io_close(int exit_status, int error) +{ + /* TODO: something here */ +} + +static int +io_version(int verbose) +{ + sudo_log(SUDO_CONV_INFO_MSG, "Sample I/O plugin version %s", PACKAGE_VERSION); + return TRUE; +} + +static int +io_log_input(const char *buf, unsigned int len) +{ + /* log nothing for now */ + return TRUE; +} + +static int +io_log_output(const char *buf, unsigned int len) +{ + /* log nothing for now */ + return TRUE; +} + +struct policy_plugin sample_policy = { + SUDO_POLICY_PLUGIN, + SUDO_API_VERSION, + policy_open, + policy_close, + policy_version, + policy_check, + policy_list, + NULL, /* validate */ + NULL /* invalidate */ +}; + +struct io_plugin sample_io = { + SUDO_IO_PLUGIN, + SUDO_API_VERSION, + io_open, + io_close, + io_version, + io_log_input, + io_log_output +};