|
@@ -260,7 +260,8 @@ func authLDAP(username string, password string, cfg config.LDAPConfiguration) bo
|
|
|
|
|
|
|
|
defer connection.Close()
|
|
defer connection.Close()
|
|
|
|
|
|
|
|
- err = connection.Bind(ldapTemplateBindDN(cfg.BindDN, username), password)
|
|
|
|
|
|
|
+ bindDN := formatOptionalPercentS(cfg.BindDN, escapeForLDAPDN(username))
|
|
|
|
|
+ err = connection.Bind(bindDN, password)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
l.Warnln("LDAP Bind:", err)
|
|
l.Warnln("LDAP Bind:", err)
|
|
|
return false
|
|
return false
|
|
@@ -280,7 +281,7 @@ func authLDAP(username string, password string, cfg config.LDAPConfiguration) bo
|
|
|
// the user. If this matches precisely one user then we are good to go.
|
|
// the user. If this matches precisely one user then we are good to go.
|
|
|
// The search filter uses the same %s interpolation as the bind DN.
|
|
// The search filter uses the same %s interpolation as the bind DN.
|
|
|
|
|
|
|
|
- searchString := fmt.Sprintf(cfg.SearchFilter, username)
|
|
|
|
|
|
|
+ searchString := formatOptionalPercentS(cfg.SearchFilter, escapeForLDAPFilter(username))
|
|
|
const sizeLimit = 2 // we search for up to two users -- we only want to match one, so getting any number >1 is a failure.
|
|
const sizeLimit = 2 // we search for up to two users -- we only want to match one, so getting any number >1 is a failure.
|
|
|
const timeLimit = 60 // Search for up to a minute...
|
|
const timeLimit = 60 // Search for up to a minute...
|
|
|
searchReq := ldap.NewSearchRequest(cfg.SearchBaseDN, ldap.ScopeWholeSubtree, ldap.DerefFindingBaseObj, sizeLimit, timeLimit, false, searchString, nil, nil)
|
|
searchReq := ldap.NewSearchRequest(cfg.SearchBaseDN, ldap.ScopeWholeSubtree, ldap.DerefFindingBaseObj, sizeLimit, timeLimit, false, searchString, nil, nil)
|
|
@@ -298,13 +299,37 @@ func authLDAP(username string, password string, cfg config.LDAPConfiguration) bo
|
|
|
return true
|
|
return true
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func ldapTemplateBindDN(bindDN string, username string) string {
|
|
|
|
|
- // Check if formatting directives are included in the ldapTemplateBindDN - if so add username.
|
|
|
|
|
- // (%%s is a literal %s - unlikely for LDAP, but easy to handle here).
|
|
|
|
|
- if strings.Count(bindDN, "%s") != strings.Count(bindDN, "%%s") {
|
|
|
|
|
- bindDN = fmt.Sprintf(bindDN, username)
|
|
|
|
|
|
|
+// escapeForLDAPFilter escapes a value that will be used in a filter clause
|
|
|
|
|
+func escapeForLDAPFilter(value string) string {
|
|
|
|
|
+ // https://social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters.aspx#Special_Characters
|
|
|
|
|
+ // Backslash must always be first in the list so we don't double escape them.
|
|
|
|
|
+ return escapeRunes(value, []rune{'\\', '*', '(', ')', 0})
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// escapeForLDAPDN escapes a value that will be used in a bind DN
|
|
|
|
|
+func escapeForLDAPDN(value string) string {
|
|
|
|
|
+ // https://social.technet.microsoft.com/wiki/contents/articles/5312.active-directory-characters-to-escape.aspx
|
|
|
|
|
+ // Backslash must always be first in the list so we don't double escape them.
|
|
|
|
|
+ return escapeRunes(value, []rune{'\\', ',', '#', '+', '<', '>', ';', '"', '=', ' ', 0})
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func escapeRunes(value string, runes []rune) string {
|
|
|
|
|
+ for _, e := range runes {
|
|
|
|
|
+ value = strings.ReplaceAll(value, string(e), fmt.Sprintf("\\%X", e))
|
|
|
|
|
+ }
|
|
|
|
|
+ return value
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func formatOptionalPercentS(template string, username string) string {
|
|
|
|
|
+ var replacements []any
|
|
|
|
|
+ nReps := strings.Count(template, "%s") - strings.Count(template, "%%s")
|
|
|
|
|
+ if nReps < 0 {
|
|
|
|
|
+ nReps = 0
|
|
|
|
|
+ }
|
|
|
|
|
+ for i := 0; i < nReps; i++ {
|
|
|
|
|
+ replacements = append(replacements, username)
|
|
|
}
|
|
}
|
|
|
- return bindDN
|
|
|
|
|
|
|
+ return fmt.Sprintf(template, replacements...)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Convert an ISO-8859-1 encoded byte string to UTF-8. Works by the
|
|
// Convert an ISO-8859-1 encoded byte string to UTF-8. Works by the
|