summaryrefslogtreecommitdiffstats
path: root/lib/util/regress
diff options
context:
space:
mode:
Diffstat (limited to 'lib/util/regress')
-rw-r--r--lib/util/regress/atofoo/atofoo_test.c183
-rw-r--r--lib/util/regress/fnmatch/fnm_test.c85
-rw-r--r--lib/util/regress/fnmatch/fnm_test.in6
-rw-r--r--lib/util/regress/getgrouplist/getgrouplist_test.c104
-rw-r--r--lib/util/regress/glob/files47
-rw-r--r--lib/util/regress/glob/globtest.c216
-rw-r--r--lib/util/regress/glob/globtest.in64
-rw-r--r--lib/util/regress/mktemp/mktemp_test.c196
-rw-r--r--lib/util/regress/parse_gids/parse_gids_test.c114
-rw-r--r--lib/util/regress/progname/progname_test.c65
-rw-r--r--lib/util/regress/strsplit/strsplit_test.c111
-rw-r--r--lib/util/regress/sudo_conf/conf_test.c102
-rw-r--r--lib/util/regress/sudo_conf/test1.in73
-rw-r--r--lib/util/regress/sudo_conf/test1.out.ok7
-rw-r--r--lib/util/regress/sudo_conf/test2.in0
-rw-r--r--lib/util/regress/sudo_conf/test2.out.ok3
-rw-r--r--lib/util/regress/sudo_conf/test3.in2
-rw-r--r--lib/util/regress/sudo_conf/test3.out.ok5
-rw-r--r--lib/util/regress/sudo_conf/test4.err.ok1
-rw-r--r--lib/util/regress/sudo_conf/test4.in1
-rw-r--r--lib/util/regress/sudo_conf/test4.out.ok3
-rw-r--r--lib/util/regress/sudo_conf/test5.err.ok1
-rw-r--r--lib/util/regress/sudo_conf/test5.in1
-rw-r--r--lib/util/regress/sudo_conf/test5.out.ok3
-rw-r--r--lib/util/regress/sudo_conf/test6.in1
-rw-r--r--lib/util/regress/sudo_conf/test6.out.ok3
-rw-r--r--lib/util/regress/sudo_conf/test7.in4
-rw-r--r--lib/util/regress/sudo_conf/test7.out.ok7
-rw-r--r--lib/util/regress/sudo_parseln/parseln_test.c58
-rw-r--r--lib/util/regress/sudo_parseln/test1.in72
-rw-r--r--lib/util/regress/sudo_parseln/test1.out.ok72
-rw-r--r--lib/util/regress/sudo_parseln/test2.in8
-rw-r--r--lib/util/regress/sudo_parseln/test2.out.ok3
-rw-r--r--lib/util/regress/sudo_parseln/test3.in1
-rw-r--r--lib/util/regress/sudo_parseln/test3.out.ok1
-rw-r--r--lib/util/regress/sudo_parseln/test4.in4
-rw-r--r--lib/util/regress/sudo_parseln/test4.out.ok2
-rw-r--r--lib/util/regress/sudo_parseln/test5.in1
-rw-r--r--lib/util/regress/sudo_parseln/test5.out.ok0
-rw-r--r--lib/util/regress/sudo_parseln/test6.in3
-rw-r--r--lib/util/regress/sudo_parseln/test6.out.ok2
-rw-r--r--lib/util/regress/tailq/hltq_test.c199
-rw-r--r--lib/util/regress/vsyslog/vsyslog_test.c131
43 files changed, 1965 insertions, 0 deletions
diff --git a/lib/util/regress/atofoo/atofoo_test.c b/lib/util/regress/atofoo/atofoo_test.c
new file mode 100644
index 0000000..1ad78eb
--- /dev/null
+++ b/lib/util/regress/atofoo/atofoo_test.c
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 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.
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+#include "sudo_fatal.h"
+
+__dso_public int main(int argc, char *argv[]);
+
+/* sudo_strtobool() tests */
+static struct strtobool_data {
+ const char *bool_str;
+ int value;
+} strtobool_data[] = {
+ { "true", true },
+ { "false", false },
+ { "TrUe", true },
+ { "fAlSe", false },
+ { "1", true },
+ { "0", false },
+ { "on", true },
+ { "off", false },
+ { "yes", true },
+ { "no", false },
+ { "nope", -1 },
+ { "10", -1 },
+ { "one", -1 },
+ { "zero", -1 },
+ { NULL, 0 }
+};
+
+static int
+test_strtobool(int *ntests)
+{
+ struct strtobool_data *d;
+ int errors = 0;
+ int value;
+
+ for (d = strtobool_data; d->bool_str != NULL; d++) {
+ (*ntests)++;
+ value = sudo_strtobool(d->bool_str);
+ if (value != d->value) {
+ sudo_warnx_nodebug("FAIL: %s != %d", d->bool_str, d->value);
+ errors++;
+ }
+ }
+
+ return errors;
+}
+
+/* sudo_strtoid() tests */
+static struct strtoid_data {
+ const char *idstr;
+ id_t id;
+ const char *sep;
+ const char *ep;
+} strtoid_data[] = {
+ { "0,1", 0, ",", "," },
+ { "10", 10, NULL, NULL },
+ { "-2", -2, NULL, NULL },
+#if SIZEOF_ID_T != SIZEOF_LONG_LONG
+ { "-2", (id_t)4294967294U, NULL, NULL },
+#endif
+ { "4294967294", (id_t)4294967294U, NULL, NULL },
+ { NULL, 0, NULL, NULL }
+};
+
+static int
+test_strtoid(int *ntests)
+{
+ struct strtoid_data *d;
+ const char *errstr;
+ char *ep;
+ int errors = 0;
+ id_t value;
+
+ for (d = strtoid_data; d->idstr != NULL; d++) {
+ (*ntests)++;
+ errstr = "some error";
+ value = sudo_strtoid(d->idstr, d->sep, &ep, &errstr);
+ if (errstr != NULL) {
+ if (d->id != (id_t)-1) {
+ sudo_warnx_nodebug("FAIL: %s: %s", d->idstr, errstr);
+ errors++;
+ }
+ } else if (value != d->id) {
+ sudo_warnx_nodebug("FAIL: %s != %u", d->idstr, (unsigned int)d->id);
+ errors++;
+ } else if (d->ep != NULL && ep[0] != d->ep[0]) {
+ sudo_warnx_nodebug("FAIL: ep[0] %d != %d", (int)(unsigned char)ep[0],
+ (int)(unsigned char)d->ep[0]);
+ errors++;
+ }
+ }
+
+ return errors;
+}
+
+/* sudo_strtomode() tests */
+static struct strtomode_data {
+ const char *mode_str;
+ mode_t mode;
+} strtomode_data[] = {
+ { "755", 0755 },
+ { "007", 007 },
+ { "7", 7 },
+ { "8", (mode_t)-1 },
+ { NULL, 0 }
+};
+
+static int
+test_strtomode(int *ntests)
+{
+ struct strtomode_data *d;
+ const char *errstr;
+ int errors = 0;
+ mode_t mode;
+
+ for (d = strtomode_data; d->mode_str != NULL; d++) {
+ (*ntests)++;
+ errstr = "some error";
+ mode = sudo_strtomode(d->mode_str, &errstr);
+ if (errstr != NULL) {
+ if (d->mode != (mode_t)-1) {
+ sudo_warnx_nodebug("FAIL: %s: %s", d->mode_str, errstr);
+ errors++;
+ }
+ } else if (mode != d->mode) {
+ sudo_warnx_nodebug("FAIL: %s != 0%o", d->mode_str,
+ (unsigned int) d->mode);
+ errors++;
+ }
+ }
+
+ return errors;
+}
+
+/*
+ * Simple tests for sudo_strtobool(), sudo_strtoid(), sudo_strtomode().
+ */
+int
+main(int argc, char *argv[])
+{
+ int errors = 0;
+ int ntests = 0;
+
+ initprogname(argc > 0 ? argv[0] : "atofoo");
+
+ errors += test_strtobool(&ntests);
+ errors += test_strtoid(&ntests);
+ errors += test_strtomode(&ntests);
+
+ if (ntests != 0) {
+ printf("%s: %d tests run, %d errors, %d%% success rate\n",
+ getprogname(), ntests, errors, (ntests - errors) * 100 / ntests);
+ }
+
+ exit(errors);
+}
diff --git a/lib/util/regress/fnmatch/fnm_test.c b/lib/util/regress/fnmatch/fnm_test.c
new file mode 100644
index 0000000..a70a847
--- /dev/null
+++ b/lib/util/regress/fnmatch/fnm_test.c
@@ -0,0 +1,85 @@
+/* $OpenBSD: fnm_test.c,v 1.1 2008/10/01 23:04:58 millert Exp $ */
+
+/*
+ * Public domain, 2008, Todd C. Miller <Todd.Miller@sudo.ws>
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+
+#ifdef HAVE_FNMATCH
+# include <fnmatch.h>
+#else
+# include "compat/fnmatch.h"
+#endif
+
+__dso_public int main(int argc, char *argv[]);
+
+int
+main(int argc, char *argv[])
+{
+ FILE *fp = stdin;
+ char pattern[1024], string[1024], flagstr[1024];
+ int errors = 0, tests = 0, flags, got, want;
+
+ initprogname(argc > 0 ? argv[0] : "fnm_test");
+
+ if (argc > 1) {
+ if ((fp = fopen(argv[1], "r")) == NULL) {
+ perror(argv[1]);
+ exit(1);
+ }
+ }
+
+ /*
+ * Read in test file, which is formatted thusly:
+ *
+ * pattern string flags expected_result
+ *
+ */
+ for (;;) {
+ got = fscanf(fp, "%s %s %s %d\n", pattern, string, flagstr,
+ &want);
+ if (got == EOF)
+ break;
+ if (got == 4) {
+ flags = 0;
+ if (strcmp(flagstr, "FNM_NOESCAPE") == 0)
+ flags |= FNM_NOESCAPE;
+ else if (strcmp(flagstr, "FNM_PATHNAME") == 0)
+ flags |= FNM_PATHNAME;
+ else if (strcmp(flagstr, "FNM_PERIOD") == 0)
+ flags |= FNM_PERIOD;
+ else if (strcmp(flagstr, "FNM_LEADING_DIR") == 0)
+ flags |= FNM_LEADING_DIR;
+ else if (strcmp(flagstr, "FNM_CASEFOLD") == 0)
+ flags |= FNM_CASEFOLD;
+ got = fnmatch(pattern, string, flags);
+ if (got != want) {
+ fprintf(stderr,
+ "fnmatch: %s %s %d: want %d, got %d\n",
+ pattern, string, flags, want, got);
+ errors++;
+ }
+ tests++;
+ }
+ }
+ if (tests != 0) {
+ printf("fnmatch: %d test%s run, %d errors, %d%% success rate\n",
+ tests, tests == 1 ? "" : "s", errors,
+ (tests - errors) * 100 / tests);
+ }
+ exit(errors);
+}
diff --git a/lib/util/regress/fnmatch/fnm_test.in b/lib/util/regress/fnmatch/fnm_test.in
new file mode 100644
index 0000000..3f53f93
--- /dev/null
+++ b/lib/util/regress/fnmatch/fnm_test.in
@@ -0,0 +1,6 @@
+/bin/[[:alpha:][:alnum:]]* /bin/ls FNM_PATHNAME 0
+/bin/[[:alpha:][:alnum:]]* /bin/LS FNM_CASEFOLD 0
+/bin/[[:opper:][:alnum:]]* /bin/ls NONE 1
+[[:alpha:][:alnum:]]*.c foo1.c FNM_PERIOD 0
+[[:upper:]]* FOO NONE 0
+[![:space:]]* bar NONE 0
diff --git a/lib/util/regress/getgrouplist/getgrouplist_test.c b/lib/util/regress/getgrouplist/getgrouplist_test.c
new file mode 100644
index 0000000..4d44cf2
--- /dev/null
+++ b/lib/util/regress/getgrouplist/getgrouplist_test.c
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2018 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.
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif
+#include <pwd.h>
+#include <grp.h>
+
+#include "sudo_compat.h"
+#include "sudo_fatal.h"
+#include "sudo_util.h"
+
+__dso_public int main(int argc, char *argv[]);
+
+/*
+ * Test that sudo_getgrouplist2() works as expected.
+ */
+
+int
+main(int argc, char *argv[])
+{
+ int errors = 0;
+#ifndef HAVE_GETGROUPLIST_2
+ GETGROUPS_T *groups = NULL;
+ struct passwd *pw;
+ struct group *grp;
+ char *username;
+ int i, j, ntests = 0;
+ int ngroups;
+ gid_t basegid;
+ initprogname(argc > 0 ? argv[0] : "getgrouplist_test");
+
+ if ((pw = getpwuid(0)) == NULL)
+ sudo_fatal_nodebug("getpwuid(0)");
+ basegid = pw->pw_gid;
+ if ((username = strdup(pw->pw_name)) == NULL)
+ sudo_fatal_nodebug(NULL);
+
+ if (sudo_getgrouplist2(username, basegid, &groups, &ngroups) == -1)
+ sudo_fatal_nodebug("sudo_getgroulist2");
+
+ for (i = 0; i < ngroups; i++) {
+ ntests++;
+
+ /* Verify group ID exists. */
+ if ((grp = getgrgid(groups[i])) == NULL) {
+ sudo_warnx_nodebug("unable to look up group ID %u",
+ (unsigned int)groups[i]);
+ errors++;
+ continue;
+ }
+
+ /* Check user's primary gid from the passwd file. */
+ if (grp->gr_gid == basegid)
+ continue;
+
+ /* Verify group membership. */
+ for (j = 0; grp->gr_mem[j] != NULL; j++) {
+ if (strcmp(username, grp->gr_mem[j]) == 0) {
+ /* match */
+ break;
+ }
+ }
+ if (grp->gr_mem[j] == NULL) {
+ sudo_warnx_nodebug("unable to find %s in group %s",
+ username, grp->gr_name);
+ errors++;
+ continue;
+ }
+ }
+ if (errors != 0) {
+ printf("%s: %d tests run, %d errors, %d%% success rate\n",
+ getprogname(), ntests, errors, (ntests - errors) * 100 / ntests);
+ }
+#endif /* HAVE_GETGROUPLIST_2 */
+ exit(errors);
+}
diff --git a/lib/util/regress/glob/files b/lib/util/regress/glob/files
new file mode 100644
index 0000000..c5e92aa
--- /dev/null
+++ b/lib/util/regress/glob/files
@@ -0,0 +1,47 @@
+fake/bin/[
+fake/bin/cat
+fake/bin/chgrp
+fake/bin/chio
+fake/bin/chmod
+fake/bin/cksum
+fake/bin/cp
+fake/bin/cpio
+fake/bin/csh
+fake/bin/date
+fake/bin/dd
+fake/bin/df
+fake/bin/domainname
+fake/bin/echo
+fake/bin/ed
+fake/bin/eject
+fake/bin/expr
+fake/bin/hostname
+fake/bin/kill
+fake/bin/ksh
+fake/bin/ln
+fake/bin/ls
+fake/bin/md5
+fake/bin/mkdir
+fake/bin/mt
+fake/bin/mv
+fake/bin/pax
+fake/bin/ps
+fake/bin/pwd
+fake/bin/rcp
+fake/bin/rksh
+fake/bin/rm
+fake/bin/rmail
+fake/bin/rmd160
+fake/bin/rmdir
+fake/bin/sh
+fake/bin/sha1
+fake/bin/sha256
+fake/bin/sha384
+fake/bin/sha512
+fake/bin/sleep
+fake/bin/stty
+fake/bin/sum
+fake/bin/sync
+fake/bin/systrace
+fake/bin/tar
+fake/bin/test
diff --git a/lib/util/regress/glob/globtest.c b/lib/util/regress/glob/globtest.c
new file mode 100644
index 0000000..99859f0
--- /dev/null
+++ b/lib/util/regress/glob/globtest.c
@@ -0,0 +1,216 @@
+/* $OpenBSD: globtest.c,v 1.1 2008/10/01 23:04:36 millert Exp $ */
+
+/*
+ * Public domain, 2008, Todd C. Miller <Todd.Miller@sudo.ws>
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+#ifdef HAVE_GLOB
+# include <glob.h>
+#else
+# include "compat/glob.h"
+#endif
+#include <errno.h>
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+
+#define MAX_RESULTS 256
+
+struct gl_entry {
+ int flags;
+ int nresults;
+ char pattern[1024];
+ char *results[MAX_RESULTS];
+};
+
+int test_glob(struct gl_entry *);
+__dso_public int main(int argc, char *argv[]);
+
+int
+main(int argc, char **argv)
+{
+ FILE *fp = stdin;
+ char buf[2048], *cp, *ep;
+ int errors = 0, tests = 0, lineno;
+ struct gl_entry entry;
+ size_t len;
+
+ initprogname(argc > 0 ? argv[0] : "globtest");
+
+ if (argc > 1) {
+ if ((fp = fopen(argv[1], "r")) == NULL) {
+ perror(argv[1]);
+ exit(1);
+ }
+ }
+
+ /*
+ * Read in test file, which is formatted thusly:
+ *
+ * [pattern] <flags>
+ * result1
+ * result2
+ * result3
+ * ...
+ *
+ */
+ lineno = 0;
+ memset(&entry, 0, sizeof(entry));
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ lineno++;
+ len = strlen(buf);
+ if (len > 0) {
+ if (buf[len - 1] != '\n') {
+ fprintf(stderr,
+ "globtest: missing newline at EOF\n");
+ exit(1);
+ }
+ buf[--len] = '\0';
+ }
+ if (len == 0)
+ continue; /* blank line */
+
+ if (buf[0] == '[') {
+ /* check previous pattern */
+ if (entry.pattern[0]) {
+ errors += test_glob(&entry);
+ tests++;
+ }
+
+ /* start new entry */
+ if ((cp = strrchr(buf + 1, ']')) == NULL) {
+ fprintf(stderr,
+ "globtest: invalid entry on line %d\n",
+ lineno);
+ exit(1);
+ }
+ len = cp - buf - 1;
+ if (len >= sizeof(entry.pattern)) {
+ fprintf(stderr,
+ "globtest: pattern too big on line %d\n",
+ lineno);
+ exit(1);
+ }
+ memcpy(entry.pattern, buf + 1, len);
+ entry.pattern[len] = '\0';
+
+ cp += 2;
+ if (*cp++ != '<') {
+ fprintf(stderr,
+ "globtest: invalid entry on line %d\n",
+ lineno);
+ exit(1);
+ }
+ ep = strchr(cp, '>');
+ if (ep == NULL) {
+ fprintf(stderr,
+ "globtest: invalid entry on line %d\n",
+ lineno);
+ exit(1);
+ }
+ *ep = '\0';
+ entry.flags = 0;
+ for ((cp = strtok_r(cp, "|", &ep)); cp != NULL; (cp = strtok_r(NULL, "|", &ep))) {
+ if (strcmp(cp, "GLOB_APPEND") == 0)
+ entry.flags |= GLOB_APPEND;
+ else if (strcmp(cp, "GLOB_DOOFFS") == 0)
+ entry.flags |= GLOB_DOOFFS;
+ else if (strcmp(cp, "GLOB_ERR") == 0)
+ entry.flags |= GLOB_ERR;
+ else if (strcmp(cp, "GLOB_MARK") == 0)
+ entry.flags |= GLOB_MARK;
+ else if (strcmp(cp, "GLOB_NOCHECK") == 0)
+ entry.flags |= GLOB_NOCHECK;
+ else if (strcmp(cp, "GLOB_NOSORT") == 0)
+ entry.flags |= GLOB_NOSORT;
+ else if (strcmp(cp, "GLOB_NOESCAPE") == 0)
+ entry.flags |= GLOB_NOESCAPE;
+ else if (strcmp(cp, "GLOB_BRACE") == 0)
+ entry.flags |= GLOB_BRACE;
+ else if (strcmp(cp, "GLOB_TILDE") == 0)
+ entry.flags |= GLOB_TILDE;
+ else if (strcmp(cp, "NONE") != 0) {
+ fprintf(stderr,
+ "globtest: invalid flags on line %d\n",
+ lineno);
+ exit(1);
+ }
+ }
+ entry.nresults = 0;
+ continue;
+ }
+ if (!entry.pattern[0]) {
+ fprintf(stderr, "globtest: missing entry on line %d\n",
+ lineno);
+ exit(1);
+ }
+
+ if (entry.nresults + 1 > MAX_RESULTS) {
+ fprintf(stderr,
+ "globtest: too many results for %s, max %d\n",
+ entry.pattern, MAX_RESULTS);
+ exit(1);
+ }
+ entry.results[entry.nresults++] = strdup(buf);
+ }
+ if (entry.pattern[0]) {
+ errors += test_glob(&entry); /* test last pattern */
+ tests++;
+ }
+ if (tests != 0) {
+ printf("glob: %d test%s run, %d errors, %d%% success rate\n",
+ tests, tests == 1 ? "" : "s", errors,
+ (tests - errors) * 100 / tests);
+ }
+ exit(errors);
+}
+
+int test_glob(struct gl_entry *entry)
+{
+ glob_t gl;
+ char **ap;
+ int nmatches = 0, i = 0;
+
+ if (glob(entry->pattern, entry->flags, NULL, &gl) != 0) {
+ fprintf(stderr, "glob failed: %s: %s\n", entry->pattern,
+ strerror(errno));
+ exit(1);
+ }
+
+ for (ap = gl.gl_pathv; *ap != NULL; ap++)
+ nmatches++;
+
+ if (nmatches != entry->nresults)
+ goto mismatch;
+
+ for (i = 0; i < entry->nresults; i++) {
+ if (strcmp(gl.gl_pathv[i], entry->results[i]) != 0)
+ goto mismatch;
+ free(entry->results[i]);
+ }
+ return 0;
+ mismatch:
+ if (nmatches != entry->nresults) {
+ fprintf(stderr,
+ "globtest: mismatch in number of results (found %d, expected %d) for pattern %s\n",
+ nmatches, entry->nresults, entry->pattern);
+ } else {
+ fprintf(stderr, "globtest: mismatch for pattern %s, flags 0x%x "
+ "(found \"%s\", expected \"%s\")\n", entry->pattern, entry->flags,
+ gl.gl_pathv[i], entry->results[i]);
+ while (i < entry->nresults)
+ free(entry->results[i++]);
+ }
+ return 1;
+}
diff --git a/lib/util/regress/glob/globtest.in b/lib/util/regress/glob/globtest.in
new file mode 100644
index 0000000..20a86c1
--- /dev/null
+++ b/lib/util/regress/glob/globtest.in
@@ -0,0 +1,64 @@
+[fake/bin/[[:alpha:]]*] <NONE>
+fake/bin/cat
+fake/bin/chgrp
+fake/bin/chio
+fake/bin/chmod
+fake/bin/cksum
+fake/bin/cp
+fake/bin/cpio
+fake/bin/csh
+fake/bin/date
+fake/bin/dd
+fake/bin/df
+fake/bin/domainname
+fake/bin/echo
+fake/bin/ed
+fake/bin/eject
+fake/bin/expr
+fake/bin/hostname
+fake/bin/kill
+fake/bin/ksh
+fake/bin/ln
+fake/bin/ls
+fake/bin/md5
+fake/bin/mkdir
+fake/bin/mt
+fake/bin/mv
+fake/bin/pax
+fake/bin/ps
+fake/bin/pwd
+fake/bin/rcp
+fake/bin/rksh
+fake/bin/rm
+fake/bin/rmail
+fake/bin/rmd160
+fake/bin/rmdir
+fake/bin/sh
+fake/bin/sha1
+fake/bin/sha256
+fake/bin/sha384
+fake/bin/sha512
+fake/bin/sleep
+fake/bin/stty
+fake/bin/sum
+fake/bin/sync
+fake/bin/systrace
+fake/bin/tar
+fake/bin/test
+
+[fake/bin/rm{,dir,ail}] <GLOB_BRACE>
+fake/bin/rm
+fake/bin/rmdir
+fake/bin/rmail
+
+[fake/bin/sha[[:digit:]]] <NONE>
+fake/bin/sha1
+
+[fake/bin/sha[[:digit:]]*] <NONE>
+fake/bin/sha1
+fake/bin/sha256
+fake/bin/sha384
+fake/bin/sha512
+
+[fake/bin/ca[a-z]] <NONE>
+fake/bin/cat
diff --git a/lib/util/regress/mktemp/mktemp_test.c b/lib/util/regress/mktemp/mktemp_test.c
new file mode 100644
index 0000000..c8b9ceb
--- /dev/null
+++ b/lib/util/regress/mktemp/mktemp_test.c
@@ -0,0 +1,196 @@
+/*
+ * Copyright (c) 2010 Philip Guenther <guenther@openbsd.org>
+ *
+ * Public domain.
+ *
+ * Verify that mkdtemp() and mkstemps() doesn't overrun or underrun
+ * the template buffer and that it can generate names that don't
+ * contain any X's
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+#include <fcntl.h>
+#include <unistd.h>
+
+#define SUDO_ERROR_WRAP 0
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+#include "sudo_fatal.h"
+
+#ifndef MAP_ANON
+# if defined(MAP_ANONYMOUS)
+# define MAP_ANON MAP_ANONYMOUS
+# endif
+#endif
+
+#define MAX_TEMPLATE_LEN 10
+#define MAX_TRIES 100
+#define MIN_Xs 6
+
+#define SUFFIX ".suff"
+#define SLEN (sizeof SUFFIX - 1)
+
+__dso_public int main(int argc, char *argv[]);
+
+/*
+ * verify that a path generated by mkdtemp() or mkstemp() looks like a
+ * reasonable expansion of the template and matches the fd. Returns true
+ * if all the X's were replaced with non-X's
+ */
+int
+check(int fd, char const *kind, char const *path, char const *prefix,
+ size_t plen, char const *suffix, size_t slen, int tlen)
+{
+ struct stat sb, fsb;
+ char const *p;
+
+ if (tlen < MIN_Xs) {
+ if (fd != -1)
+ sudo_fatalx("%s(%s) succeed with too few Xs", kind, path);
+ if (errno != EINVAL)
+ sudo_fatal("%s(%s) failed with wrong errno: %d", kind, path, errno);
+ return 1;
+ }
+ if (fd == -1)
+ sudo_fatal("%s(%s)", kind, path);
+ if (stat(path, &sb))
+ sudo_fatal("%s: stat(%s)", kind, path);
+ if (fd >= 0) {
+ if (fstat(fd, &fsb))
+ sudo_fatal("%s: fstat(%d==%s)", kind, fd, path);
+ if (sb.st_dev != fsb.st_dev || sb.st_ino != fsb.st_ino)
+ sudo_fatalx("%s: stat mismatch", kind);
+ }
+ if (memcmp(path, prefix, plen) != 0)
+ sudo_fatalx("%s: prefix changed! %s vs %s", kind, prefix, path);
+ if (memcmp(path + plen + tlen, suffix, slen + 1) != 0)
+ sudo_fatalx("%s: suffix changed! %s vs %s", kind, suffix, path);
+ for (p = path + plen; p < path + plen + tlen; p++)
+ if (*p == '\0')
+ sudo_fatalx("%s: unexpected truncation", kind);
+ else if (*p == 'X')
+ return 0;
+ return 1;
+}
+
+void
+try_mkdtemp(char *p, char const *prefix, int len)
+{
+ size_t plen = strlen(prefix);
+ int fd, tries, ok;
+
+ for (tries = 0; tries < MAX_TRIES; tries++) {
+ memcpy(p, prefix, plen);
+ memset(p + plen, 'X', len);
+ p[plen + len] = '\0';
+ fd = mkdtemp(p) ? -2 : -1;
+ ok = check(fd, "mkdtemp", p, prefix, plen, "", 0, len);
+ rmdir(p);
+ if (ok)
+ return;
+ }
+ sudo_fatalx("mkdtemp: exceeded MAX_TRIES");
+}
+
+void
+try_mkstemps(char *p, char const *prefix, int len, char const *suffix)
+{
+ size_t plen = strlen(prefix);
+ size_t slen = strlen(suffix);
+ int tries, fd, ok;
+
+ for (tries = 0; tries < MAX_TRIES; tries++) {
+ memcpy(p, prefix, plen);
+ memset(p + plen, 'X', len);
+ memcpy(p + plen + len, suffix, slen + 1);
+ fd = mkstemps(p, slen);
+ ok = check(fd, "mkstemp", p, prefix, plen, suffix, slen, len);
+ close(fd);
+ unlink(p);
+ if (ok)
+ return;
+ }
+ sudo_fatalx("mkstemps: exceeded MAX_TRIES");
+}
+
+int
+main(int argc, char *argv[])
+{
+ char cwd[PATH_MAX + 1];
+ char *p;
+ size_t clen;
+ long pg;
+ int i;
+
+ initprogname(argc > 0 ? argv[0] : "mktemp_test");
+
+ pg = sysconf(_SC_PAGESIZE);
+ if (getcwd(cwd, sizeof cwd - 1) == NULL)
+ sudo_fatal("getcwd");
+ clen = strlen(cwd);
+ cwd[clen++] = '/';
+ cwd[clen] = '\0';
+#ifdef MAP_ANON
+ p = mmap(NULL, pg * 3, PROT_READ | PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0);
+#else
+ i = open("/dev/zero", O_RDWR);
+ if (i == -1)
+ sudo_fatal("/dev/zero");
+ p = mmap(NULL, pg * 3, PROT_READ | PROT_WRITE, MAP_PRIVATE, i, 0);
+#endif
+ if (p == MAP_FAILED)
+ sudo_fatal("mmap");
+ if (mprotect(p, pg, PROT_NONE) || mprotect(p + pg * 2, pg, PROT_NONE))
+ sudo_fatal("mprotect");
+ p += pg;
+
+ i = MAX_TEMPLATE_LEN + 1;
+ while (i-- > 0) {
+ /* try first at the start of a page, no prefix */
+ try_mkdtemp(p, "", i);
+ /* now at the end of the page, no prefix */
+ try_mkdtemp(p + pg - i - 1, "", i);
+ /* start of the page, prefixed with the cwd */
+ try_mkdtemp(p, cwd, i);
+ /* how about at the end of the page, prefixed with cwd? */
+ try_mkdtemp(p + pg - clen - i - 1, cwd, i);
+
+ /* again, with mkstemps() and an empty suffix */
+ /* try first at the start of a page, no prefix */
+ try_mkstemps(p, "", i, "");
+ /* now at the end of the page, no prefix */
+ try_mkstemps(p + pg - i - 1, "", i, "");
+ /* start of the page, prefixed with the cwd */
+ try_mkstemps(p, cwd, i, "");
+ /* how about at the end of the page, prefixed with cwd? */
+ try_mkstemps(p + pg - clen - i - 1, cwd, i, "");
+
+ /* mkstemps() and a non-empty suffix */
+ /* try first at the start of a page, no prefix */
+ try_mkstemps(p, "", i, SUFFIX);
+ /* now at the end of the page, no prefix */
+ try_mkstemps(p + pg - i - SLEN - 1, "", i, SUFFIX);
+ /* start of the page, prefixed with the cwd */
+ try_mkstemps(p, cwd, i, SUFFIX);
+ /* how about at the end of the page, prefixed with cwd? */
+ try_mkstemps(p + pg - clen - i - SLEN - 1, cwd, i, SUFFIX);
+ }
+
+ return 0;
+}
diff --git a/lib/util/regress/parse_gids/parse_gids_test.c b/lib/util/regress/parse_gids/parse_gids_test.c
new file mode 100644
index 0000000..674bd96
--- /dev/null
+++ b/lib/util/regress/parse_gids/parse_gids_test.c
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2015 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.
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif
+
+#include "sudo_compat.h"
+#include "sudo_fatal.h"
+#include "sudo_util.h"
+
+__dso_public int main(int argc, char *argv[]);
+
+/*
+ * Test that sudo_parse_gids() works as expected.
+ */
+
+struct parse_gids_test {
+ const char *gids;
+ gid_t *baseptr;
+ gid_t basegid;
+ int ngids;
+ const GETGROUPS_T *gidlist;
+};
+
+static const GETGROUPS_T test1_out[] = { 0, 1, 2, 3, 4 };
+static const GETGROUPS_T test2_out[] = { 1, 2, 3, 4 };
+static const GETGROUPS_T test3_out[] = { 0, 1, (gid_t)-2, 3, 4 };
+
+/* XXX - test syntax errors too */
+static struct parse_gids_test test_data[] = {
+ { "1,2,3,4", &test_data[0].basegid, 0, 5, test1_out },
+ { "1,2,3,4", NULL, 0, 4, test2_out },
+ { "1,-2,3,4", &test_data[2].basegid, 0, 5, test3_out },
+ { NULL, false, 0, 0, NULL }
+};
+
+static void
+dump_gids(const char *prefix, int ngids, const GETGROUPS_T *gidlist)
+{
+ int i;
+
+ fprintf(stderr, "%s: %s: ", getprogname(), prefix);
+ for (i = 0; i < ngids; i++) {
+ fprintf(stderr, "%s%d", i ? ", " : "", (int)gidlist[i]);
+ }
+ fputc('\n', stderr);
+}
+
+int
+main(int argc, char *argv[])
+{
+ GETGROUPS_T *gidlist = NULL;
+ int i, j, errors = 0, ntests = 0;
+ int ngids;
+ initprogname(argc > 0 ? argv[0] : "parse_gids_test");
+
+ for (i = 0; test_data[i].gids != NULL; i++) {
+ free(gidlist);
+ ngids = sudo_parse_gids(test_data[i].gids, test_data[i].baseptr, &gidlist);
+ if (ngids == -1)
+ exit(1); /* out of memory? */
+ ntests++;
+ if (ngids != test_data[i].ngids) {
+ sudo_warnx_nodebug("test #%d: expected %d gids, got %d",
+ ntests, test_data[i].ngids, ngids);
+ dump_gids("expected", test_data[i].ngids, test_data[i].gidlist);
+ dump_gids("received", ngids, gidlist);
+ errors++;
+ continue;
+ }
+ ntests++;
+ for (j = 0; j < ngids; j++) {
+ if (test_data[i].gidlist[j] != gidlist[j]) {
+ sudo_warnx_nodebug("test #%d: gid mismatch", ntests);
+ dump_gids("expected", test_data[i].ngids, test_data[i].gidlist);
+ dump_gids("received", ngids, gidlist);
+ errors++;
+ break;
+ }
+ }
+ }
+ if (ntests != 0) {
+ printf("%s: %d tests run, %d errors, %d%% success rate\n",
+ getprogname(), ntests, errors, (ntests - errors) * 100 / ntests);
+ }
+ exit(errors);
+}
diff --git a/lib/util/regress/progname/progname_test.c b/lib/util/regress/progname/progname_test.c
new file mode 100644
index 0000000..37c5c22
--- /dev/null
+++ b/lib/util/regress/progname/progname_test.c
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 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.
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+
+__dso_public int main(int argc, char *argv[]);
+
+/*
+ * Test that getprogname() returns the expected result.
+ * On some systems (AIX), we may have issues with symbolic links.
+ */
+
+int
+main(int argc, char *argv[])
+{
+ char *progbase = "progname_test";
+
+ if (argc > 0) {
+ if ((progbase = strrchr(argv[0], '/')) != NULL)
+ progbase++;
+ else
+ progbase = argv[0];
+ }
+ initprogname(progbase);
+
+ /* Make sure getprogname() matches basename of argv[0]. */
+ if (strcmp(getprogname(), progbase) != 0) {
+ printf("%s: FAIL: incorrect program name \"%s\"\n",
+ progbase, getprogname());
+ exit(1);
+ }
+
+ exit(0);
+}
diff --git a/lib/util/regress/strsplit/strsplit_test.c b/lib/util/regress/strsplit/strsplit_test.c
new file mode 100644
index 0000000..c9aecda
--- /dev/null
+++ b/lib/util/regress/strsplit/strsplit_test.c
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2015 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.
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif
+
+#include "sudo_compat.h"
+#include "sudo_fatal.h"
+#include "sudo_util.h"
+
+__dso_public int main(int argc, char *argv[]);
+
+/*
+ * Test that sudo_strsplit() works as expected.
+ */
+
+struct strsplit_test {
+ const char *input;
+ size_t input_len;
+ const char **output;
+};
+
+static const char test1_in[] = " vi ";
+static const char *test1_out[] = { "vi", NULL };
+static const char test2_in[] = "vi -r ";
+static const char *test2_out[] = { "vi", "-r", NULL };
+static const char test3_in[] = "vi -r -R abc\tdef ";
+static const char *test3_out[] = { "vi", "-r", "-R", "abc", "def", NULL };
+static const char test4_in[] = "vi -r -R abc\tdef ";
+static const char *test4_out[] = { "vi", "-r", "-R", "abc", NULL };
+static const char test5_in[] = "";
+static const char *test5_out[] = { NULL };
+
+static struct strsplit_test test_data[] = {
+ { test1_in, sizeof(test1_in) - 1, test1_out },
+ { test2_in, sizeof(test2_in) - 1, test2_out },
+ { test3_in, sizeof(test3_in) - 1, test3_out },
+ { test4_in, sizeof(test4_in) - 5, test4_out },
+ { test5_in, sizeof(test5_in) - 1, test5_out },
+ { NULL, 0, NULL }
+};
+
+int
+main(int argc, char *argv[])
+{
+ const char *cp, *ep, *input_end;
+ int i, j, errors = 0, ntests = 0;
+ size_t len;
+ initprogname(argc > 0 ? argv[0] : "strsplit_test");
+
+ for (i = 0; test_data[i].input != NULL; i++) {
+ input_end = test_data[i].input + test_data[i].input_len;
+ cp = sudo_strsplit(test_data[i].input, input_end, " \t", &ep);
+ for (j = 0; test_data[i].output[j] != NULL; j++) {
+ ntests++;
+ len = strlen(test_data[i].output[j]);
+ if ((size_t)(ep - cp) != len) {
+ sudo_warnx_nodebug("failed test #%d: bad length, expected "
+ "%zu, got %zu", ntests, len, (size_t)(ep - cp));
+ errors++;
+ continue;
+ }
+ ntests++;
+ if (strncmp(cp, test_data[i].output[j], len) != 0) {
+ sudo_warnx_nodebug("failed test #%d: expected %s, got %.*s",
+ ntests, test_data[i].output[j], (int)(ep - cp), cp);
+ errors++;
+ continue;
+ }
+ cp = sudo_strsplit(NULL, input_end, " \t", &ep);
+ }
+ ntests++;
+ if (cp != NULL) {
+ sudo_warnx_nodebug("failed test #%d: extra tokens \"%.*s\"",
+ ntests, (int)(input_end - cp), cp);
+ errors++;
+ }
+ }
+ if (ntests != 0) {
+ printf("%s: %d tests run, %d errors, %d%% success rate\n",
+ getprogname(), ntests, errors, (ntests - errors) * 100 / ntests);
+ }
+ exit(errors);
+}
diff --git a/lib/util/regress/sudo_conf/conf_test.c b/lib/util/regress/sudo_conf/conf_test.c
new file mode 100644
index 0000000..534b1df
--- /dev/null
+++ b/lib/util/regress/sudo_conf/conf_test.c
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2013-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.
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif
+
+#include "sudo_compat.h"
+#include "sudo_conf.h"
+#include "sudo_debug.h"
+#include "sudo_util.h"
+
+static void sudo_conf_dump(void);
+
+__dso_public int main(int argc, char *argv[]);
+
+/*
+ * Simple test driver for sudo_conf().
+ * Parses the given configuration file and dumps the resulting
+ * sudo_conf_data struct to the standard output.
+ */
+int
+main(int argc, char *argv[])
+{
+ initprogname(argc > 0 ? argv[0] : "conf_test");
+ if (argc != 2) {
+ fprintf(stderr, "usage: %s conf_file\n", getprogname());
+ exit(EXIT_FAILURE);
+ }
+ sudo_conf_clear_paths();
+ if (sudo_conf_read(argv[1], SUDO_CONF_ALL) == -1)
+ exit(EXIT_FAILURE);
+ sudo_conf_dump();
+
+ exit(EXIT_SUCCESS);
+}
+
+static void
+sudo_conf_dump(void)
+{
+ struct plugin_info_list *plugins = sudo_conf_plugins();
+ struct sudo_conf_debug_list *debug_list = sudo_conf_debugging();
+ struct sudo_conf_debug *debug_spec;
+ struct sudo_debug_file *debug_file;
+ struct plugin_info *info;
+
+ printf("Set disable_coredump %s\n",
+ sudo_conf_disable_coredump() ? "true" : "false");
+ printf("Set group_source %s\n",
+ sudo_conf_group_source() == GROUP_SOURCE_ADAPTIVE ? "adaptive" :
+ sudo_conf_group_source() == GROUP_SOURCE_STATIC ? "static" : "dynamic");
+ printf("Set max_groups %d\n", sudo_conf_max_groups());
+ if (sudo_conf_askpass_path() != NULL)
+ printf("Path askpass %s\n", sudo_conf_askpass_path());
+ if (sudo_conf_sesh_path() != NULL)
+ printf("Path sesh %s\n", sudo_conf_sesh_path());
+ if (sudo_conf_noexec_path() != NULL)
+ printf("Path noexec %s\n", sudo_conf_noexec_path());
+ if (sudo_conf_plugin_dir_path() != NULL)
+ printf("Path plugin_dir %s\n", sudo_conf_plugin_dir_path());
+ TAILQ_FOREACH(info, plugins, entries) {
+ printf("Plugin %s %s", info->symbol_name, info->path);
+ if (info->options) {
+ char * const * op;
+ for (op = info->options; *op != NULL; op++)
+ printf(" %s", *op);
+ }
+ putchar('\n');
+ }
+ TAILQ_FOREACH(debug_spec, debug_list, entries) {
+ TAILQ_FOREACH(debug_file, &debug_spec->debug_files, entries) {
+ printf("Debug %s %s %s\n", debug_spec->progname,
+ debug_file->debug_file, debug_file->debug_flags);
+ }
+ }
+}
diff --git a/lib/util/regress/sudo_conf/test1.in b/lib/util/regress/sudo_conf/test1.in
new file mode 100644
index 0000000..41282d7
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test1.in
@@ -0,0 +1,73 @@
+#
+# Sample /etc/sudo.conf file
+#
+# Format:
+# Plugin plugin_name plugin_path plugin_options ...
+# Path askpass /path/to/askpass
+# Path noexec /path/to/sudo_noexec.so
+# Debug sudo /var/log/sudo_debug all@warn
+# Set disable_coredump true
+#
+# Sudo plugins:
+#
+# The plugin_path is relative to ${prefix}/libexec unless fully qualified.
+# The plugin_name corresponds to a global symbol in the plugin
+# that contains the plugin interface structure.
+# The plugin_options are optional.
+#
+# The sudoers plugin is used by default if no Plugin lines are present.
+Plugin sudoers_policy sudoers.so
+Plugin sudoers_io sudoers.so
+
+#
+# Sudo askpass:
+#
+# An askpass helper program may be specified to provide a graphical
+# password prompt for "sudo -A" support. Sudo does not ship with its
+# own askpass program but can use the OpenSSH askpass.
+#
+# Use the OpenSSH askpass
+Path askpass /usr/X11R6/bin/ssh-askpass
+#
+# Use the Gnome OpenSSH askpass
+#Path askpass /usr/libexec/openssh/gnome-ssh-askpass
+
+#
+# Sudo noexec:
+#
+# Path to a shared library containing dummy versions of the execv(),
+# execve() and fexecve() library functions that just return an error.
+# This is used to implement the "noexec" functionality on systems that
+# support C<LD_PRELOAD> or its equivalent.
+# The compiled-in value is usually sufficient and should only be changed
+# if you rename or move the sudo_noexec.so file.
+#
+Path noexec /usr/local/libexec/sudo_noexec.so
+Path noexec /usr/libexec/sudo_noexec.so
+
+#
+# Core dumps:
+#
+# By default, sudo disables core dumps while it is executing (they
+# are re-enabled for the command that is run).
+# To aid in debugging sudo problems, you may wish to enable core
+# dumps by setting "disable_coredump" to false.
+#
+Set disable_coredump false
+
+#
+# User groups:
+#
+# Sudo passes the user's group list to the policy plugin.
+# If the user is a member of the maximum number of groups (usually 16),
+# sudo will query the group database directly to be sure to include
+# the full list of groups.
+#
+# On some systems, this can be expensive so the behavior is configurable.
+# The "group_source" setting has three possible values:
+# static - use the user's list of groups returned by the kernel.
+# dynamic - query the group database to find the list of groups.
+# adaptive - if user is in less than the maximum number of groups.
+# use the kernel list, else query the group database.
+#
+Set group_source static
diff --git a/lib/util/regress/sudo_conf/test1.out.ok b/lib/util/regress/sudo_conf/test1.out.ok
new file mode 100644
index 0000000..1880748
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test1.out.ok
@@ -0,0 +1,7 @@
+Set disable_coredump false
+Set group_source static
+Set max_groups -1
+Path askpass /usr/X11R6/bin/ssh-askpass
+Path noexec /usr/libexec/sudo_noexec.so
+Plugin sudoers_policy sudoers.so
+Plugin sudoers_io sudoers.so
diff --git a/lib/util/regress/sudo_conf/test2.in b/lib/util/regress/sudo_conf/test2.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test2.in
diff --git a/lib/util/regress/sudo_conf/test2.out.ok b/lib/util/regress/sudo_conf/test2.out.ok
new file mode 100644
index 0000000..af42145
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test2.out.ok
@@ -0,0 +1,3 @@
+Set disable_coredump true
+Set group_source adaptive
+Set max_groups -1
diff --git a/lib/util/regress/sudo_conf/test3.in b/lib/util/regress/sudo_conf/test3.in
new file mode 100644
index 0000000..b111a23
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test3.in
@@ -0,0 +1,2 @@
+Plugin sudoers_policy sudoers.so sudoers_file=/etc/sudoers sudoers_mode=0400 sudoers_gid=0 sudoers_uid=0
+Plugin sudoers_io sudoers.so
diff --git a/lib/util/regress/sudo_conf/test3.out.ok b/lib/util/regress/sudo_conf/test3.out.ok
new file mode 100644
index 0000000..819d638
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test3.out.ok
@@ -0,0 +1,5 @@
+Set disable_coredump true
+Set group_source adaptive
+Set max_groups -1
+Plugin sudoers_policy sudoers.so sudoers_file=/etc/sudoers sudoers_mode=0400 sudoers_gid=0 sudoers_uid=0
+Plugin sudoers_io sudoers.so
diff --git a/lib/util/regress/sudo_conf/test4.err.ok b/lib/util/regress/sudo_conf/test4.err.ok
new file mode 100644
index 0000000..2d68831
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test4.err.ok
@@ -0,0 +1 @@
+conf_test: invalid value for disable_coredump "foo" in regress/sudo_conf/test4.in, line 1
diff --git a/lib/util/regress/sudo_conf/test4.in b/lib/util/regress/sudo_conf/test4.in
new file mode 100644
index 0000000..a60236a
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test4.in
@@ -0,0 +1 @@
+Set disable_coredump foo
diff --git a/lib/util/regress/sudo_conf/test4.out.ok b/lib/util/regress/sudo_conf/test4.out.ok
new file mode 100644
index 0000000..af42145
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test4.out.ok
@@ -0,0 +1,3 @@
+Set disable_coredump true
+Set group_source adaptive
+Set max_groups -1
diff --git a/lib/util/regress/sudo_conf/test5.err.ok b/lib/util/regress/sudo_conf/test5.err.ok
new file mode 100644
index 0000000..85ef46b
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test5.err.ok
@@ -0,0 +1 @@
+conf_test: invalid max groups "0" in regress/sudo_conf/test5.in, line 1
diff --git a/lib/util/regress/sudo_conf/test5.in b/lib/util/regress/sudo_conf/test5.in
new file mode 100644
index 0000000..3a20495
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test5.in
@@ -0,0 +1 @@
+Set max_groups 0
diff --git a/lib/util/regress/sudo_conf/test5.out.ok b/lib/util/regress/sudo_conf/test5.out.ok
new file mode 100644
index 0000000..af42145
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test5.out.ok
@@ -0,0 +1,3 @@
+Set disable_coredump true
+Set group_source adaptive
+Set max_groups -1
diff --git a/lib/util/regress/sudo_conf/test6.in b/lib/util/regress/sudo_conf/test6.in
new file mode 100644
index 0000000..537fa57
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test6.in
@@ -0,0 +1 @@
+Set max_groups 16
diff --git a/lib/util/regress/sudo_conf/test6.out.ok b/lib/util/regress/sudo_conf/test6.out.ok
new file mode 100644
index 0000000..1f62f84
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test6.out.ok
@@ -0,0 +1,3 @@
+Set disable_coredump true
+Set group_source adaptive
+Set max_groups 16
diff --git a/lib/util/regress/sudo_conf/test7.in b/lib/util/regress/sudo_conf/test7.in
new file mode 100644
index 0000000..7438131
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test7.in
@@ -0,0 +1,4 @@
+Debug sudo /var/log/sudo_debug all@info
+Debug sudo /var/log/sudo_debug util@debug
+Debug visudo /var/log/sudo_debug match@debug
+Debug sudoers.so /var/log/sudoers_debug match@debug,nss@info
diff --git a/lib/util/regress/sudo_conf/test7.out.ok b/lib/util/regress/sudo_conf/test7.out.ok
new file mode 100644
index 0000000..5644109
--- /dev/null
+++ b/lib/util/regress/sudo_conf/test7.out.ok
@@ -0,0 +1,7 @@
+Set disable_coredump true
+Set group_source adaptive
+Set max_groups -1
+Debug sudo /var/log/sudo_debug all@info
+Debug sudo /var/log/sudo_debug util@debug
+Debug visudo /var/log/sudo_debug match@debug
+Debug sudoers.so /var/log/sudoers_debug match@debug,nss@info
diff --git a/lib/util/regress/sudo_parseln/parseln_test.c b/lib/util/regress/sudo_parseln/parseln_test.c
new file mode 100644
index 0000000..ac46dd8
--- /dev/null
+++ b/lib/util/regress/sudo_parseln/parseln_test.c
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 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.
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+
+__dso_public int main(int argc, char *argv[]);
+
+/*
+ * Simple test driver for sudo_parseln().
+ * Behaves similarly to "cat -n" but with comment removal
+ * and line continuation.
+ */
+
+int
+main(int argc, char *argv[])
+{
+ unsigned int lineno = 0;
+ size_t linesize = 0;
+ char *line = NULL;
+
+ initprogname(argc > 0 ? argv[0] : "parseln_test");
+
+ while (sudo_parseln(&line, &linesize, &lineno, stdin, 0) != -1)
+ printf("%6u\t%s\n", lineno, line);
+ free(line);
+ exit(0);
+}
diff --git a/lib/util/regress/sudo_parseln/test1.in b/lib/util/regress/sudo_parseln/test1.in
new file mode 100644
index 0000000..c605bb5
--- /dev/null
+++ b/lib/util/regress/sudo_parseln/test1.in
@@ -0,0 +1,72 @@
+#
+# Sample /etc/sudo.conf file
+#
+# Format:
+# Plugin plugin_name plugin_path plugin_options ...
+# Path askpass /path/to/askpass
+# Path noexec /path/to/sudo_noexec.so
+# Debug sudo /var/log/sudo_debug all@warn
+# Set disable_coredump true
+#
+# Sudo plugins:
+#
+# The plugin_path is relative to ${prefix}/libexec unless fully qualified.
+# The plugin_name corresponds to a global symbol in the plugin
+# that contains the plugin interface structure.
+# The plugin_options are optional.
+#
+# The sudoers plugin is used by default if no Plugin lines are present.
+Plugin sudoers_policy sudoers.so
+Plugin sudoers_io sudoers.so
+
+#
+# Sudo askpass:
+#
+# An askpass helper program may be specified to provide a graphical
+# password prompt for "sudo -A" support. Sudo does not ship with its
+# own askpass program but can use the OpenSSH askpass.
+#
+# Use the OpenSSH askpass
+#Path askpass /usr/X11R6/bin/ssh-askpass
+#
+# Use the Gnome OpenSSH askpass
+#Path askpass /usr/libexec/openssh/gnome-ssh-askpass
+
+#
+# Sudo noexec:
+#
+# Path to a shared library containing dummy versions of the execv(),
+# execve() and fexecve() library functions that just return an error.
+# This is used to implement the "noexec" functionality on systems that
+# support C<LD_PRELOAD> or its equivalent.
+# The compiled-in value is usually sufficient and should only be changed
+# if you rename or move the sudo_noexec.so file.
+#
+#Path noexec /usr/libexec/sudo_noexec.so
+
+#
+# Core dumps:
+#
+# By default, sudo disables core dumps while it is executing (they
+# are re-enabled for the command that is run).
+# To aid in debugging sudo problems, you may wish to enable core
+# dumps by setting "disable_coredump" to false.
+#
+#Set disable_coredump false
+
+#
+# User groups:
+#
+# Sudo passes the user's group list to the policy plugin.
+# If the user is a member of the maximum number of groups (usually 16),
+# sudo will query the group database directly to be sure to include
+# the full list of groups.
+#
+# On some systems, this can be expensive so the behavior is configurable.
+# The "group_source" setting has three possible values:
+# static - use the user's list of groups returned by the kernel.
+# dynamic - query the group database to find the list of groups.
+# adaptive - if user is in less than the maximum number of groups.
+# use the kernel list, else query the group database.
+#
+#Set group_source static
diff --git a/lib/util/regress/sudo_parseln/test1.out.ok b/lib/util/regress/sudo_parseln/test1.out.ok
new file mode 100644
index 0000000..c98ca77
--- /dev/null
+++ b/lib/util/regress/sudo_parseln/test1.out.ok
@@ -0,0 +1,72 @@
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19 Plugin sudoers_policy sudoers.so
+ 20 Plugin sudoers_io sudoers.so
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
diff --git a/lib/util/regress/sudo_parseln/test2.in b/lib/util/regress/sudo_parseln/test2.in
new file mode 100644
index 0000000..49166ee
--- /dev/null
+++ b/lib/util/regress/sudo_parseln/test2.in
@@ -0,0 +1,8 @@
+this \
+is all \
+one line
+# this is a comment, and does not get continued\
+trim the \
+ leading \
+ white \
+space
diff --git a/lib/util/regress/sudo_parseln/test2.out.ok b/lib/util/regress/sudo_parseln/test2.out.ok
new file mode 100644
index 0000000..d921968
--- /dev/null
+++ b/lib/util/regress/sudo_parseln/test2.out.ok
@@ -0,0 +1,3 @@
+ 3 this is all one line
+ 4
+ 8 trim the leading white space
diff --git a/lib/util/regress/sudo_parseln/test3.in b/lib/util/regress/sudo_parseln/test3.in
new file mode 100644
index 0000000..e372c07
--- /dev/null
+++ b/lib/util/regress/sudo_parseln/test3.in
@@ -0,0 +1 @@
+line continuation at EOF \
diff --git a/lib/util/regress/sudo_parseln/test3.out.ok b/lib/util/regress/sudo_parseln/test3.out.ok
new file mode 100644
index 0000000..2e8d16d
--- /dev/null
+++ b/lib/util/regress/sudo_parseln/test3.out.ok
@@ -0,0 +1 @@
+ 1 line continuation at EOF
diff --git a/lib/util/regress/sudo_parseln/test4.in b/lib/util/regress/sudo_parseln/test4.in
new file mode 100644
index 0000000..3583f3b
--- /dev/null
+++ b/lib/util/regress/sudo_parseln/test4.in
@@ -0,0 +1,4 @@
+line contin\
+uation raw
+line contin\
+ uation indented
diff --git a/lib/util/regress/sudo_parseln/test4.out.ok b/lib/util/regress/sudo_parseln/test4.out.ok
new file mode 100644
index 0000000..38afbeb
--- /dev/null
+++ b/lib/util/regress/sudo_parseln/test4.out.ok
@@ -0,0 +1,2 @@
+ 2 line continuation raw
+ 4 line continuation indented
diff --git a/lib/util/regress/sudo_parseln/test5.in b/lib/util/regress/sudo_parseln/test5.in
new file mode 100644
index 0000000..57ddad2
--- /dev/null
+++ b/lib/util/regress/sudo_parseln/test5.in
@@ -0,0 +1 @@
+\
diff --git a/lib/util/regress/sudo_parseln/test5.out.ok b/lib/util/regress/sudo_parseln/test5.out.ok
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lib/util/regress/sudo_parseln/test5.out.ok
diff --git a/lib/util/regress/sudo_parseln/test6.in b/lib/util/regress/sudo_parseln/test6.in
new file mode 100644
index 0000000..95cac84
--- /dev/null
+++ b/lib/util/regress/sudo_parseln/test6.in
@@ -0,0 +1,3 @@
+ leading and trailing white space
+ # a comment
+\
diff --git a/lib/util/regress/sudo_parseln/test6.out.ok b/lib/util/regress/sudo_parseln/test6.out.ok
new file mode 100644
index 0000000..340765e
--- /dev/null
+++ b/lib/util/regress/sudo_parseln/test6.out.ok
@@ -0,0 +1,2 @@
+ 1 leading and trailing white space
+ 2
diff --git a/lib/util/regress/tailq/hltq_test.c b/lib/util/regress/tailq/hltq_test.c
new file mode 100644
index 0000000..5333a15
--- /dev/null
+++ b/lib/util/regress/tailq/hltq_test.c
@@ -0,0 +1,199 @@
+/*
+ * Copyright (c) 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.
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif
+
+#include "sudo_compat.h"
+#include "sudo_fatal.h"
+#include "sudo_queue.h"
+#include "sudo_util.h"
+
+__dso_public int main(int argc, char *argv[]);
+
+/*
+ * Note: HLTQ_ENTRY is intentionally in the middle of the struct
+ * to catch bad assumptions in the PREV/NEXT macros.
+ */
+struct test_data {
+ int a;
+ HLTQ_ENTRY(test_data) entries;
+ char b;
+};
+
+TAILQ_HEAD(test_data_list, test_data);
+
+/*
+ * Simple tests for headless tail queue macros.
+ */
+int
+main(int argc, char *argv[])
+{
+ struct test_data d1, d2, d3;
+ struct test_data *hltq;
+ struct test_data_list tq;
+ int errors = 0;
+ int ntests = 0;
+
+ initprogname(argc > 0 ? argv[0] : "hltq_test");
+
+ /*
+ * Initialize three data elements and concatenate them in order.
+ */
+ HLTQ_INIT(&d1, entries);
+ d1.a = 1;
+ d1.b = 'a';
+ if (HLTQ_FIRST(&d1) != &d1) {
+ sudo_warnx_nodebug("FAIL: HLTQ_FIRST(1 entry) doesn't return first element: got %p, expected %p", HLTQ_FIRST(&d1), &d1);
+ errors++;
+ }
+ ntests++;
+ if (HLTQ_LAST(&d1, test_data, entries) != &d1) {
+ sudo_warnx_nodebug("FAIL: HLTQ_LAST(1 entry) doesn't return first element: got %p, expected %p", HLTQ_LAST(&d1, test_data, entries), &d1);
+ errors++;
+ }
+ ntests++;
+ if (HLTQ_PREV(&d1, test_data, entries) != NULL) {
+ sudo_warnx_nodebug("FAIL: HLTQ_PREV(1 entry) doesn't return NULL: got %p", HLTQ_PREV(&d1, test_data, entries));
+ errors++;
+ }
+ ntests++;
+
+ HLTQ_INIT(&d2, entries);
+ d2.a = 2;
+ d2.b = 'b';
+
+ HLTQ_INIT(&d3, entries);
+ d3.a = 3;
+ d3.b = 'c';
+
+ HLTQ_CONCAT(&d1, &d2, entries);
+ HLTQ_CONCAT(&d1, &d3, entries);
+ hltq = &d1;
+
+ /*
+ * Verify that HLTQ_FIRST, HLTQ_LAST, HLTQ_NEXT, HLTQ_PREV
+ * work as expected.
+ */
+ if (HLTQ_FIRST(hltq) != &d1) {
+ sudo_warnx_nodebug("FAIL: HLTQ_FIRST(3 entries) doesn't return first element: got %p, expected %p", HLTQ_FIRST(hltq), &d1);
+ errors++;
+ }
+ ntests++;
+ if (HLTQ_LAST(hltq, test_data, entries) != &d3) {
+ sudo_warnx_nodebug("FAIL: HLTQ_LAST(3 entries) doesn't return third element: got %p, expected %p", HLTQ_LAST(hltq, test_data, entries), &d3);
+ errors++;
+ }
+ ntests++;
+
+ if (HLTQ_NEXT(&d1, entries) != &d2) {
+ sudo_warnx_nodebug("FAIL: HLTQ_NEXT(&d1) doesn't return &d2: got %p, expected %p", HLTQ_NEXT(&d1, entries), &d2);
+ errors++;
+ }
+ ntests++;
+ if (HLTQ_NEXT(&d2, entries) != &d3) {
+ sudo_warnx_nodebug("FAIL: HLTQ_NEXT(&d2) doesn't return &d3: got %p, expected %p", HLTQ_NEXT(&d2, entries), &d3);
+ errors++;
+ }
+ ntests++;
+ if (HLTQ_NEXT(&d3, entries) != NULL) {
+ sudo_warnx_nodebug("FAIL: HLTQ_NEXT(&d3) doesn't return NULL: got %p", HLTQ_NEXT(&d3, entries));
+ errors++;
+ }
+ ntests++;
+
+ if (HLTQ_PREV(&d1, test_data, entries) != NULL) {
+ sudo_warnx_nodebug("FAIL: HLTQ_PREV(&d1) doesn't return NULL: got %p", HLTQ_PREV(&d1, test_data, entries));
+ errors++;
+ }
+ ntests++;
+ if (HLTQ_PREV(&d2, test_data, entries) != &d1) {
+ sudo_warnx_nodebug("FAIL: HLTQ_PREV(&d2) doesn't return &d1: got %p, expected %p", HLTQ_PREV(&d2, test_data, entries), &d1);
+ errors++;
+ }
+ ntests++;
+ if (HLTQ_PREV(&d3, test_data, entries) != &d2) {
+ sudo_warnx_nodebug("FAIL: HLTQ_PREV(&d3) doesn't return &d2: got %p, expected %p", HLTQ_PREV(&d3, test_data, entries), &d2);
+ errors++;
+ }
+ ntests++;
+
+ /* Test conversion to TAILQ. */
+ HLTQ_TO_TAILQ(&tq, hltq, entries);
+
+ if (TAILQ_FIRST(&tq) != &d1) {
+ sudo_warnx_nodebug("FAIL: TAILQ_FIRST(&tq) doesn't return first element: got %p, expected %p", TAILQ_FIRST(&tq), &d1);
+ errors++;
+ }
+ ntests++;
+ if (TAILQ_LAST(&tq, test_data_list) != &d3) {
+ sudo_warnx_nodebug("FAIL: TAILQ_LAST(&tq) doesn't return third element: got %p, expected %p", TAILQ_LAST(&tq, test_data_list), &d3);
+ errors++;
+ }
+ ntests++;
+
+ if (TAILQ_NEXT(&d1, entries) != &d2) {
+ sudo_warnx_nodebug("FAIL: TAILQ_NEXT(&d1) doesn't return &d2: got %p, expected %p", TAILQ_NEXT(&d1, entries), &d2);
+ errors++;
+ }
+ ntests++;
+ if (TAILQ_NEXT(&d2, entries) != &d3) {
+ sudo_warnx_nodebug("FAIL: TAILQ_NEXT(&d2) doesn't return &d3: got %p, expected %p", TAILQ_NEXT(&d2, entries), &d3);
+ errors++;
+ }
+ ntests++;
+ if (TAILQ_NEXT(&d3, entries) != NULL) {
+ sudo_warnx_nodebug("FAIL: TAILQ_NEXT(&d3) doesn't return NULL: got %p", TAILQ_NEXT(&d3, entries));
+ errors++;
+ }
+ ntests++;
+
+ if (TAILQ_PREV(&d1, test_data_list, entries) != NULL) {
+ sudo_warnx_nodebug("FAIL: TAILQ_PREV(&d1) doesn't return NULL: got %p", TAILQ_PREV(&d1, test_data_list, entries));
+ errors++;
+ }
+ ntests++;
+ if (TAILQ_PREV(&d2, test_data_list, entries) != &d1) {
+ sudo_warnx_nodebug("FAIL: TAILQ_PREV(&d2) doesn't return &d1: got %p, expected %p", TAILQ_PREV(&d2, test_data_list, entries), &d1);
+ errors++;
+ }
+ ntests++;
+ if (TAILQ_PREV(&d3, test_data_list, entries) != &d2) {
+ sudo_warnx_nodebug("FAIL: TAILQ_PREV(&d3) doesn't return &d2: got %p, expected %p", TAILQ_PREV(&d3, test_data_list, entries), &d2);
+ errors++;
+ }
+ ntests++;
+
+ printf("%s: %d tests run, %d errors, %d%% success rate\n", getprogname(),
+ ntests, errors, (ntests - errors) * 100 / ntests);
+
+ exit(errors);
+}
diff --git a/lib/util/regress/vsyslog/vsyslog_test.c b/lib/util/regress/vsyslog/vsyslog_test.c
new file mode 100644
index 0000000..70a8d0d
--- /dev/null
+++ b/lib/util/regress/vsyslog/vsyslog_test.c
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2017 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.
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif
+#include <errno.h>
+
+#include "sudo_compat.h"
+#include "sudo_fatal.h"
+#include "sudo_util.h"
+
+__dso_public int main(int argc, char *argv[]);
+
+/*
+ * Test that sudo_vsyslog() works as expected.
+ */
+static char *expected_result;
+static int errors;
+static int ntests;
+
+/*
+ * Dummy version of syslog to verify the message
+ */
+void
+syslog(int priority, const char *fmt, ...)
+{
+ va_list ap;
+ const char *msg;
+
+ if (strcmp(fmt, "%s") != 0)
+ sudo_fatalx_nodebug("Expected syslog format \"%%s\", got \"%s\"", fmt);
+
+ va_start(ap, fmt);
+ msg = va_arg(ap, char *);
+ if (strcmp(msg, expected_result) != 0) {
+ sudo_warnx_nodebug("Expected \"%s\", got \"%s\"", expected_result, msg);
+ errors++;
+ } else {
+ ntests++;
+ }
+ va_end(ap);
+}
+
+static void
+test_vsyslog(int priority, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ sudo_vsyslog(priority, fmt, ap);
+ va_end(ap);
+}
+
+int
+main(int argc, char *argv[])
+{
+ char buf1[1024 * 16], buf2[1024 * 16];
+ initprogname(argc > 0 ? argv[0] : "vsyslog_test");
+
+ /* Test small buffer. */
+ expected_result = "sudo: millert : TTY=ttypa ; PWD=/etc/mail ; USER=root ; TSID=000AB0 ; COMMAND=/usr/sbin/newaliases";
+ test_vsyslog(0,
+ "%s: %s : TTY=%s ; PWD=%s ; USER=%s ; TSID=%s ; COMMAND=%s",
+ "sudo", "millert", "ttypa", "/etc/mail", "root", "000AB0",
+ "/usr/sbin/newaliases");
+
+ /* Test small buffer w/ errno. */
+ snprintf(buf1, sizeof(buf1),
+ "unable to open %s: %s", "/var/log/sudo-io/seq", strerror(ENOENT));
+ expected_result = buf1;
+ errno = ENOENT;
+ test_vsyslog(0, "unable to open %s: %m", "/var/log/sudo-io/seq");
+
+ /* Test large buffer > 8192 bytes. */
+ memset(buf1, 'a', 8192);
+ buf1[8192] = '\0';
+ expected_result = buf1;
+ test_vsyslog(0, "%s", buf1);
+
+ /* Test large buffer w/ errno > 8192 bytes. */
+ memset(buf1, 'b', 8184);
+ buf1[8184] = '\0';
+ snprintf(buf2, sizeof(buf2), "%s: %s", buf1, strerror(EINVAL));
+ expected_result = buf2;
+ errno = EINVAL;
+ test_vsyslog(0, "%s: %m", buf1);
+
+ /* Test large format string > 8192 bytes, expect truncation to 2048. */
+ memset(buf1, 'b', 8184);
+ buf1[8184] = '\0';
+ snprintf(buf2, sizeof(buf2), "%.*s", 2047, buf1);
+ expected_result = buf2;
+ test_vsyslog(0, buf1);
+
+ if (ntests != 0) {
+ printf("%s: %d tests run, %d errors, %d%% success rate\n",
+ getprogname(), ntests, errors, (ntests - errors) * 100 / ntests);
+ } else {
+ printf("%s: error, no tests run!\n", getprogname());
+ errors = 1;
+ }
+ exit(errors);
+}