diff --git a/readme-vars.yml b/readme-vars.yml index 8d2dd05..c3ed53d 100644 --- a/readme-vars.yml +++ b/readme-vars.yml @@ -42,6 +42,7 @@ app_setup_block: | # changelog changelogs: + - { date: "20.06.23:", desc: "Sync upstream changes, including the ability to disable referrals with `X-Ldap-DisableReferrals`." } - { date: "25.05.23:", desc: "Rebase to Alpine 3.18, deprecate armhf." } - { date: "30.12.22:", desc: "Rebase to alpine 3.17." } - { date: "19.09.22:", desc: "Rebase to alpine 3.15." } diff --git a/root/app/nginx-ldap-auth-daemon.py b/root/app/nginx-ldap-auth-daemon.py index bed4734..22aa8f4 100644 --- a/root/app/nginx-ldap-auth-daemon.py +++ b/root/app/nginx-ldap-auth-daemon.py @@ -5,7 +5,7 @@ # Copyright (C) 2014-2015 Nginx, Inc. # Copyright (C) 2018 LinuxServer.io -import sys, os, signal, base64, ldap, argparse +import sys, os, signal, base64, ldap, ldap.filter, argparse if sys.version_info.major == 2: from Cookie import BaseCookie from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler @@ -101,7 +101,7 @@ def do_GET(self): self.log_error(e) return True - ctx['user'] = user + ctx['user'] = ldap.filter.escape_filter_chars(user) ctx['pass'] = passwd # Continue request processing @@ -172,6 +172,7 @@ class LDAPAuthHandler(AuthHandler): 'realm': ('X-Ldap-Realm', 'Restricted'), 'url': ('X-Ldap-URL', None), 'starttls': ('X-Ldap-Starttls', 'false'), + 'disable_referrals': ('X-Ldap-DisableReferrals', 'false'), 'basedn': ('X-Ldap-BaseDN', None), 'template': ('X-Ldap-Template', '(cn=%(username)s)'), 'binddn': ('X-Ldap-BindDN', ''), @@ -233,9 +234,9 @@ def do_GET(self): if ctx['starttls'] == 'true': ldap_obj.start_tls_s() - # See http://www.python-ldap.org/faq.shtml - # uncomment, if required - # ldap_obj.set_option(ldap.OPT_REFERRALS, 0) + # See https://www.python-ldap.org/en/latest/faq.html + if ctx['disable_referrals'] == 'true': + ldap_obj.set_option(ldap.OPT_REFERRALS, 0) ctx['action'] = 'binding as search user' ldap_obj.bind_s(ctx['binddn'], ctx['bindpasswd'], ldap.AUTH_SIMPLE) @@ -252,13 +253,27 @@ def do_GET(self): searchfilter, ['objectclass'], 1) ctx['action'] = 'verifying search query results' - if len(results) < 1: + + nres = len(results) + + if nres < 1: self.auth_failed(ctx, 'no objects found') return - ctx['action'] = 'binding as an existing user' - ldap_dn = results[0][0] - ctx['action'] += ' "%s"' % ldap_dn + if nres > 1: + self.log_message("note: filter match multiple objects: %d, using first" % nres) + + user_entry = results[0] + ldap_dn = user_entry[0] + + if ldap_dn == None: + self.auth_failed(ctx, 'matched object has no dn') + return + + self.log_message('attempting to bind using dn "%s"' % (ldap_dn)) + + ctx['action'] = 'binding as an existing user "%s"' % ldap_dn + ldap_obj.bind_s(ldap_dn, ctx['pass'], ldap.AUTH_SIMPLE) self.log_message('Auth OK for user "%s"' % (ctx['user'])) @@ -328,6 +343,7 @@ def exit_handler(signal, frame): 'realm': ('X-Ldap-Realm', args.realm), 'url': ('X-Ldap-URL', args.url), 'starttls': ('X-Ldap-Starttls', args.starttls), + 'disable_referrals': ('X-Ldap-DisableReferrals', args.disable_referrals), 'basedn': ('X-Ldap-BaseDN', args.basedn), 'template': ('X-Ldap-Template', args.filter), 'binddn': ('X-Ldap-BindDN', args.binddn),