From db17cadaf65c7fc85d54c9f1a126136f6a9297b0 Mon Sep 17 00:00:00 2001 From: "Todd C. Miller" Date: Thu, 6 Feb 2020 12:49:11 -0700 Subject: [PATCH] Add an approval plugin type that runs after the policy plugin. The basic idea is that the approval plugin adds an additional layer of policy. There can be multiple approval plugins. --- doc/sudo_plugin.man.in | 349 ++++++++++++++++++++++++++++++++++++++-- doc/sudo_plugin.mdoc.in | 322 ++++++++++++++++++++++++++++++++++-- include/sudo_plugin.h | 16 ++ src/load_plugins.c | 30 +++- src/sudo.c | 124 +++++++++++++- src/sudo_plugin_int.h | 7 +- 6 files changed, 800 insertions(+), 48 deletions(-) diff --git a/doc/sudo_plugin.man.in b/doc/sudo_plugin.man.in index c03479e0c..155e7eadc 100644 --- a/doc/sudo_plugin.man.in +++ b/doc/sudo_plugin.man.in @@ -1751,7 +1751,7 @@ If an output logging function rejects the data by returning 0, the command will be terminated and the data will not be written to the terminal, though it will still be sent to any other I/O logging plugins. .PP -The io_plugin struct has the following fields: +The audit_plugin struct has the following fields: .TP 6n type The @@ -3103,6 +3103,321 @@ See the \fIPolicy plugin API\fR section for a description of \fRderegister_hooks\fR. +.SS "Approval plugin API" +.nf +.RS 0n +struct approval_plugin { +#define SUDO_APPROVAL_PLUGIN 4 + unsigned int type; /* always SUDO_APPROVAL_PLUGIN */ + unsigned int version; /* always SUDO_API_VERSION */ + int (*check)(unsigned int version, sudo_conv_t conversation, + sudo_printf_t sudo_printf, char * const settings[], + char * const user_info[], int submit_optind, + char * const submit_argv[], char * const submit_envp[], + char * const command_info[], char * const run_argv[], + char * const run_envp[], char * const plugin_options[], + const char **errstr); + int (*show_version)(unsigned int version, sudo_conv_t conversation, + sudo_printf_t sudo_printf, int verbose); +}; +.RE +.fi +.PP +An approval plugin can be used to apply extra constraints after a +command has been accepted by the policy plugin. +Unlike the other plugin types, there are no +\fBopen\fR() +or +\fBclose\fR() +functions; functions in an approval function are stand-alone. +Multiple approval plugins may be specified in +sudo.conf(@mansectform@). +.PP +The approval_plugin struct has the following fields: +.TP 6n +type +The +\fRtype\fR +field should always be set to +\fRSUDO_APPROVAL_PLUGIN\fR. +.TP 6n +version +The +\fRversion\fR +field should be set to +\fRSUDO_API_VERSION\fR. +.sp +This allows +\fBsudo\fR +to determine the API version the plugin was +built against. +.TP 6n +check +.br +.nf +.RS 6n +int (*check)(unsigned int version, sudo_conv_t conversation, + sudo_printf_t sudo_printf, char * const settings[], + char * const user_info[], int submit_optind, + char * const submit_argv[], char * const submit_envp[], + char * const command_info[], char * const run_argv[], + char * const run_envp[], char * const plugin_options[], + const char **errstr); +.RE +.fi +.RS 6n +.sp +The approval +\fBcheck\fR() +function is run after the policy plugin +\fBcheck\fR() +function and before any I/O logging plugins. +If multiple approval plugins are loaded, they must all succeeed for +the command to be allowed. +It returns 1 on success, 0 on failure, \-1 if a general error occurred, +or \-2 if there was a usage error. +In the latter case, +\fBsudo\fR +will print a usage message before it exits. +If an error occurs, the plugin may optionally call the +\fBconversation\fR() +or +\fBplugin_printf\fR() +function with +\fRSUDO_CONF_ERROR_MSG\fR +to present additional error information to the user. +.sp +The function arguments are as follows: +.TP 6n +version +The version passed in by +\fBsudo\fR +allows the plugin to determine the +major and minor version number of the plugin API supported by +\fBsudo\fR. +.TP 6n +conversation +A pointer to the +\fBconversation\fR() +function that can be used by the plugin to interact with the user (see +\fIConversation API\fR +for details). +Returns 0 on success and \-1 on failure. +.TP 6n +plugin_printf +A pointer to a +\fBprintf\fR()-style +function that may be used to display informational or error messages (see +\fIConversation API\fR +for details). +Returns the number of characters printed on success and \-1 on failure. +.TP 6n +settings +A vector of user-supplied +\fBsudo\fR +settings in the form of +\(lqname=value\(rq +strings. +The vector is terminated by a +\fRNULL\fR +pointer. +These settings correspond to options the user specified when running +\fBsudo\fR. +As such, they will only be present when the corresponding option has +been specified on the command line. +.sp +When parsing +\fIsettings\fR, +the plugin should split on the +\fBfirst\fR +equal sign +(\(oq=\(cq) +since the +\fIname\fR +field will never include one +itself but the +\fIvalue\fR +might. +.sp +See the +\fIPolicy plugin API\fR +section for a list of all possible settings. +.TP 6n +user_info +A vector of information about the user running the command in the form of +\(lqname=value\(rq +strings. +The vector is terminated by a +\fRNULL\fR +pointer. +.sp +When parsing +\fIuser_info\fR, +the plugin should split on the +\fBfirst\fR +equal sign +(\(oq=\(cq) +since the +\fIname\fR +field will never include one +itself but the +\fIvalue\fR +might. +.sp +See the +\fIPolicy plugin API\fR +section for a list of all possible strings. +.TP 6n +submit_optind +The index into +\fIsubmit_argv\fR +that corresponds to the first entry that is not a command line option. +If +\fIsubmit_argv\fR +only consists of options, which may be the case with the +\fB\-l\fR +or +\fB\-v\fR +options, +\fRsubmit_argv[submit_optind]\fR +will evaluate to the NULL pointer. +.TP 6n +submit_argv +The argument vector +\fBsudo\fR +was invoked with, including all command line options. +The +\fIsubmit_optind\fR +argument can be used to determine the end of the command line options. +.TP 6n +submit_envp +The invoking user's environment in the form of a +\fRNULL\fR-terminated +vector of +\(lqname=value\(rq +strings. +.sp +When parsing +\fIsubmit_envp\fR, +the plugin should split on the +\fBfirst\fR +equal sign +(\(oq=\(cq) +since the +\fIname\fR +field will never include one +itself but the +\fIvalue\fR +might. +.TP 6n +command_info +A vector of information describing the command being run in the form of +\(lqname=value\(rq +strings. +The vector is terminated by a +\fRNULL\fR +pointer. +.sp +When parsing +\fIcommand_info\fR, +the plugin should split on the +\fBfirst\fR +equal sign +(\(oq=\(cq) +since the +\fIname\fR +field will never include one +itself but the +\fIvalue\fR +might. +.sp +See the +\fIPolicy plugin API\fR +section for a list of all possible strings. +.TP 6n +run_argv +A +\fRNULL\fR-terminated +argument vector describing a command that will be run in the +same form as what would be passed to the +execve(2) +system call. +.TP 6n +run_envp +The environment the command will be run with in the form of a +\fRNULL\fR-terminated +vector of +\(lqname=value\(rq +strings. +.sp +When parsing +\fIrun_envp\fR, +the plugin should split on the +\fBfirst\fR +equal sign +(\(oq=\(cq) +since the +\fIname\fR +field will never include one +itself but the +\fIvalue\fR +might. +.TP 6n +plugin_options +Any (non-comment) strings immediately after the plugin path are +treated as arguments to the plugin. +These arguments are split on a white space boundary and are passed to +the plugin in the form of a +\fRNULL\fR-terminated +array of strings. +If no arguments were specified, +\fIplugin_options\fR +will be the +\fRNULL\fR +pointer. +.TP 6n +errstr +If the +\fBopen\fR() +function returns a value other than 1, the plugin may +store a message describing the failure or error in +\fIerrstr\fR. +The +\fBsudo\fR +front end will then pass this value to any registered audit plugins. +.PD 0 +.PP +.RE +.PD +.TP 6n +show_version +.nf +.RS 6n +int (*show_version)(int verbose); +.RE +.fi +.RS 6n +.sp +The +\fBshow_version\fR() +function is called by +\fBsudo\fR +when the user specifies +the +\fB\-V\fR +option. +The plugin may display its version information to the user via the +\fBconversation\fR() +or +\fBplugin_printf\fR() +function using +\fRSUDO_CONV_INFO_MSG\fR. +If the user requests detailed version information, the verbose flag will be set. +.sp +Returns 1 on success, 0 on failure, \-1 if a general error occurred, +or \-2 if there was a usage error, although the return value is currently +ignored. +.RE .SS "Signal handlers" The \fBsudo\fR @@ -3403,8 +3718,7 @@ function. Events are described by the following structure: .nf .RS 0n -typedef void (*sudo_plugin_ev_callback_t)(int fd, int what, - void *closure); +typedef void (*sudo_plugin_ev_callback_t)(int fd, int what, void *closure); struct sudo_plugin_event { int (*set)(struct sudo_plugin_event *pev, int fd, int events, @@ -3590,7 +3904,8 @@ function returns 1 on success, and \-1 if a error occurred. \fBpending\fR() .nf .RS 6n -int (*pending)(struct sudo_plugin_event *pev, int events, struct timespec *ts); +int (*pending)(struct sudo_plugin_event *pev, int events, + struct timespec *ts); .RE .fi .RS 6n @@ -3798,9 +4113,8 @@ function: .sp .RS 0n typedef int (*sudo_conv_t)(int num_msgs, - const struct sudo_conv_message msgs[], - struct sudo_conv_reply replies[], - struct sudo_conv_callback *callback); + const struct sudo_conv_message msgs[], + struct sudo_conv_reply replies[], struct sudo_conv_callback *callback); typedef int (*sudo_printf_t)(int msg_type, const char *fmt, ...); .RE @@ -3984,12 +4298,12 @@ initialization, cleanup and group lookup. .sp .RS 0n struct sudoers_group_plugin { - unsigned int version; - int (*init)(int version, sudo_printf_t sudo_printf, - char *const argv[]); - void (*cleanup)(void); - int (*query)(const char *user, const char *group, - const struct passwd *pwd); + unsigned int version; + int (*init)(int version, sudo_printf_t sudo_printf, + char *const argv[]); + void (*cleanup)(void); + int (*query)(const char *user, const char *group, + const struct passwd *pwd); }; .RE .fi @@ -4012,7 +4326,7 @@ init .nf .RS 6n int (*init)(int version, sudo_printf_t plugin_printf, - char *const argv[]); + char *const argv[]); .RE .fi .RS 6n @@ -4029,8 +4343,7 @@ If an error occurs, the plugin may call the \fBplugin_printf\fR() function with \fRSUDO_CONF_ERROR_MSG\fR -to present additional error information -to the user. +to present additional error information to the user. .sp The function arguments are as follows: .TP 6n @@ -4085,7 +4398,7 @@ query .nf .RS 6n int (*query)(const char *user, const char *group, - const struct passwd *pwd); + const struct passwd *pwd); .RE .fi .RS 6n @@ -4340,6 +4653,8 @@ command was not run. .sp \fRSUDO_CONV_REPL_MAX\fR has increased from 255 to 1023 bytes. +.sp +Support for audit and approval plugins was added. .SH "SEE ALSO" sudo.conf(@mansectform@), sudoers(@mansectform@), diff --git a/doc/sudo_plugin.mdoc.in b/doc/sudo_plugin.mdoc.in index fb81d4a7f..14e778cba 100644 --- a/doc/sudo_plugin.mdoc.in +++ b/doc/sudo_plugin.mdoc.in @@ -1565,7 +1565,7 @@ If an output logging function rejects the data by returning 0, the command will be terminated and the data will not be written to the terminal, though it will still be sent to any other I/O logging plugins. .Pp -The io_plugin struct has the following fields: +The audit_plugin struct has the following fields: .Bl -tag -width 4n .It type The @@ -2748,6 +2748,294 @@ See the section for a description of .Li deregister_hooks . .El +.Ss Approval plugin API +.Bd -literal +struct approval_plugin { +#define SUDO_APPROVAL_PLUGIN 4 + unsigned int type; /* always SUDO_APPROVAL_PLUGIN */ + unsigned int version; /* always SUDO_API_VERSION */ + int (*check)(unsigned int version, sudo_conv_t conversation, + sudo_printf_t sudo_printf, char * const settings[], + char * const user_info[], int submit_optind, + char * const submit_argv[], char * const submit_envp[], + char * const command_info[], char * const run_argv[], + char * const run_envp[], char * const plugin_options[], + const char **errstr); + int (*show_version)(unsigned int version, sudo_conv_t conversation, + sudo_printf_t sudo_printf, int verbose); +}; +.Ed +.Pp +An approval plugin can be used to apply extra constraints after a +command has been accepted by the policy plugin. +Unlike the other plugin types, there are no +.Fn open +or +.Fn close +functions; functions in an approval function are stand-alone. +Multiple approval plugins may be specified in +.Xr sudo.conf @mansectform@ . +.Pp +The approval_plugin struct has the following fields: +.Bl -tag -width 4n +.It type +The +.Li type +field should always be set to +.Dv SUDO_APPROVAL_PLUGIN . +.It version +The +.Li version +field should be set to +.Dv SUDO_API_VERSION . +.Pp +This allows +.Nm sudo +to determine the API version the plugin was +built against. +.It check +.Bd -literal -compact +int (*check)(unsigned int version, sudo_conv_t conversation, + sudo_printf_t sudo_printf, char * const settings[], + char * const user_info[], int submit_optind, + char * const submit_argv[], char * const submit_envp[], + char * const command_info[], char * const run_argv[], + char * const run_envp[], char * const plugin_options[], + const char **errstr); +.Ed +.Pp +The approval +.Fn check +function is run after the policy plugin +.Fn check +function and before any I/O logging plugins. +If multiple approval plugins are loaded, they must all succeeed for +the command to be allowed. +It returns 1 on success, 0 on failure, \-1 if a general error occurred, +or \-2 if there was a usage error. +In the latter case, +.Nm sudo +will print a usage message before it exits. +If an error occurs, the plugin may optionally call the +.Fn conversation +or +.Fn plugin_printf +function with +.Dv SUDO_CONF_ERROR_MSG +to present additional error information to the user. +.Pp +The function arguments are as follows: +.Bl -tag -width 4n +.It version +The version passed in by +.Nm sudo +allows the plugin to determine the +major and minor version number of the plugin API supported by +.Nm sudo . +.It conversation +A pointer to the +.Fn conversation +function that can be used by the plugin to interact with the user (see +.Sx Conversation API +for details). +Returns 0 on success and \-1 on failure. +.It plugin_printf +A pointer to a +.Fn printf Ns -style +function that may be used to display informational or error messages (see +.Sx Conversation API +for details). +Returns the number of characters printed on success and \-1 on failure. +.It settings +A vector of user-supplied +.Nm sudo +settings in the form of +.Dq name=value +strings. +The vector is terminated by a +.Dv NULL +pointer. +These settings correspond to options the user specified when running +.Nm sudo . +As such, they will only be present when the corresponding option has +been specified on the command line. +.Pp +When parsing +.Em settings , +the plugin should split on the +.Sy first +equal sign +.Pq Ql = +since the +.Em name +field will never include one +itself but the +.Em value +might. +.Pp +See the +.Sx Policy plugin API +section for a list of all possible settings. +.It user_info +A vector of information about the user running the command in the form of +.Dq name=value +strings. +The vector is terminated by a +.Dv NULL +pointer. +.Pp +When parsing +.Em user_info , +the plugin should split on the +.Sy first +equal sign +.Pq Ql = +since the +.Em name +field will never include one +itself but the +.Em value +might. +.Pp +See the +.Sx Policy plugin API +section for a list of all possible strings. +.It submit_optind +The index into +.Fa submit_argv +that corresponds to the first entry that is not a command line option. +If +.Fa submit_argv +only consists of options, which may be the case with the +.Fl l +or +.Fl v +options, +.Li submit_argv[submit_optind] +will evaluate to the NULL pointer. +.It submit_argv +The argument vector +.Nm sudo +was invoked with, including all command line options. +The +.Fa submit_optind +argument can be used to determine the end of the command line options. +.It submit_envp +The invoking user's environment in the form of a +.Dv NULL Ns -terminated +vector of +.Dq name=value +strings. +.Pp +When parsing +.Em submit_envp , +the plugin should split on the +.Sy first +equal sign +.Pq Ql = +since the +.Em name +field will never include one +itself but the +.Em value +might. +.It command_info +A vector of information describing the command being run in the form of +.Dq name=value +strings. +The vector is terminated by a +.Dv NULL +pointer. +.Pp +When parsing +.Em command_info , +the plugin should split on the +.Sy first +equal sign +.Pq Ql = +since the +.Em name +field will never include one +itself but the +.Em value +might. +.Pp +See the +.Sx Policy plugin API +section for a list of all possible strings. +.It run_argv +A +.Dv NULL Ns -terminated +argument vector describing a command that will be run in the +same form as what would be passed to the +.Xr execve 2 +system call. +.It run_envp +The environment the command will be run with in the form of a +.Dv NULL Ns -terminated +vector of +.Dq name=value +strings. +.Pp +When parsing +.Em run_envp , +the plugin should split on the +.Sy first +equal sign +.Pq Ql = +since the +.Em name +field will never include one +itself but the +.Em value +might. +.It plugin_options +Any (non-comment) strings immediately after the plugin path are +treated as arguments to the plugin. +These arguments are split on a white space boundary and are passed to +the plugin in the form of a +.Dv NULL Ns -terminated +array of strings. +If no arguments were specified, +.Em plugin_options +will be the +.Dv NULL +pointer. +.It errstr +If the +.Fn open +function returns a value other than 1, the plugin may +store a message describing the failure or error in +.Em errstr . +The +.Nm sudo +front end will then pass this value to any registered audit plugins. +.El +.It show_version +.Bd -literal -compact +int (*show_version)(int verbose); +.Ed +.Pp +The +.Fn show_version +function is called by +.Nm sudo +when the user specifies +the +.Fl V +option. +The plugin may display its version information to the user via the +.Fn conversation +or +.Fn plugin_printf +function using +.Dv SUDO_CONV_INFO_MSG . +If the user requests detailed version information, the verbose flag will be set. +.Pp +Returns 1 on success, 0 on failure, \-1 if a general error occurred, +or \-2 if there was a usage error, although the return value is currently +ignored. +.El .Ss Signal handlers The .Nm sudo @@ -2993,8 +3281,7 @@ function. Events are described by the following structure: .Pp .Bd -literal -compact -typedef void (*sudo_plugin_ev_callback_t)(int fd, int what, - void *closure); +typedef void (*sudo_plugin_ev_callback_t)(int fd, int what, void *closure); struct sudo_plugin_event { int (*set)(struct sudo_plugin_event *pev, int fd, int events, @@ -3141,7 +3428,8 @@ The function returns 1 on success, and \-1 if a error occurred. .It Fn pending .Bd -literal -compact -int (*pending)(struct sudo_plugin_event *pev, int events, struct timespec *ts); +int (*pending)(struct sudo_plugin_event *pev, int events, + struct timespec *ts); .Ed .Pp The @@ -3322,9 +3610,8 @@ The following type definitions can be used in the declaration of the function: .Bd -literal typedef int (*sudo_conv_t)(int num_msgs, - const struct sudo_conv_message msgs[], - struct sudo_conv_reply replies[], - struct sudo_conv_callback *callback); + const struct sudo_conv_message msgs[], + struct sudo_conv_reply replies[], struct sudo_conv_callback *callback); typedef int (*sudo_printf_t)(int msg_type, const char *fmt, ...); .Ed @@ -3502,12 +3789,12 @@ This structure contains pointers to the functions that implement plugin initialization, cleanup and group lookup. .Bd -literal struct sudoers_group_plugin { - unsigned int version; - int (*init)(int version, sudo_printf_t sudo_printf, - char *const argv[]); - void (*cleanup)(void); - int (*query)(const char *user, const char *group, - const struct passwd *pwd); + unsigned int version; + int (*init)(int version, sudo_printf_t sudo_printf, + char *const argv[]); + void (*cleanup)(void); + int (*query)(const char *user, const char *group, + const struct passwd *pwd); }; .Ed .Pp @@ -3527,7 +3814,7 @@ was built against. .It init .Bd -literal -compact int (*init)(int version, sudo_printf_t plugin_printf, - char *const argv[]); + char *const argv[]); .Ed .Pp The @@ -3542,8 +3829,7 @@ If an error occurs, the plugin may call the .Fn plugin_printf function with .Dv SUDO_CONF_ERROR_MSG -to present additional error information -to the user. +to present additional error information to the user. .Pp The function arguments are as follows: .Bl -tag -width 4n @@ -3585,7 +3871,7 @@ The plugin should free any memory it has allocated and close open file handles. .It query .Bd -literal -compact int (*query)(const char *user, const char *group, - const struct passwd *pwd); + const struct passwd *pwd); .Ed .Pp The @@ -3815,6 +4101,8 @@ command was not run. .Pp .Dv SUDO_CONV_REPL_MAX has increased from 255 to 1023 bytes. +.Pp +Support for audit and approval plugins was added. .El .Sh SEE ALSO .Xr sudo.conf @mansectform@ , diff --git a/include/sudo_plugin.h b/include/sudo_plugin.h index d2f21405a..e857be266 100644 --- a/include/sudo_plugin.h +++ b/include/sudo_plugin.h @@ -237,6 +237,22 @@ struct audit_plugin { void (*deregister_hooks)(int version, int (*deregister_hook)(struct sudo_hook *hook)); }; +/* Approval plugin type and defines */ +struct approval_plugin { +#define SUDO_APPROVAL_PLUGIN 4 + unsigned int type; /* always SUDO_APPROVAL_PLUGIN */ + unsigned int version; /* always SUDO_API_VERSION */ + int (*check)(unsigned int version, sudo_conv_t conversation, + sudo_printf_t sudo_printf, char * const settings[], + char * const user_info[], int submit_optind, + char * const submit_argv[], char * const submit_envp[], + char * const command_info[], char * const run_argv[], + char * const run_envp[], char * const plugin_options[], + const char **errstr); + int (*show_version)(unsigned int version, sudo_conv_t conversation, + sudo_printf_t sudo_printf, int verbose); +}; + /* Sudoers group plugin version major/minor */ #define GROUP_API_VERSION_MAJOR 1 #define GROUP_API_VERSION_MINOR 0 diff --git a/src/load_plugins.c b/src/load_plugins.c index 28006ec4d..2724a8527 100644 --- a/src/load_plugins.c +++ b/src/load_plugins.c @@ -243,7 +243,9 @@ cleanup: static bool sudo_load_plugin(struct plugin_container *policy_plugin, struct plugin_container_list *io_plugins, - struct plugin_container_list *audit_plugins, struct plugin_info *info) + struct plugin_container_list *audit_plugins, + struct plugin_container_list *approval_plugins, + struct plugin_info *info) { struct plugin_container *container = NULL; struct generic_plugin *plugin; @@ -330,6 +332,20 @@ sudo_load_plugin(struct plugin_container *policy_plugin, goto done; TAILQ_INSERT_TAIL(audit_plugins, container, entries); break; + case SUDO_APPROVAL_PLUGIN: + if (plugin_exists(approval_plugins, info)) { + plugin = sudo_plugin_try_to_clone(handle, info->symbol_name); + if (plugin == NULL) { + sudo_warnx(U_("ignoring duplicate approval plugin \"%s\" in %s, line %d"), + info->symbol_name, _PATH_SUDO_CONF, info->lineno); + ret = true; + goto done; + } + } + if ((container = new_container(handle, path, plugin, info)) == NULL) + goto done; + TAILQ_INSERT_TAIL(approval_plugins, container, entries); + break; default: sudo_warnx(U_("error in %s, line %d while loading plugin \"%s\""), _PATH_SUDO_CONF, info->lineno, info->symbol_name); @@ -370,7 +386,8 @@ free_plugin_info(struct plugin_info *info) bool sudo_load_plugins(struct plugin_container *policy_plugin, struct plugin_container_list *io_plugins, - struct plugin_container_list *audit_plugins) + struct plugin_container_list *audit_plugins, + struct plugin_container_list *approval_plugins) { struct plugin_container *container; struct plugin_info_list *plugins; @@ -381,7 +398,8 @@ sudo_load_plugins(struct plugin_container *policy_plugin, /* Walk the plugin list from sudo.conf, if any and free it. */ plugins = sudo_conf_plugins(); TAILQ_FOREACH_SAFE(info, plugins, entries, next) { - ret = sudo_load_plugin(policy_plugin, io_plugins, audit_plugins, info); + ret = sudo_load_plugin(policy_plugin, io_plugins, audit_plugins, + approval_plugins, info); if (!ret) goto done; free_plugin_info(info); @@ -407,7 +425,8 @@ sudo_load_plugins(struct plugin_container *policy_plugin, goto done; } /* info->options = NULL; */ - ret = sudo_load_plugin(policy_plugin, io_plugins, audit_plugins, info); + ret = sudo_load_plugin(policy_plugin, io_plugins, audit_plugins, + approval_plugins, info); free_plugin_info(info); if (!ret) goto done; @@ -427,7 +446,8 @@ sudo_load_plugins(struct plugin_container *policy_plugin, goto done; } /* info->options = NULL; */ - ret = sudo_load_plugin(policy_plugin, io_plugins, audit_plugins, info); + ret = sudo_load_plugin(policy_plugin, io_plugins, audit_plugins, + approval_plugins, info); free_plugin_info(info); if (!ret) goto done; diff --git a/src/sudo.c b/src/sudo.c index 9a6301fc3..90bd72fe4 100644 --- a/src/sudo.c +++ b/src/sudo.c @@ -76,6 +76,7 @@ struct plugin_container policy_plugin; struct plugin_container_list io_plugins = TAILQ_HEAD_INITIALIZER(io_plugins); struct plugin_container_list audit_plugins = TAILQ_HEAD_INITIALIZER(audit_plugins); +struct plugin_container_list approval_plugins = TAILQ_HEAD_INITIALIZER(approval_plugins); struct user_details user_details; const char *list_user; /* extern for parse_args.c */ int sudo_debug_instance = SUDO_DEBUG_INSTANCE_INITIALIZER; @@ -138,6 +139,13 @@ static void audit_accept(const char *plugin_name, unsigned int plugin_type, char * const command_info[], char * const run_argv[], char * const run_envp[]); +/* Approval plugin convenience functions. */ +static void approval_check(struct sudo_settings *settings, + char * const user_info[], int submit_optind, char * const submit_argv[], + char * const submit_envp[], char * const command_info[], + char * const run_argv[], char * const run_envp[]); +static void approval_show_version(int verbose); + __dso_public int main(int argc, char *argv[], char *envp[]); int @@ -220,7 +228,8 @@ main(int argc, char *argv[], char *envp[]) sudo_warn_set_conversation(sudo_conversation); /* Load plugins. */ - if (!sudo_load_plugins(&policy_plugin, &io_plugins, &audit_plugins)) + if (!sudo_load_plugins(&policy_plugin, &io_plugins, &audit_plugins, + &approval_plugins)) sudo_fatalx(U_("fatal error, unable to load plugins")); /* Allocate event base so plugin can use it. */ @@ -237,6 +246,7 @@ main(int argc, char *argv[], char *envp[]) policy_show_version(!user_details.uid); iolog_show_version(!user_details.uid, settings, user_info, nargc, nargv, envp); + approval_show_version(!user_details.uid); audit_show_version(!user_details.uid); break; case MODE_VALIDATE: @@ -266,14 +276,14 @@ main(int argc, char *argv[], char *envp[]) if (nargc == 0) sudo_fatalx(U_("plugin did not return a command to execute")); - /* Open I/O plugin once policy plugin succeeds. */ + /* Approval plugins run after policy plugin accepts the command. */ + approval_check(settings, user_info, submit_optind, argv, envp, + command_info, nargv, user_env_out); + + /* Open I/O plugin once policy and approval plugins succeed. */ iolog_open(settings, user_info, command_info, nargc, nargv, user_env_out); - /* Audit command we are going to run. */ - audit_accept(policy_plugin.name, SUDO_POLICY_PLUGIN, command_info, - nargv, user_env_out); - /* Setup command details and run command/edit. */ command_info_to_details(command_info, &command_details); command_details.tty = user_details.tty; @@ -958,7 +968,6 @@ run_command(struct command_details *details) #endif policy_close(status, 0); iolog_close(status, 0); - audit_close(SUDO_PLUGIN_WAIT_STATUS, status); break; default: /* TODO: handle front end error conditions. */ @@ -1166,6 +1175,8 @@ policy_check(int argc, char * const argv[], audit_close(SUDO_PLUGIN_NO_STATUS, 0); exit(EXIT_FAILURE); /* policy plugin printed error message */ } + audit_accept(policy_plugin.name, SUDO_POLICY_PLUGIN, *command_info, + *argv_out, *user_env_out); debug_return; } @@ -1691,6 +1702,105 @@ audit_error(const char *plugin_name, unsigned int plugin_type, debug_return; } +static int +approval_check_int(struct plugin_container *plugin, + struct sudo_settings *settings, char * const user_info[], + int submit_optind, char * const submit_argv[], char * const submit_envp[], + char * const command_info[], char * const run_argv[], + char * const run_envp[], const char **errstr) +{ + char **plugin_settings; + int ret; + debug_decl(approval_check_int, SUDO_DEBUG_PCOMM); + + /* Convert struct sudo_settings to plugin_settings[] */ + plugin_settings = format_plugin_settings(plugin, settings); + if (plugin_settings == NULL) { + sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + debug_return_int(-1); + } + + sudo_debug_set_active_instance(plugin->debug_instance); + ret = plugin->u.approval->check(SUDO_API_VERSION, sudo_conversation, + sudo_conversation_printf, plugin_settings, user_info, + submit_optind, submit_argv, submit_envp, command_info, run_argv, + run_envp, plugin->options, errstr); + sudo_debug_set_active_instance(sudo_debug_instance); + + sudo_debug_printf(SUDO_DEBUG_INFO, "approval plugin %s returns %d (%s)", + plugin->name, ret, *errstr ? *errstr : ""); + + debug_return_int(ret); +} + +/* + * Run approval checks (there may be more than one). + * This is a "one-shot" plugin that has no open/close and is only + * called if the policy plugin accepts the command first. + */ +static void +approval_check(struct sudo_settings *settings, char * const user_info[], + int submit_optind, char * const submit_argv[], char * const submit_envp[], + char * const command_info[], char * const run_argv[], + char * const run_envp[]) +{ + struct plugin_container *plugin, *next; + const char *errstr = NULL; + debug_decl(approval_check, SUDO_DEBUG_PCOMM); + + TAILQ_FOREACH_SAFE(plugin, &approval_plugins, entries, next) { + int ok = approval_check_int(plugin, settings, user_info, + submit_optind, submit_argv, submit_envp, command_info, run_argv, + run_envp, &errstr); + + switch (ok) { + case 0: + audit_reject(plugin->name, SUDO_APPROVAL_PLUGIN, + errstr ? errstr : _("command rejected by approver"), + command_info); + break; + case 1: + audit_accept(plugin->name, SUDO_APPROVAL_PLUGIN, command_info, + run_argv, run_envp); + continue; + case -1: + audit_error(plugin->name, SUDO_APPROVAL_PLUGIN, + errstr ? errstr : _("approval plugin error"), + command_info); + break; + case -2: + usage(1); + break; + } + + if (policy_plugin.u.policy->version >= SUDO_API_MKVERSION(1, 15)) + policy_close(0, EPERM); + audit_close(SUDO_PLUGIN_NO_STATUS, 0); + exit(EXIT_FAILURE); /* approval plugin printed error message */ + } + + debug_return; +} + +static void +approval_show_version(int verbose) +{ + struct plugin_container *plugin; + debug_decl(approval_show_version, SUDO_DEBUG_PCOMM); + + TAILQ_FOREACH(plugin, &approval_plugins, entries) { + if (plugin->u.approval->show_version != NULL) { + /* Return value of show_version currently ignored. */ + sudo_debug_set_active_instance(plugin->debug_instance); + plugin->u.approval->show_version(SUDO_API_VERSION, + sudo_conversation, sudo_conversation_printf, verbose); + sudo_debug_set_active_instance(sudo_debug_instance); + } + } + + debug_return; +} + static void plugin_event_callback(int fd, int what, void *v) { diff --git a/src/sudo_plugin_int.h b/src/sudo_plugin_int.h index 6eb7abb43..0f83fb724 100644 --- a/src/sudo_plugin_int.h +++ b/src/sudo_plugin_int.h @@ -1,7 +1,7 @@ /* * SPDX-License-Identifier: ISC * - * Copyright (c) 2010-2014 Todd C. Miller + * Copyright (c) 2010-2020 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 @@ -101,6 +101,7 @@ struct plugin_container { struct io_plugin_1_0 *io_1_0; struct io_plugin_1_1 *io_1_1; struct audit_plugin *audit; + struct approval_plugin *approval; } u; }; TAILQ_HEAD(plugin_container_list, plugin_container); @@ -119,6 +120,7 @@ struct sudo_plugin_event_int { extern struct plugin_container policy_plugin; extern struct plugin_container_list io_plugins; extern struct plugin_container_list audit_plugins; +extern struct plugin_container_list approval_plugins; int sudo_conversation(int num_msgs, const struct sudo_conv_message msgs[], struct sudo_conv_reply replies[], struct sudo_conv_callback *callback); @@ -128,6 +130,7 @@ int sudo_conversation_printf(int msg_type, const char *fmt, ...); bool sudo_load_plugins(struct plugin_container *policy_plugin, struct plugin_container_list *io_plugins, - struct plugin_container_list *audit_plugins); + struct plugin_container_list *audit_plugins, + struct plugin_container_list *approval_plugins); #endif /* SUDO_PLUGIN_INT_H */