// Go Ldap Api // Copyright (c) 2022 yo000 // package main import ( "fmt" "sync" "strings" "github.com/go-ldap/ldap/v3" log "github.com/sirupsen/logrus" ) type MyLdap struct { Conn *ldap.Conn Host string User string Pass string BaseDN string } var ( mutex sync.Mutex ) func connectLdap(myldap *MyLdap) (*MyLdap, error) { var err error var conLdap *MyLdap myldap.Conn, err = ldap.DialURL(myldap.Host) if err != nil { //log.Errorf("Error dialing LDAP: %v", err) return conLdap, err } err = myldap.Conn.Bind(myldap.User, myldap.Pass) if err != nil { //log.Errorf("Error binding LDAP: %v", err) return conLdap, err } return myldap, err } func ldapSearch(conLdap *MyLdap, searchReq *ldap.SearchRequest, attempt int) (*ldap.SearchResult, error) { var err error if conLdap.Conn == nil { conLdap, err = connectLdap(conLdap) if err != nil { return nil, err } } mutex.Lock() result, err := conLdap.Conn.Search(searchReq) mutex.Unlock() // Manage connection errors here if err != nil && strings.HasSuffix(err.Error(), "ldap: connection closed") { log.Error("LDAP connection closed, retrying") mutex.Lock() conLdap.Conn.Close() conLdap, err = connectLdap(conLdap) mutex.Unlock() if err != nil { return result, err } else { attempt = attempt + 1 return ldapSearch(conLdap, searchReq, attempt) } } return result, err } func doLdapSearch(myldap *MyLdap, baseDn, cn, class, attributes string) (*ldap.SearchResult, error) { var filter string var realBaseDn string var realAttributes []string // Build search filter if strings.EqualFold(class, "ALL") { filter = fmt.Sprintf("(objectClass=*)") } else { filter = fmt.Sprintf("(objectClass=%s)", class) } if false == strings.EqualFold(cn, "ALL") { filter = fmt.Sprintf("(&%s(cn=%s))", filter, ldap.EscapeFilter(cn)) } log.Debugf("LDAP search filter: %s", filter) // Build absolute search base DN from configuration & provided DN (which is relative) if strings.EqualFold(baseDn, "ALL") || len(baseDn) == 0 { realBaseDn = fmt.Sprintf("%s", myldap.BaseDN) } else { realBaseDn = fmt.Sprintf("%s,%s", baseDn, myldap.BaseDN) } log.Debugf("LDAP search base dn: %s", realBaseDn) // Build attributes array if false == strings.EqualFold(attributes, "ALL") { for _, a := range strings.Split(attributes, ",") { realAttributes = append(realAttributes, a) } } log.Debugf("LDAP search attributes to return (all if empty): %v", realAttributes) searchReq := ldap.NewSearchRequest(realBaseDn, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, realAttributes, []ldap.Control{}) result, err := ldapSearch(myldap, searchReq, 0) if err != nil { return nil, err } return result, nil } func findUserFullDN(myldap *MyLdap, username string) (string, error) { sr, err := doLdapSearch(myldap, "", username, "ALL", "") if err != nil { return "", err } if len(sr.Entries) == 0 { return "", fmt.Errorf("User not found with cn=%s", username) } else if len(sr.Entries) > 1 { return "", fmt.Errorf("More than one object (%d) found with cn=%s", len(sr.Entries), username) } result := sr.Entries[0].DN return result, nil }