summaryrefslogtreecommitdiffstats
path: root/lib/iolog/iolog_filter.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/iolog/iolog_filter.c')
-rw-r--r--lib/iolog/iolog_filter.c248
1 files changed, 248 insertions, 0 deletions
diff --git a/lib/iolog/iolog_filter.c b/lib/iolog/iolog_filter.c
new file mode 100644
index 0000000..ecccf18
--- /dev/null
+++ b/lib/iolog/iolog_filter.c
@@ -0,0 +1,248 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2022 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#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 <regex.h>
+#include <string.h>
+#include <time.h>
+
+#include "sudo_compat.h"
+#include "sudo_debug.h"
+#include "sudo_fatal.h"
+#include "sudo_gettext.h"
+#include "sudo_iolog.h"
+#include "sudo_queue.h"
+#include "sudo_util.h"
+
+struct pwfilt_regex {
+ TAILQ_ENTRY(pwfilt_regex) entries;
+ char *pattern;
+ regex_t regex;
+};
+TAILQ_HEAD(pwfilt_regex_list, pwfilt_regex);
+
+struct pwfilt_handle {
+ struct pwfilt_regex_list filters;
+ bool is_filtered;
+};
+
+/*
+ * Allocate a new filter handle.
+ */
+void *
+iolog_pwfilt_alloc(void)
+{
+ struct pwfilt_handle *handle;
+ debug_decl(iolog_pwfilt_alloc, SUDO_DEBUG_UTIL);
+
+ handle = malloc(sizeof(*handle));
+ if (handle != NULL) {
+ TAILQ_INIT(&handle->filters);
+ handle->is_filtered = false;
+ }
+
+ debug_return_ptr(handle);
+}
+
+/*
+ * Unlink filt from filters and free it.
+ */
+static void
+iolog_pwfilt_free_filter(struct pwfilt_regex_list *filters,
+ struct pwfilt_regex *filt)
+{
+ debug_decl(iolog_pwfilt_free_filter, SUDO_DEBUG_UTIL);
+
+ if (filt != NULL) {
+ TAILQ_REMOVE(filters, filt, entries);
+ regfree(&filt->regex);
+ free(filt->pattern);
+ free(filt);
+ }
+
+ debug_return;
+}
+
+/*
+ * Free the given password filter handle.
+ */
+void
+iolog_pwfilt_free(void *vhandle)
+{
+ struct pwfilt_handle *handle = vhandle;
+ struct pwfilt_regex *filt;
+ debug_decl(iolog_pwfilt_free, SUDO_DEBUG_UTIL);
+
+ if (handle != NULL) {
+ while ((filt = TAILQ_FIRST(&handle->filters)) != NULL) {
+ iolog_pwfilt_free_filter(&handle->filters, filt);
+ }
+ free(handle);
+ }
+ debug_return;
+}
+
+/*
+ * Add a pattern to the password filter list.
+ */
+bool
+iolog_pwfilt_add(void *vhandle, const char *pattern)
+{
+ struct pwfilt_handle *handle = vhandle;
+ struct pwfilt_regex *filt;
+ const char *errstr;
+ debug_decl(iolog_pwfilt_add, SUDO_DEBUG_UTIL);
+
+ filt = malloc(sizeof(*filt));
+ if (filt == NULL)
+ goto oom;
+ filt->pattern = strdup(pattern);
+ if (filt->pattern == NULL)
+ goto oom;
+
+ if (!sudo_regex_compile(&filt->regex, filt->pattern, &errstr)) {
+ sudo_warnx(U_("invalid regular expression \"%s\": %s"),
+ pattern, U_(errstr));
+ goto bad;
+ }
+
+ TAILQ_INSERT_TAIL(&handle->filters, filt, entries);
+ debug_return_bool(true);
+
+oom:
+ sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+bad:
+ if (filt != NULL) {
+ free(filt->pattern);
+ free(filt);
+ }
+ debug_return_bool(false);
+}
+
+/*
+ * Remove a pattern from the password filter list.
+ */
+bool
+iolog_pwfilt_remove(void *vhandle, const char *pattern)
+{
+ struct pwfilt_handle *handle = vhandle;
+ struct pwfilt_regex *filt, *next;
+ bool ret = false;
+ debug_decl(iolog_pwfilt_remove, SUDO_DEBUG_UTIL);
+
+ TAILQ_FOREACH_SAFE(filt, &handle->filters, entries, next) {
+ if (strcmp(filt->pattern, pattern) == 0) {
+ iolog_pwfilt_free_filter(&handle->filters, filt);
+ ret = true;
+ }
+ }
+ debug_return_bool(ret);
+}
+
+/*
+ * If logging output and filtering is _not_ enabled, match buf against the
+ * password filter list patterns and, if there is a match, enable filtering.
+ * If logging output and filtering _is_ enabled, disable filtering.
+ * If logging input and filtering is enabled, replace all characters in
+ * buf with stars ('*') up to the next linefeed or carriage return.
+ */
+bool
+iolog_pwfilt_run(void *vhandle, int event, const char *buf,
+ unsigned int len, char **newbuf)
+{
+ struct pwfilt_handle *handle = vhandle;
+ struct pwfilt_regex *filt;
+ char *copy;
+ debug_decl(iolog_pwfilt_run, SUDO_DEBUG_UTIL);
+
+ /*
+ * We only filter ttyin/ttyout. It is only possible to disable
+ * echo when a tty is present. Filtering passwords in the input
+ * log when they appear in the output is pointless. This does
+ * assume that the password prompt is written to the tty as well.
+ */
+ switch (event) {
+ case IO_EVENT_TTYOUT:
+ /* If filtering passwords and we receive output, disable it. */
+ if (handle->is_filtered)
+ handle->is_filtered = false;
+
+ /* Make a copy of buf that is NUL-terminated. */
+ copy = malloc(len + 1);
+ if (copy == NULL) {
+ sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ debug_return_bool(false);
+ }
+ memcpy(copy, buf, len);
+ copy[len] = '\0';
+
+ /* Check output for a password prompt. */
+ TAILQ_FOREACH(filt, &handle->filters, entries) {
+ if (regexec(&filt->regex, copy, 0, NULL, 0) == 0) {
+ handle->is_filtered = true;
+ break;
+ }
+ }
+ free(copy);
+ break;
+ case IO_EVENT_TTYIN:
+ if (handle->is_filtered) {
+ unsigned int i;
+
+ for (i = 0; i < len; i++) {
+ /* We will stop filtering after reaching cr/nl. */
+ if (buf[i] == '\r' || buf[i] == '\n') {
+ handle->is_filtered = false;
+ break;
+ }
+ }
+ if (i != 0) {
+ /* Filtered, replace buffer with '*' chars. */
+ copy = malloc(len);
+ if (copy == NULL) {
+ sudo_warnx(U_("%s: %s"), __func__,
+ U_("unable to allocate memory"));
+ debug_return_bool(false);
+ }
+ memset(copy, '*', i);
+ if (i != len) {
+ /* Done filtering, copy cr/nl and subsequent characters. */
+ memcpy(copy + i, buf + i, len - i);
+ }
+ *newbuf = copy;
+ }
+ }
+ break;
+ }
+
+ debug_return_bool(true);
+}