summaryrefslogtreecommitdiffstats
path: root/lib/eventlog/regress
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 13:14:46 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 13:14:46 +0000
commit025c439e829e0db9ac511cd9c1b8d5fd53475ead (patch)
treefa6986b4690f991613ffb97cea1f6942427baf5d /lib/eventlog/regress
parentInitial commit. (diff)
downloadsudo-025c439e829e0db9ac511cd9c1b8d5fd53475ead.tar.xz
sudo-025c439e829e0db9ac511cd9c1b8d5fd53475ead.zip
Adding upstream version 1.9.15p5.upstream/1.9.15p5upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'lib/eventlog/regress')
-rw-r--r--lib/eventlog/regress/eventlog_store/store_json_test.c199
-rw-r--r--lib/eventlog/regress/eventlog_store/store_sudo_test.c209
-rw-r--r--lib/eventlog/regress/eventlog_store/test1.json.in52
-rw-r--r--lib/eventlog/regress/eventlog_store/test1.json.out.ok31
-rw-r--r--lib/eventlog/regress/eventlog_store/test1.sudo.out.ok2
-rw-r--r--lib/eventlog/regress/eventlog_store/test2.json.in48
-rw-r--r--lib/eventlog/regress/eventlog_store/test2.json.out.ok29
-rw-r--r--lib/eventlog/regress/eventlog_store/test2.sudo.out.ok2
-rw-r--r--lib/eventlog/regress/eventlog_store/test3.json.in49
-rw-r--r--lib/eventlog/regress/eventlog_store/test3.json.out.ok30
-rw-r--r--lib/eventlog/regress/eventlog_store/test3.sudo.out.ok2
-rw-r--r--lib/eventlog/regress/eventlog_store/test4.json.in47
-rw-r--r--lib/eventlog/regress/eventlog_store/test4.json.out.ok31
-rw-r--r--lib/eventlog/regress/eventlog_store/test4.sudo.out.ok2
-rw-r--r--lib/eventlog/regress/logwrap/check_wrap.c124
-rw-r--r--lib/eventlog/regress/logwrap/check_wrap.in4
-rw-r--r--lib/eventlog/regress/logwrap/check_wrap.out.ok179
-rw-r--r--lib/eventlog/regress/parse_json/check_parse_json.c270
-rw-r--r--lib/eventlog/regress/parse_json/test1.in34
-rw-r--r--lib/eventlog/regress/parse_json/test2.in28
-rw-r--r--lib/eventlog/regress/parse_json/test2.out.ok34
-rw-r--r--lib/eventlog/regress/parse_json/test3.in22
-rw-r--r--lib/eventlog/regress/parse_json/test3.out.ok22
23 files changed, 1450 insertions, 0 deletions
diff --git a/lib/eventlog/regress/eventlog_store/store_json_test.c b/lib/eventlog/regress/eventlog_store/store_json_test.c
new file mode 100644
index 0000000..5c8f6fe
--- /dev/null
+++ b/lib/eventlog/regress/eventlog_store/store_json_test.c
@@ -0,0 +1,199 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2020, 2023 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#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_eventlog.h>
+#include <sudo_fatal.h>
+#include <sudo_util.h>
+
+#include <parse_json.h>
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+sudo_noreturn static void
+usage(void)
+{
+ fprintf(stderr, "usage: %s [-cv] input_file ...\n",
+ getprogname());
+ exit(EXIT_FAILURE);
+}
+
+static bool
+compare(FILE *fp, const char *infile, struct json_container *jsonc)
+{
+ const char *cp;
+ unsigned int lineno = 0;
+ size_t linesize = 0;
+ char *line = NULL;
+ ssize_t len;
+
+ cp = sudo_json_get_buf(jsonc);
+
+ 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, (size_t)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[])
+{
+ int ch, i, ntests = 0, errors = 0;
+ bool cat = false;
+
+ initprogname(argc > 0 ? argv[0] : "store_json_test");
+
+ while ((ch = getopt(argc, argv, "cv")) != -1) {
+ switch (ch) {
+ case 'c':
+ cat = true;
+ break;
+ case 'v':
+ /* ignored */
+ break;
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1)
+ usage();
+
+ for (i = 0; i < argc; i++) {
+ struct eventlog_json_object *root;
+ struct eventlog *evlog = NULL;
+ struct json_container jsonc;
+ const char *infile = argv[i];
+ const char *outfile = argv[i];
+ const char *cp;
+ char pathbuf[PATH_MAX];
+ FILE *infp = NULL;
+ FILE *outfp = NULL;
+
+ ntests++;
+
+ if (!sudo_json_init(&jsonc, 4, false, true, true)) {
+ errors++;
+ continue;
+ }
+
+ /* Parse input file. */
+ if ((infp = fopen(infile, "r")) == NULL) {
+ sudo_warn("%s", argv[i]);
+ errors++;
+ continue;
+ }
+ root = eventlog_json_read(infp, infile);
+ if (root == NULL) {
+ errors++;
+ goto next;
+ }
+
+ /* Convert JSON to event log. */
+ evlog = calloc(1, sizeof(*evlog));
+ if (evlog == NULL) {
+ sudo_warnx("%s: %s", __func__, "unable to allocate memory");
+ errors++;
+ goto next;
+ }
+ if (!eventlog_json_parse(root, evlog)) {
+ errors++;
+ goto next;
+ }
+
+ /* Format event log as JSON. */
+ if (!eventlog_store_json(&jsonc, evlog)) {
+ 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, &jsonc))
+ errors++;
+
+ /* Write the formatted output to stdout for -c (cat) */
+ if (cat) {
+ fprintf(stdout, "{%s\n}\n", sudo_json_get_buf(&jsonc));
+ fflush(stdout);
+ }
+
+next:
+ eventlog_free(evlog);
+ eventlog_json_free(root);
+ sudo_json_free(&jsonc);
+ if (infp != NULL)
+ fclose(infp);
+ if (outfp != NULL && outfp != infp)
+ fclose(outfp);
+ }
+
+ if (ntests != 0) {
+ printf("%s: %d test%s run, %d errors, %d%% success rate\n",
+ getprogname(), ntests, ntests == 1 ? "" : "s", errors,
+ (ntests - errors) * 100 / ntests);
+ }
+
+ return errors;
+}
diff --git a/lib/eventlog/regress/eventlog_store/store_sudo_test.c b/lib/eventlog/regress/eventlog_store/store_sudo_test.c
new file mode 100644
index 0000000..f873cd5
--- /dev/null
+++ b/lib/eventlog/regress/eventlog_store/store_sudo_test.c
@@ -0,0 +1,209 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2020, 2023 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#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_eventlog.h>
+#include <sudo_fatal.h>
+#include <sudo_lbuf.h>
+#include <sudo_util.h>
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+sudo_noreturn static void
+usage(void)
+{
+ fprintf(stderr, "usage: %s [-cv] input_file ...\n",
+ getprogname());
+ exit(EXIT_FAILURE);
+}
+
+static bool
+compare(FILE *fp, const char *infile, const char *output)
+{
+ static size_t linesize = 0;
+ static char *line = NULL;
+ int i;
+
+ /* Expect two log lines, one for accept, one for exit. */
+ for (i = 0; i < 2; i++) {
+ ssize_t output_len = (ssize_t)strcspn(output, "\n");
+ ssize_t len = getdelim(&line, &linesize, '\n', fp);
+ if (len == -1) {
+ sudo_warn("getdelim");
+ return false;
+ }
+ if (line[len - 1] == '\n')
+ len--;
+
+ if (len != output_len || strncmp(line, output, (size_t)len) != 0) {
+ fprintf(stderr, "%s: %s mismatch\n", infile, i ? "exit" : "accept");
+ fprintf(stderr, "expected: %.*s\n", (int)len, line);
+ fprintf(stderr, "got : %.*s\n", (int)output_len, output);
+ return false;
+ }
+ if (i == 0) {
+ /* Skip past newline in accept record output. */
+ output += output_len;
+ if (output[0] != '\n' || output[1] == '\0') {
+ sudo_warnx("missing exit record");
+ return false;
+ }
+ output++;
+ }
+ }
+
+ return true;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int ch, i, ntests = 0, errors = 0;
+ struct sudo_lbuf lbuf;
+ bool cat = false;
+
+ initprogname(argc > 0 ? argv[0] : "store_sudo_test");
+
+ while ((ch = getopt(argc, argv, "v")) != -1) {
+ switch (ch) {
+ case 'c':
+ cat = true;
+ break;
+ case 'v':
+ /* ignored */
+ break;
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1)
+ usage();
+
+ sudo_lbuf_init(&lbuf, NULL, 0, NULL, 0);
+ for (i = 0; i < argc; i++) {
+ struct eventlog_json_object *root;
+ struct eventlog *evlog = NULL;
+ const char *infile = argv[i];
+ const char *outfile = argv[i];
+ char pathbuf[PATH_MAX];
+ FILE *infp = NULL;
+ FILE *outfp = NULL;
+ size_t len;
+
+ ntests++;
+
+ /* Parse input file. */
+ if ((infp = fopen(infile, "r")) == NULL) {
+ sudo_warn("%s", argv[i]);
+ errors++;
+ continue;
+ }
+ root = eventlog_json_read(infp, infile);
+ if (root == NULL) {
+ errors++;
+ goto next;
+ }
+
+ /* Convert JSON to event log. */
+ evlog = calloc(1, sizeof(*evlog));
+ if (evlog == NULL) {
+ sudo_warnx("%s: %s", __func__, "unable to allocate memory");
+ errors++;
+ goto next;
+ }
+ if (!eventlog_json_parse(root, evlog)) {
+ errors++;
+ goto next;
+ }
+
+ /* Format event log in sudo log format. */
+ if (!eventlog_store_sudo(EVLOG_ACCEPT, evlog, &lbuf)) {
+ errors++;
+ goto next;
+ }
+ sudo_lbuf_append(&lbuf, "\n");
+ if (!eventlog_store_sudo(EVLOG_EXIT, evlog, &lbuf)) {
+ errors++;
+ goto next;
+ }
+ sudo_lbuf_append(&lbuf, "\n");
+
+ /* Write the formatted output to stdout for -c (cat) */
+ if (cat) {
+ puts(lbuf.buf);
+ fflush(stdout);
+ }
+
+ /* Check for a .out.ok file in the same location as the .in file. */
+ len = strlen(infile);
+ if (len < sizeof(".json.in")) {
+ sudo_warnx("%s must end in .json.in", infile);
+ errors++;
+ goto next;
+ }
+ len -= sizeof(".json.in") - 1;
+ if (strcmp(&infile[len], ".json.in") != 0) {
+ sudo_warnx("%s must end in .json.in", infile);
+ errors++;
+ goto next;
+ }
+ snprintf(pathbuf, sizeof(pathbuf), "%.*s.sudo.out.ok",
+ (int)len, infile);
+ if ((outfp = fopen(pathbuf, "r")) == NULL) {
+ sudo_warn("%s", pathbuf);
+ errors++;
+ goto next;
+ }
+
+ /* Compare output to expected output. */
+ if (!compare(outfp, outfile, lbuf.buf))
+ errors++;
+
+next:
+ lbuf.len = 0;
+ eventlog_json_free(root);
+ eventlog_free(evlog);
+ if (infp != NULL)
+ fclose(infp);
+ if (outfp != NULL && outfp != infp)
+ fclose(outfp);
+ }
+ sudo_lbuf_destroy(&lbuf);
+
+ if (ntests != 0) {
+ printf("%s: %d test%s run, %d errors, %d%% success rate\n",
+ getprogname(), ntests, ntests == 1 ? "" : "s", errors,
+ (ntests - errors) * 100 / ntests);
+ }
+
+ return errors;
+}
diff --git a/lib/eventlog/regress/eventlog_store/test1.json.in b/lib/eventlog/regress/eventlog_store/test1.json.in
new file mode 100644
index 0000000..0e73df9
--- /dev/null
+++ b/lib/eventlog/regress/eventlog_store/test1.json.in
@@ -0,0 +1,52 @@
+{
+ "uuid": "94109a6eb8-9bed-41ba-0ff1-79926f3947",
+ "server_time": {
+ "seconds": 1677638673,
+ "nanoseconds": 98769807,
+ "iso8601": "20230301024433Z",
+ "localtime": "Tue Feb 28 19:44:33 MST 2023"
+ },
+ "submit_time": {
+ "seconds": 1677638673,
+ "nanoseconds": 93789768,
+ "iso8601": "20230301024433Z",
+ "localtime": "Tue Feb 28 19:44:33 MST 2023"
+ },
+ "peeraddr": "172.30.200.2",
+ "iolog_path": "/var/log/sudo-logsrvd/millert/00/03/FI",
+ "iolog_file": "00/03/FI",
+ "columns": 80,
+ "command": "/usr/bin/ci",
+ "lines": 24,
+ "runargv": [
+ "ci",
+ "-u",
+ "aliases\n"
+ ],
+ "runenv": [
+ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
+ "TERM=tmux",
+ "LANG=en_US.UTF-8",
+ "MAIL=/var/mail/root",
+ "LOGNAME=root",
+ "USER=root",
+ "HOME=/root",
+ "SHELL=/bin/bash",
+ "SUDO_COMMAND=/usr/bin/ci -u aliases",
+ "SUDO_USER=millert",
+ "SUDO_UID=8036",
+ "SUDO_GID=20"
+ ],
+ "runenv_override": [
+ "KRB5CCNAME=bogus",
+ "LD_LIBRARY_PATH=/opt/sudo/libexec"
+ ],
+ "runuid": 0,
+ "runuser": "root",
+ "source": "/etc/sudoers:89:24",
+ "submitcwd": "/etc/mail",
+ "submithost": "xerxes.sudo.ws",
+ "submituser": "millert",
+ "ttyname": "/dev/ttypb",
+ "exit_value": 1
+}
diff --git a/lib/eventlog/regress/eventlog_store/test1.json.out.ok b/lib/eventlog/regress/eventlog_store/test1.json.out.ok
new file mode 100644
index 0000000..2ddc8c5
--- /dev/null
+++ b/lib/eventlog/regress/eventlog_store/test1.json.out.ok
@@ -0,0 +1,31 @@
+{
+ "submituser": "millert",
+ "command": "/usr/bin/ci",
+ "runuser": "root",
+ "source": "/etc/sudoers:89:24",
+ "ttyname": "/dev/ttypb",
+ "submithost": "xerxes.sudo.ws",
+ "submitcwd": "/etc/mail",
+ "runuid": 0,
+ "columns": 80,
+ "lines": 24,
+ "runargv": [
+ "ci",
+ "-u",
+ "aliases\n"
+ ],
+ "runenv": [
+ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
+ "TERM=tmux",
+ "LANG=en_US.UTF-8",
+ "MAIL=/var/mail/root",
+ "LOGNAME=root",
+ "USER=root",
+ "HOME=/root",
+ "SHELL=/bin/bash",
+ "SUDO_COMMAND=/usr/bin/ci -u aliases",
+ "SUDO_USER=millert",
+ "SUDO_UID=8036",
+ "SUDO_GID=20"
+ ]
+}
diff --git a/lib/eventlog/regress/eventlog_store/test1.sudo.out.ok b/lib/eventlog/regress/eventlog_store/test1.sudo.out.ok
new file mode 100644
index 0000000..17c344e
--- /dev/null
+++ b/lib/eventlog/regress/eventlog_store/test1.sudo.out.ok
@@ -0,0 +1,2 @@
+HOST=xerxes.sudo.ws ; TTY=ttypb ; CWD=/etc/mail ; USER=root ; TSID=0003FI ; ENV=KRB5CCNAME=bogus LD_LIBRARY_PATH=/opt/sudo/libexec ; COMMAND=/usr/bin/ci -u aliases#012
+HOST=xerxes.sudo.ws ; TTY=ttypb ; CWD=/etc/mail ; USER=root ; TSID=0003FI ; ENV=KRB5CCNAME=bogus LD_LIBRARY_PATH=/opt/sudo/libexec ; COMMAND=/usr/bin/ci -u aliases#012 ; EXIT=1
diff --git a/lib/eventlog/regress/eventlog_store/test2.json.in b/lib/eventlog/regress/eventlog_store/test2.json.in
new file mode 100644
index 0000000..6af3a31
--- /dev/null
+++ b/lib/eventlog/regress/eventlog_store/test2.json.in
@@ -0,0 +1,48 @@
+{
+ "uuid": "a17521dd52-1b7f-4ca1-5086-6957336362",
+ "server_time": {
+ "seconds": 1671070212,
+ "nanoseconds": 246006550,
+ "iso8601": "20221215021012Z",
+ "localtime": "Wed Dec 14 19:10:12 MST 2022"
+ },
+ "submit_time": {
+ "seconds": 1671070212,
+ "nanoseconds": 243017337,
+ "iso8601": "20221215021012Z",
+ "localtime": "Wed Dec 14 19:10:12 MST 2022"
+ },
+ "peeraddr": "172.30.200.2",
+ "iolog_path": "/var/log/sudo-logsrvd/millert/00/03/5Q",
+ "iolog_file": "00/03/5Q",
+ "columns": 80,
+ "command": "/usr/bin/id",
+ "lines": 24,
+ "runargv": [
+ "id"
+ ],
+ "runenv": [
+ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
+ "TERM=tmux",
+ "LANG=en_US.UTF-8",
+ "MAIL=/var/mail/root",
+ "LOGNAME=root",
+ "USER=root",
+ "HOME=/root",
+ "SHELL=/bin/bash",
+ "SUDO_COMMAND=/usr/bin/id",
+ "SUDO_USER=millert",
+ "SUDO_UID=8036",
+ "SUDO_GID=20"
+ ],
+ "runenv_override": [
+ "KRB5CCNAME=bogus"
+ ],
+ "runuid": 0,
+ "runuser": "root",
+ "source": "sudoRole %wheel",
+ "submitcwd": "/usr/src/local/millert/sudo/trunk",
+ "submithost": "xerxes.sudo.ws",
+ "submituser": "millert",
+ "ttyname": "/dev/ttyp0"
+}
diff --git a/lib/eventlog/regress/eventlog_store/test2.json.out.ok b/lib/eventlog/regress/eventlog_store/test2.json.out.ok
new file mode 100644
index 0000000..e26290c
--- /dev/null
+++ b/lib/eventlog/regress/eventlog_store/test2.json.out.ok
@@ -0,0 +1,29 @@
+{
+ "submituser": "millert",
+ "command": "/usr/bin/id",
+ "runuser": "root",
+ "source": "sudoRole %wheel",
+ "ttyname": "/dev/ttyp0",
+ "submithost": "xerxes.sudo.ws",
+ "submitcwd": "/usr/src/local/millert/sudo/trunk",
+ "runuid": 0,
+ "columns": 80,
+ "lines": 24,
+ "runargv": [
+ "id"
+ ],
+ "runenv": [
+ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
+ "TERM=tmux",
+ "LANG=en_US.UTF-8",
+ "MAIL=/var/mail/root",
+ "LOGNAME=root",
+ "USER=root",
+ "HOME=/root",
+ "SHELL=/bin/bash",
+ "SUDO_COMMAND=/usr/bin/id",
+ "SUDO_USER=millert",
+ "SUDO_UID=8036",
+ "SUDO_GID=20"
+ ]
+}
diff --git a/lib/eventlog/regress/eventlog_store/test2.sudo.out.ok b/lib/eventlog/regress/eventlog_store/test2.sudo.out.ok
new file mode 100644
index 0000000..51faf04
--- /dev/null
+++ b/lib/eventlog/regress/eventlog_store/test2.sudo.out.ok
@@ -0,0 +1,2 @@
+HOST=xerxes.sudo.ws ; TTY=ttyp0 ; CWD=/usr/src/local/millert/sudo/trunk ; USER=root ; TSID=00035Q ; ENV=KRB5CCNAME=bogus ; COMMAND=/usr/bin/id
+HOST=xerxes.sudo.ws ; TTY=ttyp0 ; CWD=/usr/src/local/millert/sudo/trunk ; USER=root ; TSID=00035Q ; ENV=KRB5CCNAME=bogus ; COMMAND=/usr/bin/id ; EXIT=0
diff --git a/lib/eventlog/regress/eventlog_store/test3.json.in b/lib/eventlog/regress/eventlog_store/test3.json.in
new file mode 100644
index 0000000..184333f
--- /dev/null
+++ b/lib/eventlog/regress/eventlog_store/test3.json.in
@@ -0,0 +1,49 @@
+{
+ "uuid": "54e6806305-0f50-44bf-fe6a-c8fa7a65ac",
+ "server_time": {
+ "seconds": 1678475729,
+ "nanoseconds": 286796437,
+ "iso8601": "20230310191529Z",
+ "localtime": "Fri Mar 10 12:15:29 MST 2023"
+ },
+ "submit_time": {
+ "seconds": 1633116433,
+ "nanoseconds": 682537651,
+ "iso8601": "20211001192713Z",
+ "localtime": "Fri Oct 1 13:27:13 MDT 2021"
+ },
+ "peeraddr": "172.30.200.50",
+ "iolog_path": "/var/log/sudo-logsrvd/millert/00/00/5H",
+ "iolog_file": "00/00/5H",
+ "columns": 80,
+ "command": "/usr/bin/find",
+ "lines": 24,
+ "runargv": [
+ "find",
+ "build/out/sudoers/"
+ ],
+ "runenv": [
+ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
+ "TERM=tmux",
+ "LANG=en_US.UTF-8",
+ "MAIL=/var/mail/root",
+ "LOGNAME=root",
+ "USER=root",
+ "HOME=/root",
+ "SHELL=/bin/bash",
+ "SUDO_COMMAND=/usr/bin/find build/out/sudoers/",
+ "SUDO_USER=millert",
+ "SUDO_UID=8036",
+ "SUDO_GID=20"
+ ],
+ "runuid": 0,
+ "runuser": "root",
+ "source": "/etc/sudoers:89:24",
+ "submitcwd": "/home/millert/sudo/oss-fuzz",
+ "submithost": "linux-build",
+ "submituser": "millert",
+ "ttyname": "/dev/pts/1",
+ "exit_value": 131,
+ "signal": "QUIT",
+ "dumped_core": true
+}
diff --git a/lib/eventlog/regress/eventlog_store/test3.json.out.ok b/lib/eventlog/regress/eventlog_store/test3.json.out.ok
new file mode 100644
index 0000000..967a75c
--- /dev/null
+++ b/lib/eventlog/regress/eventlog_store/test3.json.out.ok
@@ -0,0 +1,30 @@
+{
+ "submituser": "millert",
+ "command": "/usr/bin/find",
+ "runuser": "root",
+ "source": "/etc/sudoers:89:24",
+ "ttyname": "/dev/pts/1",
+ "submithost": "linux-build",
+ "submitcwd": "/home/millert/sudo/oss-fuzz",
+ "runuid": 0,
+ "columns": 80,
+ "lines": 24,
+ "runargv": [
+ "find",
+ "build/out/sudoers/"
+ ],
+ "runenv": [
+ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
+ "TERM=tmux",
+ "LANG=en_US.UTF-8",
+ "MAIL=/var/mail/root",
+ "LOGNAME=root",
+ "USER=root",
+ "HOME=/root",
+ "SHELL=/bin/bash",
+ "SUDO_COMMAND=/usr/bin/find build/out/sudoers/",
+ "SUDO_USER=millert",
+ "SUDO_UID=8036",
+ "SUDO_GID=20"
+ ]
+}
diff --git a/lib/eventlog/regress/eventlog_store/test3.sudo.out.ok b/lib/eventlog/regress/eventlog_store/test3.sudo.out.ok
new file mode 100644
index 0000000..5f7fee2
--- /dev/null
+++ b/lib/eventlog/regress/eventlog_store/test3.sudo.out.ok
@@ -0,0 +1,2 @@
+HOST=linux-build ; TTY=pts/1 ; CWD=/home/millert/sudo/oss-fuzz ; USER=root ; TSID=00005H ; COMMAND=/usr/bin/find build/out/sudoers/
+HOST=linux-build ; TTY=pts/1 ; CWD=/home/millert/sudo/oss-fuzz ; USER=root ; TSID=00005H ; COMMAND=/usr/bin/find build/out/sudoers/ ; SIGNAL=QUIT ; EXIT=131
diff --git a/lib/eventlog/regress/eventlog_store/test4.json.in b/lib/eventlog/regress/eventlog_store/test4.json.in
new file mode 100644
index 0000000..8836a44
--- /dev/null
+++ b/lib/eventlog/regress/eventlog_store/test4.json.in
@@ -0,0 +1,47 @@
+{
+ "uuid": "0bf9f26a7c-5e8b-4f82-f6c1-24a49a254c",
+ "server_time": {
+ "seconds": 1677638637,
+ "nanoseconds": 369091862,
+ "iso8601": "20230301024357Z",
+ "localtime": "Tue Feb 28 19:43:57 MST 2023"
+ },
+ "submit_time": {
+ "seconds": 1677638637,
+ "nanoseconds": 364571747,
+ "iso8601": "20230301024357Z",
+ "localtime": "Tue Feb 28 19:43:57 MST 2023"
+ },
+ "peeraddr": "172.30.200.2",
+ "iolog_path": "/var/log/sudo-logsrvd/millert/00/03/FG",
+ "iolog_file": "00/03/FG",
+ "columns": 80,
+ "command": "/usr/bin/vi",
+ "lines": 24,
+ "runargv": [
+ "vi",
+ "aliases"
+ ],
+ "runenv": [
+ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
+ "TERM=tmux",
+ "LANG=en_US.UTF-8",
+ "MAIL=/var/mail/root",
+ "LOGNAME=root",
+ "USER=root",
+ "HOME=/root",
+ "SHELL=/bin/bash",
+ "SUDO_COMMAND=/usr/bin/vi aliases",
+ "SUDO_USER=millert",
+ "SUDO_UID=8036",
+ "SUDO_GID=20",
+ "KRB5CCNAME=bogus"
+ ],
+ "runuid": 0,
+ "runuser": "root",
+ "source": "/etc/sudoers:89:24",
+ "submitcwd": "/etc/mail",
+ "submithost": "xerxes.sudo.ws",
+ "submituser": "millert",
+ "ttyname": "/dev/ttypb"
+}
diff --git a/lib/eventlog/regress/eventlog_store/test4.json.out.ok b/lib/eventlog/regress/eventlog_store/test4.json.out.ok
new file mode 100644
index 0000000..2f15940
--- /dev/null
+++ b/lib/eventlog/regress/eventlog_store/test4.json.out.ok
@@ -0,0 +1,31 @@
+{
+ "submituser": "millert",
+ "command": "/usr/bin/vi",
+ "runuser": "root",
+ "source": "/etc/sudoers:89:24",
+ "ttyname": "/dev/ttypb",
+ "submithost": "xerxes.sudo.ws",
+ "submitcwd": "/etc/mail",
+ "runuid": 0,
+ "columns": 80,
+ "lines": 24,
+ "runargv": [
+ "vi",
+ "aliases"
+ ],
+ "runenv": [
+ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
+ "TERM=tmux",
+ "LANG=en_US.UTF-8",
+ "MAIL=/var/mail/root",
+ "LOGNAME=root",
+ "USER=root",
+ "HOME=/root",
+ "SHELL=/bin/bash",
+ "SUDO_COMMAND=/usr/bin/vi aliases",
+ "SUDO_USER=millert",
+ "SUDO_UID=8036",
+ "SUDO_GID=20",
+ "KRB5CCNAME=bogus"
+ ]
+}
diff --git a/lib/eventlog/regress/eventlog_store/test4.sudo.out.ok b/lib/eventlog/regress/eventlog_store/test4.sudo.out.ok
new file mode 100644
index 0000000..c1589d0
--- /dev/null
+++ b/lib/eventlog/regress/eventlog_store/test4.sudo.out.ok
@@ -0,0 +1,2 @@
+HOST=xerxes.sudo.ws ; TTY=ttypb ; CWD=/etc/mail ; USER=root ; TSID=0003FG ; COMMAND=/usr/bin/vi aliases
+HOST=xerxes.sudo.ws ; TTY=ttypb ; CWD=/etc/mail ; USER=root ; TSID=0003FG ; COMMAND=/usr/bin/vi aliases ; EXIT=0
diff --git a/lib/eventlog/regress/logwrap/check_wrap.c b/lib/eventlog/regress/logwrap/check_wrap.c
new file mode 100644
index 0000000..15134f0
--- /dev/null
+++ b/lib/eventlog/regress/logwrap/check_wrap.c
@@ -0,0 +1,124 @@
+/*
+ * 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 <unistd.h>
+
+#define SUDO_ERROR_WRAP 0
+
+#include <sudo_compat.h>
+#include <sudo_eventlog.h>
+#include <sudo_fatal.h>
+#include <sudo_plugin.h>
+#include <sudo_util.h>
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+sudo_noreturn static void
+usage(void)
+{
+ fprintf(stderr, "usage: %s [-v] inputfile\n", getprogname());
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int ch, lineno = 0, which = 0;
+ char *line, lines[2][2048];
+ const char *infile;
+ unsigned int len;
+ FILE *fp;
+
+ initprogname(argc > 0 ? argv[0] : "check_wrap");
+
+ while ((ch = getopt(argc, argv, "v")) != -1) {
+ switch (ch) {
+ case 'v':
+ /* ignored */
+ break;
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1)
+ usage();
+ infile = argv[0];
+
+ fp = fopen(infile, "r");
+ if (fp == NULL)
+ sudo_fatalx("unable to open %s", infile);
+
+ /*
+ * Each test record consists of a log entry on one line and a list of
+ * line lengths to test it with on the next. E.g.
+ *
+ * Jun 30 14:49:51 : millert : TTY=ttypn ; PWD=/usr/src/local/millert/hg/sudo/trunk/plugins/sudoers ; USER=root ; TSID=0004LD ; COMMAND=/usr/local/sbin/visudo
+ * 60-80,40
+ */
+ while ((line = fgets(lines[which], sizeof(lines[which]), fp)) != NULL) {
+ char *cp, *last;
+
+ line[strcspn(line, "\n")] = '\0';
+
+ /* If we read the 2nd line, parse list of line lengths and check. */
+ if (which) {
+ lineno++;
+ for (cp = strtok_r(lines[1], ",", &last); cp != NULL; cp = strtok_r(NULL, ",", &last)) {
+ unsigned int maxlen;
+ const char *errstr;
+ char *dash;
+
+ /* May be either a number or a range. */
+ dash = strchr(cp, '-');
+ if (dash != NULL) {
+ *dash = '\0';
+ len = (unsigned int)sudo_strtonum(cp, 0, INT_MAX, &errstr);
+ if (errstr == NULL)
+ maxlen = (unsigned int)sudo_strtonum(dash + 1, 0, INT_MAX, &errstr);
+ } else {
+ len = maxlen = (unsigned int)sudo_strtonum(cp, 0, INT_MAX, &errstr);
+ }
+ if (errstr != NULL) {
+ sudo_fatalx("%s: invalid length on line %d", infile, lineno);
+ }
+ while (len <= maxlen) {
+ if (len == 0) {
+ puts("# word wrap disabled");
+ } else {
+ printf("# word wrap at %u characters\n", len);
+ }
+ eventlog_writeln(stdout, lines[0], strlen(lines[0]), len);
+ len++;
+ }
+ }
+ }
+ which = !which;
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/lib/eventlog/regress/logwrap/check_wrap.in b/lib/eventlog/regress/logwrap/check_wrap.in
new file mode 100644
index 0000000..e8e7081
--- /dev/null
+++ b/lib/eventlog/regress/logwrap/check_wrap.in
@@ -0,0 +1,4 @@
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ; PWD=/home/tu2sp3-a ; USER=root ; COMMAND=/opt/quest/bin/vastool list users
+0,60-80,120,140
+Jun 26 18:00:06 : millert : TTY=ttypm ; PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ; TSID=0004KT ; COMMAND=/bin/rm /root/.bash_profile
+0,60-80,120,140
diff --git a/lib/eventlog/regress/logwrap/check_wrap.out.ok b/lib/eventlog/regress/logwrap/check_wrap.out.ok
new file mode 100644
index 0000000..55e9da8
--- /dev/null
+++ b/lib/eventlog/regress/logwrap/check_wrap.out.ok
@@ -0,0 +1,179 @@
+# word wrap disabled
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ; PWD=/home/tu2sp3-a ; USER=root ; COMMAND=/opt/quest/bin/vastool list users
+# word wrap at 60 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1
+ ; PWD=/home/tu2sp3-a ; USER=root ;
+ COMMAND=/opt/quest/bin/vastool list users
+# word wrap at 61 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1
+ ; PWD=/home/tu2sp3-a ; USER=root ;
+ COMMAND=/opt/quest/bin/vastool list users
+# word wrap at 62 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ;
+ PWD=/home/tu2sp3-a ; USER=root ;
+ COMMAND=/opt/quest/bin/vastool list users
+# word wrap at 63 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ;
+ PWD=/home/tu2sp3-a ; USER=root ;
+ COMMAND=/opt/quest/bin/vastool list users
+# word wrap at 64 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ;
+ PWD=/home/tu2sp3-a ; USER=root ;
+ COMMAND=/opt/quest/bin/vastool list users
+# word wrap at 65 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ;
+ PWD=/home/tu2sp3-a ; USER=root ;
+ COMMAND=/opt/quest/bin/vastool list users
+# word wrap at 66 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ;
+ PWD=/home/tu2sp3-a ; USER=root ;
+ COMMAND=/opt/quest/bin/vastool list users
+# word wrap at 67 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ;
+ PWD=/home/tu2sp3-a ; USER=root ; COMMAND=/opt/quest/bin/vastool
+ list users
+# word wrap at 68 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ;
+ PWD=/home/tu2sp3-a ; USER=root ; COMMAND=/opt/quest/bin/vastool
+ list users
+# word wrap at 69 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ;
+ PWD=/home/tu2sp3-a ; USER=root ; COMMAND=/opt/quest/bin/vastool
+ list users
+# word wrap at 70 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ;
+ PWD=/home/tu2sp3-a ; USER=root ; COMMAND=/opt/quest/bin/vastool
+ list users
+# word wrap at 71 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ;
+ PWD=/home/tu2sp3-a ; USER=root ; COMMAND=/opt/quest/bin/vastool
+ list users
+# word wrap at 72 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ;
+ PWD=/home/tu2sp3-a ; USER=root ; COMMAND=/opt/quest/bin/vastool list
+ users
+# word wrap at 73 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ;
+ PWD=/home/tu2sp3-a ; USER=root ; COMMAND=/opt/quest/bin/vastool list
+ users
+# word wrap at 74 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ;
+ PWD=/home/tu2sp3-a ; USER=root ; COMMAND=/opt/quest/bin/vastool list
+ users
+# word wrap at 75 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ;
+ PWD=/home/tu2sp3-a ; USER=root ; COMMAND=/opt/quest/bin/vastool list
+ users
+# word wrap at 76 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ;
+ PWD=/home/tu2sp3-a ; USER=root ; COMMAND=/opt/quest/bin/vastool list
+ users
+# word wrap at 77 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ;
+ PWD=/home/tu2sp3-a ; USER=root ; COMMAND=/opt/quest/bin/vastool list
+ users
+# word wrap at 78 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ;
+ PWD=/home/tu2sp3-a ; USER=root ; COMMAND=/opt/quest/bin/vastool list users
+# word wrap at 79 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ;
+ PWD=/home/tu2sp3-a ; USER=root ; COMMAND=/opt/quest/bin/vastool list users
+# word wrap at 80 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ;
+ PWD=/home/tu2sp3-a ; USER=root ; COMMAND=/opt/quest/bin/vastool list users
+# word wrap at 120 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ; PWD=/home/tu2sp3-a ; USER=root ;
+ COMMAND=/opt/quest/bin/vastool list users
+# word wrap at 140 characters
+Jul 11 11:30:17 : tu2sp3-a : command not allowed ; TTY=pts/1 ; PWD=/home/tu2sp3-a ; USER=root ; COMMAND=/opt/quest/bin/vastool list users
+# word wrap disabled
+Jun 26 18:00:06 : millert : TTY=ttypm ; PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ; TSID=0004KT ; COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 60 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ;
+ PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ;
+ TSID=0004KT ; COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 61 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ;
+ PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ;
+ TSID=0004KT ; COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 62 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ;
+ PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ;
+ TSID=0004KT ; COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 63 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ;
+ PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ;
+ TSID=0004KT ; COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 64 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ;
+ PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ;
+ TSID=0004KT ; COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 65 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ;
+ PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ;
+ TSID=0004KT ; COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 66 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ;
+ PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ;
+ TSID=0004KT ; COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 67 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ;
+ PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ;
+ TSID=0004KT ; COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 68 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ;
+ PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ;
+ TSID=0004KT ; COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 69 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ;
+ PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ;
+ TSID=0004KT ; COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 70 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ;
+ PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ; TSID=0004KT
+ ; COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 71 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ;
+ PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ; TSID=0004KT
+ ; COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 72 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ;
+ PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ; TSID=0004KT ;
+ COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 73 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ;
+ PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ; TSID=0004KT ;
+ COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 74 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ;
+ PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ; TSID=0004KT ;
+ COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 75 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ;
+ PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ; TSID=0004KT ;
+ COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 76 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ;
+ PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ; TSID=0004KT ;
+ COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 77 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ;
+ PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ; TSID=0004KT ;
+ COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 78 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ;
+ PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ; TSID=0004KT ;
+ COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 79 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ;
+ PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ; TSID=0004KT ;
+ COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 80 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ; PWD=/usr/src/local/millert/hg/sudo/build
+ ; USER=root ; TSID=0004KT ; COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 120 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ; PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ; TSID=0004KT ;
+ COMMAND=/bin/rm /root/.bash_profile
+# word wrap at 140 characters
+Jun 26 18:00:06 : millert : TTY=ttypm ; PWD=/usr/src/local/millert/hg/sudo/build ; USER=root ; TSID=0004KT ; COMMAND=/bin/rm
+ /root/.bash_profile
diff --git a/lib/eventlog/regress/parse_json/check_parse_json.c b/lib/eventlog/regress/parse_json/check_parse_json.c
new file mode 100644
index 0000000..496fac9
--- /dev/null
+++ b/lib/eventlog/regress/parse_json/check_parse_json.c
@@ -0,0 +1,270 @@
+/*
+ * 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_eventlog.h>
+#include <sudo_fatal.h>
+#include <sudo_util.h>
+
+#include <parse_json.h>
+
+sudo_dso_public int main(int argc, char *argv[]);
+
+static bool
+json_print_object(struct json_container *jsonc, struct eventlog_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(jsonc, 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(jsonc, item->name, &json_value))
+ goto oom;
+ break;
+ case JSON_OBJECT:
+ if (!sudo_json_open_object(jsonc, item->name))
+ goto oom;
+ if (!json_print_object(jsonc, &item->u.child))
+ goto done;
+ if (!sudo_json_close_object(jsonc))
+ goto oom;
+ break;
+ case JSON_ARRAY:
+ if (!sudo_json_open_array(jsonc, item->name))
+ goto oom;
+ if (!json_print_object(jsonc, &item->u.child))
+ goto done;
+ if (!sudo_json_close_array(jsonc))
+ goto oom;
+ break;
+ case JSON_BOOL:
+ json_value.type = JSON_BOOL;
+ json_value.u.boolean = item->u.boolean;
+ if (!sudo_json_add_value(jsonc, item->name, &json_value))
+ goto oom;
+ break;
+ case JSON_NULL:
+ json_value.type = JSON_NULL;
+ if (!sudo_json_add_value(jsonc, 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 *jsonc, struct eventlog_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(jsonc, object))
+ goto done;
+
+ ret = true;
+
+done:
+ return ret;
+}
+
+sudo_noreturn static void
+usage(void)
+{
+ fprintf(stderr, "usage: %s [-cv] input_file ...\n",
+ getprogname());
+ exit(EXIT_FAILURE);
+}
+
+static bool
+compare(FILE *fp, const char *infile, struct json_container *jsonc)
+{
+ const char *cp;
+ unsigned int lineno = 0;
+ size_t linesize = 0;
+ char *line = NULL;
+ ssize_t len;
+
+ cp = sudo_json_get_buf(jsonc);
+
+ 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, (size_t)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[])
+{
+ int ch, i, ntests = 0, errors = 0;
+ bool cat = false;
+
+ initprogname(argc > 0 ? argv[0] : "check_parse_json");
+
+ while ((ch = getopt(argc, argv, "cv")) != -1) {
+ switch (ch) {
+ case 'c':
+ cat = true;
+ break;
+ case 'v':
+ /* ignored */
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1)
+ usage();
+
+ for (i = 0; i < argc; i++) {
+ struct eventlog_json_object *root;
+ struct json_container jsonc;
+ const char *infile = argv[i];
+ const char *outfile = argv[i];
+ const char *cp;
+ char pathbuf[PATH_MAX];
+ FILE *infp = NULL;
+ FILE *outfp = NULL;
+
+ ntests++;
+
+ if (!sudo_json_init(&jsonc, 4, false, true, true)) {
+ errors++;
+ continue;
+ }
+
+ /* Parse input file. */
+ if ((infp = fopen(infile, "r")) == NULL) {
+ sudo_warn("%s", argv[i]);
+ errors++;
+ continue;
+ }
+ root = eventlog_json_read(infp, infile);
+ if (root == NULL) {
+ errors++;
+ goto next;
+ }
+
+ /* Format as pretty-printed JSON */
+ if (!json_format(&jsonc, 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, &jsonc))
+ errors++;
+
+ /* Write the formatted output to stdout for -c (cat) */
+ if (cat) {
+ fprintf(stdout, "{%s\n}\n", sudo_json_get_buf(&jsonc));
+ fflush(stdout);
+ }
+
+next:
+ eventlog_json_free(root);
+ sudo_json_free(&jsonc);
+ if (infp != NULL)
+ fclose(infp);
+ if (outfp != NULL && outfp != infp)
+ fclose(outfp);
+ }
+
+ if (ntests != 0) {
+ printf("%s: %d test%s run, %d errors, %d%% success rate\n",
+ getprogname(), ntests, ntests == 1 ? "" : "s", errors,
+ (ntests - errors) * 100 / ntests);
+ }
+
+ return errors;
+}
diff --git a/lib/eventlog/regress/parse_json/test1.in b/lib/eventlog/regress/parse_json/test1.in
new file mode 100644
index 0000000..8ad3689
--- /dev/null
+++ b/lib/eventlog/regress/parse_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/eventlog/regress/parse_json/test2.in b/lib/eventlog/regress/parse_json/test2.in
new file mode 100644
index 0000000..df7170f
--- /dev/null
+++ b/lib/eventlog/regress/parse_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/eventlog/regress/parse_json/test2.out.ok b/lib/eventlog/regress/parse_json/test2.out.ok
new file mode 100644
index 0000000..8ad3689
--- /dev/null
+++ b/lib/eventlog/regress/parse_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/eventlog/regress/parse_json/test3.in b/lib/eventlog/regress/parse_json/test3.in
new file mode 100644
index 0000000..6f243e3
--- /dev/null
+++ b/lib/eventlog/regress/parse_json/test3.in
@@ -0,0 +1,22 @@
+{
+ "true": false,
+ "false": true,
+ "number": 1234567890,
+ "null": null,
+ "string": "non\u0073ense",
+ "scope": {
+ "a": "b",
+ "bah": null
+ },
+ "array1": [
+ "foo",
+ "bar",
+ [
+ 123,
+ null,
+ false,
+ "fizz",
+ "buzz"
+ ]
+ ]
+}
diff --git a/lib/eventlog/regress/parse_json/test3.out.ok b/lib/eventlog/regress/parse_json/test3.out.ok
new file mode 100644
index 0000000..ea2df89
--- /dev/null
+++ b/lib/eventlog/regress/parse_json/test3.out.ok
@@ -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"
+ ]
+ ]
+}