File 0001-LDAP-auth-support.patch of Package mpd

From e3e558ba7c615f997708baad77c1b1954ec3caf4 Mon Sep 17 00:00:00 2001
From: Mia Herkt <mia@0x0.st>
Date: Fri, 28 Mar 2025 01:44:13 +0100
Subject: [PATCH] LDAP auth support

---
 meson.build              |   6 ++
 src/Permission.cxx       | 144 ++++++++++++++++++++++++++++++++++++++-
 src/config/Option.hxx    |   6 ++
 src/config/Templates.cxx |   6 ++
 4 files changed, 160 insertions(+), 2 deletions(-)

diff --git a/meson.build b/meson.build
index a3193254..6445fc7e 100644
--- a/meson.build
+++ b/meson.build
@@ -609,6 +609,11 @@ else
   install_dir = get_option('bindir')
 endif
 
+ldap_deps = [
+	c_compiler.find_library('ldap'),
+	c_compiler.find_library('lber'),
+]
+
 mpd = build_target(
   target_name,
   sources,
@@ -641,6 +646,7 @@ mpd = build_target(
     more_deps,
     chromaprint_dep,
     fmt_dep,
+	ldap_deps,
   ],
   link_args: link_args,
   build_by_default: not get_option('fuzzer'),
diff --git a/src/Permission.cxx b/src/Permission.cxx
index d7d54718..7b248e0d 100644
--- a/src/Permission.cxx
+++ b/src/Permission.cxx
@@ -34,6 +34,15 @@ static constexpr struct {
 };
 
 static std::map<std::string, unsigned, std::less<>> permission_passwords;
+static struct LDAPDirectory {
+	const char
+		* uri,
+		* dn,
+		* pw,
+		* authmeth,
+		* base,
+		* scope;
+} directory;
 
 static unsigned permission_default;
 
@@ -124,6 +133,18 @@ initPermissions(const ConfigData &config)
 		});
 	}
 #endif
+
+	auto getp = [&](ConfigOption c) -> const char* {
+		const ConfigParam* cp = config.GetParam(c);
+		if (cp == nullptr) return nullptr;
+		return cp->value.c_str();
+	};
+	directory.uri     = getp(ConfigOption::LDAP_BIND_URI);
+	directory.dn      = getp(ConfigOption::LDAP_BIND_DN);
+	directory.pw      = getp(ConfigOption::LDAP_BIND_PW);
+	directory.authmeth= getp(ConfigOption::LDAP_BIND_METH);
+	directory.base    = getp(ConfigOption::LDAP_Q_BASE);
+	directory.scope   = getp(ConfigOption::LDAP_Q_SCOPE);
 }
 
 #ifdef HAVE_TCP
@@ -140,12 +161,131 @@ GetPermissionsFromAddress(SocketAddress address) noexcept
 
 #endif
 
+namespace ldap { extern "C" {
+#	include <ldap.h>
+	char** ldap_get_values   (LDAP*, LDAPMessage*, char*);
+	int    ldap_count_values (char**);
+	void   ldap_value_free   (char**);
+	int    ldap_unbind_s     (LDAP*);
+	int    ldap_simple_bind_s(LDAP*, const char*, const char*);
+}}
+
+static ldap::LDAP*
+openLDAPConnection() {
+	using namespace ldap;
+	LDAP* ctx;
+	ldap_initialize(&ctx, directory.uri);
+	{int v=3; ldap_set_option(ctx, LDAP_OPT_PROTOCOL_VERSION, &v);}
+	int res;
+	if (directory.authmeth == nullptr) {
+		res = ldap_simple_bind_s(ctx, directory.dn, directory.pw);
+	} else {
+		res = ldap_sasl_interactive_bind_s(ctx, directory.dn, directory.authmeth, nullptr, nullptr, LDAP_SASL_QUIET, nullptr, nullptr);
+	}
+	if (res != 0) {
+		ldap_unbind_s(ctx);
+		throw std::runtime_error(ldap_err2string(res));
+		// return nullptr;
+	}
+
+	return ctx;
+}
+
+static int
+getPermissionFromLDAPLookup(const char* password, unsigned* permission) {
+	using namespace ldap;
+	typedef char const* str;
+
+	LDAP* l = openLDAPConnection();
+	if (l == nullptr) return -1;
+
+	str const filter  = "(&(preSharedKey=*)(authorizedService=music*))";
+	str const attrs[] = {
+		"authorizedService",
+		"preSharedKey",
+		// "cn",
+		// "aRecord",
+		// "aAAARecord",
+		// "associatedDomain",
+		nullptr,
+	};
+
+	LDAPMessage* m;
+	int e = ldap_search_ext_s(l, directory.base, LDAP_SCOPE_SUBTREE, filter, const_cast<char**>(attrs),
+			/*     attrs only*/ 0,
+			/*server controls*/ nullptr,
+			/*client controls*/ nullptr,
+			/*        timeout*/ nullptr,
+			/*      sizelimit*/ 0, &m);
+
+	bool found = false;
+
+	if (e != LDAP_SUCCESS) {
+		throw std::runtime_error(ldap_err2string(e));
+		//goto fail;
+	}
+
+	int permset = 0;
+	for (LDAPMessage* ent = ldap_first_entry(l, m); ent != nullptr;
+	                  ent = ldap_next_entry(l, ent)) {
+		char* dn = ldap_get_dn(l, ent);
+		char** keys = ldap_get_values(l, ent, const_cast<char*>("preSharedKey"));
+		char** svcs = ldap_get_values(l, ent, const_cast<char*>("authorizedService"));
+
+		size_t nkeys = ldap_count_values(keys);
+		size_t nsvcs = ldap_count_values(svcs);
+
+		bool keyMatch = false;
+		for (size_t i = 0; i < nkeys; ++i) {
+			if (strcmp(password, keys[i]) == 0) {
+				keyMatch = true;
+				break;
+			}
+		}
+
+		if (keyMatch) for (size_t i = 0; i < nsvcs; ++i) {
+			auto svc = svcs[i];
+			if (strncmp(svc, "music", 5) == 0) {
+				if (svc[5] == 0) /* all privileges */ {
+					found = true;
+					permset |= (unsigned)-1;
+				} else if(svc[5] == '=') /* defined privs */ {
+					found = true;
+					char* priv = svc + 6, *s;
+					for (char* p = strtok_r(priv, ",", &s); p != nullptr;
+							   p = strtok_r(nullptr, ",", &s)) {
+						permset |= ParsePermission(p);
+					}
+				}
+			}
+		}
+
+		ldap_value_free(keys);
+		ldap_value_free(svcs);
+
+		ldap_memfree(dn);
+		if (found) break;
+	}
+
+	if(found) {
+		*permission = permset;
+	}
+
+	/*fail:*/ ldap_msgfree(m);
+		  ldap_unbind_s(l);
+		  return found ? 0 : -1;
+}
+
 std::optional<unsigned>
 GetPermissionFromPassword(const char *password) noexcept
 {
 	auto i = permission_passwords.find(password);
-	if (i == permission_passwords.end())
-		return std::nullopt;
+	if (i == permission_passwords.end()) {
+		unsigned perm;
+		if (getPermissionFromLDAPLookup(password, &perm) != 0)
+			return std::nullopt;
+		return perm;
+	}
 
 	return i->second;
 }
diff --git a/src/config/Option.hxx b/src/config/Option.hxx
index 31353788..9bed17ba 100644
--- a/src/config/Option.hxx
+++ b/src/config/Option.hxx
@@ -59,6 +59,12 @@ enum class ConfigOption {
 	GAPLESS_MP3_PLAYBACK,
 	AUTO_UPDATE,
 	AUTO_UPDATE_DEPTH,
+	LDAP_BIND_URI,
+	LDAP_BIND_DN,
+	LDAP_BIND_PW,
+	LDAP_BIND_METH,
+	LDAP_Q_BASE,
+	LDAP_Q_SCOPE,
 
 	MIXRAMP_ANALYZER,
 
diff --git a/src/config/Templates.cxx b/src/config/Templates.cxx
index e3794e00..0df87bd3 100644
--- a/src/config/Templates.cxx
+++ b/src/config/Templates.cxx
@@ -58,6 +58,12 @@ const ConfigTemplate config_param_templates[] = {
 	{ "auto_update" },
 	{ "auto_update_depth" },
 	{ "mixramp_analyzer" },
+	{ "ldap_bind_uri" },
+	{ "ldap_bind_dn" },
+	{ "ldap_bind_pw" },
+	{ "ldap_bind_meth" },
+	{ "ldap_q_base" },
+	{ "ldap_q_scope" },
 };
 
 static constexpr unsigned n_config_param_templates =
-- 
2.49.0

openSUSE Build Service is sponsored by