From: Todd C. Miller Date: Wed, 21 Mar 2018 18:24:11 +0000 (-0600) Subject: Initial support filtering by user, group and host in cvtsudoers. X-Git-Tag: SUDO_1_8_23^2~73 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=ff79de85924a3afdb91e0d308ed2bba8fac47dc3;p=sudo Initial support filtering by user, group and host in cvtsudoers. Currently forces alias expansion when a filter is applied and the entire matching user or host list is printed, even the non-matching entries. This effectively allows you to grep sudoers by user, group and host. --- diff --git a/doc/cvtsudoers.cat b/doc/cvtsudoers.cat index 006581a48..e6361f58c 100644 --- a/doc/cvtsudoers.cat +++ b/doc/cvtsudoers.cat @@ -4,9 +4,9 @@ NNAAMMEE ccvvttssuuddooeerrss - convert between sudoers file formats SSYYNNOOPPSSIISS - ccvvttssuuddooeerrss [--eehhVV] [--bb _d_n] [--cc _c_o_n_f___f_i_l_e] [--II _i_n_c_r_e_m_e_n_t] [--ii _i_n_p_u_t___f_o_r_m_a_t] - [--ff _o_u_t_p_u_t___f_o_r_m_a_t] [--OO _s_t_a_r_t___p_o_i_n_t] [--oo _o_u_t_p_u_t___f_i_l_e] - [_i_n_p_u_t___f_i_l_e] + ccvvttssuuddooeerrss [--eehhVV] [--bb _d_n] [--cc _c_o_n_f___f_i_l_e] [--ff _o_u_t_p_u_t___f_o_r_m_a_t] + [--ii _i_n_p_u_t___f_o_r_m_a_t] [--II _i_n_c_r_e_m_e_n_t] [--mm _f_i_l_t_e_r] [--oo _o_u_t_p_u_t___f_i_l_e] + [--OO _s_t_a_r_t___p_o_i_n_t] [_i_n_p_u_t___f_i_l_e] DDEESSCCRRIIPPTTIIOONN ccvvttssuuddooeerrss can be used to convert between _s_u_d_o_e_r_s security policy file @@ -52,14 +52,13 @@ DDEESSCCRRIIPPTTIIOONN Conversion to LDIF has the following limitations: - ++oo Command, host, runas and user-specific - Defaults lines cannot be translated as they - don't have an equivalent in the sudoers LDAP - schema. + ++oo Command, host, runas and user-specific Defaults + lines cannot be translated as they don't have an + equivalent in the sudoers LDAP schema. - ++oo Command, host, runas and user aliases are not - supported by the sudoers LDAP schema so they - are expanded during the conversion. + ++oo Command, host, runas and user aliases are not + supported by the sudoers LDAP schema so they are + expanded during the conversion. sudoers Traditional sudoers format. A new sudoers file will be reconstructed from the parsed input file. @@ -68,11 +67,6 @@ DDEESSCCRRIIPPTTIIOONN --hh, ----hheellpp Display a short help message to the standard output and exit. - --II _i_n_c_r_e_m_e_n_t, ----iinnccrreemmeenntt=_i_n_c_r_e_m_e_n_t - When generating LDIF output, increment each sudoOrder - attribute by the specified number. Defaults to an increment - of 1. - --ii _i_n_p_u_t___f_o_r_m_a_t, ----iinnppuutt--ffoorrmmaatt=_i_n_p_u_t___f_o_r_m_a_t Specify the input format. The following formats are supported: @@ -88,6 +82,28 @@ DDEESSCCRRIIPPTTIIOONN sudoers Traditional sudoers format. This is the default input format. + --II _i_n_c_r_e_m_e_n_t, ----iinnccrreemmeenntt=_i_n_c_r_e_m_e_n_t + When generating LDIF output, increment each sudoOrder + attribute by the specified number. Defaults to an increment + of 1. + + --mm _f_i_l_t_e_r, ----mmaattcchh=_f_i_l_t_e_r + Only output rules that match the specified _f_i_l_t_e_r. A _f_i_l_t_e_r + expression is made up of one or more kkeeyy == _v_a_l_u_e pairs, + separated by a comma (`,'). The kkeeyy may be "user", "group" + or "host". For example, uusseerr = _o_p_e_r_a_t_o_r or hhoosstt = _w_w_w. + + The password and group databases are not consulted when + matching against the filter so the users and groups do not + need to be present on the local system. Only aliases that + are referenced by the filtered policy rules will be + displayed. + + --oo _o_u_t_p_u_t___f_i_l_e, ----oouuttppuutt=_o_u_t_p_u_t___f_i_l_e + Write the converted output to _o_u_t_p_u_t___f_i_l_e. If no _o_u_t_p_u_t___f_i_l_e + is specified, or if it is `-', the converted _s_u_d_o_e_r_s policy + will be written to the standard output. + --OO _s_t_a_r_t___p_o_i_n_t, ----oorrddeerr--ssttaarrtt=_s_t_a_r_t___p_o_i_n_t When generating LDIF output, use the number specified by _s_t_a_r_t___p_o_i_n_t in the sudoOrder attribute of the first sudoRole @@ -97,11 +113,6 @@ DDEESSCCRRIIPPTTIIOONN point of 0 will disable the generation of sudoOrder attributes in the resulting LDIF file. - --oo _o_u_t_p_u_t___f_i_l_e, ----oouuttppuutt=_o_u_t_p_u_t___f_i_l_e - Write the converted output to _o_u_t_p_u_t___f_i_l_e. If no _o_u_t_p_u_t___f_i_l_e - is specified, or if it is `-', the converted _s_u_d_o_e_r_s policy - will be written to the standard output. - --VV, ----vveerrssiioonn Print the ccvvttssuuddooeerrss and _s_u_d_o_e_r_s grammar versions and exit. @@ -115,6 +126,9 @@ DDEESSCCRRIIPPTTIIOONN iinnppuutt__ffoorrmmaatt == _l_d_i_f | _s_u_d_o_e_r_s See the description of the --ii command line option. + mmaattcchh == _f_i_l_t_e_r + See the description of the --mm command line option. + oorrddeerr__iinnccrreemmeenntt == _i_n_c_r_e_m_e_n_t See the description of the --II command line option. @@ -162,4 +176,4 @@ DDIISSCCLLAAIIMMEERR file distributed with ssuuddoo or https://www.sudo.ws/license.html for complete details. -Sudo 1.8.23 February 23, 2018 Sudo 1.8.23 +Sudo 1.8.23 March 21, 2018 Sudo 1.8.23 diff --git a/doc/cvtsudoers.man.in b/doc/cvtsudoers.man.in index 28c23bcd6..599f64e9d 100644 --- a/doc/cvtsudoers.man.in +++ b/doc/cvtsudoers.man.in @@ -16,7 +16,7 @@ .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.TH "CVTSUDOERS" "8" "February 23, 2018" "Sudo @PACKAGE_VERSION@" "System Manager's Manual" +.TH "CVTSUDOERS" "8" "March 21, 2018" "Sudo @PACKAGE_VERSION@" "System Manager's Manual" .nh .if n .ad l .SH "NAME" @@ -28,11 +28,12 @@ [\fB\-ehV\fR] [\fB\-b\fR\ \fIdn\fR] [\fB\-c\fR\ \fIconf_file\fR] -[\fB\-I\fR\ \fIincrement\fR] -[\fB\-i\fR\ \fIinput_format\fR] [\fB\-f\fR\ \fIoutput_format\fR] -[\fB\-O\fR\ \fIstart_point\fR] +[\fB\-i\fR\ \fIinput_format\fR] +[\fB\-I\fR\ \fIincrement\fR] +[\fB\-m\fR\ \fIfilter\fR] [\fB\-o\fR\ \fIoutput_file\fR] +[\fB\-O\fR\ \fIstart_point\fR] [\fIinput_file\fR] .SH "DESCRIPTION" \fBcvtsudoers\fR @@ -105,12 +106,12 @@ Conversion to LDIF has the following limitations: .PP .RS 10n .PD 0 -.TP 6n +.TP 3n \fB\(bu\fR Command, host, runas and user-specific Defaults lines cannot be translated as they don't have an equivalent in the sudoers LDAP schema. .PD -.TP 6n +.TP 3n \fB\(bu\fR Command, host, runas and user aliases are not supported by the sudoers LDAP schema so they are expanded during the conversion. @@ -132,11 +133,6 @@ output inline. \fB\-h\fR, \fB\--help\fR Display a short help message to the standard output and exit. .TP 12n -\fB\-I\fR \fIincrement\fR, \fB\--increment\fR=\fIincrement\fR -When generating LDIF output, increment each sudoOrder attribute by -the specified number. -Defaults to an increment of 1. -.TP 12n \fB\-i\fR \fIinput_format\fR, \fB\--input-format\fR=\fIinput_format\fR Specify the input format. The following formats are supported: @@ -162,6 +158,49 @@ This is the default input format. .RE .PD .TP 12n +\fB\-I\fR \fIincrement\fR, \fB\--increment\fR=\fIincrement\fR +When generating LDIF output, increment each sudoOrder attribute by +the specified number. +Defaults to an increment of 1. +.TP 12n +\fB\-m\fR \fIfilter\fR, \fB\--match\fR=\fIfilter\fR +Only output rules that match the specified +\fIfilter\fR. +A +\fIfilter\fR +expression is made up of one or more +\fBkey =\fR \fIvalue\fR +pairs, separated by a comma +(\(oq\&,\(cq). +The +\fBkey\fR +may be +\(Lquser\(Rq, +\(Lqgroup\(Rq +or +\(Lqhost\(Rq. +For example, +\fBuser\fR = \fIoperator\fR +or +\fBhost\fR = \fIwww\fR. +.sp +The password and group databases are not consulted when matching +against the filter so the users and groups do not need to be present +on the local system. +Only aliases that are referenced by the filtered policy rules will +be displayed. +.TP 12n +\fB\-o\fR \fIoutput_file\fR, \fB\--output\fR=\fIoutput_file\fR +Write the converted output to +\fIoutput_file\fR. +If no +\fIoutput_file\fR +is specified, or if it is +\(oq-\(cq, +the converted +\fIsudoers\fR +policy will be written to the standard output. +.TP 12n \fB\-O\fR \fIstart_point\fR, \fB\--order-start\fR=\fIstart_point\fR When generating LDIF output, use the number specified by \fIstart_point\fR @@ -175,17 +214,6 @@ Defaults to a starting point of 1. A starting point of 0 will disable the generation of sudoOrder attributes in the resulting LDIF file. .TP 12n -\fB\-o\fR \fIoutput_file\fR, \fB\--output\fR=\fIoutput_file\fR -Write the converted output to -\fIoutput_file\fR. -If no -\fIoutput_file\fR -is specified, or if it is -\(oq-\(cq, -the converted -\fIsudoers\fR -policy will be written to the standard output. -.TP 12n \fB\-V\fR, \fB\--version\fR Print the \fBcvtsudoers\fR @@ -210,6 +238,11 @@ See the description of the \fB\-i\fR command line option. .TP 6n +\fBmatch =\fR \fIfilter\fR +See the description of the +\fB\-m\fR +command line option. +.TP 6n \fBorder_increment =\fR \fIincrement\fR See the description of the \fB\-I\fR diff --git a/doc/cvtsudoers.mdoc.in b/doc/cvtsudoers.mdoc.in index 6a1df133d..81e492c1d 100644 --- a/doc/cvtsudoers.mdoc.in +++ b/doc/cvtsudoers.mdoc.in @@ -14,7 +14,7 @@ .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.Dd February 23, 2018 +.Dd March 21, 2018 .Dt CVTSUDOERS @mansectsu@ .Os Sudo @PACKAGE_VERSION@ .Sh NAME @@ -25,11 +25,12 @@ .Op Fl ehV .Op Fl b Ar dn .Op Fl c Ar conf_file -.Op Fl I Ar increment -.Op Fl i Ar input_format .Op Fl f Ar output_format -.Op Fl O Ar start_point +.Op Fl i Ar input_format +.Op Fl I Ar increment +.Op Fl m Ar filter .Op Fl o Ar output_file +.Op Fl O Ar start_point .Op Ar input_file .Sh DESCRIPTION .Nm @@ -91,7 +92,7 @@ server for use with .Xr sudoers.ldap @mansectform@ . .Pp Conversion to LDIF has the following limitations: -.Bl -bullet -width 4n +.Bl -bullet -width 1n .It Command, host, runas and user-specific Defaults lines cannot be translated as they don't have an equivalent in the sudoers LDAP schema. @@ -107,10 +108,6 @@ output inline. .El .It Fl h , Fl -help Display a short help message to the standard output and exit. -.It Fl I Ar increment , Fl -increment Ns = Ns Ar increment -When generating LDIF output, increment each sudoOrder attribute by -the specified number. -Defaults to an increment of 1. .It Fl i Ar input_format , Fl -input-format Ns = Ns Ar input_format Specify the input format. The following formats are supported: @@ -127,6 +124,46 @@ LDIF to sudoers format. Traditional sudoers format. This is the default input format. .El +.It Fl I Ar increment , Fl -increment Ns = Ns Ar increment +When generating LDIF output, increment each sudoOrder attribute by +the specified number. +Defaults to an increment of 1. +.It Fl m Ar filter , Fl -match Ns = Ns Ar filter +Only output rules that match the specified +.Ar filter . +A +.Ar filter +expression is made up of one or more +.Sy key = Ar value +pairs, separated by a comma +.Pq Ql \&, . +The +.Sy key +may be +.Dq user , +.Dq group +or +.Dq host . +For example, +.Sy user No = Ar operator +or +.Sy host No = Ar www . +.Pp +The password and group databases are not consulted when matching +against the filter so the users and groups do not need to be present +on the local system. +Only aliases that are referenced by the filtered policy rules will +be displayed. +.It Fl o Ar output_file , Fl -output Ns = Ns Ar output_file +Write the converted output to +.Ar output_file . +If no +.Ar output_file +is specified, or if it is +.Ql - , +the converted +.Em sudoers +policy will be written to the standard output. .It Fl O Ar start_point , Fl -order-start Ns = Ns Ar start_point When generating LDIF output, use the number specified by .Ar start_point @@ -139,16 +176,6 @@ option for details. Defaults to a starting point of 1. A starting point of 0 will disable the generation of sudoOrder attributes in the resulting LDIF file. -.It Fl o Ar output_file , Fl -output Ns = Ns Ar output_file -Write the converted output to -.Ar output_file . -If no -.Ar output_file -is specified, or if it is -.Ql - , -the converted -.Em sudoers -policy will be written to the standard output. .It Fl V , -version Print the .Nm @@ -172,6 +199,10 @@ command line option. See the description of the .Fl i command line option. +.It Sy match = Ar filter +See the description of the +.Fl m +command line option. .It Sy order_increment = Ar increment See the description of the .Fl I diff --git a/plugins/sudoers/Makefile.in b/plugins/sudoers/Makefile.in index 9bc6fd046..c540f1645 100644 --- a/plugins/sudoers/Makefile.in +++ b/plugins/sudoers/Makefile.in @@ -163,7 +163,8 @@ SUDOERS_OBJS = $(AUTH_OBJS) boottime.lo check.lo editor.lo env.lo \ VISUDO_OBJS = editor.o find_path.o goodpath.o locale.o stubs.o sudo_printf.o visudo.o CVTSUDOERS_OBJS = cvtsudoers.o cvtsudoers_json.o cvtsudoers_ldif.o \ - fmtsudoers.o locale.o stubs.o sudo_printf.o ldap_util.o + cvtsudoers_pwutil.o fmtsudoers.o locale.o stubs.o \ + sudo_printf.o ldap_util.o REPLAY_OBJS = getdate.o sudoreplay.o @@ -747,14 +748,26 @@ cvtsudoers_ldif.o: $(srcdir)/cvtsudoers_ldif.c $(devdir)/def_data.h \ $(devdir)/gram.h $(incdir)/compat/stdbool.h \ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \ $(incdir)/sudo_debug.h $(incdir)/sudo_fatal.h \ - $(incdir)/sudo_gettext.h $(incdir)/sudo_plugin.h \ - $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \ - $(srcdir)/cvtsudoers.h $(srcdir)/defaults.h \ - $(srcdir)/logging.h $(srcdir)/parse.h $(srcdir)/redblack.h \ - $(srcdir)/sudo_ldap.h $(srcdir)/sudo_nss.h \ - $(srcdir)/sudoers.h $(srcdir)/sudoers_debug.h \ - $(top_builddir)/config.h $(top_builddir)/pathnames.h + $(incdir)/sudo_gettext.h $(incdir)/sudo_lbuf.h \ + $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \ + $(incdir)/sudo_util.h $(srcdir)/cvtsudoers.h \ + $(srcdir)/defaults.h $(srcdir)/logging.h $(srcdir)/parse.h \ + $(srcdir)/redblack.h $(srcdir)/sudo_ldap.h \ + $(srcdir)/sudo_nss.h $(srcdir)/sudoers.h \ + $(srcdir)/sudoers_debug.h $(top_builddir)/config.h \ + $(top_builddir)/pathnames.h $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/cvtsudoers_ldif.c +cvtsudoers_pwutil.o: $(srcdir)/cvtsudoers_pwutil.c $(devdir)/def_data.h \ + $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \ + $(incdir)/sudo_conf.h $(incdir)/sudo_debug.h \ + $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \ + $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \ + $(incdir)/sudo_util.h $(srcdir)/cvtsudoers.h \ + $(srcdir)/defaults.h $(srcdir)/logging.h \ + $(srcdir)/pwutil.h $(srcdir)/sudo_nss.h \ + $(srcdir)/sudoers.h $(srcdir)/sudoers_debug.h \ + $(top_builddir)/config.h $(top_builddir)/pathnames.h + $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/cvtsudoers_pwutil.c dce.lo: $(authdir)/dce.c $(devdir)/def_data.h $(incdir)/compat/stdbool.h \ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h $(incdir)/sudo_debug.h \ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \ @@ -1321,10 +1334,10 @@ sudoreplay.o: $(srcdir)/sudoreplay.c $(incdir)/compat/getopt.h \ $(top_builddir)/pathnames.h $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/sudoreplay.c testsudoers.o: $(srcdir)/testsudoers.c $(devdir)/def_data.h $(devdir)/gram.h \ - $(incdir)/compat/fnmatch.h $(incdir)/compat/stdbool.h \ - $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \ - $(incdir)/sudo_debug.h $(incdir)/sudo_fatal.h \ - $(incdir)/sudo_gettext.h $(incdir)/sudo_plugin.h \ + $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \ + $(incdir)/sudo_conf.h $(incdir)/sudo_debug.h \ + $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \ + $(incdir)/sudo_lbuf.h $(incdir)/sudo_plugin.h \ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \ $(srcdir)/defaults.h $(srcdir)/interfaces.h $(srcdir)/logging.h \ $(srcdir)/parse.h $(srcdir)/sudo_nss.h $(srcdir)/sudoers.h \ diff --git a/plugins/sudoers/alias.c b/plugins/sudoers/alias.c index b8e70fc7a..efd380f7e 100644 --- a/plugins/sudoers/alias.c +++ b/plugins/sudoers/alias.c @@ -39,7 +39,7 @@ /* * Globals */ -struct rbtree *aliases; +static struct rbtree *aliases; /* * Comparison function for the red-black tree. @@ -166,6 +166,20 @@ no_aliases(void) debug_return_bool(rbisempty(aliases)); } +/* + * Replace the aliases tree with a new one, returns the old. + */ +struct rbtree * +replace_aliases(struct rbtree *new_aliases) +{ + struct rbtree *old_aliases = aliases; + debug_decl(replace_aliases, SUDOERS_DEBUG_ALIAS) + + aliases = new_aliases; + + debug_return_ptr(old_aliases); +} + /* * Free memory used by an alias struct and its members. */ diff --git a/plugins/sudoers/cvtsudoers.c b/plugins/sudoers/cvtsudoers.c index e3fd02153..cbbc0803e 100644 --- a/plugins/sudoers/cvtsudoers.c +++ b/plugins/sudoers/cvtsudoers.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include "sudoers.h" @@ -39,6 +40,7 @@ #include "sudoers_version.h" #include "sudo_conf.h" #include "sudo_lbuf.h" +#include "redblack.h" #include "cvtsudoers.h" #include @@ -51,9 +53,10 @@ /* * Globals */ +struct cvtsudoers_filter *filters; struct sudo_user sudo_user; struct passwd *list_pw; -static const char short_opts[] = "b:c:ef:hi:I:o:O:V"; +static const char short_opts[] = "b:c:ef:hi:I:m:o:O:V"; static struct option long_opts[] = { { "base", required_argument, NULL, 'b' }, { "config", required_argument, NULL, 'c' }, @@ -62,6 +65,7 @@ static struct option long_opts[] = { { "help", no_argument, NULL, 'h' }, { "input-format", required_argument, NULL, 'i' }, { "increment", required_argument, NULL, 'I' }, + { "match", required_argument, NULL, 'm' }, { "order-start", required_argument, NULL, 'O' }, { "output", required_argument, NULL, 'o' }, { "version", no_argument, NULL, 'V' }, @@ -73,8 +77,12 @@ static void help(void) __attribute__((__noreturn__)); static void usage(int); static bool convert_sudoers_sudoers(const char *output_file, struct cvtsudoers_config *conf); static bool parse_sudoers(const char *input_file, struct cvtsudoers_config *conf); +static bool parse_filter(char *expression); +static bool alias_remove_unused(void); static struct cvtsudoers_config *cvtsudoers_conf_read(const char *conf_file); static void cvtsudoers_conf_free(struct cvtsudoers_config *conf); +static void filter_userspecs(void); +static void filter_defaults(void); int main(int argc, char *argv[]) @@ -174,6 +182,9 @@ main(int argc, char *argv[]) usage(1); } break; + case 'm': + conf->filter = optarg; + break; case 'o': output_file = optarg; break; @@ -223,6 +234,11 @@ main(int argc, char *argv[]) usage(1); } } + if (conf->filter != NULL) { + /* We always expand aliases when filtering (may change in future). */ + if (!parse_filter(conf->filter)) + usage(1); + } /* If no base DN specified, check SUDOERS_BASE. */ if (conf->sudoers_base == NULL) { @@ -269,6 +285,15 @@ main(int argc, char *argv[]) sudo_fatalx("error: unhandled input %d", input_format); } + /* Apply filters. */ + if (conf->filter != NULL) { + filter_userspecs(); + + filter_defaults(); + + alias_remove_unused(); + } + switch (output_format) { case format_json: exitcode = !convert_sudoers_json(output_file, conf); @@ -299,6 +324,7 @@ static struct cvtsudoers_conf_table cvtsudoers_conf_vars[] = { { "sudoers_base", CONF_STR, &cvtsudoers_config.sudoers_base }, { "input_format", CONF_STR, &cvtsudoers_config.input_format }, { "output_format", CONF_STR, &cvtsudoers_config.output_format }, + { "match", CONF_STR, &cvtsudoers_config.filter }, { "expand_aliases", CONF_BOOL, &cvtsudoers_config.expand_aliases } }; @@ -410,6 +436,60 @@ cvtsudoers_conf_free(struct cvtsudoers_config *conf) debug_return; } +static bool +parse_filter(char *expression) +{ + char *last = NULL, *cp = expression; + debug_decl(parse_filter, SUDOERS_DEBUG_UTIL) + + if (filters == NULL) { + if ((filters = malloc(sizeof(*filters))) == NULL) { + sudo_fatalx(U_("%s: %s"), __func__, + U_("unable to allocate memory")); + } + STAILQ_INIT(&filters->users); + STAILQ_INIT(&filters->groups); + STAILQ_INIT(&filters->hosts); + } + + for ((cp = strtok_r(cp, ",", &last)); cp != NULL; (cp = strtok_r(NULL, ",", &last))) { + /* + * Filter expression: + * user=foo,group=bar,host=baz + */ + char *keyword; + struct cvtsudoers_string *s; + + if ((s = malloc(sizeof(*s))) == NULL) { + sudo_fatalx(U_("%s: %s"), __func__, + U_("unable to allocate memory")); + } + + /* Parse keyword = value */ + keyword = cp; + if ((cp = strchr(cp, '=')) == NULL) { + sudo_warnx(U_("invalid filter: %s"), keyword);; + debug_return_bool(false); + } + *cp++ = '\0'; + s->str = cp; + + if (strcmp(keyword, "user") == 0 ){ + STAILQ_INSERT_TAIL(&filters->users, s, entries); + } else if (strcmp(keyword, "group") == 0 ){ + STAILQ_INSERT_TAIL(&filters->groups, s, entries); + } else if (strcmp(keyword, "host") == 0 ){ + STAILQ_INSERT_TAIL(&filters->hosts, s, entries); + } else { + sudo_warnx(U_("invalid filter: %s"), keyword);; + free(s); + debug_return_bool(false); + } + } + + debug_return_bool(true); +} + static bool parse_sudoers(const char *input_file, struct cvtsudoers_config *conf) { @@ -446,6 +526,89 @@ open_sudoers(const char *sudoers, bool doedit, bool *keepopen) return fopen(sudoers, "r"); } +bool +userlist_matches_filter(struct member_list *userlist) +{ + struct cvtsudoers_string *s; + bool matches = false; + debug_decl(userlist_matches_filter, SUDOERS_DEBUG_UTIL) + + if (filters == NULL || + (STAILQ_EMPTY(&filters->users) && STAILQ_EMPTY(&filters->groups))) + debug_return_bool(true); + + if (STAILQ_EMPTY(&filters->users)) { + struct passwd pw; + + /* + * Only groups in filter, make a dummy user so userlist_matches() + * can do its thing. + */ + memset(&pw, 0, sizeof(pw)); + pw.pw_name = "_nobody"; + pw.pw_uid = (uid_t)-1; + pw.pw_gid = (gid_t)-1; + if (userlist_matches(&pw, userlist) == true) + matches = true; + } + + STAILQ_FOREACH(s, &filters->users, entries) { + struct passwd *pw = NULL; + if (s->str[0] == '#') { + const char *errstr; + uid_t uid = sudo_strtoid(s->str + 1, NULL, NULL, &errstr); + if (errstr == NULL) + pw = sudo_getpwuid(uid); + } + if (pw == NULL) + pw = sudo_getpwnam(s->str); + if (pw == NULL) + continue; + if (userlist_matches(pw, userlist) == true) + matches = true; + sudo_pw_delref(pw); + if (matches) + break; + } + + debug_return_bool(matches); +} + +bool +hostlist_matches_filter(struct member_list *hostlist) +{ + struct cvtsudoers_string *s; + bool matches = false; + debug_decl(hostlist_matches_filter, SUDOERS_DEBUG_UTIL) + + if (filters == NULL || STAILQ_EMPTY(&filters->hosts)) + debug_return_bool(true); + + STAILQ_FOREACH(s, &filters->hosts, entries) { + user_runhost = s->str; + if ((user_srunhost = strchr(user_runhost, '.')) != NULL) { + user_srunhost = strndup(user_runhost, + (size_t)(user_srunhost - user_runhost)); + if (user_srunhost == NULL) { + sudo_fatalx(U_("%s: %s"), __func__, + U_("unable to allocate memory")); + } + } else { + user_srunhost = user_runhost; + } + /* XXX - can't use netgroup_tuple with NULL pw */ + if (hostlist_matches(NULL, hostlist) == true) + matches = true; + if (user_srunhost != user_runhost) + free(user_srunhost); + user_runhost = user_host; + user_srunhost = user_shost; + if (matches) + break; + } + debug_return_bool(matches); +} + /* * Display Defaults entries */ @@ -502,6 +665,195 @@ convert_sudoers_output(const char *buf) return fputs(buf, output_fp); } +/* + * Apply filters to userspecs, removing non-matching entries. + */ +static void +filter_userspecs(void) +{ + struct userspec *us, *next_us; + struct privilege *priv, *next_priv; + debug_decl(filter_userspecs, SUDOERS_DEBUG_UTIL) + + /* + * Does not currently prune out non-matching entries in the user or + * host lists. It acts more like a grep than a true filter. + * In the future, we may want to add a prune option. + */ + TAILQ_FOREACH_SAFE(us, &userspecs, entries, next_us) { + if (!userlist_matches_filter(&us->users)) { + TAILQ_REMOVE(&userspecs, us, entries); + free_userspec(us); + continue; + } + TAILQ_FOREACH_SAFE(priv, &us->privileges, entries, next_priv) { + if (!hostlist_matches_filter(&priv->hostlist)) { + TAILQ_REMOVE(&us->privileges, priv, entries); + free_privilege(priv); + } + } + if (TAILQ_EMPTY(&us->privileges)) { + TAILQ_REMOVE(&userspecs, us, entries); + free_userspec(us); + continue; + } + } + debug_return; +} + +/* + * Apply filters to host/user-based Defaults, removing non-matching entries. + */ +static void +filter_defaults(void) +{ + struct defaults *def, *next; + struct member_list *binding = NULL; + debug_decl(filter_defaults, SUDOERS_DEBUG_DEFAULTS) + + TAILQ_FOREACH_SAFE(def, &defaults, entries, next) { + switch (def->type) { + case DEFAULTS_USER: + if (!userlist_matches_filter(def->binding)) { + TAILQ_REMOVE(&defaults, def, entries); + binding = free_default(def, binding); + } else { + binding = def->binding; + } + break; + case DEFAULTS_HOST: + if (!hostlist_matches_filter(def->binding)) { + TAILQ_REMOVE(&defaults, def, entries); + binding = free_default(def, binding); + } else { + binding = def->binding; + } + break; + default: + break; + } + } + debug_return; +} + +/* + * Remove the alias of the specified type as well as any other aliases + * referenced by that alias. + * XXX - share with visudo + */ +static bool +alias_remove_recursive(char *name, int type, struct rbtree *freelist) +{ + struct member *m; + struct alias *a; + bool ret = true; + debug_decl(alias_remove_recursive, SUDOERS_DEBUG_ALIAS) + + if ((a = alias_remove(name, type)) != NULL) { + TAILQ_FOREACH(m, &a->members, entries) { + if (m->type == ALIAS) { + if (!alias_remove_recursive(m->name, type, freelist)) + ret = false; + } + } + if (rbinsert(freelist, a, NULL) != 0) + ret = false; + } + debug_return_bool(ret); +} + +/* + * Remove unreferenced aliases. + * XXX - share with visudo + */ +static bool +alias_remove_unused(void) +{ + struct cmndspec *cs; + struct member *m; + struct privilege *priv; + struct userspec *us; + struct defaults *d; + int atype, errors = 0; + struct rbtree *used_aliases; + struct rbtree *unused_aliases; + debug_decl(alias_remove_unused, SUDOERS_DEBUG_ALIAS) + + used_aliases = rbcreate(alias_compare); + if (used_aliases == NULL) { + sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + debug_return_int(-1); + } + + /* Move referenced aliases to used_aliases. */ + TAILQ_FOREACH(us, &userspecs, entries) { + TAILQ_FOREACH(m, &us->users, entries) { + if (m->type == ALIAS) { + if (!alias_remove_recursive(m->name, USERALIAS, used_aliases)) + errors++; + } + } + TAILQ_FOREACH(priv, &us->privileges, entries) { + TAILQ_FOREACH(m, &priv->hostlist, entries) { + if (m->type == ALIAS) { + if (!alias_remove_recursive(m->name, HOSTALIAS, used_aliases)) + errors++; + } + } + TAILQ_FOREACH(cs, &priv->cmndlist, entries) { + if (cs->runasuserlist != NULL) { + TAILQ_FOREACH(m, cs->runasuserlist, entries) { + if (m->type == ALIAS) { + if (!alias_remove_recursive(m->name, RUNASALIAS, used_aliases)) + errors++; + } + } + } + if (cs->runasgrouplist != NULL) { + TAILQ_FOREACH(m, cs->runasgrouplist, entries) { + if (m->type == ALIAS) { + if (!alias_remove_recursive(m->name, RUNASALIAS, used_aliases)) + errors++; + } + } + } + if ((m = cs->cmnd)->type == ALIAS) { + if (!alias_remove_recursive(m->name, CMNDALIAS, used_aliases)) + errors++; + } + } + } + } + TAILQ_FOREACH(d, &defaults, entries) { + switch (d->type) { + case DEFAULTS_HOST: + atype = HOSTALIAS; + break; + case DEFAULTS_USER: + atype = USERALIAS; + break; + case DEFAULTS_RUNAS: + atype = RUNASALIAS; + break; + case DEFAULTS_CMND: + atype = CMNDALIAS; + break; + default: + continue; /* not an alias */ + } + TAILQ_FOREACH(m, d->binding, entries) { + if (m->type == ALIAS) { + if (!alias_remove_recursive(m->name, atype, used_aliases)) + errors++; + } + } + } + unused_aliases = replace_aliases(used_aliases); + rbdestroy(unused_aliases, alias_free); + + debug_return_int(errors ? false : true); +} + /* * Convert back to sudoers. */ @@ -571,7 +923,7 @@ usage(int fatal) { (void) fprintf(fatal ? stderr : stdout, "usage: %s [-ehV] [-b dn] " "[-c conf_file ] [-f output_format] [-i input_format] [-I increment] " - "[-o output_file] [-O start_point] [input_file]\n", + "[-m filter] [-o output_file] [-O start_point] [input_file]\n", getprogname()); if (fatal) exit(1); @@ -586,11 +938,12 @@ help(void) " -b, --base=dn the base DN for sudo LDAP queries\n" " -e, --expand-aliases expand aliases when converting\n" " -f, --output-format=format set output format: JSON, LDIF or sudoers\n" - " -I, --increment=num amount to increase each sudoOrder by\n" " -i, --input-format=format set input format: LDIF or sudoers\n" + " -I, --increment=num amount to increase each sudoOrder by\n" " -h, --help display help message and exit\n" - " -O, --order-start=num starting point for first sudoOrder\n" + " -m, --match=filter only convert entries that match the filter expression\n" " -o, --output=output_file write converted sudoers to output_file\n" + " -O, --order-start=num starting point for first sudoOrder\n" " -V, --version display version information and exit")); exit(0); } diff --git a/plugins/sudoers/cvtsudoers.h b/plugins/sudoers/cvtsudoers.h index f24f15472..efce454fd 100644 --- a/plugins/sudoers/cvtsudoers.h +++ b/plugins/sudoers/cvtsudoers.h @@ -24,11 +24,26 @@ enum sudoers_formats { format_sudoers }; +/* + * Simple string list with optional reference count. + * XXX - move this so fmtsudoers can use it + */ +struct cvtsudoers_string { + STAILQ_ENTRY(cvtsudoers_string) entries; + char *str; +}; +struct cvtsudoers_str_list { + struct cvtsudoers_string *stqh_first; + struct cvtsudoers_string **stqh_last; + unsigned int refcnt; +}; + /* cvtsudoers.conf settings */ struct cvtsudoers_config { char *sudoers_base; char *input_format; char *output_format; + char *filter; unsigned int sudo_order; unsigned int order_increment; bool expand_aliases; @@ -36,7 +51,7 @@ struct cvtsudoers_config { }; /* Initial config settings for above. */ -#define INITIAL_CONFIG { NULL, NULL, NULL, 1, 1, false, true } +#define INITIAL_CONFIG { NULL, NULL, NULL, NULL, 1, 1, false, true } #define CONF_BOOL 0 #define CONF_UINT 1 @@ -48,9 +63,27 @@ struct cvtsudoers_conf_table { void *valp; /* pointer into cvtsudoers_config */ }; +struct cvtsudoers_filter { + struct cvtsudoers_str_list users; + struct cvtsudoers_str_list groups; + struct cvtsudoers_str_list hosts; +}; + bool convert_sudoers_json(const char *output_file, struct cvtsudoers_config *conf); bool convert_sudoers_ldif(const char *output_file, struct cvtsudoers_config *conf); bool parse_ldif(const char *input_file, struct cvtsudoers_config *conf); void get_hostname(void); +struct member_list; +struct userspec_list; +bool userlist_matches_filter(struct member_list *userlist); +bool hostlist_matches_filter(struct member_list *hostlist); + +struct cvtsudoers_str_list *str_list_alloc(void); +void str_list_free(void *v); +struct cvtsudoers_string *cvtsudoers_string_alloc(const char *s); +void cvtsudoers_string_free(struct cvtsudoers_string *ls); + +extern struct cvtsudoers_filter *filters; + #endif /* SUDOERS_CVTSUDOERS_H */ diff --git a/plugins/sudoers/cvtsudoers_ldif.c b/plugins/sudoers/cvtsudoers_ldif.c index 3209a03e7..c9ef0731f 100644 --- a/plugins/sudoers/cvtsudoers_ldif.c +++ b/plugins/sudoers/cvtsudoers_ldif.c @@ -465,7 +465,7 @@ print_userspecs_ldif(FILE *fp, struct cvtsudoers_config *conf) { struct userspec *us; debug_decl(print_userspecs_ldif, SUDOERS_DEBUG_UTIL) - + TAILQ_FOREACH(us, &userspecs, entries) { if (!print_userspec_ldif(fp, us, conf)) debug_return_bool(false); @@ -513,36 +513,27 @@ convert_sudoers_ldif(const char *output_file, struct cvtsudoers_config *conf) debug_return_bool(ret); } -struct ldif_string { - STAILQ_ENTRY(ldif_string) entries; - char *str; -}; -struct ldif_str_list { - struct ldif_string *stqh_first; - struct ldif_string **stqh_last; - unsigned int refcnt; -}; - struct sudo_role { STAILQ_ENTRY(sudo_role) entries; char *cn; char *notbefore; char *notafter; double order; - struct ldif_str_list *cmnds; - struct ldif_str_list *hosts; - struct ldif_str_list *users; - struct ldif_str_list *runasusers; - struct ldif_str_list *runasgroups; - struct ldif_str_list *options; + struct cvtsudoers_str_list *cmnds; + struct cvtsudoers_str_list *hosts; + struct cvtsudoers_str_list *users; + struct cvtsudoers_str_list *runasusers; + struct cvtsudoers_str_list *runasgroups; + struct cvtsudoers_str_list *options; }; STAILQ_HEAD(sudo_role_list, sudo_role); -static struct ldif_string * -ldif_string_alloc(const char *s) +/* XXX - move to cvtsudoers.c */ +struct cvtsudoers_string * +cvtsudoers_string_alloc(const char *s) { - struct ldif_string *ls; - debug_decl(ldif_string_alloc, SUDOERS_DEBUG_UTIL) + struct cvtsudoers_string *ls; + debug_decl(cvtsudoers_string_alloc, SUDOERS_DEBUG_UTIL) if ((ls = malloc(sizeof(*ls))) != NULL) { if ((ls->str = strdup(s)) == NULL) { @@ -554,17 +545,17 @@ ldif_string_alloc(const char *s) debug_return_ptr(ls); } -static void -ldif_string_free(struct ldif_string *ls) +void +cvtsudoers_string_free(struct cvtsudoers_string *ls) { free(ls->str); free(ls); } -static struct ldif_str_list * +struct cvtsudoers_str_list * str_list_alloc(void) { - struct ldif_str_list *strlist; + struct cvtsudoers_str_list *strlist; debug_decl(str_list_alloc, SUDOERS_DEBUG_UTIL) strlist = malloc(sizeof(*strlist)); @@ -574,17 +565,17 @@ str_list_alloc(void) debug_return_ptr(strlist); } -static void +void str_list_free(void *v) { - struct ldif_str_list *strlist = v; - struct ldif_string *first; + struct cvtsudoers_str_list *strlist = v; + struct cvtsudoers_string *first; debug_decl(str_list_free, SUDOERS_DEBUG_UTIL) if (--strlist->refcnt == 0) { while ((first = STAILQ_FIRST(strlist)) != NULL) { STAILQ_REMOVE_HEAD(strlist, entries); - ldif_string_free(first); + cvtsudoers_string_free(first); } free(strlist); } @@ -644,25 +635,25 @@ sudo_role_free(struct sudo_role *role) } /* - * Allocate a struct ldif_string, store str in it and + * Allocate a struct cvtsudoers_string, store str in it and * insert into the specified strlist. */ static void -ldif_store_string(const char *str, struct ldif_str_list *strlist, bool sorted) +ldif_store_string(const char *str, struct cvtsudoers_str_list *strlist, bool sorted) { - struct ldif_string *ls; + struct cvtsudoers_string *ls; debug_decl(ldif_store_string, SUDOERS_DEBUG_UTIL) while (isblank((unsigned char)*str)) str++; - if ((ls = ldif_string_alloc(str)) == NULL) { + if ((ls = cvtsudoers_string_alloc(str)) == NULL) { sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory")); } if (!sorted) { STAILQ_INSERT_TAIL(strlist, ls, entries); } else { - struct ldif_string *prev, *next; + struct cvtsudoers_string *prev, *next; /* Insertion sort, list is small. */ prev = STAILQ_FIRST(strlist); @@ -683,13 +674,13 @@ ldif_store_string(const char *str, struct ldif_str_list *strlist, bool sorted) /* * Iterator for sudo_ldap_role_to_priv(). - * Takes a pointer to a struct ldif_string *. + * Takes a pointer to a struct cvtsudoers_string *. * Returns the string or NULL if we've reached the end. */ -static char * -ldif_string_iter(void **vp) +char * +cvtsudoers_string_iter(void **vp) { - struct ldif_string *ls = *vp; + struct cvtsudoers_string *ls = *vp; if (ls == NULL) return NULL; @@ -714,10 +705,10 @@ role_order_cmp(const void *va, const void *vb) * Parse list of sudoOption and store in global defaults list. */ static void -ldif_store_options(struct ldif_str_list *options) +ldif_store_options(struct cvtsudoers_str_list *options) { struct defaults *d; - struct ldif_string *ls; + struct cvtsudoers_string *ls; char *var, *val; debug_decl(ldif_store_options, SUDOERS_DEBUG_UTIL) @@ -748,10 +739,10 @@ ldif_store_options(struct ldif_str_list *options) static int str_list_cmp(const void *aa, const void *bb) { - const struct ldif_str_list *a = aa; - const struct ldif_str_list *b = bb; - const struct ldif_string *lsa = STAILQ_FIRST(a); - const struct ldif_string *lsb = STAILQ_FIRST(b); + const struct cvtsudoers_str_list *a = aa; + const struct cvtsudoers_str_list *b = bb; + const struct cvtsudoers_string *lsa = STAILQ_FIRST(a); + const struct cvtsudoers_string *lsb = STAILQ_FIRST(b); int ret; while (lsa != NULL && lsb != NULL) { @@ -764,9 +755,9 @@ str_list_cmp(const void *aa, const void *bb) } static int -str_list_cache(struct rbtree *cache, struct ldif_str_list **strlistp) +str_list_cache(struct rbtree *cache, struct cvtsudoers_str_list **strlistp) { - struct ldif_str_list *strlist = *strlistp; + struct cvtsudoers_str_list *strlist = *strlistp; struct rbnode *node; int ret; debug_decl(str_list_cache, SUDOERS_DEBUG_UTIL) @@ -797,7 +788,7 @@ role_to_sudoers(struct sudo_role *role, bool store_options, bool reuse_userspec, bool reuse_privilege, bool reuse_runas) { struct privilege *priv; - struct ldif_string *ls; + struct cvtsudoers_string *ls; struct userspec *us; struct member *m; debug_decl(role_to_sudoers, SUDOERS_DEBUG_UTIL) @@ -882,7 +873,7 @@ role_to_sudoers(struct sudo_role *role, bool store_options, STAILQ_FIRST(role->runasusers), STAILQ_FIRST(role->runasgroups), STAILQ_FIRST(role->cmnds), STAILQ_FIRST(role->options), role->notbefore, role->notafter, true, store_options, - ldif_string_iter); + cvtsudoers_string_iter); if (priv == NULL) { sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory")); diff --git a/plugins/sudoers/cvtsudoers_pwutil.c b/plugins/sudoers/cvtsudoers_pwutil.c new file mode 100644 index 000000000..4f8de07b5 --- /dev/null +++ b/plugins/sudoers/cvtsudoers_pwutil.c @@ -0,0 +1,479 @@ +/* + * Copyright (c) 1996, 1998-2005, 2007-2017 + * 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. + * + * Sponsored in part by the Defense Advanced Research Projects + * Agency (DARPA) and Air Force Research Laboratory, Air Force + * Materiel Command, USAF, under agreement number F39502-99-1-0512. + */ + +#include + +#include +#include +#include +#ifdef HAVE_STRING_H +# include +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include +#endif /* HAVE_STRINGS_H */ +#include +#include +#include +#include +#include + +#include "sudoers.h" +#include "cvtsudoers.h" +#include "pwutil.h" + +#ifndef LOGIN_NAME_MAX +# ifdef _POSIX_LOGIN_NAME_MAX +# define LOGIN_NAME_MAX _POSIX_LOGIN_NAME_MAX +# else +# define LOGIN_NAME_MAX 9 +# endif +#endif /* LOGIN_NAME_MAX */ + +#define FIELD_SIZE(src, name, size) \ +do { \ + if ((src)->name) { \ + size = strlen((src)->name) + 1; \ + total += size; \ + } \ +} while (0) + +#define FIELD_COPY(src, dst, name, size) \ +do { \ + if ((src)->name) { \ + memcpy(cp, (src)->name, size); \ + (dst)->name = cp; \ + cp += size; \ + } \ +} while (0) + +/* + * Dynamically allocate space for a struct item plus the key and data + * elements. If name is non-NULL it is used as the key, else the + * uid is the key. Fills in datum from struct password. + * Returns NULL on calloc error or unknown name/id, setting errno + * to ENOMEM or ENOENT respectively. + */ +struct cache_item * +sudo_make_pwitem(uid_t uid, const char *name) +{ + char *cp, uidstr[MAX_UID_T_LEN + 2]; + size_t nsize, psize, csize, gsize, dsize, ssize, total; + struct cache_item_pw *pwitem; + struct passwd pw, *newpw; + struct cvtsudoers_string *s = NULL; + debug_decl(sudo_make_pwitem, SUDOERS_DEBUG_NSS) + + /* Look up name or uid in filter list. */ + if (name != NULL) { + STAILQ_FOREACH(s, &filters->users, entries) { + if (strcasecmp(name, s->str) == 0) { + uid = (uid_t)-1; + break; + } + } + } else { + STAILQ_FOREACH(s, &filters->users, entries) { + const char *errstr; + uid_t filter_uid; + + if (s->str[0] != '#') + continue; + + filter_uid = sudo_strtoid(s->str + 1, NULL, NULL, &errstr); + if (errstr == NULL) { + if (uid != filter_uid) + continue; + snprintf(uidstr, sizeof(uidstr), "#%u", (unsigned int)uid); + break; + } + } + } + if (s == NULL) { + errno = ENOENT; + debug_return_ptr(NULL); + } + + /* Fake up a passwd struct. */ + memset(&pw, 0, sizeof(pw)); + pw.pw_name = name ? s->str : uidstr; + pw.pw_passwd = "*"; + pw.pw_uid = uid; + pw.pw_gid = (gid_t)-1; + pw.pw_shell = _PATH_BSHELL; + pw.pw_dir = "/"; + + /* Allocate in one big chunk for easy freeing. */ + nsize = psize = csize = gsize = dsize = ssize = 0; + total = sizeof(*pwitem); + FIELD_SIZE(&pw, pw_name, nsize); + FIELD_SIZE(&pw, pw_passwd, psize); +#ifdef HAVE_LOGIN_CAP_H + FIELD_SIZE(&pw, pw_class, csize); +#endif + FIELD_SIZE(&pw, pw_gecos, gsize); + FIELD_SIZE(&pw, pw_dir, dsize); + FIELD_SIZE(&pw, pw_shell, ssize); + if (name != NULL) + total += strlen(name) + 1; + + /* Allocate space for struct item, struct passwd and the strings. */ + if ((pwitem = calloc(1, total)) == NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to allocate memory"); + debug_return_ptr(NULL); + } + newpw = &pwitem->pw; + + /* + * Copy in passwd contents and make strings relative to space + * at the end of the struct. + */ + memcpy(newpw, &pw, sizeof(pw)); + cp = (char *)(pwitem + 1); + FIELD_COPY(&pw, newpw, pw_name, nsize); + FIELD_COPY(&pw, newpw, pw_passwd, psize); +#ifdef HAVE_LOGIN_CAP_H + FIELD_COPY(&pw, newpw, pw_class, csize); +#endif + FIELD_COPY(&pw, newpw, pw_gecos, gsize); + FIELD_COPY(&pw, newpw, pw_dir, dsize); + FIELD_COPY(&pw, newpw, pw_shell, ssize); + + /* Set key and datum. */ + if (name != NULL) { + memcpy(cp, name, strlen(name) + 1); + pwitem->cache.k.name = cp; + } else { + pwitem->cache.k.uid = pw.pw_uid; + } + pwitem->cache.d.pw = newpw; + pwitem->cache.refcnt = 1; + + debug_return_ptr(&pwitem->cache); +} + +/* + * Dynamically allocate space for a struct item plus the key and data + * elements. If name is non-NULL it is used as the key, else the + * gid is the key. Fills in datum from struct group. + * Returns NULL on calloc error or unknown name/id, setting errno + * to ENOMEM or ENOENT respectively. + */ +struct cache_item * +sudo_make_gritem(gid_t gid, const char *name) +{ + char *cp, gidstr[MAX_UID_T_LEN + 2]; + size_t nsize, psize, nmem, total, len; + struct cache_item_gr *gritem; + struct group gr, *newgr; + struct cvtsudoers_string *s = NULL; + debug_decl(sudo_make_gritem, SUDOERS_DEBUG_NSS) + + /* Look up name or gid in filter list. */ + if (name != NULL) { + STAILQ_FOREACH(s, &filters->users, entries) { + if (strcasecmp(name, s->str) == 0) { + gid = (gid_t)-1; + break; + } + } + } else { + STAILQ_FOREACH(s, &filters->users, entries) { + const char *errstr; + gid_t filter_gid; + + if (s->str[0] != '#') + continue; + + filter_gid = sudo_strtoid(s->str + 1, NULL, NULL, &errstr); + if (errstr == NULL) { + if (gid != filter_gid) + continue; + snprintf(gidstr, sizeof(gidstr), "#%u", (unsigned int)gid); + break; + } + } + } + if (s == NULL) { + errno = ENOENT; + debug_return_ptr(NULL); + } + + /* Fake up a group struct with all filter users as members. */ + memset(&gr, 0, sizeof(gr)); + gr.gr_name = name ? s->str : gidstr; + gr.gr_gid = gid; + + /* Allocate in one big chunk for easy freeing. */ + nsize = psize = nmem = 0; + total = sizeof(*gritem); + FIELD_SIZE(&gr, gr_name, nsize); + FIELD_SIZE(&gr, gr_passwd, psize); + if (!STAILQ_EMPTY(&filters->users)) { + STAILQ_FOREACH(s, &filters->users, entries) { + total += strlen(s->str) + 1; + nmem++; + } + total += sizeof(char *) * nmem; + } + if (name != NULL) + total += strlen(name) + 1; + + if ((gritem = calloc(1, total)) == NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to allocate memory"); + debug_return_ptr(NULL); + } + + /* + * Copy in group contents and make strings relative to space + * at the end of the buffer. Note that gr_mem must come + * immediately after struct group to guarantee proper alignment. + */ + newgr = &gritem->gr; + memcpy(newgr, &gr, sizeof(gr)); + cp = (char *)(gritem + 1); + if (nmem != 0) { + STAILQ_FOREACH(s, &filters->groups, entries) { + total += strlen(s->str) + 1; + } + } + if (nmem != 0) { + newgr->gr_mem = (char **)cp; + cp += sizeof(char *) * nmem; + nmem = 0; + STAILQ_FOREACH(s, &filters->groups, entries) { + len = strlen(s->str) + 1; + memcpy(cp, s->str, len); + newgr->gr_mem[nmem++] = cp; + cp += len; + } + newgr->gr_mem[nmem] = NULL; + } + FIELD_COPY(&gr, newgr, gr_passwd, psize); + FIELD_COPY(&gr, newgr, gr_name, nsize); + + /* Set key and datum. */ + if (name != NULL) { + memcpy(cp, name, strlen(name) + 1); + gritem->cache.k.name = cp; + } else { + gritem->cache.k.gid = gr.gr_gid; + } + gritem->cache.d.gr = newgr; + gritem->cache.refcnt = 1; + + debug_return_ptr(&gritem->cache); +} + +static struct cache_item_gidlist *gidlist_item; + +/* + * Dynamically allocate space for a struct item plus the key and data + * elements. Fills in datum from user_gids or from getgrouplist(3). + */ +struct cache_item * +sudo_make_gidlist_item(const struct passwd *pw, char * const *unused1, + unsigned int type) +{ + char *cp; + size_t nsize, total; + struct cache_item_gidlist *glitem; + struct cvtsudoers_string *s; + struct gid_list *gidlist; + GETGROUPS_T *gids = NULL; + int i, ngids = 0; + debug_decl(sudo_make_gidlist_item, SUDOERS_DEBUG_NSS) + + /* + * There's only a single gid list. + */ + if (gidlist_item != NULL) { + gidlist_item->cache.refcnt++; + debug_return_ptr(&gidlist_item->cache); + } + + /* Count number of possible gids in the filter. */ + STAILQ_FOREACH(s, &filters->groups, entries) { + if (s->str[0] == '#') + ngids++; + } + + /* Allocate gids[] array and fill it with parsed gids. */ + if (ngids != 0) { + gids = reallocarray(NULL, ngids, sizeof(GETGROUPS_T)); + if (gids == NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to allocate memory"); + debug_return_ptr(NULL); + } + ngids = 0; + STAILQ_FOREACH(s, &filters->groups, entries) { + if (s->str[0] == '#') { + const char *errstr; + gid_t gid = sudo_strtoid(s->str + 1, NULL, NULL, &errstr); + if (errstr == NULL) { + /* Valid gid. */ + gids[ngids++] = gid; + } + } + } + } + if (ngids == 0) { + free(gids); + errno = ENOENT; + debug_return_ptr(NULL); + } + + /* Allocate in one big chunk for easy freeing. */ + nsize = strlen(pw->pw_name) + 1; + total = sizeof(*glitem) + nsize; + total += sizeof(gid_t *) * ngids; + + if ((glitem = calloc(1, total)) == NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to allocate memory"); + free(gids); + debug_return_ptr(NULL); + } + + /* + * Copy in group list and make pointers relative to space + * at the end of the buffer. Note that the groups array must come + * immediately after struct group to guarantee proper alignment. + */ + gidlist = &glitem->gidlist; + cp = (char *)(glitem + 1); + gidlist->gids = (gid_t *)cp; + cp += sizeof(gid_t) * ngids; + + /* Set key and datum. */ + memcpy(cp, pw->pw_name, nsize); + glitem->cache.k.name = cp; + glitem->cache.d.gidlist = gidlist; + glitem->cache.refcnt = 1; + glitem->cache.type = type; + + /* + * Store group IDs. + */ + for (i = 0; i < ngids; i++) + gidlist->gids[i] = gids[i]; + gidlist->ngids = ngids; + free(gids); + + debug_return_ptr(&glitem->cache); +} + +static struct cache_item_gidlist *grlist_item; + +/* + * Dynamically allocate space for a struct item plus the key and data + * elements. Fills in group names from a call to sudo_get_gidlist(). + */ +struct cache_item * +sudo_make_grlist_item(const struct passwd *pw, char * const *unused1) +{ + char *cp; + size_t nsize, ngroups, total, len; + struct cache_item_grlist *grlitem; + struct cvtsudoers_string *s; + struct group_list *grlist; + int groupname_len; + debug_decl(sudo_make_grlist_item, SUDOERS_DEBUG_NSS) + + /* + * There's only a single group list. + */ + if (grlist_item != NULL) { + grlist_item->cache.refcnt++; + debug_return_ptr(&grlist_item->cache); + } + + /* Count number of groups in the filter. */ + ngroups = 0; + STAILQ_FOREACH(s, &filters->groups, entries) { + ngroups++; + } + +#ifdef _SC_LOGIN_NAME_MAX + groupname_len = MAX((int)sysconf(_SC_LOGIN_NAME_MAX), 32); +#else + groupname_len = MAX(LOGIN_NAME_MAX, 32); +#endif + + /* Allocate in one big chunk for easy freeing. */ + nsize = strlen(pw->pw_name) + 1; + total = sizeof(*grlitem) + nsize; + total += groupname_len * ngroups; + +again: + if ((grlitem = calloc(1, total)) == NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to allocate memory"); + debug_return_ptr(NULL); + } + + /* + * Copy in group list and make pointers relative to space + * at the end of the buffer. Note that the groups array must come + * immediately after struct group to guarantee proper alignment. + */ + grlist = &grlitem->grlist; + cp = (char *)(grlitem + 1); + grlist->groups = (char **)cp; + cp += sizeof(char *) * ngroups; + + /* Set key and datum. */ + memcpy(cp, pw->pw_name, nsize); + grlitem->cache.k.name = cp; + grlitem->cache.d.grlist = grlist; + grlitem->cache.refcnt = 1; + cp += nsize; + + /* + * Copy groups from filter. + */ + ngroups = 0; + STAILQ_FOREACH(s, &filters->groups, entries) { + if (s->str[0] == '#') { + const char *errstr; + sudo_strtoid(s->str + 1, NULL, NULL, &errstr); + if (errstr == NULL) { + /* Group ID not name, ignore it. */ + continue; + } + } + len = strlen(s->str) + 1; + if (cp - (char *)grlitem + len > total) { + total += len + groupname_len; + free(grlitem); + goto again; + } + memcpy(cp, s->str, len); + grlist->groups[ngroups++] = cp; + cp += len; + } + grlist->ngroups = ngroups; + + debug_return_ptr(&grlitem->cache); +} diff --git a/plugins/sudoers/parse.h b/plugins/sudoers/parse.h index 347baa77c..1d7770ff0 100644 --- a/plugins/sudoers/parse.h +++ b/plugins/sudoers/parse.h @@ -252,6 +252,7 @@ extern struct defaults_list defaults; /* alias.c */ bool no_aliases(void); +struct rbtree *replace_aliases(struct rbtree *new_aliases); const char *alias_add(char *name, int type, char *file, int lineno, struct member *members); const char *alias_type_to_string(int alias_type); int alias_compare(const void *a1, const void *a2);