summaryrefslogtreecommitdiffstats
path: root/lib/iolog/regress
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 12:52:13 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 12:52:13 +0000
commitf8e5c55a036f0e2e2a958e30456270f3f9eba933 (patch)
tree4a06ff510774a7a3373e492df4e2984d7b0664b1 /lib/iolog/regress
parentInitial commit. (diff)
downloadsudo-f8e5c55a036f0e2e2a958e30456270f3f9eba933.tar.xz
sudo-f8e5c55a036f0e2e2a958e30456270f3f9eba933.zip
Adding upstream version 1.9.5p2.upstream/1.9.5p2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--lib/iolog/regress/host_port/host_port_test.c145
-rw-r--r--lib/iolog/regress/iolog_json/check_iolog_json.c265
-rw-r--r--lib/iolog/regress/iolog_json/test1.in34
-rw-r--r--lib/iolog/regress/iolog_json/test2.in28
-rw-r--r--lib/iolog/regress/iolog_json/test2.out.ok34
-rw-r--r--lib/iolog/regress/iolog_json/test3.in22
-rw-r--r--lib/iolog/regress/iolog_mkpath/check_iolog_mkpath.c91
-rw-r--r--lib/iolog/regress/iolog_path/check_iolog_path.c273
-rw-r--r--lib/iolog/regress/iolog_path/data96
-rw-r--r--lib/iolog/regress/iolog_util/check_iolog_util.c148
10 files changed, 1136 insertions, 0 deletions
diff --git a/lib/iolog/regress/host_port/host_port_test.c b/lib/iolog/regress/host_port/host_port_test.c
new file mode 100644
index 0000000..109393a
--- /dev/null
+++ b/lib/iolog/regress/host_port/host_port_test.c
@@ -0,0 +1,145 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2019-2020 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# include "compat/stdbool.h"
+#endif
+#include <time.h>
+#include <unistd.h>
+
+#include "sudo_compat.h"
+#include "sudo_fatal.h"
+#include "sudo_iolog.h"
+#include "sudo_util.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+/*
+ * Test that iolog_parse_host_port() works as expected.
+ */
+
+struct host_port_test {
+ const char *str; /* input string */
+ const char *host; /* parsed host */
+ const char *port; /* parsed port */
+ bool tls; /* parsed TLS flag */
+ char *defport; /* default port */
+ char *defport_tls; /* default port */
+ bool ret; /* return value */
+};
+
+static struct host_port_test test_data[] = {
+ /* No TLS */
+ { "xerxes", "xerxes", "12345", false, "12345", NULL, true },
+ { "xerxes:12345", "xerxes", "12345", false, "67890", NULL, true },
+ { "127.0.0.1", "127.0.0.1", "12345", false, "12345", NULL, true },
+ { "127.0.0.1:12345", "127.0.0.1", "12345", false, "67890", NULL, true },
+ { "[::1]", "::1", "12345", false, "12345", NULL, true },
+ { "[::1]:12345", "::1", "12345", false, "67890", NULL, true },
+
+ /* With TLS */
+ { "xerxes(tls)", "xerxes", "12345", true, "5678", "12345", true },
+ { "xerxes:12345(tls)", "xerxes", "12345", true, "5678", "67890", true },
+ { "127.0.0.1(tls)", "127.0.0.1", "12345", true, "5678", "12345", true },
+ { "127.0.0.1:12345(tls)", "127.0.0.1", "12345", true, "5678", "67890", true },
+ { "[::1](tls)", "::1", "12345", true, "5678", "12345", true },
+ { "[::1]:12345(tls)", "::1", "12345", true, "5678", "67890", true },
+
+ /* Errors */
+ { "xerxes:", NULL, NULL, false, "12345", NULL, false }, /* missing port */
+ { "127.0.0.1:", NULL, NULL, false, "12345", NULL, false }, /* missing port */
+ { "[::1:12345", NULL, NULL, false, "67890", NULL, false }, /* missing bracket */
+ { "[::1]:", NULL, NULL, false, "12345", NULL, false }, /* missing port */
+ { NULL }
+};
+
+int
+main(int argc, char *argv[])
+{
+ int i, errors = 0, ntests = 0;
+ char *host, *port, *copy = NULL;
+ bool ret, tls;
+
+ initprogname(argc > 0 ? argv[0] : "host_port_test");
+
+ for (i = 0; test_data[i].str != NULL; i++) {
+ host = port = NULL;
+ tls = false;
+ free(copy);
+ if ((copy = strdup(test_data[i].str)) == NULL)
+ sudo_fatal_nodebug(NULL);
+
+ ntests++;
+ ret = iolog_parse_host_port(copy, &host, &port, &tls,
+ test_data[i].defport, test_data[i].defport_tls);
+ if (ret != test_data[i].ret) {
+ sudo_warnx_nodebug("test #%d: %s: returned %s, expected %s",
+ ntests, test_data[i].str, ret ? "true" : "false",
+ test_data[i].ret ? "true" : "false");
+ errors++;
+ continue;
+ }
+ if (!ret)
+ continue;
+
+ if (host == NULL) {
+ sudo_warnx_nodebug("test #%d: %s: NULL host",
+ ntests, test_data[i].str);
+ errors++;
+ continue;
+ }
+ if (strcmp(host, test_data[i].host) != 0) {
+ sudo_warnx_nodebug("test #%d: %s: bad host, expected %s, got %s",
+ ntests, test_data[i].str, test_data[i].host, host);
+ errors++;
+ continue;
+ }
+ if (port == NULL) {
+ sudo_warnx_nodebug("test #%d: %s: NULL port",
+ ntests, test_data[i].str);
+ errors++;
+ continue;
+ }
+ if (strcmp(port, test_data[i].port) != 0) {
+ sudo_warnx_nodebug("test #%d: %s: bad port, expected %s, got %s",
+ ntests, test_data[i].str, test_data[i].port, port);
+ errors++;
+ continue;
+ }
+ if (tls != test_data[i].tls) {
+ sudo_warnx_nodebug("test #%d: %s: bad tls, expected %s, got %s",
+ ntests, test_data[i].str, test_data[i].tls ? "true" : "false",
+ tls ? "true" : "false");
+ errors++;
+ continue;
+ }
+ }
+ free(copy);
+ 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/iolog/regress/iolog_json/check_iolog_json.c b/lib/iolog/regress/iolog_json/check_iolog_json.c
new file mode 100644
index 0000000..a967ba6
--- /dev/null
+++ b/lib/iolog/regress/iolog_json/check_iolog_json.c
@@ -0,0 +1,265 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2020 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <unistd.h>
+
+#define SUDO_ERROR_WRAP 0
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+#include "sudo_fatal.h"
+
+#include "iolog_json.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+bool
+json_print_object(struct json_container *json, struct json_object *object)
+{
+ struct json_item *item;
+ struct json_value json_value;
+ bool ret = false;
+
+ TAILQ_FOREACH(item, &object->items, entries) {
+ switch (item->type) {
+ case JSON_STRING:
+ json_value.type = JSON_STRING;
+ json_value.u.string = item->u.string;
+ if (!sudo_json_add_value(json, item->name, &json_value))
+ goto oom;
+ break;
+ case JSON_NUMBER:
+ json_value.type = JSON_NUMBER;
+ json_value.u.number = item->u.number;
+ if (!sudo_json_add_value(json, item->name, &json_value))
+ goto oom;
+ break;
+ case JSON_OBJECT:
+ if (!sudo_json_open_object(json, item->name))
+ goto oom;
+ if (!json_print_object(json, &item->u.child))
+ goto done;
+ if (!sudo_json_close_object(json))
+ goto oom;
+ break;
+ case JSON_ARRAY:
+ if (!sudo_json_open_array(json, item->name))
+ goto oom;
+ if (!json_print_object(json, &item->u.child))
+ goto done;
+ if (!sudo_json_close_array(json))
+ goto oom;
+ break;
+ case JSON_BOOL:
+ json_value.type = JSON_BOOL;
+ json_value.u.boolean = item->u.boolean;
+ if (!sudo_json_add_value(json, item->name, &json_value))
+ goto oom;
+ break;
+ case JSON_NULL:
+ json_value.type = JSON_NULL;
+ if (!sudo_json_add_value(json, item->name, &json_value))
+ goto oom;
+ break;
+ default:
+ sudo_warnx("unsupported JSON type %d", item->type);
+ goto done;
+ }
+ }
+
+ ret = true;
+ goto done;
+
+oom:
+ sudo_warnx("%s: %s", __func__, "unable to allocate memory");
+done:
+ return ret;
+}
+
+static bool
+json_format(struct json_container *json, struct json_object *object)
+{
+ struct json_item *item;
+ bool ret = false;
+
+ /* First object holds all the actual data. */
+ item = TAILQ_FIRST(&object->items);
+ if (item->type != JSON_OBJECT) {
+ sudo_warnx("expected JSON_OBJECT, got %d", item->type);
+ goto done;
+ }
+ object = &item->u.child;
+
+ if (!json_print_object(json, object))
+ goto done;
+
+ ret = true;
+
+done:
+ return ret;
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: %s [-c] input_file ...\n",
+ getprogname());
+ exit(EXIT_FAILURE);
+}
+
+static bool
+compare(FILE *fp, const char *infile, struct json_container *json)
+{
+ const char *cp;
+ unsigned int lineno = 0;
+ size_t linesize = 0;
+ char *line = NULL;
+ ssize_t len;
+
+ cp = sudo_json_get_buf(json);
+
+ while ((len = getdelim(&line, &linesize, '\n', fp)) != -1) {
+ lineno++;
+
+ /* skip open/close brace, not present in formatted output */
+ if (lineno == 1 && strcmp(line, "{\n") == 0)
+ continue;
+ if (*cp == '\0' && strcmp(line, "}\n") == 0)
+ continue;
+
+ /* Ignore newlines in output to make comparison easier. */
+ if (*cp == '\n')
+ cp++;
+ if (line[len - 1] == '\n')
+ len--;
+
+ if (strncmp(line, cp, len) != 0) {
+ fprintf(stderr, "%s: mismatch on line %u\n", infile, lineno);
+ fprintf(stderr, "expected: %s", line);
+ fprintf(stderr, "got : %.*s\n", (int)len, cp);
+ return false;
+ }
+ cp += len;
+ }
+ free(line);
+
+ return true;
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct json_object root;
+ int ch, i, tests = 0, errors = 0;
+ bool cat = false;
+
+ initprogname(argc > 0 ? argv[0] : "check_iolog_json");
+
+ while ((ch = getopt(argc, argv, "c")) != -1) {
+ switch (ch) {
+ case 'c':
+ cat = true;
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1)
+ usage();
+
+ for (i = 0; i < argc; i++) {
+ struct json_container json;
+ const char *infile = argv[i];
+ const char *outfile = argv[i];
+ const char *cp;
+ char pathbuf[PATH_MAX];
+ FILE *infp = NULL;
+ FILE *outfp = NULL;
+
+ tests++;
+
+ if (!sudo_json_init(&json, 4, false, true)) {
+ errors++;
+ continue;
+ }
+
+ /* Parse input file. */
+ if ((infp = fopen(infile, "r")) == NULL) {
+ sudo_warn("%s", argv[1]);
+ errors++;
+ goto next;
+ }
+ if (!iolog_parse_json(infp, infile, &root)) {
+ errors++;
+ goto next;
+ }
+
+ /* Format as pretty-printed JSON */
+ if (!json_format(&json, &root)) {
+ errors++;
+ goto next;
+ }
+
+ /* Check for a .out.ok file in the same location as the .in file. */
+ cp = strrchr(infile, '.');
+ if (cp != NULL && strcmp(cp, ".in") == 0) {
+ snprintf(pathbuf, sizeof(pathbuf), "%.*s.out.ok",
+ (int)(cp - infile), infile);
+ if ((outfp = fopen(pathbuf, "r")) != NULL)
+ outfile = pathbuf;
+ }
+ if (outfp == NULL)
+ outfp = infp;
+
+ /* Compare output to expected output. */
+ rewind(outfp);
+ if (!compare(outfp, outfile, &json))
+ errors++;
+
+ /* Write the formatted output to stdout for -c (cat) */
+ if (cat) {
+ fprintf(stdout, "{%s\n}\n", sudo_json_get_buf(&json));
+ fflush(stdout);
+ }
+
+next:
+ free_json_items(&root.items);
+ sudo_json_free(&json);
+ if (infp != NULL)
+ fclose(infp);
+ if (outfp != NULL && outfp != infp)
+ fclose(outfp);
+ }
+
+ if (tests != 0) {
+ printf("iolog_json: %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/iolog/regress/iolog_json/test1.in b/lib/iolog/regress/iolog_json/test1.in
new file mode 100644
index 0000000..8ad3689
--- /dev/null
+++ b/lib/iolog/regress/iolog_json/test1.in
@@ -0,0 +1,34 @@
+{
+ "timestamp": {
+ "seconds": 1584993067,
+ "nanoseconds": 880288287
+ },
+ "columns": 80,
+ "command": "/usr/bin/make",
+ "lines": 24,
+ "runargv": [
+ "make",
+ "test"
+ ],
+ "runenv": [
+ "LANG=en_US.UTF-8",
+ "PATH=/bin:/sbin:/usr/games:/usr/bin:/usr/sbin:/usr/X11R6/bin:/usr/local/bin:/usr/local/sbin",
+ "TERM=vt100",
+ "MAIL=/var/mail/root",
+ "LOGNAME=root",
+ "USER=root",
+ "HOME=/root",
+ "SHELL=/bin/ksh",
+ "SUDO_COMMAND=/usr/bin/make test",
+ "SUDO_USER=millert",
+ "SUDO_UID=8036",
+ "SUDO_GID=20",
+ "A__z=\"*SHLVL"
+ ],
+ "runuid": 0,
+ "runuser": "root",
+ "submitcwd": "/home/test",
+ "submithost": "sudo.ws",
+ "submituser": "millert",
+ "ttyname": "/dev/console"
+}
diff --git a/lib/iolog/regress/iolog_json/test2.in b/lib/iolog/regress/iolog_json/test2.in
new file mode 100644
index 0000000..df7170f
--- /dev/null
+++ b/lib/iolog/regress/iolog_json/test2.in
@@ -0,0 +1,28 @@
+{
+ "timestamp": { "seconds": 1584993067, "nanoseconds": 880288287 },
+ "columns": 80,
+ "command": "/usr/bin/make",
+ "lines": 24,
+ "runargv": [ "make", "test" ],
+ "runenv": [
+ "LANG=en_US.UTF-8",
+ "PATH=/bin:/sbin:/usr/games:/usr/bin:/usr/sbin:/usr/X11R6/bin:/usr/local/bin:/usr/local/sbin",
+ "TERM=vt100",
+ "MAIL=/var/mail/root",
+ "LOGNAME=root",
+ "USER=root",
+ "HOME=/root",
+ "SHELL=/bin/ksh",
+ "SUDO_COMMAND=/usr/bin/make test",
+ "SUDO_USER=millert",
+ "SUDO_UID=8036",
+ "SUDO_GID=20",
+ "A__z=\"*SHLVL"
+ ],
+ "runuid": 0,
+ "runuser": "root",
+ "submitcwd": "/home/test",
+ "submithost": "sudo.ws",
+ "submituser": "millert",
+ "ttyname": "/dev/console"
+}
diff --git a/lib/iolog/regress/iolog_json/test2.out.ok b/lib/iolog/regress/iolog_json/test2.out.ok
new file mode 100644
index 0000000..8ad3689
--- /dev/null
+++ b/lib/iolog/regress/iolog_json/test2.out.ok
@@ -0,0 +1,34 @@
+{
+ "timestamp": {
+ "seconds": 1584993067,
+ "nanoseconds": 880288287
+ },
+ "columns": 80,
+ "command": "/usr/bin/make",
+ "lines": 24,
+ "runargv": [
+ "make",
+ "test"
+ ],
+ "runenv": [
+ "LANG=en_US.UTF-8",
+ "PATH=/bin:/sbin:/usr/games:/usr/bin:/usr/sbin:/usr/X11R6/bin:/usr/local/bin:/usr/local/sbin",
+ "TERM=vt100",
+ "MAIL=/var/mail/root",
+ "LOGNAME=root",
+ "USER=root",
+ "HOME=/root",
+ "SHELL=/bin/ksh",
+ "SUDO_COMMAND=/usr/bin/make test",
+ "SUDO_USER=millert",
+ "SUDO_UID=8036",
+ "SUDO_GID=20",
+ "A__z=\"*SHLVL"
+ ],
+ "runuid": 0,
+ "runuser": "root",
+ "submitcwd": "/home/test",
+ "submithost": "sudo.ws",
+ "submituser": "millert",
+ "ttyname": "/dev/console"
+}
diff --git a/lib/iolog/regress/iolog_json/test3.in b/lib/iolog/regress/iolog_json/test3.in
new file mode 100644
index 0000000..ea2df89
--- /dev/null
+++ b/lib/iolog/regress/iolog_json/test3.in
@@ -0,0 +1,22 @@
+{
+ "true": false,
+ "false": true,
+ "number": 1234567890,
+ "null": null,
+ "string": "nonsense",
+ "scope": {
+ "a": "b",
+ "bah": null
+ },
+ "array1": [
+ "foo",
+ "bar",
+ [
+ 123,
+ null,
+ false,
+ "fizz",
+ "buzz"
+ ]
+ ]
+}
diff --git a/lib/iolog/regress/iolog_mkpath/check_iolog_mkpath.c b/lib/iolog/regress/iolog_mkpath/check_iolog_mkpath.c
new file mode 100644
index 0000000..bb13787
--- /dev/null
+++ b/lib/iolog/regress/iolog_mkpath/check_iolog_mkpath.c
@@ -0,0 +1,91 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2020 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/wait.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#define SUDO_ERROR_WRAP 0
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+#include "sudo_fatal.h"
+#include "sudo_iolog.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+static const char *test_paths[] = {
+ "testdir/a/b/c/user", /* create new */
+ "testdir/a/b/c/user", /* open existing */
+ "testdir/a/b/c/user.XXXXXX", /* mkdtemp new */
+ NULL
+};
+
+static void
+test_iolog_mkpath(const char *testdir, int *ntests, int *nerrors)
+{
+ const char **tp;
+ char *path;
+
+ iolog_set_owner(geteuid(), getegid());
+
+ for (tp = test_paths; *tp != NULL; tp++) {
+ if (asprintf(&path, "%s/%s", testdir, *tp) == -1)
+ sudo_fatalx("unable to allocate memory");
+
+ (*ntests)++;
+ if (!iolog_mkpath(path)) {
+ sudo_warnx("unable to mkpath %s", path);
+ (*nerrors)++;
+ }
+ free(path);
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ char testdir[] = "mkpath.XXXXXX";
+ char *rmargs[] = { "rm", "-rf", NULL, NULL };
+ int status, tests = 0, errors = 0;
+
+ initprogname(argc > 0 ? argv[0] : "check_iolog_mkpath");
+
+ if (mkdtemp(testdir) == NULL)
+ sudo_fatal("unable to create test dir");
+ rmargs[2] = testdir;
+
+ test_iolog_mkpath(testdir, &tests, &errors);
+
+ if (tests != 0) {
+ printf("iolog_mkpath: %d test%s run, %d errors, %d%% success rate\n",
+ tests, tests == 1 ? "" : "s", errors,
+ (tests - errors) * 100 / tests);
+ }
+
+ /* Clean up (avoid running via shell) */
+ execvp("rm", rmargs);
+ wait(&status);
+
+ exit(errors);
+}
diff --git a/lib/iolog/regress/iolog_path/check_iolog_path.c b/lib/iolog/regress/iolog_path/check_iolog_path.c
new file mode 100644
index 0000000..8598384
--- /dev/null
+++ b/lib/iolog/regress/iolog_path/check_iolog_path.c
@@ -0,0 +1,273 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2011-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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <time.h>
+#include <unistd.h>
+
+#define SUDO_ERROR_WRAP 0
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+#include "sudo_fatal.h"
+#include "sudo_iolog.h"
+
+static struct iolog_escape_data {
+ char sessid[7];
+ char *user;
+ char *group;
+ char *runas_user;
+ char *runas_group;
+ char *host;
+ char *command;
+} escape_data;
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: %s datafile\n", getprogname());
+ exit(EXIT_FAILURE);
+}
+
+static void
+reset_escape_data(struct iolog_escape_data *data)
+{
+ free(data->user);
+ free(data->group);
+ free(data->runas_user);
+ free(data->runas_group);
+ free(data->host);
+ free(data->command);
+ memset(data, 0, sizeof(*data));
+}
+
+static size_t
+fill_seq(char *str, size_t strsize, void *unused)
+{
+ int len;
+
+ /* Path is of the form /var/log/sudo-io/00/00/01. */
+ len = snprintf(str, strsize, "%c%c/%c%c/%c%c", escape_data.sessid[0],
+ escape_data.sessid[1], escape_data.sessid[2], escape_data.sessid[3],
+ escape_data.sessid[4], escape_data.sessid[5]);
+ if (len < 0)
+ return strsize; /* handle non-standard snprintf() */
+ return len;
+}
+
+static size_t
+fill_user(char *str, size_t strsize, void *unused)
+{
+ return strlcpy(str, escape_data.user, strsize);
+}
+
+static size_t
+fill_group(char *str, size_t strsize, void *unused)
+{
+ return strlcpy(str, escape_data.group, strsize);
+}
+
+static size_t
+fill_runas_user(char *str, size_t strsize, void *unused)
+{
+ return strlcpy(str, escape_data.runas_user, strsize);
+}
+
+static size_t
+fill_runas_group(char *str, size_t strsize, void *unused)
+{
+ return strlcpy(str, escape_data.runas_group, strsize);
+}
+
+static size_t
+fill_hostname(char *str, size_t strsize, void *unused)
+{
+ return strlcpy(str, escape_data.host, strsize);
+}
+
+static size_t
+fill_command(char *str, size_t strsize, void *unused)
+{
+ return strlcpy(str, escape_data.command, strsize);
+}
+
+/* Note: "seq" must be first in the list. */
+static struct iolog_path_escape path_escapes[] = {
+ { "seq", fill_seq },
+ { "user", fill_user },
+ { "group", fill_group },
+ { "runas_user", fill_runas_user },
+ { "runas_group", fill_runas_group },
+ { "hostname", fill_hostname },
+ { "command", fill_command },
+ { NULL, NULL }
+};
+
+static int
+do_check(char *dir_in, char *file_in, char *tdir_out, char *tfile_out)
+{
+ char dir[PATH_MAX], dir_out[PATH_MAX];
+ char file[PATH_MAX], file_out[PATH_MAX];
+ struct tm *timeptr;
+ time_t now;
+ int error = 0;
+
+ /*
+ * Expand any strftime(3) escapes
+ * XXX - want to pass timeptr to expand_iolog_path
+ */
+ time(&now);
+ timeptr = localtime(&now);
+ if (timeptr == NULL)
+ sudo_fatalx("localtime returned NULL");
+ strftime(dir_out, sizeof(dir_out), tdir_out, timeptr);
+ strftime(file_out, sizeof(file_out), tfile_out, timeptr);
+
+ if (!expand_iolog_path(dir_in, dir, sizeof(dir), &path_escapes[1], NULL))
+ sudo_fatalx("unable to expand I/O log dir");
+ if (!expand_iolog_path(file_in, file, sizeof(file), &path_escapes[0], dir))
+ sudo_fatalx("unable to expand I/O log file");
+
+ if (strcmp(dir, dir_out) != 0) {
+ sudo_warnx("%s: expected %s, got %s", dir_in, dir_out, dir);
+ error = 1;
+ }
+ if (strcmp(file, file_out) != 0) {
+ sudo_warnx("%s: expected %s, got %s", file_in, file_out, file);
+ error = 1;
+ }
+
+ return error;
+}
+
+#define MAX_STATE 12
+
+int
+main(int argc, char *argv[])
+{
+ size_t len;
+ FILE *fp;
+ char line[2048];
+ char *file_in = NULL, *file_out = NULL;
+ char *dir_in = NULL, *dir_out = NULL;
+ int state = 0;
+ int errors = 0;
+ int tests = 0;
+
+ initprogname(argc > 0 ? argv[0] : "check_iolog_path");
+
+ if (argc != 2)
+ usage();
+
+ fp = fopen(argv[1], "r");
+ if (fp == NULL)
+ sudo_fatalx("unable to open %s", argv[1]);
+
+ /*
+ * Input consists of 12 lines:
+ * sequence number
+ * user name
+ * user gid
+ * runas user name
+ * runas gid
+ * hostname [short form]
+ * command
+ * dir [with escapes]
+ * file [with escapes]
+ * expanded dir
+ * expanded file
+ * empty line
+ */
+ while (fgets(line, sizeof(line), fp) != NULL) {
+ len = strcspn(line, "\n");
+ line[len] = '\0';
+
+ switch (state) {
+ case 0:
+ strlcpy(escape_data.sessid, line, sizeof(escape_data.sessid));
+ break;
+ case 1:
+ if ((escape_data.user = strdup(line)) == NULL)
+ sudo_fatal(NULL);
+ break;
+ case 2:
+ if ((escape_data.group = strdup(line)) == NULL)
+ sudo_fatal(NULL);
+ break;
+ case 3:
+ if ((escape_data.runas_user = strdup(line)) == NULL)
+ sudo_fatal(NULL);
+ break;
+ case 4:
+ if ((escape_data.runas_group = strdup(line)) == NULL)
+ sudo_fatal(NULL);
+ break;
+ case 5:
+ if ((escape_data.host = strdup(line)) == NULL)
+ sudo_fatal(NULL);
+ break;
+ case 6:
+ if ((escape_data.command = strdup(line)) == NULL)
+ sudo_fatal(NULL);
+ break;
+ case 7:
+ if (dir_in != NULL)
+ free(dir_in);
+ dir_in = strdup(line);
+ break;
+ case 8:
+ if (file_in != NULL)
+ free(file_in);
+ file_in = strdup(line);
+ break;
+ case 9:
+ if (dir_out != NULL)
+ free(dir_out);
+ dir_out = strdup(line);
+ break;
+ case 10:
+ if (file_out != NULL)
+ free(file_out);
+ file_out = strdup(line);
+ break;
+ case 11:
+ errors += do_check(dir_in, file_in, dir_out, file_out);
+ tests++;
+ reset_escape_data(&escape_data);
+ break;
+ default:
+ sudo_fatalx("internal error, invalid state %d", state);
+ }
+ state = (state + 1) % MAX_STATE;
+ }
+
+ if (tests != 0) {
+ printf("iolog_path: %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/iolog/regress/iolog_path/data b/lib/iolog/regress/iolog_path/data
new file mode 100644
index 0000000..48dc87e
--- /dev/null
+++ b/lib/iolog/regress/iolog_path/data
@@ -0,0 +1,96 @@
+000001
+nobody
+nogroup
+root
+root
+somehost
+id
+/var/log/sudo-io
+%%{bogus}
+/var/log/sudo-io
+%%{bogus}
+
+000001
+nobody
+nogroup
+root
+wheel
+somehost
+id
+/var/log/sudo-io
+%%{seq}
+/var/log/sudo-io
+%%{seq}
+
+000001
+nobody
+nogroup
+root
+wheel
+somehost
+id
+/var/log/sudo-io
+%{seq}
+/var/log/sudo-io
+00/00/01
+
+000001
+nobody
+nogroup
+root
+wheel
+somehost
+id
+/var/log/sudo-io/%{user}
+%{seq}
+/var/log/sudo-io/nobody
+00/00/01
+
+000001
+nobody
+nogroup
+root
+wheel
+somehost
+su
+/var/log/sudo-io/%{user}/%{runas_user}
+%{command}_%Y%m%s_%H%M
+/var/log/sudo-io/nobody/root
+su_%Y%m%s_%H%M
+
+000001
+nobody
+nogroup
+root
+wheel
+somehost
+su
+/var/log/sudo-io/
+//%{user}/%{runas_user}/%{command}_%Y%m%s_%H%M
+/var/log/sudo-io
+/nobody/root/su_%Y%m%s_%H%M
+
+000001
+nobody
+nogroup
+root
+wheel
+somehost
+su
+/var/log/sudo-io/%d%m%Y
+%{user}/%{runas_user}/%{command}
+/var/log/sudo-io/%d%m%Y
+nobody/root/su
+
+000001
+nobody
+nogroup
+root
+wheel
+somehost
+su
+////////
+%{user}/%{runas_user}/%{command}
+
+nobody/root/su
+
diff --git a/lib/iolog/regress/iolog_util/check_iolog_util.c b/lib/iolog/regress/iolog_util/check_iolog_util.c
new file mode 100644
index 0000000..de94ee1
--- /dev/null
+++ b/lib/iolog/regress/iolog_util/check_iolog_util.c
@@ -0,0 +1,148 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#define SUDO_ERROR_WRAP 0
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+#include "sudo_fatal.h"
+#include "sudo_iolog.h"
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+static struct parse_delay_test {
+ const char *input;
+ const char *next_field;
+ struct timespec expected_delay;
+} parse_delay_tests[] = {
+ { "10.99999999999 X", "X", { 10, 999999999 } }, /* clamp to nsec */
+ { "10.999999999 X", "X", { 10, 999999999 } }, /* nsec */
+ { "10.999999 X", "X", { 10, 999999000 } }, /* usec -> nsec */
+ { "10.000999999 X", "X", { 10, 999999 } },
+ { "10.9 X", "X", { 10, 900000000 } },
+ { "10.0 X", "X", { 10, 0 } }
+};
+
+/*
+ * Test iolog_parse_delay()
+ */
+void
+test_parse_delay(int *ntests, int *nerrors)
+{
+ unsigned int i;
+
+ for (i = 0; i < nitems(parse_delay_tests); i++) {
+ struct timespec delay;
+ struct parse_delay_test *test = &parse_delay_tests[i];
+ char *cp = iolog_parse_delay(test->input, &delay, ".");
+ if (cp == NULL) {
+ sudo_warnx("%s:%u failed to parse delay: %s", __func__,
+ i, test->input);
+ (*nerrors)++;
+ continue;
+ }
+ if (strcmp(cp, test->next_field) != 0) {
+ sudo_warnx("%s:%u next field (want \"%s\", got \"%s\"", __func__,
+ i, test->next_field, cp);
+ (*nerrors)++;
+ continue;
+ }
+ if (delay.tv_sec != test->expected_delay.tv_sec) {
+ sudo_warnx("%s:%u wrong seconds (want %lld, got %lld)", __func__,
+ i, (long long)test->expected_delay.tv_sec,
+ (long long)delay.tv_sec);
+ (*nerrors)++;
+ continue;
+ }
+ if (delay.tv_nsec != test->expected_delay.tv_nsec) {
+ sudo_warnx("%s:%u wrong nanoseconds (want %ld, got %ld)", __func__,
+ i, test->expected_delay.tv_nsec, delay.tv_nsec);
+ (*nerrors)++;
+ continue;
+ }
+ }
+ (*ntests) += i;
+}
+
+static struct adjust_delay_test {
+ struct timespec in_delay;
+ struct timespec out_delay;
+ struct timespec max_delay;
+ double scale_factor;
+} adjust_delay_tests[] = {
+ { { 10, 300 }, { 10, 300 }, { 0, 0 }, 1.0 },
+ { { 10, 300 }, { 5, 150 }, { 0, 0 }, 2.0 },
+ { { 5, 300 }, { 2, 500000150 }, { 0, 0 }, 2.0 },
+ { { 0, 1000000 }, { 0, 333333 }, { 0, 0 }, 3 },
+ { { 10, 1000000 }, { 3, 333666666 }, { 0, 0 }, 3 },
+ { { 5, 150 }, { 10, 300 }, { 0, 0 }, 0.5 },
+ { { 5, 500000000 }, { 11, 0 }, { 0, 0 }, 0.5 },
+ { { 5, 150 }, { 5, 0 }, { 5, 0 }, 0.5 }
+};
+
+/*
+ * Test iolog_adjust_delay()
+ */
+void
+test_adjust_delay(int *ntests, int *nerrors)
+{
+ unsigned int i;
+
+ for (i = 0; i < nitems(adjust_delay_tests); i++) {
+ struct adjust_delay_test *test = &adjust_delay_tests[i];
+
+ iolog_adjust_delay(&test->in_delay,
+ sudo_timespecisset(&test->max_delay) ? &test->max_delay : NULL,
+ test->scale_factor);
+ if (!sudo_timespeccmp(&test->in_delay, &test->out_delay, ==)) {
+ sudo_warnx("%s:%u want {%lld, %ld}, got {%lld, %ld}", __func__, i,
+ (long long)test->out_delay.tv_sec, test->out_delay.tv_nsec,
+ (long long)test->in_delay.tv_sec, test->in_delay.tv_nsec);
+ (*nerrors)++;
+ }
+ }
+ (*ntests) += i;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int tests = 0, errors = 0;
+
+ initprogname(argc > 0 ? argv[0] : "check_iolog_util");
+
+ test_parse_delay(&tests, &errors);
+
+ test_adjust_delay(&tests, &errors);
+
+ if (tests != 0) {
+ printf("iolog_util: %d test%s run, %d errors, %d%% success rate\n",
+ tests, tests == 1 ? "" : "s", errors,
+ (tests - errors) * 100 / tests);
+ }
+
+ exit(errors);
+}