2018-11-12 22:31:10 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/rand"
|
|
|
|
"crypto/tls"
|
|
|
|
"encoding/base64"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
"github.com/amoghe/go-crypt"
|
|
|
|
"gopkg.in/ldap.v2"
|
|
|
|
)
|
|
|
|
|
|
|
|
type LDAP struct {
|
|
|
|
Host string
|
|
|
|
Port int
|
|
|
|
Starttls bool
|
|
|
|
Ssl bool
|
|
|
|
BaseDN string
|
|
|
|
ServiceDN string
|
|
|
|
ServicePassword 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 {
|
2020-09-02 13:09:14 +00:00
|
|
|
return nil, errors.New("unable to establish LDAPS connection to " + fmt.Sprintf("%s:%d", l.Host, l.Port) + ": " + err.Error())
|
2018-11-12 22:31:10 +00:00
|
|
|
} else {
|
|
|
|
return &LDAPConn{
|
2020-09-02 13:09:14 +00:00
|
|
|
LDAP: l,
|
2018-11-12 22:31:10 +00:00
|
|
|
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{
|
2020-09-02 13:09:14 +00:00
|
|
|
LDAP: l,
|
2018-11-12 22:31:10 +00:00
|
|
|
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) (string, error) {
|
|
|
|
searchRequest := ldap.NewSearchRequest(
|
|
|
|
l.BaseDN,
|
|
|
|
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
|
|
|
|
fmt.Sprintf("(&(objectClass=organizationalPerson)(uid=%s))", 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
|
|
|
|
}
|
|
|
|
|
2020-09-02 13:09:14 +00:00
|
|
|
hashedpasswd, err := crypt.Crypt(rawpassword, "$6$"+salt+"$")
|
2018-11-12 22:31:10 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
modify := ldap.NewModifyRequest(dn)
|
|
|
|
modify.Replace("userPassword", []string{"{CRYPT}" + hashedpasswd})
|
|
|
|
|
|
|
|
return l.connection.Modify(modify)
|
|
|
|
}
|