package main import ( "crypto/tls" "errors" "fmt" "log" "net" "strings" "time" "github.com/pyke369/golang-support/rcache" "gopkg.in/ldap.v2" ) type ldapConfig struct { servers []string baseDN string bindCn string bindPw string searchFilter string attributes []string validGroups []string mfaType string certAuth string ipMin net.IP ipMax net.IP upgradeFrom string routes []string } func (l *ldapConfig) addIPRange(s string) error { ips := strings.Split(s, "-") if len(ips) != 2 { return errors.New("invalid IPs") } for k, v := range []*net.IP{&(l.ipMin), &(l.ipMax)} { if ip := net.ParseIP(strings.Trim(ips[k], " ")); ip == nil { return errors.New(fmt.Sprintf("invalid IP '%s'", ips[k])) } else { *v = ip } } return nil } // auth loop. Try all auth profiles from startProfile // return the last possible profile and the mail if we found a mail like login func (s *OpenVpnMgt) AuthLoop(startProfile, user, pass string, overridePwdCheck bool) (string, string, string) { login := []string{user} profile := startProfile mail := "" otpSalt := "" re := rcache.Get("^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$") for { if mail == "" && re.MatchString(login[0]) { mail = login[0] } n := profile for k, ldap := range s.ldap { if ldap.upgradeFrom != profile { continue } err, userOk, passOk, attributes := ldap.Auth(login, pass) if otpSalt == "" && len(attributes) > 1 && len(attributes[1]) > 0 { otpSalt = attributes[1][0] } // if there is an error, try the other configurations if err != nil { log.Printf("user %s not validated as %s\n", user, k) continue } // we did find a valid User if userOk { // the login for the new auth level is given by the current one login = attributes[0] if passOk && profile != "" { // it's at least the second auth level, and we have a valid // password on 2 different auth system. It's a dupplicate // password, let's log it log.Printf("User %s has a dupplicate password\n", user) } // we have either a positive auth ok a previous valid one if passOk || profile != "" || overridePwdCheck { profile = k } } } // no profile update this turn, no need to continue if n == profile { break } } return profile, mail, otpSalt } // override the real DialTLS function func myDialTLS(network, addr string, config *tls.Config) (*ldap.Conn, error) { dc, err := net.DialTimeout(network, addr, 3*time.Second) if err != nil { return nil, ldap.NewError(ldap.ErrorNetwork, err) } c := tls.Client(dc, config) if err = c.Handshake(); err != nil { // Handshake error, close the established connection before we return an error dc.Close() return nil, ldap.NewError(ldap.ErrorNetwork, err) } conn := ldap.NewConn(c, true) conn.Start() return conn, nil } func (conf *ldapConfig) Auth(logins []string, pass string) (e error, userOk, passOk bool, attributes [][]string) { // special case. This configuration is a filter on the previous one if len(conf.servers) == 0 && len(conf.validGroups) > 0 { if inArray(logins, conf.validGroups) { return nil, true, false, [][]string{logins} } } // no server ldap or multiple login should not happen here if len(logins) != 1 || len(conf.servers) == 0 { return nil, false, false, nil } attributes = [][]string{logins} for _, s := range conf.servers { // we force ldaps because we can l, err := myDialTLS("tcp", s+":636", &tls.Config{ServerName: s}) if err != nil { log.Println(err) continue } defer l.Close() // First bind with a read only user if err = l.Bind(conf.bindCn, conf.bindPw); err != nil { log.Println(err) return err, false, false, nil } search := append(conf.attributes, "dn") // search the user searchRequest := ldap.NewSearchRequest( conf.baseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, fmt.Sprintf(conf.searchFilter, logins[0]), search, nil, ) sr, err := l.Search(searchRequest) if err != nil { log.Println(err) return err, false, false, nil } if len(sr.Entries) != 1 { return errors.New("User does not exist or too many entries returned"), false, false, nil } // check the attributes requested in the search // a valid account must be part of the correct group (per instance) ret := [][]string{} for _, needed := range conf.attributes { ok := false for _, attribute := range sr.Entries[0].Attributes { if (*attribute).Name == needed { ret = append(ret, attribute.Values) ok = true } } if !ok { ret = append(ret, []string{}) } } // user must have both the first and second attributes if len(ret[0]) == 0 { log.Printf("User %s has no %s attribute", logins[0], conf.attributes[0]) return nil, false, false, nil } if len(ret[1]) == 0 { log.Printf("User %s has no %s attribute", logins[0], conf.attributes[1]) return nil, false, false, nil } // check if the first attribute valus are in the validGroups list if len(conf.validGroups) > 0 && !inArray(conf.validGroups, ret[0]) { return nil, false, false, nil } // if there is no validGroups check, pass the first attribute values to the // next level if len(conf.validGroups) == 0 { attributes = [][]string{ret[0]} } else { attributes = [][]string{ret[1]} } if len(ret) > 2 { attributes = append(attributes, ret[2:]...) } log.Printf("User %s has a valid account on %s", logins[0], s) userdn := sr.Entries[0].DN // if the password is empty, stop here if pass == "" { return nil, true, false, attributes } // if there is an error, it's because the password is invalid if err = l.Bind(userdn, pass); err != nil { return nil, true, false, attributes } // everything is fine, log.Printf("User %s has a valid password on %s", logins[0], s) return nil, true, true, attributes } // if we are here, no server is responding, rejectif auth log.Println("can't join any ldap server") return }