package main import ( "crypto/rand" "crypto/tls" "encoding/base64" "errors" "fmt" "github.com/amoghe/go-crypt" "github.com/go-ldap/ldap/v3" ) type LDAP struct { Host string Port int Starttls bool Ssl bool BaseDN string ServiceDN string ServicePassword string MailHost string MailPort int MailUser string MailPassword string } func (l LDAP) Connect() (*LDAPConn, error) { if l.Ssl { if c, err := ldap.DialTLS("tcp", fmt.Sprintf("%s:%d", l.Host, l.Port), &tls.Config{ServerName: l.Host}); err != nil { return nil, errors.New("unable to establish LDAPS connection to " + fmt.Sprintf("%s:%d", l.Host, l.Port) + ": " + err.Error()) } else { return &LDAPConn{ LDAP: l, connection: c, }, nil } } else if c, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", l.Host, l.Port)); err != nil { return nil, errors.New("unable to establish LDAP connection to " + fmt.Sprintf("%s:%d", l.Host, l.Port) + ": " + err.Error()) } else { if l.Starttls { if err = c.StartTLS(&tls.Config{ServerName: l.Host}); err != nil { c.Close() return nil, errors.New("unable to StartTLS: " + err.Error()) } } return &LDAPConn{ LDAP: l, connection: c, }, nil } } type LDAPConn struct { LDAP connection *ldap.Conn } func (l LDAPConn) ServiceBind() error { return l.connection.Bind(l.ServiceDN, l.ServicePassword) } func (l LDAPConn) Bind(username string, password string) error { return l.connection.Bind(username, password) } func (l LDAPConn) SearchDN(username string, person bool) (string, error) { objectClass := "organizationalPerson" if !person { objectClass = "simpleSecurityObject" } searchRequest := ldap.NewSearchRequest( l.BaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, fmt.Sprintf("(&(objectClass=%s)(uid=%s))", objectClass, username), []string{"dn"}, nil, ) sr, err := l.connection.Search(searchRequest) if err != nil { return "", err } if len(sr.Entries) != 1 { return "", errors.New("User does not exist or too many entries returned") } return sr.Entries[0].DN, nil } func (l LDAPConn) GetEntry(dn string) ([]*ldap.EntryAttribute, error) { searchRequest := ldap.NewSearchRequest( dn, ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false, "(objectClass=*)", []string{}, nil, ) sr, err := l.connection.Search(searchRequest) if err != nil { return nil, err } if len(sr.Entries) != 1 { return nil, errors.New("User does not exist or too many entries returned") } return sr.Entries[0].Attributes, nil } func genSalt() (string, error) { b := make([]byte, 16) if _, err := rand.Read(b); err != nil { return "", err } return base64.StdEncoding.EncodeToString(b), nil } func (l LDAPConn) ChangePassword(dn string, rawpassword string) error { salt, err := genSalt() if err != nil { return err } hashedpasswd, err := crypt.Crypt(rawpassword, "$6$"+salt+"$") if err != nil { return err } modify := ldap.NewModifyRequest(dn, nil) modify.Replace("userPassword", []string{"{CRYPT}" + hashedpasswd}) return l.connection.Modify(modify) }