summaryrefslogtreecommitdiffstats
path: root/plugins/sudoers/ldap_innetgr.c
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/sudoers/ldap_innetgr.c')
-rw-r--r--plugins/sudoers/ldap_innetgr.c264
1 files changed, 264 insertions, 0 deletions
diff --git a/plugins/sudoers/ldap_innetgr.c b/plugins/sudoers/ldap_innetgr.c
new file mode 100644
index 0000000..2309485
--- /dev/null
+++ b/plugins/sudoers/ldap_innetgr.c
@@ -0,0 +1,264 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 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.
+ */
+
+/*
+ * 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 <sys/time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+#include <time.h>
+#include <ctype.h>
+#ifdef HAVE_LBER_H
+# include <lber.h>
+#endif
+#include <ldap.h>
+
+#include <sudoers.h>
+#include <sudo_ldap.h>
+#include <sudo_ldap_conf.h>
+
+/*
+ * Compare str to netgroup string ngstr of length nglen where str is a
+ * NUL-terminated string and ngstr is part of a netgroup triple string.
+ * Uses innetgr(3)-style matching rules.
+ * Returns true if the strings match, else false.
+ */
+static bool
+sudo_ldap_netgroup_match_str(const char *str, const char *ngstr, size_t nglen,
+ bool ignore_case)
+{
+ debug_decl(sudo_ldap_netgroup_match_str, SUDOERS_DEBUG_LDAP);
+
+ /* Skip leading whitespace. */
+ while (isspace((unsigned char)*ngstr) && nglen > 0) {
+ ngstr++;
+ nglen--;
+ }
+ /* Skip trailing whitespace. */
+ while (nglen > 0 && isspace((unsigned char)ngstr[nglen - 1])) {
+ nglen--;
+ }
+
+ sudo_debug_printf(SUDO_DEBUG_DEBUG, "%s: compare \"%s\" to \"%.*s\"",
+ __func__, str ? str : "", (int)nglen, ngstr);
+
+ if (nglen == 0 || str == NULL) {
+ /* An empty string is a wildcard. */
+ debug_return_bool(true);
+ }
+ if (*ngstr == '-' && nglen == 1) {
+ /* '-' means no valid value. */
+ debug_return_bool(false);
+ }
+ if (ignore_case) {
+ if (strncasecmp(str, ngstr, nglen) == 0 && str[nglen] == '\0')
+ debug_return_bool(true);
+ } else {
+ if (strncmp(str, ngstr, nglen) == 0 && str[nglen] == '\0')
+ debug_return_bool(true);
+ }
+ debug_return_bool(false);
+}
+
+/*
+ * Match the specified netgroup triple using the given host,
+ * user and domain. Matching rules as per innetgr(3).
+ * Returns 1 on match, else 0.
+ */
+static int
+sudo_ldap_match_netgroup(const char *triple, const char *host,
+ const char *user, const char *domain)
+{
+ const char *cp, *ep;
+ debug_decl(sudo_ldap_match_netgroup, SUDOERS_DEBUG_LDAP);
+
+ /* Trim leading space, check for opening paren. */
+ while (isspace((unsigned char)*triple))
+ triple++;
+ if (*triple != '(') {
+ sudo_debug_printf(SUDO_DEBUG_ERROR, "%s: invalid triple: %s",
+ __func__, triple);
+ debug_return_int(0);
+ }
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: matching (%s,%s,%s) against %s",
+ __func__, host ? host : "", user ? user : "", domain ? domain : "",
+ triple);
+
+ /* Parse host. */
+ cp = triple + 1;
+ ep = strchr(cp, ',');
+ if (ep == NULL || !sudo_ldap_netgroup_match_str(host, cp, (size_t)(ep - cp), true))
+ debug_return_int(0);
+
+ /* Parse user. */
+ cp = ep + 1;
+ ep = strchr(cp, ',');
+ if (ep == NULL || !sudo_ldap_netgroup_match_str(user, cp, (size_t)(ep - cp), def_case_insensitive_user))
+ debug_return_int(0);
+
+ /* Parse domain. */
+ cp = ep + 1;
+ ep = strchr(cp, ')');
+ if (ep == NULL || !sudo_ldap_netgroup_match_str(domain, cp, (size_t)(ep - cp), true))
+ debug_return_int(0);
+
+ debug_return_int(1);
+}
+
+#define MAX_NETGROUP_DEPTH 128
+struct netgroups_seen {
+ const char *groups[MAX_NETGROUP_DEPTH];
+ size_t len;
+};
+
+static int
+sudo_ldap_innetgr_base(LDAP *ld, const char *base,
+ struct timeval *timeout, const char *netgr, const char *host,
+ const char *user, const char *domain, struct netgroups_seen *seen)
+{
+ char *escaped_netgr = NULL, *filt = NULL;
+ LDAPMessage *entry, *result = NULL;
+ int rc, ret = 0;
+ size_t n;
+ debug_decl(sudo_ldap_innetgr_base, SUDOERS_DEBUG_LDAP);
+
+ /* Cycle detection. */
+ for (n = 0; n < seen->len; n++) {
+ if (strcmp(netgr, seen->groups[n]) == 0) {
+ DPRINTF1("%s: cycle in netgroups", netgr);
+ goto done;
+ }
+ }
+ if (seen->len + 1 > MAX_NETGROUP_DEPTH) {
+ DPRINTF1("%s: too many nested netgroups", netgr);
+ goto done;
+ }
+ seen->groups[seen->len++] = netgr;
+
+ /* Escape the netgroup name per RFC 4515. */
+ if ((escaped_netgr = sudo_ldap_value_dup(netgr)) == NULL)
+ goto done;
+
+ /* Build nisNetgroup query. */
+ rc = asprintf(&filt, "(&%s(cn=%s))",
+ ldap_conf.netgroup_search_filter, escaped_netgr);
+ if (rc == -1)
+ goto done;
+ DPRINTF1("ldap netgroup search filter: '%s'", filt);
+
+ /* Perform an LDAP query for nisNetgroup. */
+ DPRINTF1("searching from netgroup_base '%s'", base);
+ rc = ldap_search_ext_s(ld, base, LDAP_SCOPE_SUBTREE, filt,
+ NULL, 0, NULL, NULL, timeout, 0, &result);
+ free(filt);
+ if (rc != LDAP_SUCCESS) {
+ DPRINTF1("ldap netgroup search failed: %s", ldap_err2string(rc));
+ goto done;
+ }
+
+ LDAP_FOREACH(entry, ld, result) {
+ struct berval **bv, **p;
+
+ /* Check all nisNetgroupTriple entries. */
+ bv = ldap_get_values_len(ld, entry, "nisNetgroupTriple");
+ if (bv == NULL) {
+ const int optrc = ldap_get_option(ld, LDAP_OPT_RESULT_CODE, &rc);
+ if (optrc != LDAP_OPT_SUCCESS || rc == LDAP_NO_MEMORY)
+ goto done;
+ } else {
+ for (p = bv; *p != NULL && !ret; p++) {
+ char *val = (*p)->bv_val;
+ if (sudo_ldap_match_netgroup(val, host, user, domain)) {
+ ret = 1;
+ break;
+ }
+ }
+ ldap_value_free_len(bv);
+ if (ret == 1)
+ break;
+ }
+
+ /* Handle nested netgroups. */
+ bv = ldap_get_values_len(ld, entry, "memberNisNetgroup");
+ if (bv == NULL) {
+ const int optrc = ldap_get_option(ld, LDAP_OPT_RESULT_CODE, &rc);
+ if (optrc != LDAP_OPT_SUCCESS || rc == LDAP_NO_MEMORY)
+ goto done;
+ } else {
+ for (p = bv; *p != NULL && !ret; p++) {
+ const char *val = (*p)->bv_val;
+ const size_t saved_len = seen->len;
+ ret = sudo_ldap_innetgr_base(ld, base, timeout, val, host,
+ user, domain, seen);
+ /* Restore seen state to avoid use-after-free. */
+ seen->len = saved_len;
+ }
+ ldap_value_free_len(bv);
+ }
+ }
+
+done:
+ ldap_msgfree(result);
+ free(escaped_netgr);
+
+ debug_return_int(ret);
+}
+
+int
+sudo_ldap_innetgr_int(void *v, const char *netgr, const char *host,
+ const char *user, const char *domain)
+{
+ LDAP *ld = v;
+ struct timeval tv, *tvp = NULL;
+ struct ldap_config_str *base;
+ struct netgroups_seen seen;
+ int ret = 0;
+ debug_decl(sudo_ldap_innetgr, SUDOERS_DEBUG_LDAP);
+
+ if (STAILQ_EMPTY(&ldap_conf.netgroup_base)) {
+ /* LDAP netgroups not configured. */
+ debug_return_int(-1);
+ }
+
+ if (ldap_conf.timeout > 0) {
+ tv.tv_sec = ldap_conf.timeout;
+ tv.tv_usec = 0;
+ tvp = &tv;
+ }
+
+ /* Perform an LDAP query for nisNetgroup. */
+ STAILQ_FOREACH(base, &ldap_conf.netgroup_base, entries) {
+ seen.len = 0;
+ ret = sudo_ldap_innetgr_base(ld, base->val, tvp, netgr, host,
+ user, domain, &seen);
+ if (ret != 0)
+ break;
+ }
+
+ debug_return_int(ret);
+}