summaryrefslogtreecommitdiffstats
path: root/plugins/group_file
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/group_file')
-rw-r--r--plugins/group_file/Makefile.in234
-rw-r--r--plugins/group_file/getgrent.c193
-rw-r--r--plugins/group_file/group_file.c129
-rw-r--r--plugins/group_file/group_file.exp1
-rw-r--r--plugins/group_file/plugin_test.c222
5 files changed, 779 insertions, 0 deletions
diff --git a/plugins/group_file/Makefile.in b/plugins/group_file/Makefile.in
new file mode 100644
index 0000000..3223292
--- /dev/null
+++ b/plugins/group_file/Makefile.in
@@ -0,0 +1,234 @@
+#
+# SPDX-License-Identifier: ISC
+#
+# Copyright (c) 2010-2023 Todd C. Miller <Todd.Miller@sudo.ws>
+#
+# 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.
+#
+# @configure_input@
+#
+
+#### Start of system configuration section. ####
+
+srcdir = @srcdir@
+abs_srcdir = @abs_srcdir@
+top_srcdir = @top_srcdir@
+abs_top_srcdir = @abs_top_srcdir@
+top_builddir = @top_builddir@
+abs_top_builddir = @abs_top_builddir@
+devdir = @devdir@
+scriptdir = $(top_srcdir)/scripts
+incdir = $(top_srcdir)/include
+cross_compiling = @CROSS_COMPILING@
+
+# Compiler & tools to use
+CC = @CC@
+LIBTOOL = @LIBTOOL@
+SED = @SED@
+AWK = @AWK@
+
+# Our install program supports extra flags...
+INSTALL = $(SHELL) $(scriptdir)/install-sh -c
+INSTALL_OWNER = -o $(install_uid) -g $(install_gid)
+INSTALL_BACKUP = @INSTALL_BACKUP@
+
+# Libraries
+LT_LIBS = $(top_builddir)/lib/util/libsudo_util.la
+LIBS = $(LT_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 = @LDFLAGS@
+LT_LDFLAGS = @LT_LDFLAGS@ @LT_LDEXPORTS@
+
+# Flags to pass to libtool
+LTFLAGS = --tag=disable-static
+
+# Address sanitizer flags
+ASAN_CFLAGS = @ASAN_CFLAGS@
+ASAN_LDFLAGS = @ASAN_LDFLAGS@
+
+# PIE flags
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+
+# Stack smashing protection flags
+HARDENING_CFLAGS = @HARDENING_CFLAGS@
+HARDENING_LDFLAGS = @HARDENING_LDFLAGS@
+
+# cppcheck options, usually set in the top-level Makefile
+CPPCHECK_OPTS = -q --enable=warning,performance,portability --suppress=constStatement --suppress=compareBoolExpressionWithInt --error-exitcode=1 --inline-suppr -Dva_copy=va_copy -U__cplusplus -UQUAD_MAX -UQUAD_MIN -UUQUAD_MAX -U_POSIX_HOST_NAME_MAX -U_POSIX_PATH_MAX -U__NBBY -DNSIG=64
+
+# splint options, usually set in the top-level Makefile
+SPLINT_OPTS = -D__restrict= -checks
+
+# PVS-studio options
+PVS_CFG = $(top_srcdir)/PVS-Studio.cfg
+PVS_IGNORE = 'V707,V011,V002,V536'
+PVS_LOG_OPTS = -a 'GA:1,2' -e -t errorfile -d $(PVS_IGNORE)
+
+# Where to install things...
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+bindir = @bindir@
+sbindir = @sbindir@
+sysconfdir = @sysconfdir@
+adminconfdir = @adminconfdir@
+libexecdir = @libexecdir@
+datarootdir = @datarootdir@
+localstatedir = @localstatedir@
+plugindir = @plugindir@
+
+# File mode and map file to use for shared libraries/objects
+shlib_enable = @SHLIB_ENABLE@
+shlib_mode = @SHLIB_MODE@
+shlib_exp = $(srcdir)/group_file.exp
+shlib_map = group_file.map
+shlib_opt = group_file.opt
+
+# User and group ids the installed files should be "owned" by
+install_uid = 0
+install_gid = 0
+
+#### End of system configuration section. ####
+
+SHELL = @SHELL@
+
+OBJS = group_file.lo getgrent.lo
+
+IOBJS = $(OBJS:.lo=.i)
+
+POBJS = $(IOBJS:.i=.plog)
+
+LIBOBJDIR = $(top_builddir)/@ac_config_libobj_dir@/
+
+VERSION = @PACKAGE_VERSION@
+
+all: group_file.la
+
+depend:
+ $(scriptdir)/mkdep.pl --srcdir=$(abs_top_srcdir) \
+ --builddir=$(abs_top_builddir) plugins/group_file/Makefile.in
+ cd $(top_builddir) && ./config.status --file plugins/group_file/Makefile
+
+Makefile: $(srcdir)/Makefile.in
+ cd $(top_builddir) && ./config.status --file plugins/group_file/Makefile
+
+.SUFFIXES: .c .h .i .lo .plog
+
+.c.lo:
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $<
+
+.c.i:
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+
+.i.plog:
+ ifile=$<; rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $${ifile%i}c --i-file $< --output-file $@
+
+$(shlib_map): $(shlib_exp)
+ @$(AWK) 'BEGIN { print "{\n\tglobal:" } { print "\t\t"$$0";" } END { print "\tlocal:\n\t\t*;\n};" }' $(shlib_exp) > $@
+
+$(shlib_opt): $(shlib_exp)
+ @$(SED) 's/^/+e /' $(shlib_exp) > $@
+
+group_file.la: $(OBJS) $(LT_LIBS) @LT_LDDEP@
+ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) $(LDFLAGS) $(ASAN_LDFLAGS) $(HARDENING_LDFLAGS) $(LT_LDFLAGS) -o $@ $(OBJS) $(LIBS) -module -avoid-version -rpath $(plugindir) -shrext .so
+
+pre-install:
+
+install: install-plugin
+
+install-dirs:
+ $(SHELL) $(scriptdir)/mkinstalldirs $(DESTDIR)$(plugindir)
+
+install-binaries:
+
+install-includes:
+
+install-doc:
+
+install-plugin: install-dirs group_file.la
+ if [ X"$(shlib_enable)" = X"yes" ]; then \
+ INSTALL_BACKUP='$(INSTALL_BACKUP)' $(LIBTOOL) $(LTFLAGS) --mode=install $(INSTALL) $(INSTALL_OWNER) -m $(shlib_mode) group_file.la $(DESTDIR)$(plugindir); \
+ fi
+
+install-fuzzer:
+
+uninstall:
+ -$(LIBTOOL) $(LTFLAGS) --mode=uninstall rm -f $(DESTDIR)$(plugindir)/group_file.la
+ -test -z "$(INSTALL_BACKUP)" || \
+ rm -f $(DESTDIR)$(plugindir)/group_file.so$(INSTALL_BACKUP)
+
+splint:
+ splint $(SPLINT_OPTS) -I$(incdir) -I$(top_builddir) $(srcdir)/*.c
+
+cppcheck:
+ cppcheck $(CPPCHECK_OPTS) -I$(incdir) -I$(top_builddir) $(srcdir)/*.c
+
+pvs-log-files: $(POBJS)
+
+pvs-studio: $(POBJS)
+ plog-converter $(PVS_LOG_OPTS) $(POBJS)
+
+fuzz:
+
+check-fuzzer:
+
+check: check-fuzzer
+
+check-verbose: check
+
+clean:
+ -$(LIBTOOL) $(LTFLAGS) --mode=clean rm -f *.lo *.o *.la
+ -rm -f *.i *.plog stamp-* core *.core core.*
+
+mostlyclean: clean
+
+distclean: clean
+ -rm -rf Makefile .libs $(shlib_map) $(shlib_opt)
+
+clobber: distclean
+
+realclean: distclean
+ rm -f TAGS tags
+
+cleandir: realclean
+
+.PHONY: clean mostlyclean distclean cleandir clobber realclean
+
+# Autogenerated dependencies, do not modify
+getgrent.lo: $(srcdir)/getgrent.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/getgrent.c
+getgrent.i: $(srcdir)/getgrent.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_util.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+getgrent.plog: getgrent.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/getgrent.c --i-file $< --output-file $@
+group_file.lo: $(srcdir)/group_file.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_plugin.h \
+ $(top_builddir)/config.h
+ $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/group_file.c
+group_file.i: $(srcdir)/group_file.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_plugin.h \
+ $(top_builddir)/config.h
+ $(CC) -E -o $@ $(CPPFLAGS) $<
+group_file.plog: group_file.i
+ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/group_file.c --i-file $< --output-file $@
diff --git a/plugins/group_file/getgrent.c b/plugins/group_file/getgrent.c
new file mode 100644
index 0000000..031aed5
--- /dev/null
+++ b/plugins/group_file/getgrent.c
@@ -0,0 +1,193 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2005,2008,2010-2015,2022 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * 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.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+/*
+ * Trivial replacements for the libc getgrent() family of functions.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <grp.h>
+
+#include <sudo_compat.h>
+#include <sudo_util.h>
+
+#undef GRMEM_MAX
+#define GRMEM_MAX 200
+
+static FILE *grf;
+static const char *grfile = "/etc/group";
+static int gr_stayopen;
+
+void mysetgrfile(const char *);
+void mysetgrent(void);
+void myendgrent(void);
+int mysetgroupent(int);
+struct group *mygetgrent(void);
+struct group *mygetgrnam(const char *);
+struct group *mygetgrgid(gid_t);
+
+void
+mysetgrfile(const char *file)
+{
+ grfile = file;
+ if (grf != NULL)
+ myendgrent();
+}
+
+static int
+open_group(int reset)
+{
+ if (grf == NULL) {
+ grf = fopen(grfile, "r");
+ if (grf != NULL) {
+ if (fcntl(fileno(grf), F_SETFD, FD_CLOEXEC) == -1) {
+ fclose(grf);
+ grf = NULL;
+ }
+ }
+ if (grf == NULL)
+ return 0;
+ } else if (reset) {
+ rewind(grf);
+ }
+ return 1;
+}
+
+int
+mysetgroupent(int stayopen)
+{
+ if (!open_group(1))
+ return 0;
+ gr_stayopen = stayopen;
+ return 1;
+}
+
+void
+mysetgrent(void)
+{
+ mysetgroupent(0);
+}
+
+void
+myendgrent(void)
+{
+ if (grf != NULL) {
+ fclose(grf);
+ grf = NULL;
+ }
+ gr_stayopen = 0;
+}
+
+struct group *
+mygetgrent(void)
+{
+ static struct group gr;
+ static char grbuf[LINE_MAX], *gr_mem[GRMEM_MAX+1];
+ size_t len;
+ id_t id;
+ char *cp, *colon;
+ const char *errstr;
+ int n;
+
+ if (!open_group(0))
+ return NULL;
+
+next_entry:
+ if ((colon = fgets(grbuf, sizeof(grbuf), grf)) == NULL)
+ return NULL;
+
+ memset(&gr, 0, sizeof(gr));
+ if ((colon = strchr(cp = colon, ':')) == NULL)
+ goto next_entry;
+ *colon++ = '\0';
+ gr.gr_name = cp;
+ if ((colon = strchr(cp = colon, ':')) == NULL)
+ goto next_entry;
+ *colon++ = '\0';
+ gr.gr_passwd = cp;
+ if ((colon = strchr(cp = colon, ':')) == NULL)
+ goto next_entry;
+ *colon++ = '\0';
+ id = sudo_strtoid(cp, &errstr);
+ if (errstr != NULL)
+ goto next_entry;
+ gr.gr_gid = (gid_t)id;
+ len = strlen(colon);
+ if (len > 0 && colon[len - 1] == '\n')
+ colon[len - 1] = '\0';
+ if (*colon != '\0') {
+ char *last;
+
+ gr.gr_mem = gr_mem;
+ cp = strtok_r(colon, ",", &last);
+ for (n = 0; cp != NULL && n < GRMEM_MAX; n++) {
+ gr.gr_mem[n] = cp;
+ cp = strtok_r(NULL, ",", &last);
+ }
+ gr.gr_mem[n] = NULL;
+ } else
+ gr.gr_mem = NULL;
+ return &gr;
+}
+
+struct group *
+mygetgrnam(const char *name)
+{
+ struct group *gr;
+
+ if (!open_group(1))
+ return NULL;
+ while ((gr = mygetgrent()) != NULL) {
+ if (strcmp(gr->gr_name, name) == 0)
+ break;
+ }
+ if (!gr_stayopen) {
+ fclose(grf);
+ grf = NULL;
+ }
+ return gr;
+}
+
+struct group *
+mygetgrgid(gid_t gid)
+{
+ struct group *gr;
+
+ if (!open_group(1))
+ return NULL;
+ while ((gr = mygetgrent()) != NULL) {
+ if (gr->gr_gid == gid)
+ break;
+ }
+ if (!gr_stayopen) {
+ fclose(grf);
+ grf = NULL;
+ }
+ return gr;
+}
diff --git a/plugins/group_file/group_file.c b/plugins/group_file/group_file.c
new file mode 100644
index 0000000..de9a491
--- /dev/null
+++ b/plugins/group_file/group_file.c
@@ -0,0 +1,129 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2010-2014 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * 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.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <sys/stat.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include <compat/stdbool.h>
+#endif /* HAVE_STDBOOL_H */
+#include <string.h>
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+
+#include <sudo_plugin.h>
+#include <sudo_compat.h>
+
+/*
+ * Sample sudoers group plugin that uses an extra group file with the
+ * same format as /etc/group.
+ */
+
+static sudo_printf_t sudo_log;
+
+extern void mysetgrfile(const char *);
+extern int mysetgroupent(int);
+extern void myendgrent(void);
+extern struct group *mygetgrnam(const char *);
+
+static int
+sample_init(int version, sudo_printf_t sudo_printf, char *const argv[])
+{
+ struct stat sb;
+
+ sudo_log = sudo_printf;
+
+ if (SUDO_API_VERSION_GET_MAJOR(version) != GROUP_API_VERSION_MAJOR) {
+ sudo_log(SUDO_CONV_ERROR_MSG,
+ "group_file: incompatible major version %d, expected %d\n",
+ SUDO_API_VERSION_GET_MAJOR(version),
+ GROUP_API_VERSION_MAJOR);
+ return -1;
+ }
+
+ /* Check that the group file exists and has a safe mode. */
+ if (argv == NULL || argv[0] == NULL) {
+ sudo_log(SUDO_CONV_ERROR_MSG,
+ "group_file: path to group file not specified\n");
+ return -1;
+ }
+ if (stat(argv[0], &sb) != 0) {
+ sudo_log(SUDO_CONV_ERROR_MSG,
+ "group_file: %s: %s\n", argv[0], strerror(errno));
+ return -1;
+ }
+ if ((sb.st_mode & (S_IWGRP|S_IWOTH)) != 0) {
+ sudo_log(SUDO_CONV_ERROR_MSG,
+ "%s must be only be writable by owner\n", argv[0]);
+ return -1;
+ }
+
+ mysetgrfile(argv[0]);
+ if (!mysetgroupent(1))
+ return false;
+
+ return true;
+}
+
+static void
+sample_cleanup(void)
+{
+ myendgrent();
+}
+
+/*
+ * Returns true if "user" is a member of "group", else false.
+ */
+static int
+sample_query(const char *user, const char *group, const struct passwd *pwd)
+{
+ struct group *grp;
+ char **member;
+
+ grp = mygetgrnam(group);
+ if (grp != NULL && grp->gr_mem != NULL) {
+ for (member = grp->gr_mem; *member != NULL; member++) {
+ if (strcasecmp(user, *member) == 0)
+ return true;
+ }
+ }
+
+ return false;
+}
+
+sudo_dso_public struct sudoers_group_plugin group_plugin = {
+ GROUP_API_VERSION,
+ sample_init,
+ sample_cleanup,
+ sample_query
+};
diff --git a/plugins/group_file/group_file.exp b/plugins/group_file/group_file.exp
new file mode 100644
index 0000000..a859d6c
--- /dev/null
+++ b/plugins/group_file/group_file.exp
@@ -0,0 +1 @@
+group_plugin
diff --git a/plugins/group_file/plugin_test.c b/plugins/group_file/plugin_test.c
new file mode 100644
index 0000000..ec47f73
--- /dev/null
+++ b/plugins/group_file/plugin_test.c
@@ -0,0 +1,222 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2010-2013 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * 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.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+#include <ctype.h>
+#include <dlfcn.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sudo_plugin.h>
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+/*
+ * Simple driver to test sudoer group plugins.
+ * usage: plugin_test [-p "plugin.so plugin_args ..."] user:group ...
+ */
+
+static void *group_handle;
+static struct sudoers_group_plugin *group_plugin;
+
+static int
+plugin_printf(int msg_type, const char * restrict fmt, ...)
+{
+ va_list ap;
+ FILE *fp;
+
+ switch (msg_type) {
+ case SUDO_CONV_INFO_MSG:
+ fp = stdout;
+ break;
+ case SUDO_CONV_ERROR_MSG:
+ fp = stderr;
+ break;
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+
+ va_start(ap, fmt);
+ vfprintf(fp, fmt, ap);
+ va_end(ap);
+
+ return 0;
+}
+
+/*
+ * Load the specified plugin and run its init function.
+ * Returns -1 if unable to open the plugin, else it returns
+ * the value from the plugin's init function.
+ */
+static int
+group_plugin_load(char *plugin_info)
+{
+ char *args, path[PATH_MAX], savedch;
+ char **argv = NULL;
+ int rc;
+
+ /*
+ * Fill in .so path and split out args (if any).
+ */
+ if ((args = strpbrk(plugin_info, " \t")) != NULL) {
+ savedch = *args;
+ *args = '\0';
+ }
+ if (strlcpy(path, plugin_info, sizeof(path)) >= sizeof(path)) {
+ fprintf(stderr, "path too long: %s\n", plugin_info);
+ return -1;
+ }
+ if (args != NULL)
+ *args++ = savedch;
+
+ /* Open plugin and map in symbol. */
+ group_handle = dlopen(path, RTLD_LAZY);
+ if (!group_handle) {
+ fprintf(stderr, "unable to dlopen %s: %s\n", path, dlerror());
+ return -1;
+ }
+ group_plugin = dlsym(group_handle, "group_plugin");
+ if (group_plugin == NULL) {
+ fprintf(stderr, "unable to find symbol \"group_plugin\" in %s\n", path);
+ return -1;
+ }
+
+ if (SUDO_API_VERSION_GET_MAJOR(group_plugin->version) != GROUP_API_VERSION_MAJOR) {
+ fprintf(stderr,
+ "%s: incompatible group plugin major version %u, expected %u\n",
+ path, SUDO_API_VERSION_GET_MAJOR(group_plugin->version),
+ GROUP_API_VERSION_MAJOR);
+ return -1;
+ }
+
+ /*
+ * Split args into a vector if specified.
+ */
+ if (args != NULL) {
+ int ac = 0, wasblank = 1;
+ char *cp, *last;
+
+ for (cp = args; *cp != '\0'; cp++) {
+ if (isblank((unsigned char)*cp)) {
+ wasblank = 1;
+ } else if (wasblank) {
+ wasblank = 0;
+ ac++;
+ }
+ }
+ if (ac != 0) {
+ argv = malloc((ac + 1) * sizeof(char *));
+ if (argv == NULL) {
+ perror(NULL);
+ return -1;
+ }
+ ac = 0;
+ cp = strtok_r(args, " \t", &last);
+ while (cp != NULL) {
+ argv[ac++] = cp;
+ cp = strtok_r(NULL, " \t", &last);
+ }
+ argv[ac] = NULL;
+ }
+ }
+
+ rc = (group_plugin->init)(GROUP_API_VERSION, plugin_printf, argv);
+
+ free(argv);
+
+ return rc;
+}
+
+static void
+group_plugin_unload(void)
+{
+ (group_plugin->cleanup)();
+ dlclose(group_handle);
+ group_handle = NULL;
+}
+
+static int
+group_plugin_query(const char *user, const char *group,
+ const struct passwd *pwd)
+{
+ return (group_plugin->query)(user, group, pwd);
+}
+
+static void
+usage(void)
+{
+ fputs("usage: plugin_test [-p \"plugin.so plugin_args ...\"] user:group ...\n",
+ stderr);
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int ch, found;
+ size_t i;
+ char *plugin = "group_file.so";
+ char *user, *group;
+ struct passwd *pwd;
+
+ while ((ch = getopt(argc, argv, "p:")) != -1) {
+ switch (ch) {
+ case 'p':
+ plugin = optarg;
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1)
+ usage();
+
+ if (group_plugin_load(plugin) != 1) {
+ fprintf(stderr, "unable to load plugin: %s\n", plugin);
+ return EXIT_FAILURE;
+ }
+
+ for (i = 0; argv[i] != NULL; i++) {
+ user = argv[i];
+ group = strchr(argv[i], ':');
+ if (group == NULL)
+ continue;
+ *group++ = '\0';
+ pwd = getpwnam(user);
+ found = group_plugin_query(user, group, pwd);
+ printf("user %s %s in group %s\n", user, found ? "is" : "NOT ", group);
+ }
+ group_plugin_unload();
+
+ return EXIT_SUCCESS;
+}
+