blank state
This commit is contained in:
parent
2c289f8125
commit
0e72c3a242
|
@ -1,4 +1,3 @@
|
||||||
test.sh
|
test.sh
|
||||||
openvpn-dm-mgt-server
|
openvpn-mgt
|
||||||
openvpn-dm-mgt-server.conf
|
openvpn-dm-mgt-server.conf
|
||||||
iproute/iproute
|
|
||||||
|
|
34
dhcp.go
34
dhcp.go
|
@ -1,34 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *OpenVpnMgt) isFree(ip string) bool {
|
|
||||||
for _, remote := range s.clients {
|
|
||||||
for _, c := range remote {
|
|
||||||
if c.PrivIP == ip {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// internal DHCP
|
|
||||||
func (s *OpenVpnMgt) getIP(c *vpnSession) (string, error) {
|
|
||||||
s.m.Lock()
|
|
||||||
defer s.m.Unlock()
|
|
||||||
|
|
||||||
ipmax := nextIP(s.ldap[c.Profile].ipMax).String()
|
|
||||||
|
|
||||||
sip := s.ldap[c.Profile].ipMin.String()
|
|
||||||
for ip := s.ldap[c.Profile].ipMin; sip != ipmax; ip = nextIP(ip) {
|
|
||||||
sip = ip.String()
|
|
||||||
if s.isFree(sip) {
|
|
||||||
return sip, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", errors.New("no more IP")
|
|
||||||
}
|
|
18
httpd.go
18
httpd.go
|
@ -119,11 +119,9 @@ func (h *HttpServer) ajaxHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
webuser := strings.Replace(r.TLS.PeerCertificates[0].Subject.CommonName, " ", "", -1)
|
webuser := strings.Replace(r.TLS.PeerCertificates[0].Subject.CommonName, " ", "", -1)
|
||||||
_, _, _, profilePath := h.ovpn.AuthLoop(h.minProfile, webuser, "", false)
|
|
||||||
if !inArray(h.neededProfiles, profilePath) {
|
//TODO security
|
||||||
http.Error(w, fmt.Sprintf("You need on of %s profile and you have %s", h.neededProfiles, profilePath), 403)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Printf("%s is connected via the web interfaces\n", webuser)
|
log.Printf("%s is connected via the web interfaces\n", webuser)
|
||||||
|
|
||||||
req, err := parseJsonQuery(r)
|
req, err := parseJsonQuery(r)
|
||||||
|
@ -135,17 +133,7 @@ func (h *HttpServer) ajaxHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
switch req.Action {
|
switch req.Action {
|
||||||
case "stats":
|
case "stats":
|
||||||
jsonStr, err := json.Marshal(h.ovpn.Stats())
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, fmt.Sprintf("%s", err), 500)
|
|
||||||
}
|
|
||||||
fmt.Fprintf(w, "%s", jsonStr)
|
|
||||||
|
|
||||||
case "kill":
|
case "kill":
|
||||||
if err := h.ovpn.Kill(req.Params.Session, req.Params.Id, webuser); err != nil {
|
|
||||||
http.Error(w, fmt.Sprintf("%s", err), 500)
|
|
||||||
}
|
|
||||||
fmt.Fprintf(w, "{}")
|
|
||||||
default:
|
default:
|
||||||
http.Error(w, "Invalid request", 500)
|
http.Error(w, "Invalid request", 500)
|
||||||
}
|
}
|
||||||
|
|
250
ldap.go
250
ldap.go
|
@ -1,250 +0,0 @@
|
||||||
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, []string) {
|
|
||||||
login := []string{user}
|
|
||||||
profile := startProfile
|
|
||||||
mail := ""
|
|
||||||
otpSalt := ""
|
|
||||||
profilePath := []string{}
|
|
||||||
|
|
||||||
re := rcache.Get("^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$")
|
|
||||||
|
|
||||||
for {
|
|
||||||
// the first login that match the mail regexp is the mail address
|
|
||||||
if mail == "" && re.MatchString(login[0]) {
|
|
||||||
mail = login[0]
|
|
||||||
}
|
|
||||||
n := profile
|
|
||||||
for k, ldap := range s.ldap {
|
|
||||||
// check if the current profile is an upgrade of the previous one
|
|
||||||
// check the startup profile first
|
|
||||||
ok := (profile == "") && (len(ldap.upgradeFrom) == 0)
|
|
||||||
for _, possible := range ldap.upgradeFrom {
|
|
||||||
if possible == profile {
|
|
||||||
ok = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
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
|
|
||||||
profilePath = append(profilePath, profile)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// no profile update this turn, no need to continue
|
|
||||||
if n == profile {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return profile, mail, otpSalt, profilePath
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
152
logs.go
152
logs.go
|
@ -1,152 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"net/smtp"
|
|
||||||
"text/template"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *vpnSession) LogPrintln(v ...interface{}) {
|
|
||||||
log.Println(c.Login, c.IP, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *OpenVpnMgt) Log(c *vpnSession) error {
|
|
||||||
if s.vpnlogUrl != "" {
|
|
||||||
if err := c.getASInfos(s.vpnlogUrl); err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Time = time.Now().Round(time.Second)
|
|
||||||
|
|
||||||
jsonStr, err := json.Marshal(c)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Println(string(jsonStr))
|
|
||||||
|
|
||||||
if err := s.SendMail(c); err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *vpnSession) getASInfos(vpnlogUrl string) error {
|
|
||||||
jsonStr, err := json.Marshal(c)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequest("POST", vpnlogUrl, bytes.NewBuffer(jsonStr))
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
timeout := time.Duration(3 * time.Second)
|
|
||||||
client := http.Client{
|
|
||||||
Timeout: timeout,
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
body, _ := ioutil.ReadAll(resp.Body)
|
|
||||||
err = json.Unmarshal(body, c)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *OpenVpnMgt) MailTemplate(c *vpnSession) error {
|
|
||||||
var buf1 bytes.Buffer
|
|
||||||
var buf2 bytes.Buffer
|
|
||||||
|
|
||||||
tmpl, err := template.New("pwnTemplate").Parse(s.pwnTemplate)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := tmpl.Execute(&buf1, c); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.pwnMail = buf1.String()
|
|
||||||
|
|
||||||
tmpl, err = template.New("newAsTemplate").Parse(s.newAsTemplate)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := tmpl.Execute(&buf2, c); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.newAsMail = buf2.String()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *OpenVpnMgt) SendMail(c *vpnSession) error {
|
|
||||||
if c.Mail == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if (s.newAsTemplate == "" || !c.NewAS) &&
|
|
||||||
(s.pwnTemplate == "" || !c.PwnedPasswd) {
|
|
||||||
// can not send mail without template or cause
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// needed for the templating
|
|
||||||
c.MailFrom = s.MailFrom
|
|
||||||
c.CcPwnPassword = s.CcPwnPassword
|
|
||||||
|
|
||||||
// complete the templates
|
|
||||||
if err := s.MailTemplate(c); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
mail, err := smtp.Dial(s.mailRelay)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer mail.Close()
|
|
||||||
|
|
||||||
if c.PwnedPasswd {
|
|
||||||
mail.Mail(s.MailFrom)
|
|
||||||
mail.Rcpt(c.Mail)
|
|
||||||
if c.TooMuchPwn && s.CcPwnPassword != "" {
|
|
||||||
mail.Rcpt(s.CcPwnPassword)
|
|
||||||
}
|
|
||||||
wc, err := mail.Data()
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
defer wc.Close()
|
|
||||||
buf := bytes.NewBufferString(c.pwnMail)
|
|
||||||
if _, err = buf.WriteTo(wc); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
wc.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.NewAS {
|
|
||||||
mail.Mail(s.MailFrom)
|
|
||||||
mail.Rcpt(c.Mail)
|
|
||||||
wc, err := mail.Data()
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
defer wc.Close()
|
|
||||||
buf := bytes.NewBufferString(c.newAsMail)
|
|
||||||
if _, err = buf.WriteTo(wc); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
wc.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
42
main.go
42
main.go
|
@ -6,7 +6,6 @@ import (
|
||||||
"log/syslog"
|
"log/syslog"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pyke369/golang-support/uconfig"
|
"github.com/pyke369/golang-support/uconfig"
|
||||||
|
@ -31,19 +30,6 @@ func main() {
|
||||||
rand.Seed(time.Now().UnixNano())
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
|
||||||
server := NewVPNServer(config.GetString("config.openvpnPort", "127.0.0.01:5000"))
|
server := NewVPNServer(config.GetString("config.openvpnPort", "127.0.0.01:5000"))
|
||||||
server.vpnlogUrl = config.GetString("config.vpnLogUrl", "")
|
|
||||||
server.mailRelay = config.GetString("config.mailRelay", "")
|
|
||||||
server.MailFrom = config.GetString("config.mailFrom", "")
|
|
||||||
server.CcPwnPassword = config.GetString("config.ccPwnPassword", "")
|
|
||||||
server.pwnTemplate = config.GetString("config.pwnTemplate", "")
|
|
||||||
server.newAsTemplate = config.GetString("config.newAsTemplate", "")
|
|
||||||
server.cacheDir = config.GetString("config.cacheDir", "")
|
|
||||||
server.authCa = config.GetString("config.authCa", "")
|
|
||||||
server.otpMasterSecrets = parseConfigArray(config, "config.masterSecrets")
|
|
||||||
|
|
||||||
if len(server.otpMasterSecrets) == 0 {
|
|
||||||
server.otpMasterSecrets = append(server.otpMasterSecrets, "*******************")
|
|
||||||
}
|
|
||||||
|
|
||||||
server.syslog = false
|
server.syslog = false
|
||||||
if *logToSyslog {
|
if *logToSyslog {
|
||||||
|
@ -61,34 +47,6 @@ func main() {
|
||||||
server.debug = true
|
server.debug = true
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, profile := range config.GetPaths("config.profiles") {
|
|
||||||
profileName := strings.Split(profile, ".")[2]
|
|
||||||
ldapConf := ldapConfig{
|
|
||||||
servers: parseConfigArray(config, profile+".servers"),
|
|
||||||
baseDN: config.GetString(profile+".baseDN", ""),
|
|
||||||
bindCn: config.GetString(profile+".bindCn", ""),
|
|
||||||
bindPw: config.GetString(profile+".bindPw", ""),
|
|
||||||
searchFilter: config.GetString(profile+".searchFilter", ""),
|
|
||||||
attributes: parseConfigArray(config, profile+".attributes"),
|
|
||||||
validGroups: parseConfigArray(config, profile+".validGroups"),
|
|
||||||
routes: parseConfigArray(config, profile+".routes"),
|
|
||||||
mfaType: config.GetString(profile+".mfa", ""),
|
|
||||||
certAuth: config.GetString(profile+".cert", "optionnal"),
|
|
||||||
upgradeFrom: parseConfigArray(config, profile+".upgradeFrom"),
|
|
||||||
}
|
|
||||||
if err := ldapConf.addIPRange(config.GetString(profile+".IPRange", "")); err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(ldapConf.servers) > 0 && len(ldapConf.attributes) < 2 {
|
|
||||||
log.Println("valud ldap configuration must have 2 attributes")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
server.ldap[profileName] = ldapConf
|
|
||||||
}
|
|
||||||
|
|
||||||
// time to start the listeners
|
// time to start the listeners
|
||||||
go server.Run()
|
go server.Run()
|
||||||
NewHTTPServer(
|
NewHTTPServer(
|
||||||
|
|
57
otp.go
57
otp.go
|
@ -1,57 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *OpenVpnMgt) GenerateOTP(user string) ([]string, error) {
|
|
||||||
return s.GenerateOTPGeneric(user, 30, "sha1", 10, 6)
|
|
||||||
}
|
|
||||||
|
|
||||||
// alternative OTP generator, not used at the moment
|
|
||||||
// func (s *OpenVpnMgt) GenerateSlackOTP(user string) ([]string, error) {
|
|
||||||
// return s.GenerateOTPGeneric(user, 60, "sha256", 30, 8)
|
|
||||||
// }
|
|
||||||
|
|
||||||
func (s *OpenVpnMgt) TokenPassword(c *vpnSession) (bool, string) {
|
|
||||||
now := time.Now().Unix()
|
|
||||||
if len(c.password) > 40 {
|
|
||||||
salt := c.password[:4]
|
|
||||||
for i := 0; i < 3; i++ {
|
|
||||||
test := encode64(ComputeHmac256(c.baseHash(salt, now/30-int64(i)), s.otpMasterSecrets[0]))
|
|
||||||
if salt+test == c.password {
|
|
||||||
return true, c.password
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
salt := NewSalt()
|
|
||||||
return false, salt + encode64(ComputeHmac256(c.baseHash(salt, now/30), s.otpMasterSecrets[0]))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *OpenVpnMgt) GenerateOTPGeneric(user string, period int, algo string, secretLen int, digits int) ([]string, error) {
|
|
||||||
codes := []string{}
|
|
||||||
now := time.Now()
|
|
||||||
|
|
||||||
secret := encodeSecret(ComputeHmac256(user, s.otpMasterSecrets[0])[:secretLen])
|
|
||||||
code, err := GenericTotpCode(secret, now, algo, digits, period)
|
|
||||||
if err != nil {
|
|
||||||
return codes, err
|
|
||||||
}
|
|
||||||
// the first code is the generic one
|
|
||||||
codes = append(codes, code)
|
|
||||||
|
|
||||||
for i := 1; i < 4; i++ {
|
|
||||||
code, _ = GenericTotpCode(secret, now.Add(-1*time.Second*time.Duration(period*i)), algo, digits, period)
|
|
||||||
codes = append(codes, code)
|
|
||||||
}
|
|
||||||
|
|
||||||
for j := 1; j < len(s.otpMasterSecrets); j++ {
|
|
||||||
secret = encodeSecret(ComputeHmac256(user, s.otpMasterSecrets[j])[:secretLen])
|
|
||||||
for i := 0; i < 3; i++ {
|
|
||||||
code, _ = GenericTotpCode(secret, now.Add(-1*time.Second*time.Duration(period*i)), algo, digits, period)
|
|
||||||
codes = append(codes, code)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return codes, nil
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
# external files
|
|
||||||
tls-auth /etc/openvpn/tlsauth.key
|
|
||||||
dh /etc/openvpn/dh2048.pem
|
|
||||||
ca /usr/local/share/ca-certificates/Dailymotion.crt
|
|
||||||
cert /etc/ssl/certs/vpn.dailymotion.com-cert.pem
|
|
||||||
key /etc/ssl/private/vpn.dailymotion.com-key.pem
|
|
||||||
|
|
||||||
# local parameters
|
|
||||||
port 41690
|
|
||||||
tls-server
|
|
||||||
mode server
|
|
||||||
ifconfig 192.168.200.1 255.255.248.0
|
|
||||||
topology subnet
|
|
||||||
dev vpnadmin
|
|
||||||
dev-type tun
|
|
||||||
#local 188.65.121.190
|
|
||||||
|
|
||||||
# security
|
|
||||||
user openvpn
|
|
||||||
group openvpn
|
|
||||||
reneg-sec 43200
|
|
||||||
management 127.0.0.1 4000
|
|
||||||
management-client
|
|
||||||
management-client-auth
|
|
||||||
auth-user-pass-optional
|
|
||||||
client-cert-not-required
|
|
||||||
username-as-common-name
|
|
||||||
|
|
||||||
# push
|
|
||||||
push "dhcp-option DNS 10.190.32.2"
|
|
||||||
push "dhcp-option DNS 10.190.32.20"
|
|
||||||
push "route-gateway 192.168.200.1"
|
|
||||||
push "topology subnet"
|
|
||||||
|
|
||||||
# crypto
|
|
||||||
cipher aes-128-cbc
|
|
||||||
keepalive 10 120
|
|
||||||
persist-key
|
|
||||||
|
|
||||||
ifconfig-nowarn
|
|
||||||
persist-remote-ip
|
|
||||||
persist-tun
|
|
||||||
verb 0
|
|
|
@ -1,5 +0,0 @@
|
||||||
sudo: false
|
|
||||||
language: go
|
|
||||||
go:
|
|
||||||
- 1.11.x
|
|
||||||
- tip
|
|
|
@ -1,19 +0,0 @@
|
||||||
Copyright (C) 2018 by Matt Evans
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
|
@ -1,62 +0,0 @@
|
||||||
# pwned-passwords
|
|
||||||
|
|
||||||
[![GoDoc](https://godoc.org/github.com/mattevans/pwned-passwords?status.svg)](https://godoc.org/github.com/mattevans/pwned-passwords)
|
|
||||||
[![Build Status](https://travis-ci.org/mattevans/pwned-passwords.svg?branch=master)](https://travis-ci.org/mattevans/pwned-passwords)
|
|
||||||
[![Go Report Card](https://goreportcard.com/badge/github.com/mattevans/pwned-passwords)](https://goreportcard.com/report/github.com/mattevans/pwned-passwords)
|
|
||||||
[![license](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/mattevans/pwned-passwords/blob/master/LICENSE)
|
|
||||||
|
|
||||||
A simple [Go](http://golang.org) client library for checking compromised passwords against [HIBP Pwned Passwords](https://haveibeenpwned.com/Passwords).
|
|
||||||
|
|
||||||
Upon request, results will be cached (in-memory), keyed by hash. With a two hour expiry window, subsequent requests will use cached data or fetch fresh data accordingly.
|
|
||||||
|
|
||||||
Installation
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
`go get -u github.com/mattevans/pwned-passwords`
|
|
||||||
|
|
||||||
Usage
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
hibp "github.com/mattevans/pwned-passwords"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// Init a client.
|
|
||||||
client := hibp.NewClient()
|
|
||||||
|
|
||||||
// Check to see if your given string is compromised.
|
|
||||||
pwned, err := client.Pwned.Compromised("string to check")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Pwned failed")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if pwned {
|
|
||||||
// Oh dear!
|
|
||||||
// You should avoid using that password
|
|
||||||
} else {
|
|
||||||
// Woo!
|
|
||||||
// All clear!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Expire in-memory cache**
|
|
||||||
|
|
||||||
```go
|
|
||||||
client.Cache.Expire(HASHED_VALUE)
|
|
||||||
```
|
|
||||||
|
|
||||||
```go
|
|
||||||
client.Cache.ExpireAll()
|
|
||||||
```
|
|
||||||
|
|
||||||
Contributing
|
|
||||||
-----------------
|
|
||||||
If you've found a bug or would like to contribute, please create an issue here on GitHub, or better yet fork the project and submit a pull request!
|
|
|
@ -1,65 +0,0 @@
|
||||||
package hibp
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
// cache holds our our cached hash/compromised pairs results.
|
|
||||||
var cache map[string]*PwnedStore
|
|
||||||
|
|
||||||
// cacheTTL stores the time to live of our cache (2 hours).
|
|
||||||
var cacheTTL = 2 * time.Hour
|
|
||||||
|
|
||||||
// CacheService handles in-memory caching of our hash/compromised pairs.
|
|
||||||
type CacheService service
|
|
||||||
|
|
||||||
// Get will return our stored in-memory hash/compromised pairs, if we have them.
|
|
||||||
func (s *CacheService) Get(hash string) *PwnedStore {
|
|
||||||
// Is our cache expired?
|
|
||||||
if s.IsExpired(hash) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use stored results.
|
|
||||||
return cache[hash]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store will save our hash/compromised pairs to a PwnedStore.
|
|
||||||
func (s *CacheService) Store(hash string, compromised bool) {
|
|
||||||
// No cache? Initialize it.
|
|
||||||
if cache == nil {
|
|
||||||
cache = map[string]*PwnedStore{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store
|
|
||||||
tn := time.Now()
|
|
||||||
cache[hash] = &PwnedStore{
|
|
||||||
Hash: hash,
|
|
||||||
Compromised: compromised,
|
|
||||||
UpdatedAt: &tn,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsExpired checks if we have cached hash and that it isn't expired.
|
|
||||||
func (s *CacheService) IsExpired(hash string) bool {
|
|
||||||
// No cache? bail.
|
|
||||||
if cache[hash] == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expired cache? bail.
|
|
||||||
lastUpdated := cache[hash].UpdatedAt
|
|
||||||
if lastUpdated != nil && lastUpdated.Add(cacheTTL).Before(time.Now()) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expire will expire the cache for a given hash.
|
|
||||||
func (s *CacheService) Expire(hash string) {
|
|
||||||
cache[hash] = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExpireAll will expire all cache.
|
|
||||||
func (s *CacheService) ExpireAll() {
|
|
||||||
cache = nil
|
|
||||||
}
|
|
|
@ -1,115 +0,0 @@
|
||||||
package hibp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
packageVersion = "0.0.1"
|
|
||||||
backendURL = "https://api.pwnedpasswords.com"
|
|
||||||
userAgent = "pwned-passwords-golang/" + packageVersion
|
|
||||||
)
|
|
||||||
|
|
||||||
// Client holds a connection to the HIBP API.
|
|
||||||
type Client struct {
|
|
||||||
client *http.Client
|
|
||||||
AppID string
|
|
||||||
UserAgent string
|
|
||||||
BackendURL *url.URL
|
|
||||||
|
|
||||||
// Services used for communicating with the API.
|
|
||||||
Pwned *PwnedService
|
|
||||||
Cache *CacheService
|
|
||||||
}
|
|
||||||
|
|
||||||
type service struct {
|
|
||||||
client *Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClient creates a new Client with the appropriate connection details and
|
|
||||||
// services used for communicating with the API.
|
|
||||||
func NewClient() *Client {
|
|
||||||
// Init new http.Client.
|
|
||||||
httpClient := http.DefaultClient
|
|
||||||
|
|
||||||
// Parse BE URL.
|
|
||||||
baseURL, _ := url.Parse(backendURL)
|
|
||||||
|
|
||||||
c := &Client{
|
|
||||||
client: httpClient,
|
|
||||||
BackendURL: baseURL,
|
|
||||||
UserAgent: userAgent,
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Pwned = &PwnedService{client: c}
|
|
||||||
c.Cache = &CacheService{client: c}
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRequest creates an API request. A relative URL can be provided in urlPath,
|
|
||||||
// which will be resolved to the BackendURL of the Client.
|
|
||||||
func (c *Client) NewRequest(method, urlPath string, body interface{}) (*http.Request, error) {
|
|
||||||
// Parse our URL.
|
|
||||||
rel, err := url.Parse(urlPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve to absolute URI.
|
|
||||||
u := c.BackendURL.ResolveReference(rel)
|
|
||||||
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
if body != nil {
|
|
||||||
err = json.NewEncoder(buf).Encode(body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the request.
|
|
||||||
req, err := http.NewRequest(method, u.String(), buf)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add our packages UA.
|
|
||||||
req.Header.Add("User-Agent", c.UserAgent)
|
|
||||||
|
|
||||||
return req, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do sends an API request and returns the API response.
|
|
||||||
func (c *Client) Do(req *http.Request) ([]string, error) {
|
|
||||||
resp, err := c.client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if rerr := resp.Body.Close(); err == nil {
|
|
||||||
err = rerr
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Error if anything else but 200.
|
|
||||||
// The API should always return a 200 (unless something is wrong) as per
|
|
||||||
// https://haveibeenpwned.com/API/v2#SearchingPwnedPasswordsByRange
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return nil, fmt.Errorf("Unexpected API response status: %v", resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse our resp.Body.
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Response is returned as new-line'd string, split and return.
|
|
||||||
return strings.Split(string(body), "\r\n"), err
|
|
||||||
}
|
|
|
@ -1,89 +0,0 @@
|
||||||
package hibp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/sha1"
|
|
||||||
"encoding/hex"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PwnedService handles retrieving pwned hashes from in-memory cache or
|
|
||||||
// by fetching fresh results.
|
|
||||||
type PwnedService service
|
|
||||||
|
|
||||||
// PwnedStore holds our pwned password hashes and compromised status.
|
|
||||||
type PwnedStore struct {
|
|
||||||
Hash string `json:"hash"`
|
|
||||||
Compromised bool `json:"compromised"`
|
|
||||||
UpdatedAt *time.Time `json:"updated_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compromised will build and execute a request to HIBP to check to see
|
|
||||||
// if the passed value is compromised or not.
|
|
||||||
func (s *PwnedService) Compromised(value string) (bool, error) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// Our value being checked is empty, we don't want that.
|
|
||||||
if value == "" {
|
|
||||||
return false, errors.New("Value for compromised check cannot be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
// SHA-1 hash our input value.
|
|
||||||
hashedStr := _hashString(value)
|
|
||||||
|
|
||||||
// If we have cached results, use them.
|
|
||||||
cache := s.client.Cache.Get(hashedStr)
|
|
||||||
if cache != nil {
|
|
||||||
hashedStr = cache.Hash
|
|
||||||
return cache.Compromised, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pop our prefix and suffix.
|
|
||||||
prefix := strings.ToUpper(hashedStr[:5])
|
|
||||||
suffix := strings.ToUpper(hashedStr[5:])
|
|
||||||
|
|
||||||
// Build request.
|
|
||||||
request, err := s.client.NewRequest("GET", fmt.Sprintf("range/%s", prefix), nil)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make request.
|
|
||||||
response, err := s.client.Do(request)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Range our response ([]string).
|
|
||||||
for _, target := range response {
|
|
||||||
// If our target, minus the compromised count matches our suffix.
|
|
||||||
if string(target[:35]) == suffix {
|
|
||||||
_, err = strconv.ParseInt(target[36:], 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store in cache as compromised.
|
|
||||||
s.client.Cache.Store(hashedStr, true)
|
|
||||||
|
|
||||||
// Return.
|
|
||||||
return true, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store in cache as non-compromised.
|
|
||||||
s.client.Cache.Store(hashedStr, false)
|
|
||||||
|
|
||||||
// Return.
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// _hashString will return a sha1 hash of the given value.
|
|
||||||
func _hashString(value string) string {
|
|
||||||
alg := sha1.New()
|
|
||||||
alg.Write([]byte(value))
|
|
||||||
return strings.ToUpper(hex.EncodeToString(alg.Sum(nil)))
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
language: go
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- go: 1.2.x
|
|
||||||
env: GOOS=linux GOARCH=amd64
|
|
||||||
- go: 1.2.x
|
|
||||||
env: GOOS=linux GOARCH=386
|
|
||||||
- go: 1.2.x
|
|
||||||
env: GOOS=windows GOARCH=amd64
|
|
||||||
- go: 1.2.x
|
|
||||||
env: GOOS=windows GOARCH=386
|
|
||||||
- go: 1.3.x
|
|
||||||
- go: 1.4.x
|
|
||||||
- go: 1.5.x
|
|
||||||
- go: 1.6.x
|
|
||||||
- go: 1.7.x
|
|
||||||
- go: 1.8.x
|
|
||||||
- go: 1.9.x
|
|
||||||
- go: 1.10.x
|
|
||||||
- go: 1.11.x
|
|
||||||
env: GOOS=linux GOARCH=amd64
|
|
||||||
- go: 1.11.x
|
|
||||||
env: GOOS=linux GOARCH=386
|
|
||||||
- go: 1.11.x
|
|
||||||
env: GOOS=windows GOARCH=amd64
|
|
||||||
- go: 1.11.x
|
|
||||||
env: GOOS=windows GOARCH=386
|
|
||||||
- go: tip
|
|
||||||
go_import_path: gopkg.in/asn-ber.v1
|
|
||||||
install:
|
|
||||||
- go list -f '{{range .Imports}}{{.}} {{end}}' ./... | xargs go get -v
|
|
||||||
- go list -f '{{range .TestImports}}{{.}} {{end}}' ./... | xargs go get -v
|
|
||||||
- go get code.google.com/p/go.tools/cmd/cover || go get golang.org/x/tools/cmd/cover
|
|
||||||
- go build -v ./...
|
|
||||||
script:
|
|
||||||
- go test -v -cover ./... || go test -v ./...
|
|
|
@ -1,22 +0,0 @@
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2011-2015 Michael Mitton (mmitton@gmail.com)
|
|
||||||
Portions copyright (c) 2015-2016 go-asn1-ber Authors
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
|
@ -1,24 +0,0 @@
|
||||||
[![GoDoc](https://godoc.org/gopkg.in/asn1-ber.v1?status.svg)](https://godoc.org/gopkg.in/asn1-ber.v1) [![Build Status](https://travis-ci.org/go-asn1-ber/asn1-ber.svg)](https://travis-ci.org/go-asn1-ber/asn1-ber)
|
|
||||||
|
|
||||||
|
|
||||||
ASN1 BER Encoding / Decoding Library for the GO programming language.
|
|
||||||
---------------------------------------------------------------------
|
|
||||||
|
|
||||||
Required libraries:
|
|
||||||
None
|
|
||||||
|
|
||||||
Working:
|
|
||||||
Very basic encoding / decoding needed for LDAP protocol
|
|
||||||
|
|
||||||
Tests Implemented:
|
|
||||||
A few
|
|
||||||
|
|
||||||
TODO:
|
|
||||||
Fix all encoding / decoding to conform to ASN1 BER spec
|
|
||||||
Implement Tests / Benchmarks
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
The Go gopher was designed by Renee French. (http://reneefrench.blogspot.com/)
|
|
||||||
The design is licensed under the Creative Commons 3.0 Attributions license.
|
|
||||||
Read this article for more details: http://blog.golang.org/gopher
|
|
|
@ -1,512 +0,0 @@
|
||||||
package ber
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"math"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MaxPacketLengthBytes specifies the maximum allowed packet size when calling ReadPacket or DecodePacket. Set to 0 for
|
|
||||||
// no limit.
|
|
||||||
var MaxPacketLengthBytes int64 = math.MaxInt32
|
|
||||||
|
|
||||||
type Packet struct {
|
|
||||||
Identifier
|
|
||||||
Value interface{}
|
|
||||||
ByteValue []byte
|
|
||||||
Data *bytes.Buffer
|
|
||||||
Children []*Packet
|
|
||||||
Description string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Identifier struct {
|
|
||||||
ClassType Class
|
|
||||||
TagType Type
|
|
||||||
Tag Tag
|
|
||||||
}
|
|
||||||
|
|
||||||
type Tag uint64
|
|
||||||
|
|
||||||
const (
|
|
||||||
TagEOC Tag = 0x00
|
|
||||||
TagBoolean Tag = 0x01
|
|
||||||
TagInteger Tag = 0x02
|
|
||||||
TagBitString Tag = 0x03
|
|
||||||
TagOctetString Tag = 0x04
|
|
||||||
TagNULL Tag = 0x05
|
|
||||||
TagObjectIdentifier Tag = 0x06
|
|
||||||
TagObjectDescriptor Tag = 0x07
|
|
||||||
TagExternal Tag = 0x08
|
|
||||||
TagRealFloat Tag = 0x09
|
|
||||||
TagEnumerated Tag = 0x0a
|
|
||||||
TagEmbeddedPDV Tag = 0x0b
|
|
||||||
TagUTF8String Tag = 0x0c
|
|
||||||
TagRelativeOID Tag = 0x0d
|
|
||||||
TagSequence Tag = 0x10
|
|
||||||
TagSet Tag = 0x11
|
|
||||||
TagNumericString Tag = 0x12
|
|
||||||
TagPrintableString Tag = 0x13
|
|
||||||
TagT61String Tag = 0x14
|
|
||||||
TagVideotexString Tag = 0x15
|
|
||||||
TagIA5String Tag = 0x16
|
|
||||||
TagUTCTime Tag = 0x17
|
|
||||||
TagGeneralizedTime Tag = 0x18
|
|
||||||
TagGraphicString Tag = 0x19
|
|
||||||
TagVisibleString Tag = 0x1a
|
|
||||||
TagGeneralString Tag = 0x1b
|
|
||||||
TagUniversalString Tag = 0x1c
|
|
||||||
TagCharacterString Tag = 0x1d
|
|
||||||
TagBMPString Tag = 0x1e
|
|
||||||
TagBitmask Tag = 0x1f // xxx11111b
|
|
||||||
|
|
||||||
// HighTag indicates the start of a high-tag byte sequence
|
|
||||||
HighTag Tag = 0x1f // xxx11111b
|
|
||||||
// HighTagContinueBitmask indicates the high-tag byte sequence should continue
|
|
||||||
HighTagContinueBitmask Tag = 0x80 // 10000000b
|
|
||||||
// HighTagValueBitmask obtains the tag value from a high-tag byte sequence byte
|
|
||||||
HighTagValueBitmask Tag = 0x7f // 01111111b
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// LengthLongFormBitmask is the mask to apply to the length byte to see if a long-form byte sequence is used
|
|
||||||
LengthLongFormBitmask = 0x80
|
|
||||||
// LengthValueBitmask is the mask to apply to the length byte to get the number of bytes in the long-form byte sequence
|
|
||||||
LengthValueBitmask = 0x7f
|
|
||||||
|
|
||||||
// LengthIndefinite is returned from readLength to indicate an indefinite length
|
|
||||||
LengthIndefinite = -1
|
|
||||||
)
|
|
||||||
|
|
||||||
var tagMap = map[Tag]string{
|
|
||||||
TagEOC: "EOC (End-of-Content)",
|
|
||||||
TagBoolean: "Boolean",
|
|
||||||
TagInteger: "Integer",
|
|
||||||
TagBitString: "Bit String",
|
|
||||||
TagOctetString: "Octet String",
|
|
||||||
TagNULL: "NULL",
|
|
||||||
TagObjectIdentifier: "Object Identifier",
|
|
||||||
TagObjectDescriptor: "Object Descriptor",
|
|
||||||
TagExternal: "External",
|
|
||||||
TagRealFloat: "Real (float)",
|
|
||||||
TagEnumerated: "Enumerated",
|
|
||||||
TagEmbeddedPDV: "Embedded PDV",
|
|
||||||
TagUTF8String: "UTF8 String",
|
|
||||||
TagRelativeOID: "Relative-OID",
|
|
||||||
TagSequence: "Sequence and Sequence of",
|
|
||||||
TagSet: "Set and Set OF",
|
|
||||||
TagNumericString: "Numeric String",
|
|
||||||
TagPrintableString: "Printable String",
|
|
||||||
TagT61String: "T61 String",
|
|
||||||
TagVideotexString: "Videotex String",
|
|
||||||
TagIA5String: "IA5 String",
|
|
||||||
TagUTCTime: "UTC Time",
|
|
||||||
TagGeneralizedTime: "Generalized Time",
|
|
||||||
TagGraphicString: "Graphic String",
|
|
||||||
TagVisibleString: "Visible String",
|
|
||||||
TagGeneralString: "General String",
|
|
||||||
TagUniversalString: "Universal String",
|
|
||||||
TagCharacterString: "Character String",
|
|
||||||
TagBMPString: "BMP String",
|
|
||||||
}
|
|
||||||
|
|
||||||
type Class uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
ClassUniversal Class = 0 // 00xxxxxxb
|
|
||||||
ClassApplication Class = 64 // 01xxxxxxb
|
|
||||||
ClassContext Class = 128 // 10xxxxxxb
|
|
||||||
ClassPrivate Class = 192 // 11xxxxxxb
|
|
||||||
ClassBitmask Class = 192 // 11xxxxxxb
|
|
||||||
)
|
|
||||||
|
|
||||||
var ClassMap = map[Class]string{
|
|
||||||
ClassUniversal: "Universal",
|
|
||||||
ClassApplication: "Application",
|
|
||||||
ClassContext: "Context",
|
|
||||||
ClassPrivate: "Private",
|
|
||||||
}
|
|
||||||
|
|
||||||
type Type uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
TypePrimitive Type = 0 // xx0xxxxxb
|
|
||||||
TypeConstructed Type = 32 // xx1xxxxxb
|
|
||||||
TypeBitmask Type = 32 // xx1xxxxxb
|
|
||||||
)
|
|
||||||
|
|
||||||
var TypeMap = map[Type]string{
|
|
||||||
TypePrimitive: "Primitive",
|
|
||||||
TypeConstructed: "Constructed",
|
|
||||||
}
|
|
||||||
|
|
||||||
var Debug bool = false
|
|
||||||
|
|
||||||
func PrintBytes(out io.Writer, buf []byte, indent string) {
|
|
||||||
data_lines := make([]string, (len(buf)/30)+1)
|
|
||||||
num_lines := make([]string, (len(buf)/30)+1)
|
|
||||||
|
|
||||||
for i, b := range buf {
|
|
||||||
data_lines[i/30] += fmt.Sprintf("%02x ", b)
|
|
||||||
num_lines[i/30] += fmt.Sprintf("%02d ", (i+1)%100)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < len(data_lines); i++ {
|
|
||||||
out.Write([]byte(indent + data_lines[i] + "\n"))
|
|
||||||
out.Write([]byte(indent + num_lines[i] + "\n\n"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func PrintPacket(p *Packet) {
|
|
||||||
printPacket(os.Stdout, p, 0, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func printPacket(out io.Writer, p *Packet, indent int, printBytes bool) {
|
|
||||||
indent_str := ""
|
|
||||||
|
|
||||||
for len(indent_str) != indent {
|
|
||||||
indent_str += " "
|
|
||||||
}
|
|
||||||
|
|
||||||
class_str := ClassMap[p.ClassType]
|
|
||||||
|
|
||||||
tagtype_str := TypeMap[p.TagType]
|
|
||||||
|
|
||||||
tag_str := fmt.Sprintf("0x%02X", p.Tag)
|
|
||||||
|
|
||||||
if p.ClassType == ClassUniversal {
|
|
||||||
tag_str = tagMap[p.Tag]
|
|
||||||
}
|
|
||||||
|
|
||||||
value := fmt.Sprint(p.Value)
|
|
||||||
description := ""
|
|
||||||
|
|
||||||
if p.Description != "" {
|
|
||||||
description = p.Description + ": "
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintf(out, "%s%s(%s, %s, %s) Len=%d %q\n", indent_str, description, class_str, tagtype_str, tag_str, p.Data.Len(), value)
|
|
||||||
|
|
||||||
if printBytes {
|
|
||||||
PrintBytes(out, p.Bytes(), indent_str)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, child := range p.Children {
|
|
||||||
printPacket(out, child, indent+1, printBytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadPacket reads a single Packet from the reader
|
|
||||||
func ReadPacket(reader io.Reader) (*Packet, error) {
|
|
||||||
p, _, err := readPacket(reader)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func DecodeString(data []byte) string {
|
|
||||||
return string(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseInt64(bytes []byte) (ret int64, err error) {
|
|
||||||
if len(bytes) > 8 {
|
|
||||||
// We'll overflow an int64 in this case.
|
|
||||||
err = fmt.Errorf("integer too large")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for bytesRead := 0; bytesRead < len(bytes); bytesRead++ {
|
|
||||||
ret <<= 8
|
|
||||||
ret |= int64(bytes[bytesRead])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shift up and down in order to sign extend the result.
|
|
||||||
ret <<= 64 - uint8(len(bytes))*8
|
|
||||||
ret >>= 64 - uint8(len(bytes))*8
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodeInteger(i int64) []byte {
|
|
||||||
n := int64Length(i)
|
|
||||||
out := make([]byte, n)
|
|
||||||
|
|
||||||
var j int
|
|
||||||
for ; n > 0; n-- {
|
|
||||||
out[j] = (byte(i >> uint((n-1)*8)))
|
|
||||||
j++
|
|
||||||
}
|
|
||||||
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func int64Length(i int64) (numBytes int) {
|
|
||||||
numBytes = 1
|
|
||||||
|
|
||||||
for i > 127 {
|
|
||||||
numBytes++
|
|
||||||
i >>= 8
|
|
||||||
}
|
|
||||||
|
|
||||||
for i < -128 {
|
|
||||||
numBytes++
|
|
||||||
i >>= 8
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodePacket decodes the given bytes into a single Packet
|
|
||||||
// If a decode error is encountered, nil is returned.
|
|
||||||
func DecodePacket(data []byte) *Packet {
|
|
||||||
p, _, _ := readPacket(bytes.NewBuffer(data))
|
|
||||||
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodePacketErr decodes the given bytes into a single Packet
|
|
||||||
// If a decode error is encountered, nil is returned
|
|
||||||
func DecodePacketErr(data []byte) (*Packet, error) {
|
|
||||||
p, _, err := readPacket(bytes.NewBuffer(data))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// readPacket reads a single Packet from the reader, returning the number of bytes read
|
|
||||||
func readPacket(reader io.Reader) (*Packet, int, error) {
|
|
||||||
identifier, length, read, err := readHeader(reader)
|
|
||||||
if err != nil {
|
|
||||||
return nil, read, err
|
|
||||||
}
|
|
||||||
|
|
||||||
p := &Packet{
|
|
||||||
Identifier: identifier,
|
|
||||||
}
|
|
||||||
|
|
||||||
p.Data = new(bytes.Buffer)
|
|
||||||
p.Children = make([]*Packet, 0, 2)
|
|
||||||
p.Value = nil
|
|
||||||
|
|
||||||
if p.TagType == TypeConstructed {
|
|
||||||
// TODO: if universal, ensure tag type is allowed to be constructed
|
|
||||||
|
|
||||||
// Track how much content we've read
|
|
||||||
contentRead := 0
|
|
||||||
for {
|
|
||||||
if length != LengthIndefinite {
|
|
||||||
// End if we've read what we've been told to
|
|
||||||
if contentRead == length {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// Detect if a packet boundary didn't fall on the expected length
|
|
||||||
if contentRead > length {
|
|
||||||
return nil, read, fmt.Errorf("expected to read %d bytes, read %d", length, contentRead)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the next packet
|
|
||||||
child, r, err := readPacket(reader)
|
|
||||||
if err != nil {
|
|
||||||
return nil, read, err
|
|
||||||
}
|
|
||||||
contentRead += r
|
|
||||||
read += r
|
|
||||||
|
|
||||||
// Test is this is the EOC marker for our packet
|
|
||||||
if isEOCPacket(child) {
|
|
||||||
if length == LengthIndefinite {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return nil, read, errors.New("eoc child not allowed with definite length")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append and continue
|
|
||||||
p.AppendChild(child)
|
|
||||||
}
|
|
||||||
return p, read, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if length == LengthIndefinite {
|
|
||||||
return nil, read, errors.New("indefinite length used with primitive type")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read definite-length content
|
|
||||||
if MaxPacketLengthBytes > 0 && int64(length) > MaxPacketLengthBytes {
|
|
||||||
return nil, read, fmt.Errorf("length %d greater than maximum %d", length, MaxPacketLengthBytes)
|
|
||||||
}
|
|
||||||
content := make([]byte, length, length)
|
|
||||||
if length > 0 {
|
|
||||||
_, err := io.ReadFull(reader, content)
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
return nil, read, io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
return nil, read, err
|
|
||||||
}
|
|
||||||
read += length
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.ClassType == ClassUniversal {
|
|
||||||
p.Data.Write(content)
|
|
||||||
p.ByteValue = content
|
|
||||||
|
|
||||||
switch p.Tag {
|
|
||||||
case TagEOC:
|
|
||||||
case TagBoolean:
|
|
||||||
val, _ := ParseInt64(content)
|
|
||||||
|
|
||||||
p.Value = val != 0
|
|
||||||
case TagInteger:
|
|
||||||
p.Value, _ = ParseInt64(content)
|
|
||||||
case TagBitString:
|
|
||||||
case TagOctetString:
|
|
||||||
// the actual string encoding is not known here
|
|
||||||
// (e.g. for LDAP content is already an UTF8-encoded
|
|
||||||
// string). Return the data without further processing
|
|
||||||
p.Value = DecodeString(content)
|
|
||||||
case TagNULL:
|
|
||||||
case TagObjectIdentifier:
|
|
||||||
case TagObjectDescriptor:
|
|
||||||
case TagExternal:
|
|
||||||
case TagRealFloat:
|
|
||||||
case TagEnumerated:
|
|
||||||
p.Value, _ = ParseInt64(content)
|
|
||||||
case TagEmbeddedPDV:
|
|
||||||
case TagUTF8String:
|
|
||||||
p.Value = DecodeString(content)
|
|
||||||
case TagRelativeOID:
|
|
||||||
case TagSequence:
|
|
||||||
case TagSet:
|
|
||||||
case TagNumericString:
|
|
||||||
case TagPrintableString:
|
|
||||||
p.Value = DecodeString(content)
|
|
||||||
case TagT61String:
|
|
||||||
case TagVideotexString:
|
|
||||||
case TagIA5String:
|
|
||||||
case TagUTCTime:
|
|
||||||
case TagGeneralizedTime:
|
|
||||||
case TagGraphicString:
|
|
||||||
case TagVisibleString:
|
|
||||||
case TagGeneralString:
|
|
||||||
case TagUniversalString:
|
|
||||||
case TagCharacterString:
|
|
||||||
case TagBMPString:
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
p.Data.Write(content)
|
|
||||||
}
|
|
||||||
|
|
||||||
return p, read, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Packet) Bytes() []byte {
|
|
||||||
var out bytes.Buffer
|
|
||||||
|
|
||||||
out.Write(encodeIdentifier(p.Identifier))
|
|
||||||
out.Write(encodeLength(p.Data.Len()))
|
|
||||||
out.Write(p.Data.Bytes())
|
|
||||||
|
|
||||||
return out.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Packet) AppendChild(child *Packet) {
|
|
||||||
p.Data.Write(child.Bytes())
|
|
||||||
p.Children = append(p.Children, child)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Encode(ClassType Class, TagType Type, Tag Tag, Value interface{}, Description string) *Packet {
|
|
||||||
p := new(Packet)
|
|
||||||
|
|
||||||
p.ClassType = ClassType
|
|
||||||
p.TagType = TagType
|
|
||||||
p.Tag = Tag
|
|
||||||
p.Data = new(bytes.Buffer)
|
|
||||||
|
|
||||||
p.Children = make([]*Packet, 0, 2)
|
|
||||||
|
|
||||||
p.Value = Value
|
|
||||||
p.Description = Description
|
|
||||||
|
|
||||||
if Value != nil {
|
|
||||||
v := reflect.ValueOf(Value)
|
|
||||||
|
|
||||||
if ClassType == ClassUniversal {
|
|
||||||
switch Tag {
|
|
||||||
case TagOctetString:
|
|
||||||
sv, ok := v.Interface().(string)
|
|
||||||
|
|
||||||
if ok {
|
|
||||||
p.Data.Write([]byte(sv))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSequence(Description string) *Packet {
|
|
||||||
return Encode(ClassUniversal, TypeConstructed, TagSequence, nil, Description)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewBoolean(ClassType Class, TagType Type, Tag Tag, Value bool, Description string) *Packet {
|
|
||||||
intValue := int64(0)
|
|
||||||
|
|
||||||
if Value {
|
|
||||||
intValue = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
p := Encode(ClassType, TagType, Tag, nil, Description)
|
|
||||||
|
|
||||||
p.Value = Value
|
|
||||||
p.Data.Write(encodeInteger(intValue))
|
|
||||||
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewInteger(ClassType Class, TagType Type, Tag Tag, Value interface{}, Description string) *Packet {
|
|
||||||
p := Encode(ClassType, TagType, Tag, nil, Description)
|
|
||||||
|
|
||||||
p.Value = Value
|
|
||||||
switch v := Value.(type) {
|
|
||||||
case int:
|
|
||||||
p.Data.Write(encodeInteger(int64(v)))
|
|
||||||
case uint:
|
|
||||||
p.Data.Write(encodeInteger(int64(v)))
|
|
||||||
case int64:
|
|
||||||
p.Data.Write(encodeInteger(v))
|
|
||||||
case uint64:
|
|
||||||
// TODO : check range or add encodeUInt...
|
|
||||||
p.Data.Write(encodeInteger(int64(v)))
|
|
||||||
case int32:
|
|
||||||
p.Data.Write(encodeInteger(int64(v)))
|
|
||||||
case uint32:
|
|
||||||
p.Data.Write(encodeInteger(int64(v)))
|
|
||||||
case int16:
|
|
||||||
p.Data.Write(encodeInteger(int64(v)))
|
|
||||||
case uint16:
|
|
||||||
p.Data.Write(encodeInteger(int64(v)))
|
|
||||||
case int8:
|
|
||||||
p.Data.Write(encodeInteger(int64(v)))
|
|
||||||
case uint8:
|
|
||||||
p.Data.Write(encodeInteger(int64(v)))
|
|
||||||
default:
|
|
||||||
// TODO : add support for big.Int ?
|
|
||||||
panic(fmt.Sprintf("Invalid type %T, expected {u|}int{64|32|16|8}", v))
|
|
||||||
}
|
|
||||||
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewString(ClassType Class, TagType Type, Tag Tag, Value, Description string) *Packet {
|
|
||||||
p := Encode(ClassType, TagType, Tag, nil, Description)
|
|
||||||
|
|
||||||
p.Value = Value
|
|
||||||
p.Data.Write([]byte(Value))
|
|
||||||
|
|
||||||
return p
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
package ber
|
|
||||||
|
|
||||||
func encodeUnsignedInteger(i uint64) []byte {
|
|
||||||
n := uint64Length(i)
|
|
||||||
out := make([]byte, n)
|
|
||||||
|
|
||||||
var j int
|
|
||||||
for ; n > 0; n-- {
|
|
||||||
out[j] = (byte(i >> uint((n-1)*8)))
|
|
||||||
j++
|
|
||||||
}
|
|
||||||
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func uint64Length(i uint64) (numBytes int) {
|
|
||||||
numBytes = 1
|
|
||||||
|
|
||||||
for i > 255 {
|
|
||||||
numBytes++
|
|
||||||
i >>= 8
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
package ber
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
func readHeader(reader io.Reader) (identifier Identifier, length int, read int, err error) {
|
|
||||||
if i, c, err := readIdentifier(reader); err != nil {
|
|
||||||
return Identifier{}, 0, read, err
|
|
||||||
} else {
|
|
||||||
identifier = i
|
|
||||||
read += c
|
|
||||||
}
|
|
||||||
|
|
||||||
if l, c, err := readLength(reader); err != nil {
|
|
||||||
return Identifier{}, 0, read, err
|
|
||||||
} else {
|
|
||||||
length = l
|
|
||||||
read += c
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate length type with identifier (x.600, 8.1.3.2.a)
|
|
||||||
if length == LengthIndefinite && identifier.TagType == TypePrimitive {
|
|
||||||
return Identifier{}, 0, read, errors.New("indefinite length used with primitive type")
|
|
||||||
}
|
|
||||||
|
|
||||||
if length < LengthIndefinite {
|
|
||||||
err = fmt.Errorf("length cannot be less than %d", LengthIndefinite)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return identifier, length, read, nil
|
|
||||||
}
|
|
|
@ -1,112 +0,0 @@
|
||||||
package ber
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
func readIdentifier(reader io.Reader) (Identifier, int, error) {
|
|
||||||
identifier := Identifier{}
|
|
||||||
read := 0
|
|
||||||
|
|
||||||
// identifier byte
|
|
||||||
b, err := readByte(reader)
|
|
||||||
if err != nil {
|
|
||||||
if Debug {
|
|
||||||
fmt.Printf("error reading identifier byte: %v\n", err)
|
|
||||||
}
|
|
||||||
return Identifier{}, read, err
|
|
||||||
}
|
|
||||||
read++
|
|
||||||
|
|
||||||
identifier.ClassType = Class(b) & ClassBitmask
|
|
||||||
identifier.TagType = Type(b) & TypeBitmask
|
|
||||||
|
|
||||||
if tag := Tag(b) & TagBitmask; tag != HighTag {
|
|
||||||
// short-form tag
|
|
||||||
identifier.Tag = tag
|
|
||||||
return identifier, read, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// high-tag-number tag
|
|
||||||
tagBytes := 0
|
|
||||||
for {
|
|
||||||
b, err := readByte(reader)
|
|
||||||
if err != nil {
|
|
||||||
if Debug {
|
|
||||||
fmt.Printf("error reading high-tag-number tag byte %d: %v\n", tagBytes, err)
|
|
||||||
}
|
|
||||||
return Identifier{}, read, err
|
|
||||||
}
|
|
||||||
tagBytes++
|
|
||||||
read++
|
|
||||||
|
|
||||||
// Lowest 7 bits get appended to the tag value (x.690, 8.1.2.4.2.b)
|
|
||||||
identifier.Tag <<= 7
|
|
||||||
identifier.Tag |= Tag(b) & HighTagValueBitmask
|
|
||||||
|
|
||||||
// First byte may not be all zeros (x.690, 8.1.2.4.2.c)
|
|
||||||
if tagBytes == 1 && identifier.Tag == 0 {
|
|
||||||
return Identifier{}, read, errors.New("invalid first high-tag-number tag byte")
|
|
||||||
}
|
|
||||||
// Overflow of int64
|
|
||||||
// TODO: support big int tags?
|
|
||||||
if tagBytes > 9 {
|
|
||||||
return Identifier{}, read, errors.New("high-tag-number tag overflow")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Top bit of 0 means this is the last byte in the high-tag-number tag (x.690, 8.1.2.4.2.a)
|
|
||||||
if Tag(b)&HighTagContinueBitmask == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return identifier, read, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodeIdentifier(identifier Identifier) []byte {
|
|
||||||
b := []byte{0x0}
|
|
||||||
b[0] |= byte(identifier.ClassType)
|
|
||||||
b[0] |= byte(identifier.TagType)
|
|
||||||
|
|
||||||
if identifier.Tag < HighTag {
|
|
||||||
// Short-form
|
|
||||||
b[0] |= byte(identifier.Tag)
|
|
||||||
} else {
|
|
||||||
// high-tag-number
|
|
||||||
b[0] |= byte(HighTag)
|
|
||||||
|
|
||||||
tag := identifier.Tag
|
|
||||||
|
|
||||||
b = append(b, encodeHighTag(tag)...)
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodeHighTag(tag Tag) []byte {
|
|
||||||
// set cap=4 to hopefully avoid additional allocations
|
|
||||||
b := make([]byte, 0, 4)
|
|
||||||
for tag != 0 {
|
|
||||||
// t := last 7 bits of tag (HighTagValueBitmask = 0x7F)
|
|
||||||
t := tag & HighTagValueBitmask
|
|
||||||
|
|
||||||
// right shift tag 7 to remove what was just pulled off
|
|
||||||
tag >>= 7
|
|
||||||
|
|
||||||
// if b already has entries this entry needs a continuation bit (0x80)
|
|
||||||
if len(b) != 0 {
|
|
||||||
t |= HighTagContinueBitmask
|
|
||||||
}
|
|
||||||
|
|
||||||
b = append(b, byte(t))
|
|
||||||
}
|
|
||||||
// reverse
|
|
||||||
// since bits were pulled off 'tag' small to high the byte slice is in reverse order.
|
|
||||||
// example: tag = 0xFF results in {0x7F, 0x01 + 0x80 (continuation bit)}
|
|
||||||
// this needs to be reversed into 0x81 0x7F
|
|
||||||
for i, j := 0, len(b)-1; i < len(b)/2; i++ {
|
|
||||||
b[i], b[j-i] = b[j-i], b[i]
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
|
@ -1,81 +0,0 @@
|
||||||
package ber
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
func readLength(reader io.Reader) (length int, read int, err error) {
|
|
||||||
// length byte
|
|
||||||
b, err := readByte(reader)
|
|
||||||
if err != nil {
|
|
||||||
if Debug {
|
|
||||||
fmt.Printf("error reading length byte: %v\n", err)
|
|
||||||
}
|
|
||||||
return 0, 0, err
|
|
||||||
}
|
|
||||||
read++
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case b == 0xFF:
|
|
||||||
// Invalid 0xFF (x.600, 8.1.3.5.c)
|
|
||||||
return 0, read, errors.New("invalid length byte 0xff")
|
|
||||||
|
|
||||||
case b == LengthLongFormBitmask:
|
|
||||||
// Indefinite form, we have to decode packets until we encounter an EOC packet (x.600, 8.1.3.6)
|
|
||||||
length = LengthIndefinite
|
|
||||||
|
|
||||||
case b&LengthLongFormBitmask == 0:
|
|
||||||
// Short definite form, extract the length from the bottom 7 bits (x.600, 8.1.3.4)
|
|
||||||
length = int(b) & LengthValueBitmask
|
|
||||||
|
|
||||||
case b&LengthLongFormBitmask != 0:
|
|
||||||
// Long definite form, extract the number of length bytes to follow from the bottom 7 bits (x.600, 8.1.3.5.b)
|
|
||||||
lengthBytes := int(b) & LengthValueBitmask
|
|
||||||
// Protect against overflow
|
|
||||||
// TODO: support big int length?
|
|
||||||
if lengthBytes > 8 {
|
|
||||||
return 0, read, errors.New("long-form length overflow")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Accumulate into a 64-bit variable
|
|
||||||
var length64 int64
|
|
||||||
for i := 0; i < lengthBytes; i++ {
|
|
||||||
b, err = readByte(reader)
|
|
||||||
if err != nil {
|
|
||||||
if Debug {
|
|
||||||
fmt.Printf("error reading long-form length byte %d: %v\n", i, err)
|
|
||||||
}
|
|
||||||
return 0, read, err
|
|
||||||
}
|
|
||||||
read++
|
|
||||||
|
|
||||||
// x.600, 8.1.3.5
|
|
||||||
length64 <<= 8
|
|
||||||
length64 |= int64(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cast to a platform-specific integer
|
|
||||||
length = int(length64)
|
|
||||||
// Ensure we didn't overflow
|
|
||||||
if int64(length) != length64 {
|
|
||||||
return 0, read, errors.New("long-form length overflow")
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
return 0, read, errors.New("invalid length byte")
|
|
||||||
}
|
|
||||||
|
|
||||||
return length, read, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodeLength(length int) []byte {
|
|
||||||
length_bytes := encodeUnsignedInteger(uint64(length))
|
|
||||||
if length > 127 || len(length_bytes) > 1 {
|
|
||||||
longFormBytes := []byte{(LengthLongFormBitmask | byte(len(length_bytes)))}
|
|
||||||
longFormBytes = append(longFormBytes, length_bytes...)
|
|
||||||
length_bytes = longFormBytes
|
|
||||||
}
|
|
||||||
return length_bytes
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
package ber
|
|
||||||
|
|
||||||
import "io"
|
|
||||||
|
|
||||||
func readByte(reader io.Reader) (byte, error) {
|
|
||||||
bytes := make([]byte, 1, 1)
|
|
||||||
_, err := io.ReadFull(reader, bytes)
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
return 0, io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return bytes[0], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isEOCPacket(p *Packet) bool {
|
|
||||||
return p != nil &&
|
|
||||||
p.Tag == TagEOC &&
|
|
||||||
p.ClassType == ClassUniversal &&
|
|
||||||
p.TagType == TypePrimitive &&
|
|
||||||
len(p.ByteValue) == 0 &&
|
|
||||||
len(p.Children) == 0
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
language: go
|
|
||||||
env:
|
|
||||||
global:
|
|
||||||
- VET_VERSIONS="1.6 1.7 1.8 1.9 tip"
|
|
||||||
- LINT_VERSIONS="1.6 1.7 1.8 1.9 tip"
|
|
||||||
go:
|
|
||||||
- 1.2
|
|
||||||
- 1.3
|
|
||||||
- 1.4
|
|
||||||
- 1.5
|
|
||||||
- 1.6
|
|
||||||
- 1.7
|
|
||||||
- 1.8
|
|
||||||
- 1.9
|
|
||||||
- tip
|
|
||||||
matrix:
|
|
||||||
fast_finish: true
|
|
||||||
allow_failures:
|
|
||||||
- go: tip
|
|
||||||
go_import_path: gopkg.in/ldap.v2
|
|
||||||
install:
|
|
||||||
- go get gopkg.in/asn1-ber.v1
|
|
||||||
- go get gopkg.in/ldap.v2
|
|
||||||
- go get code.google.com/p/go.tools/cmd/cover || go get golang.org/x/tools/cmd/cover
|
|
||||||
- go get github.com/golang/lint/golint || true
|
|
||||||
- go build -v ./...
|
|
||||||
script:
|
|
||||||
- make test
|
|
||||||
- make fmt
|
|
||||||
- if [[ "$VET_VERSIONS" == *"$TRAVIS_GO_VERSION"* ]]; then make vet; fi
|
|
||||||
- if [[ "$LINT_VERSIONS" == *"$TRAVIS_GO_VERSION"* ]]; then make lint; fi
|
|
|
@ -1,22 +0,0 @@
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2011-2015 Michael Mitton (mmitton@gmail.com)
|
|
||||||
Portions copyright (c) 2015-2016 go-ldap Authors
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
|
@ -1,52 +0,0 @@
|
||||||
.PHONY: default install build test quicktest fmt vet lint
|
|
||||||
|
|
||||||
GO_VERSION := $(shell go version | cut -d' ' -f3 | cut -d. -f2)
|
|
||||||
|
|
||||||
# Only use the `-race` flag on newer versions of Go
|
|
||||||
IS_OLD_GO := $(shell test $(GO_VERSION) -le 2 && echo true)
|
|
||||||
ifeq ($(IS_OLD_GO),true)
|
|
||||||
RACE_FLAG :=
|
|
||||||
else
|
|
||||||
RACE_FLAG := -race -cpu 1,2,4
|
|
||||||
endif
|
|
||||||
|
|
||||||
default: fmt vet lint build quicktest
|
|
||||||
|
|
||||||
install:
|
|
||||||
go get -t -v ./...
|
|
||||||
|
|
||||||
build:
|
|
||||||
go build -v ./...
|
|
||||||
|
|
||||||
test:
|
|
||||||
go test -v $(RACE_FLAG) -cover ./...
|
|
||||||
|
|
||||||
quicktest:
|
|
||||||
go test ./...
|
|
||||||
|
|
||||||
# Capture output and force failure when there is non-empty output
|
|
||||||
fmt:
|
|
||||||
@echo gofmt -l .
|
|
||||||
@OUTPUT=`gofmt -l . 2>&1`; \
|
|
||||||
if [ "$$OUTPUT" ]; then \
|
|
||||||
echo "gofmt must be run on the following files:"; \
|
|
||||||
echo "$$OUTPUT"; \
|
|
||||||
exit 1; \
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Only run on go1.5+
|
|
||||||
vet:
|
|
||||||
go tool vet -atomic -bool -copylocks -nilfunc -printf -shadow -rangeloops -unreachable -unsafeptr -unusedresult .
|
|
||||||
|
|
||||||
# https://github.com/golang/lint
|
|
||||||
# go get github.com/golang/lint/golint
|
|
||||||
# Capture output and force failure when there is non-empty output
|
|
||||||
# Only run on go1.5+
|
|
||||||
lint:
|
|
||||||
@echo golint ./...
|
|
||||||
@OUTPUT=`golint ./... 2>&1`; \
|
|
||||||
if [ "$$OUTPUT" ]; then \
|
|
||||||
echo "golint errors:"; \
|
|
||||||
echo "$$OUTPUT"; \
|
|
||||||
exit 1; \
|
|
||||||
fi
|
|
|
@ -1,53 +0,0 @@
|
||||||
[![GoDoc](https://godoc.org/gopkg.in/ldap.v2?status.svg)](https://godoc.org/gopkg.in/ldap.v2)
|
|
||||||
[![Build Status](https://travis-ci.org/go-ldap/ldap.svg)](https://travis-ci.org/go-ldap/ldap)
|
|
||||||
|
|
||||||
# Basic LDAP v3 functionality for the GO programming language.
|
|
||||||
|
|
||||||
## Install
|
|
||||||
|
|
||||||
For the latest version use:
|
|
||||||
|
|
||||||
go get gopkg.in/ldap.v2
|
|
||||||
|
|
||||||
Import the latest version with:
|
|
||||||
|
|
||||||
import "gopkg.in/ldap.v2"
|
|
||||||
|
|
||||||
## Required Libraries:
|
|
||||||
|
|
||||||
- gopkg.in/asn1-ber.v1
|
|
||||||
|
|
||||||
## Features:
|
|
||||||
|
|
||||||
- Connecting to LDAP server (non-TLS, TLS, STARTTLS)
|
|
||||||
- Binding to LDAP server
|
|
||||||
- Searching for entries
|
|
||||||
- Filter Compile / Decompile
|
|
||||||
- Paging Search Results
|
|
||||||
- Modify Requests / Responses
|
|
||||||
- Add Requests / Responses
|
|
||||||
- Delete Requests / Responses
|
|
||||||
|
|
||||||
## Examples:
|
|
||||||
|
|
||||||
- search
|
|
||||||
- modify
|
|
||||||
|
|
||||||
## Contributing:
|
|
||||||
|
|
||||||
Bug reports and pull requests are welcome!
|
|
||||||
|
|
||||||
Before submitting a pull request, please make sure tests and verification scripts pass:
|
|
||||||
```
|
|
||||||
make all
|
|
||||||
```
|
|
||||||
|
|
||||||
To set up a pre-push hook to run the tests and verify scripts before pushing:
|
|
||||||
```
|
|
||||||
ln -s ../../.githooks/pre-push .git/hooks/pre-push
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
The Go gopher was designed by Renee French. (http://reneefrench.blogspot.com/)
|
|
||||||
The design is licensed under the Creative Commons 3.0 Attributions license.
|
|
||||||
Read this article for more details: http://blog.golang.org/gopher
|
|
|
@ -1,113 +0,0 @@
|
||||||
//
|
|
||||||
// https://tools.ietf.org/html/rfc4511
|
|
||||||
//
|
|
||||||
// AddRequest ::= [APPLICATION 8] SEQUENCE {
|
|
||||||
// entry LDAPDN,
|
|
||||||
// attributes AttributeList }
|
|
||||||
//
|
|
||||||
// AttributeList ::= SEQUENCE OF attribute Attribute
|
|
||||||
|
|
||||||
package ldap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"gopkg.in/asn1-ber.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Attribute represents an LDAP attribute
|
|
||||||
type Attribute struct {
|
|
||||||
// Type is the name of the LDAP attribute
|
|
||||||
Type string
|
|
||||||
// Vals are the LDAP attribute values
|
|
||||||
Vals []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Attribute) encode() *ber.Packet {
|
|
||||||
seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Attribute")
|
|
||||||
seq.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, a.Type, "Type"))
|
|
||||||
set := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSet, nil, "AttributeValue")
|
|
||||||
for _, value := range a.Vals {
|
|
||||||
set.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, value, "Vals"))
|
|
||||||
}
|
|
||||||
seq.AppendChild(set)
|
|
||||||
return seq
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddRequest represents an LDAP AddRequest operation
|
|
||||||
type AddRequest struct {
|
|
||||||
// DN identifies the entry being added
|
|
||||||
DN string
|
|
||||||
// Attributes list the attributes of the new entry
|
|
||||||
Attributes []Attribute
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a AddRequest) encode() *ber.Packet {
|
|
||||||
request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationAddRequest, nil, "Add Request")
|
|
||||||
request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, a.DN, "DN"))
|
|
||||||
attributes := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Attributes")
|
|
||||||
for _, attribute := range a.Attributes {
|
|
||||||
attributes.AppendChild(attribute.encode())
|
|
||||||
}
|
|
||||||
request.AppendChild(attributes)
|
|
||||||
return request
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attribute adds an attribute with the given type and values
|
|
||||||
func (a *AddRequest) Attribute(attrType string, attrVals []string) {
|
|
||||||
a.Attributes = append(a.Attributes, Attribute{Type: attrType, Vals: attrVals})
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewAddRequest returns an AddRequest for the given DN, with no attributes
|
|
||||||
func NewAddRequest(dn string) *AddRequest {
|
|
||||||
return &AddRequest{
|
|
||||||
DN: dn,
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add performs the given AddRequest
|
|
||||||
func (l *Conn) Add(addRequest *AddRequest) error {
|
|
||||||
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
|
|
||||||
packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID"))
|
|
||||||
packet.AppendChild(addRequest.encode())
|
|
||||||
|
|
||||||
l.Debug.PrintPacket(packet)
|
|
||||||
|
|
||||||
msgCtx, err := l.sendMessage(packet)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer l.finishMessage(msgCtx)
|
|
||||||
|
|
||||||
l.Debug.Printf("%d: waiting for response", msgCtx.id)
|
|
||||||
packetResponse, ok := <-msgCtx.responses
|
|
||||||
if !ok {
|
|
||||||
return NewError(ErrorNetwork, errors.New("ldap: response channel closed"))
|
|
||||||
}
|
|
||||||
packet, err = packetResponse.ReadPacket()
|
|
||||||
l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if l.Debug {
|
|
||||||
if err := addLDAPDescriptions(packet); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ber.PrintPacket(packet)
|
|
||||||
}
|
|
||||||
|
|
||||||
if packet.Children[1].Tag == ApplicationAddResponse {
|
|
||||||
resultCode, resultDescription := getLDAPResultCode(packet)
|
|
||||||
if resultCode != 0 {
|
|
||||||
return NewError(resultCode, errors.New(resultDescription))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Printf("Unexpected Response: %d", packet.Children[1].Tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
l.Debug.Printf("%d: returning", msgCtx.id)
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
// +build go1.4
|
|
||||||
|
|
||||||
package ldap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync/atomic"
|
|
||||||
)
|
|
||||||
|
|
||||||
// For compilers that support it, we just use the underlying sync/atomic.Value
|
|
||||||
// type.
|
|
||||||
type atomicValue struct {
|
|
||||||
atomic.Value
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
// +build !go1.4
|
|
||||||
|
|
||||||
package ldap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This is a helper type that emulates the use of the "sync/atomic.Value"
|
|
||||||
// struct that's available in Go 1.4 and up.
|
|
||||||
type atomicValue struct {
|
|
||||||
value interface{}
|
|
||||||
lock sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (av *atomicValue) Store(val interface{}) {
|
|
||||||
av.lock.Lock()
|
|
||||||
av.value = val
|
|
||||||
av.lock.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (av *atomicValue) Load() interface{} {
|
|
||||||
av.lock.RLock()
|
|
||||||
ret := av.value
|
|
||||||
av.lock.RUnlock()
|
|
||||||
|
|
||||||
return ret
|
|
||||||
}
|
|
|
@ -1,143 +0,0 @@
|
||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package ldap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"gopkg.in/asn1-ber.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SimpleBindRequest represents a username/password bind operation
|
|
||||||
type SimpleBindRequest struct {
|
|
||||||
// Username is the name of the Directory object that the client wishes to bind as
|
|
||||||
Username string
|
|
||||||
// Password is the credentials to bind with
|
|
||||||
Password string
|
|
||||||
// Controls are optional controls to send with the bind request
|
|
||||||
Controls []Control
|
|
||||||
}
|
|
||||||
|
|
||||||
// SimpleBindResult contains the response from the server
|
|
||||||
type SimpleBindResult struct {
|
|
||||||
Controls []Control
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSimpleBindRequest returns a bind request
|
|
||||||
func NewSimpleBindRequest(username string, password string, controls []Control) *SimpleBindRequest {
|
|
||||||
return &SimpleBindRequest{
|
|
||||||
Username: username,
|
|
||||||
Password: password,
|
|
||||||
Controls: controls,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bindRequest *SimpleBindRequest) encode() *ber.Packet {
|
|
||||||
request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request")
|
|
||||||
request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version"))
|
|
||||||
request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, bindRequest.Username, "User Name"))
|
|
||||||
request.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, bindRequest.Password, "Password"))
|
|
||||||
|
|
||||||
request.AppendChild(encodeControls(bindRequest.Controls))
|
|
||||||
|
|
||||||
return request
|
|
||||||
}
|
|
||||||
|
|
||||||
// SimpleBind performs the simple bind operation defined in the given request
|
|
||||||
func (l *Conn) SimpleBind(simpleBindRequest *SimpleBindRequest) (*SimpleBindResult, error) {
|
|
||||||
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
|
|
||||||
packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID"))
|
|
||||||
encodedBindRequest := simpleBindRequest.encode()
|
|
||||||
packet.AppendChild(encodedBindRequest)
|
|
||||||
|
|
||||||
if l.Debug {
|
|
||||||
ber.PrintPacket(packet)
|
|
||||||
}
|
|
||||||
|
|
||||||
msgCtx, err := l.sendMessage(packet)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer l.finishMessage(msgCtx)
|
|
||||||
|
|
||||||
packetResponse, ok := <-msgCtx.responses
|
|
||||||
if !ok {
|
|
||||||
return nil, NewError(ErrorNetwork, errors.New("ldap: response channel closed"))
|
|
||||||
}
|
|
||||||
packet, err = packetResponse.ReadPacket()
|
|
||||||
l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if l.Debug {
|
|
||||||
if err := addLDAPDescriptions(packet); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ber.PrintPacket(packet)
|
|
||||||
}
|
|
||||||
|
|
||||||
result := &SimpleBindResult{
|
|
||||||
Controls: make([]Control, 0),
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(packet.Children) == 3 {
|
|
||||||
for _, child := range packet.Children[2].Children {
|
|
||||||
result.Controls = append(result.Controls, DecodeControl(child))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resultCode, resultDescription := getLDAPResultCode(packet)
|
|
||||||
if resultCode != 0 {
|
|
||||||
return result, NewError(resultCode, errors.New(resultDescription))
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bind performs a bind with the given username and password
|
|
||||||
func (l *Conn) Bind(username, password string) error {
|
|
||||||
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
|
|
||||||
packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID"))
|
|
||||||
bindRequest := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request")
|
|
||||||
bindRequest.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version"))
|
|
||||||
bindRequest.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, username, "User Name"))
|
|
||||||
bindRequest.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, password, "Password"))
|
|
||||||
packet.AppendChild(bindRequest)
|
|
||||||
|
|
||||||
if l.Debug {
|
|
||||||
ber.PrintPacket(packet)
|
|
||||||
}
|
|
||||||
|
|
||||||
msgCtx, err := l.sendMessage(packet)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer l.finishMessage(msgCtx)
|
|
||||||
|
|
||||||
packetResponse, ok := <-msgCtx.responses
|
|
||||||
if !ok {
|
|
||||||
return NewError(ErrorNetwork, errors.New("ldap: response channel closed"))
|
|
||||||
}
|
|
||||||
packet, err = packetResponse.ReadPacket()
|
|
||||||
l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if l.Debug {
|
|
||||||
if err := addLDAPDescriptions(packet); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ber.PrintPacket(packet)
|
|
||||||
}
|
|
||||||
|
|
||||||
resultCode, resultDescription := getLDAPResultCode(packet)
|
|
||||||
if resultCode != 0 {
|
|
||||||
return NewError(resultCode, errors.New(resultDescription))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
package ldap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Client knows how to interact with an LDAP server
|
|
||||||
type Client interface {
|
|
||||||
Start()
|
|
||||||
StartTLS(config *tls.Config) error
|
|
||||||
Close()
|
|
||||||
SetTimeout(time.Duration)
|
|
||||||
|
|
||||||
Bind(username, password string) error
|
|
||||||
SimpleBind(simpleBindRequest *SimpleBindRequest) (*SimpleBindResult, error)
|
|
||||||
|
|
||||||
Add(addRequest *AddRequest) error
|
|
||||||
Del(delRequest *DelRequest) error
|
|
||||||
Modify(modifyRequest *ModifyRequest) error
|
|
||||||
|
|
||||||
Compare(dn, attribute, value string) (bool, error)
|
|
||||||
PasswordModify(passwordModifyRequest *PasswordModifyRequest) (*PasswordModifyResult, error)
|
|
||||||
|
|
||||||
Search(searchRequest *SearchRequest) (*SearchResult, error)
|
|
||||||
SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32) (*SearchResult, error)
|
|
||||||
}
|
|
|
@ -1,85 +0,0 @@
|
||||||
// Copyright 2014 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
//
|
|
||||||
// File contains Compare functionality
|
|
||||||
//
|
|
||||||
// https://tools.ietf.org/html/rfc4511
|
|
||||||
//
|
|
||||||
// CompareRequest ::= [APPLICATION 14] SEQUENCE {
|
|
||||||
// entry LDAPDN,
|
|
||||||
// ava AttributeValueAssertion }
|
|
||||||
//
|
|
||||||
// AttributeValueAssertion ::= SEQUENCE {
|
|
||||||
// attributeDesc AttributeDescription,
|
|
||||||
// assertionValue AssertionValue }
|
|
||||||
//
|
|
||||||
// AttributeDescription ::= LDAPString
|
|
||||||
// -- Constrained to <attributedescription>
|
|
||||||
// -- [RFC4512]
|
|
||||||
//
|
|
||||||
// AttributeValue ::= OCTET STRING
|
|
||||||
//
|
|
||||||
|
|
||||||
package ldap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"gopkg.in/asn1-ber.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Compare checks to see if the attribute of the dn matches value. Returns true if it does otherwise
|
|
||||||
// false with any error that occurs if any.
|
|
||||||
func (l *Conn) Compare(dn, attribute, value string) (bool, error) {
|
|
||||||
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
|
|
||||||
packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID"))
|
|
||||||
|
|
||||||
request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationCompareRequest, nil, "Compare Request")
|
|
||||||
request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, dn, "DN"))
|
|
||||||
|
|
||||||
ava := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "AttributeValueAssertion")
|
|
||||||
ava.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute, "AttributeDesc"))
|
|
||||||
ava.AppendChild(ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagOctetString, value, "AssertionValue"))
|
|
||||||
request.AppendChild(ava)
|
|
||||||
packet.AppendChild(request)
|
|
||||||
|
|
||||||
l.Debug.PrintPacket(packet)
|
|
||||||
|
|
||||||
msgCtx, err := l.sendMessage(packet)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
defer l.finishMessage(msgCtx)
|
|
||||||
|
|
||||||
l.Debug.Printf("%d: waiting for response", msgCtx.id)
|
|
||||||
packetResponse, ok := <-msgCtx.responses
|
|
||||||
if !ok {
|
|
||||||
return false, NewError(ErrorNetwork, errors.New("ldap: response channel closed"))
|
|
||||||
}
|
|
||||||
packet, err = packetResponse.ReadPacket()
|
|
||||||
l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if l.Debug {
|
|
||||||
if err := addLDAPDescriptions(packet); err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
ber.PrintPacket(packet)
|
|
||||||
}
|
|
||||||
|
|
||||||
if packet.Children[1].Tag == ApplicationCompareResponse {
|
|
||||||
resultCode, resultDescription := getLDAPResultCode(packet)
|
|
||||||
if resultCode == LDAPResultCompareTrue {
|
|
||||||
return true, nil
|
|
||||||
} else if resultCode == LDAPResultCompareFalse {
|
|
||||||
return false, nil
|
|
||||||
} else {
|
|
||||||
return false, NewError(resultCode, errors.New(resultDescription))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, fmt.Errorf("Unexpected Response: %d", packet.Children[1].Tag)
|
|
||||||
}
|
|
|
@ -1,470 +0,0 @@
|
||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package ldap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"gopkg.in/asn1-ber.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// MessageQuit causes the processMessages loop to exit
|
|
||||||
MessageQuit = 0
|
|
||||||
// MessageRequest sends a request to the server
|
|
||||||
MessageRequest = 1
|
|
||||||
// MessageResponse receives a response from the server
|
|
||||||
MessageResponse = 2
|
|
||||||
// MessageFinish indicates the client considers a particular message ID to be finished
|
|
||||||
MessageFinish = 3
|
|
||||||
// MessageTimeout indicates the client-specified timeout for a particular message ID has been reached
|
|
||||||
MessageTimeout = 4
|
|
||||||
)
|
|
||||||
|
|
||||||
// PacketResponse contains the packet or error encountered reading a response
|
|
||||||
type PacketResponse struct {
|
|
||||||
// Packet is the packet read from the server
|
|
||||||
Packet *ber.Packet
|
|
||||||
// Error is an error encountered while reading
|
|
||||||
Error error
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadPacket returns the packet or an error
|
|
||||||
func (pr *PacketResponse) ReadPacket() (*ber.Packet, error) {
|
|
||||||
if (pr == nil) || (pr.Packet == nil && pr.Error == nil) {
|
|
||||||
return nil, NewError(ErrorNetwork, errors.New("ldap: could not retrieve response"))
|
|
||||||
}
|
|
||||||
return pr.Packet, pr.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
type messageContext struct {
|
|
||||||
id int64
|
|
||||||
// close(done) should only be called from finishMessage()
|
|
||||||
done chan struct{}
|
|
||||||
// close(responses) should only be called from processMessages(), and only sent to from sendResponse()
|
|
||||||
responses chan *PacketResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
// sendResponse should only be called within the processMessages() loop which
|
|
||||||
// is also responsible for closing the responses channel.
|
|
||||||
func (msgCtx *messageContext) sendResponse(packet *PacketResponse) {
|
|
||||||
select {
|
|
||||||
case msgCtx.responses <- packet:
|
|
||||||
// Successfully sent packet to message handler.
|
|
||||||
case <-msgCtx.done:
|
|
||||||
// The request handler is done and will not receive more
|
|
||||||
// packets.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type messagePacket struct {
|
|
||||||
Op int
|
|
||||||
MessageID int64
|
|
||||||
Packet *ber.Packet
|
|
||||||
Context *messageContext
|
|
||||||
}
|
|
||||||
|
|
||||||
type sendMessageFlags uint
|
|
||||||
|
|
||||||
const (
|
|
||||||
startTLS sendMessageFlags = 1 << iota
|
|
||||||
)
|
|
||||||
|
|
||||||
// Conn represents an LDAP Connection
|
|
||||||
type Conn struct {
|
|
||||||
conn net.Conn
|
|
||||||
isTLS bool
|
|
||||||
closing uint32
|
|
||||||
closeErr atomicValue
|
|
||||||
isStartingTLS bool
|
|
||||||
Debug debugging
|
|
||||||
chanConfirm chan struct{}
|
|
||||||
messageContexts map[int64]*messageContext
|
|
||||||
chanMessage chan *messagePacket
|
|
||||||
chanMessageID chan int64
|
|
||||||
wgClose sync.WaitGroup
|
|
||||||
outstandingRequests uint
|
|
||||||
messageMutex sync.Mutex
|
|
||||||
requestTimeout int64
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ Client = &Conn{}
|
|
||||||
|
|
||||||
// DefaultTimeout is a package-level variable that sets the timeout value
|
|
||||||
// used for the Dial and DialTLS methods.
|
|
||||||
//
|
|
||||||
// WARNING: since this is a package-level variable, setting this value from
|
|
||||||
// multiple places will probably result in undesired behaviour.
|
|
||||||
var DefaultTimeout = 60 * time.Second
|
|
||||||
|
|
||||||
// Dial connects to the given address on the given network using net.Dial
|
|
||||||
// and then returns a new Conn for the connection.
|
|
||||||
func Dial(network, addr string) (*Conn, error) {
|
|
||||||
c, err := net.DialTimeout(network, addr, DefaultTimeout)
|
|
||||||
if err != nil {
|
|
||||||
return nil, NewError(ErrorNetwork, err)
|
|
||||||
}
|
|
||||||
conn := NewConn(c, false)
|
|
||||||
conn.Start()
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DialTLS connects to the given address on the given network using tls.Dial
|
|
||||||
// and then returns a new Conn for the connection.
|
|
||||||
func DialTLS(network, addr string, config *tls.Config) (*Conn, error) {
|
|
||||||
dc, err := net.DialTimeout(network, addr, DefaultTimeout)
|
|
||||||
if err != nil {
|
|
||||||
return nil, NewError(ErrorNetwork, err)
|
|
||||||
}
|
|
||||||
c := tls.Client(dc, config)
|
|
||||||
err = c.Handshake()
|
|
||||||
if err != nil {
|
|
||||||
// Handshake error, close the established connection before we return an error
|
|
||||||
dc.Close()
|
|
||||||
return nil, NewError(ErrorNetwork, err)
|
|
||||||
}
|
|
||||||
conn := NewConn(c, true)
|
|
||||||
conn.Start()
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewConn returns a new Conn using conn for network I/O.
|
|
||||||
func NewConn(conn net.Conn, isTLS bool) *Conn {
|
|
||||||
return &Conn{
|
|
||||||
conn: conn,
|
|
||||||
chanConfirm: make(chan struct{}),
|
|
||||||
chanMessageID: make(chan int64),
|
|
||||||
chanMessage: make(chan *messagePacket, 10),
|
|
||||||
messageContexts: map[int64]*messageContext{},
|
|
||||||
requestTimeout: 0,
|
|
||||||
isTLS: isTLS,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start initializes goroutines to read responses and process messages
|
|
||||||
func (l *Conn) Start() {
|
|
||||||
go l.reader()
|
|
||||||
go l.processMessages()
|
|
||||||
l.wgClose.Add(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// isClosing returns whether or not we're currently closing.
|
|
||||||
func (l *Conn) isClosing() bool {
|
|
||||||
return atomic.LoadUint32(&l.closing) == 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// setClosing sets the closing value to true
|
|
||||||
func (l *Conn) setClosing() bool {
|
|
||||||
return atomic.CompareAndSwapUint32(&l.closing, 0, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes the connection.
|
|
||||||
func (l *Conn) Close() {
|
|
||||||
l.messageMutex.Lock()
|
|
||||||
defer l.messageMutex.Unlock()
|
|
||||||
|
|
||||||
if l.setClosing() {
|
|
||||||
l.Debug.Printf("Sending quit message and waiting for confirmation")
|
|
||||||
l.chanMessage <- &messagePacket{Op: MessageQuit}
|
|
||||||
<-l.chanConfirm
|
|
||||||
close(l.chanMessage)
|
|
||||||
|
|
||||||
l.Debug.Printf("Closing network connection")
|
|
||||||
if err := l.conn.Close(); err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
l.wgClose.Done()
|
|
||||||
}
|
|
||||||
l.wgClose.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetTimeout sets the time after a request is sent that a MessageTimeout triggers
|
|
||||||
func (l *Conn) SetTimeout(timeout time.Duration) {
|
|
||||||
if timeout > 0 {
|
|
||||||
atomic.StoreInt64(&l.requestTimeout, int64(timeout))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the next available messageID
|
|
||||||
func (l *Conn) nextMessageID() int64 {
|
|
||||||
if messageID, ok := <-l.chanMessageID; ok {
|
|
||||||
return messageID
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// StartTLS sends the command to start a TLS session and then creates a new TLS Client
|
|
||||||
func (l *Conn) StartTLS(config *tls.Config) error {
|
|
||||||
if l.isTLS {
|
|
||||||
return NewError(ErrorNetwork, errors.New("ldap: already encrypted"))
|
|
||||||
}
|
|
||||||
|
|
||||||
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
|
|
||||||
packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID"))
|
|
||||||
request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationExtendedRequest, nil, "Start TLS")
|
|
||||||
request.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, "1.3.6.1.4.1.1466.20037", "TLS Extended Command"))
|
|
||||||
packet.AppendChild(request)
|
|
||||||
l.Debug.PrintPacket(packet)
|
|
||||||
|
|
||||||
msgCtx, err := l.sendMessageWithFlags(packet, startTLS)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer l.finishMessage(msgCtx)
|
|
||||||
|
|
||||||
l.Debug.Printf("%d: waiting for response", msgCtx.id)
|
|
||||||
|
|
||||||
packetResponse, ok := <-msgCtx.responses
|
|
||||||
if !ok {
|
|
||||||
return NewError(ErrorNetwork, errors.New("ldap: response channel closed"))
|
|
||||||
}
|
|
||||||
packet, err = packetResponse.ReadPacket()
|
|
||||||
l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if l.Debug {
|
|
||||||
if err := addLDAPDescriptions(packet); err != nil {
|
|
||||||
l.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ber.PrintPacket(packet)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resultCode, message := getLDAPResultCode(packet); resultCode == LDAPResultSuccess {
|
|
||||||
conn := tls.Client(l.conn, config)
|
|
||||||
|
|
||||||
if err := conn.Handshake(); err != nil {
|
|
||||||
l.Close()
|
|
||||||
return NewError(ErrorNetwork, fmt.Errorf("TLS handshake failed (%v)", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
l.isTLS = true
|
|
||||||
l.conn = conn
|
|
||||||
} else {
|
|
||||||
return NewError(resultCode, fmt.Errorf("ldap: cannot StartTLS (%s)", message))
|
|
||||||
}
|
|
||||||
go l.reader()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Conn) sendMessage(packet *ber.Packet) (*messageContext, error) {
|
|
||||||
return l.sendMessageWithFlags(packet, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Conn) sendMessageWithFlags(packet *ber.Packet, flags sendMessageFlags) (*messageContext, error) {
|
|
||||||
if l.isClosing() {
|
|
||||||
return nil, NewError(ErrorNetwork, errors.New("ldap: connection closed"))
|
|
||||||
}
|
|
||||||
l.messageMutex.Lock()
|
|
||||||
l.Debug.Printf("flags&startTLS = %d", flags&startTLS)
|
|
||||||
if l.isStartingTLS {
|
|
||||||
l.messageMutex.Unlock()
|
|
||||||
return nil, NewError(ErrorNetwork, errors.New("ldap: connection is in startls phase"))
|
|
||||||
}
|
|
||||||
if flags&startTLS != 0 {
|
|
||||||
if l.outstandingRequests != 0 {
|
|
||||||
l.messageMutex.Unlock()
|
|
||||||
return nil, NewError(ErrorNetwork, errors.New("ldap: cannot StartTLS with outstanding requests"))
|
|
||||||
}
|
|
||||||
l.isStartingTLS = true
|
|
||||||
}
|
|
||||||
l.outstandingRequests++
|
|
||||||
|
|
||||||
l.messageMutex.Unlock()
|
|
||||||
|
|
||||||
responses := make(chan *PacketResponse)
|
|
||||||
messageID := packet.Children[0].Value.(int64)
|
|
||||||
message := &messagePacket{
|
|
||||||
Op: MessageRequest,
|
|
||||||
MessageID: messageID,
|
|
||||||
Packet: packet,
|
|
||||||
Context: &messageContext{
|
|
||||||
id: messageID,
|
|
||||||
done: make(chan struct{}),
|
|
||||||
responses: responses,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
l.sendProcessMessage(message)
|
|
||||||
return message.Context, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Conn) finishMessage(msgCtx *messageContext) {
|
|
||||||
close(msgCtx.done)
|
|
||||||
|
|
||||||
if l.isClosing() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l.messageMutex.Lock()
|
|
||||||
l.outstandingRequests--
|
|
||||||
if l.isStartingTLS {
|
|
||||||
l.isStartingTLS = false
|
|
||||||
}
|
|
||||||
l.messageMutex.Unlock()
|
|
||||||
|
|
||||||
message := &messagePacket{
|
|
||||||
Op: MessageFinish,
|
|
||||||
MessageID: msgCtx.id,
|
|
||||||
}
|
|
||||||
l.sendProcessMessage(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Conn) sendProcessMessage(message *messagePacket) bool {
|
|
||||||
l.messageMutex.Lock()
|
|
||||||
defer l.messageMutex.Unlock()
|
|
||||||
if l.isClosing() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
l.chanMessage <- message
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Conn) processMessages() {
|
|
||||||
defer func() {
|
|
||||||
if err := recover(); err != nil {
|
|
||||||
log.Printf("ldap: recovered panic in processMessages: %v", err)
|
|
||||||
}
|
|
||||||
for messageID, msgCtx := range l.messageContexts {
|
|
||||||
// If we are closing due to an error, inform anyone who
|
|
||||||
// is waiting about the error.
|
|
||||||
if l.isClosing() && l.closeErr.Load() != nil {
|
|
||||||
msgCtx.sendResponse(&PacketResponse{Error: l.closeErr.Load().(error)})
|
|
||||||
}
|
|
||||||
l.Debug.Printf("Closing channel for MessageID %d", messageID)
|
|
||||||
close(msgCtx.responses)
|
|
||||||
delete(l.messageContexts, messageID)
|
|
||||||
}
|
|
||||||
close(l.chanMessageID)
|
|
||||||
close(l.chanConfirm)
|
|
||||||
}()
|
|
||||||
|
|
||||||
var messageID int64 = 1
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case l.chanMessageID <- messageID:
|
|
||||||
messageID++
|
|
||||||
case message := <-l.chanMessage:
|
|
||||||
switch message.Op {
|
|
||||||
case MessageQuit:
|
|
||||||
l.Debug.Printf("Shutting down - quit message received")
|
|
||||||
return
|
|
||||||
case MessageRequest:
|
|
||||||
// Add to message list and write to network
|
|
||||||
l.Debug.Printf("Sending message %d", message.MessageID)
|
|
||||||
|
|
||||||
buf := message.Packet.Bytes()
|
|
||||||
_, err := l.conn.Write(buf)
|
|
||||||
if err != nil {
|
|
||||||
l.Debug.Printf("Error Sending Message: %s", err.Error())
|
|
||||||
message.Context.sendResponse(&PacketResponse{Error: fmt.Errorf("unable to send request: %s", err)})
|
|
||||||
close(message.Context.responses)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only add to messageContexts if we were able to
|
|
||||||
// successfully write the message.
|
|
||||||
l.messageContexts[message.MessageID] = message.Context
|
|
||||||
|
|
||||||
// Add timeout if defined
|
|
||||||
requestTimeout := time.Duration(atomic.LoadInt64(&l.requestTimeout))
|
|
||||||
if requestTimeout > 0 {
|
|
||||||
go func() {
|
|
||||||
defer func() {
|
|
||||||
if err := recover(); err != nil {
|
|
||||||
log.Printf("ldap: recovered panic in RequestTimeout: %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
time.Sleep(requestTimeout)
|
|
||||||
timeoutMessage := &messagePacket{
|
|
||||||
Op: MessageTimeout,
|
|
||||||
MessageID: message.MessageID,
|
|
||||||
}
|
|
||||||
l.sendProcessMessage(timeoutMessage)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
case MessageResponse:
|
|
||||||
l.Debug.Printf("Receiving message %d", message.MessageID)
|
|
||||||
if msgCtx, ok := l.messageContexts[message.MessageID]; ok {
|
|
||||||
msgCtx.sendResponse(&PacketResponse{message.Packet, nil})
|
|
||||||
} else {
|
|
||||||
log.Printf("Received unexpected message %d, %v", message.MessageID, l.isClosing())
|
|
||||||
ber.PrintPacket(message.Packet)
|
|
||||||
}
|
|
||||||
case MessageTimeout:
|
|
||||||
// Handle the timeout by closing the channel
|
|
||||||
// All reads will return immediately
|
|
||||||
if msgCtx, ok := l.messageContexts[message.MessageID]; ok {
|
|
||||||
l.Debug.Printf("Receiving message timeout for %d", message.MessageID)
|
|
||||||
msgCtx.sendResponse(&PacketResponse{message.Packet, errors.New("ldap: connection timed out")})
|
|
||||||
delete(l.messageContexts, message.MessageID)
|
|
||||||
close(msgCtx.responses)
|
|
||||||
}
|
|
||||||
case MessageFinish:
|
|
||||||
l.Debug.Printf("Finished message %d", message.MessageID)
|
|
||||||
if msgCtx, ok := l.messageContexts[message.MessageID]; ok {
|
|
||||||
delete(l.messageContexts, message.MessageID)
|
|
||||||
close(msgCtx.responses)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Conn) reader() {
|
|
||||||
cleanstop := false
|
|
||||||
defer func() {
|
|
||||||
if err := recover(); err != nil {
|
|
||||||
log.Printf("ldap: recovered panic in reader: %v", err)
|
|
||||||
}
|
|
||||||
if !cleanstop {
|
|
||||||
l.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
for {
|
|
||||||
if cleanstop {
|
|
||||||
l.Debug.Printf("reader clean stopping (without closing the connection)")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
packet, err := ber.ReadPacket(l.conn)
|
|
||||||
if err != nil {
|
|
||||||
// A read error is expected here if we are closing the connection...
|
|
||||||
if !l.isClosing() {
|
|
||||||
l.closeErr.Store(fmt.Errorf("unable to read LDAP response packet: %s", err))
|
|
||||||
l.Debug.Printf("reader error: %s", err.Error())
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
addLDAPDescriptions(packet)
|
|
||||||
if len(packet.Children) == 0 {
|
|
||||||
l.Debug.Printf("Received bad ldap packet")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
l.messageMutex.Lock()
|
|
||||||
if l.isStartingTLS {
|
|
||||||
cleanstop = true
|
|
||||||
}
|
|
||||||
l.messageMutex.Unlock()
|
|
||||||
message := &messagePacket{
|
|
||||||
Op: MessageResponse,
|
|
||||||
MessageID: packet.Children[0].Value.(int64),
|
|
||||||
Packet: packet,
|
|
||||||
}
|
|
||||||
if !l.sendProcessMessage(message) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,420 +0,0 @@
|
||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package ldap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"gopkg.in/asn1-ber.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// ControlTypePaging - https://www.ietf.org/rfc/rfc2696.txt
|
|
||||||
ControlTypePaging = "1.2.840.113556.1.4.319"
|
|
||||||
// ControlTypeBeheraPasswordPolicy - https://tools.ietf.org/html/draft-behera-ldap-password-policy-10
|
|
||||||
ControlTypeBeheraPasswordPolicy = "1.3.6.1.4.1.42.2.27.8.5.1"
|
|
||||||
// ControlTypeVChuPasswordMustChange - https://tools.ietf.org/html/draft-vchu-ldap-pwd-policy-00
|
|
||||||
ControlTypeVChuPasswordMustChange = "2.16.840.1.113730.3.4.4"
|
|
||||||
// ControlTypeVChuPasswordWarning - https://tools.ietf.org/html/draft-vchu-ldap-pwd-policy-00
|
|
||||||
ControlTypeVChuPasswordWarning = "2.16.840.1.113730.3.4.5"
|
|
||||||
// ControlTypeManageDsaIT - https://tools.ietf.org/html/rfc3296
|
|
||||||
ControlTypeManageDsaIT = "2.16.840.1.113730.3.4.2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ControlTypeMap maps controls to text descriptions
|
|
||||||
var ControlTypeMap = map[string]string{
|
|
||||||
ControlTypePaging: "Paging",
|
|
||||||
ControlTypeBeheraPasswordPolicy: "Password Policy - Behera Draft",
|
|
||||||
ControlTypeManageDsaIT: "Manage DSA IT",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Control defines an interface controls provide to encode and describe themselves
|
|
||||||
type Control interface {
|
|
||||||
// GetControlType returns the OID
|
|
||||||
GetControlType() string
|
|
||||||
// Encode returns the ber packet representation
|
|
||||||
Encode() *ber.Packet
|
|
||||||
// String returns a human-readable description
|
|
||||||
String() string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ControlString implements the Control interface for simple controls
|
|
||||||
type ControlString struct {
|
|
||||||
ControlType string
|
|
||||||
Criticality bool
|
|
||||||
ControlValue string
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetControlType returns the OID
|
|
||||||
func (c *ControlString) GetControlType() string {
|
|
||||||
return c.ControlType
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode returns the ber packet representation
|
|
||||||
func (c *ControlString) Encode() *ber.Packet {
|
|
||||||
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
|
|
||||||
packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, c.ControlType, "Control Type ("+ControlTypeMap[c.ControlType]+")"))
|
|
||||||
if c.Criticality {
|
|
||||||
packet.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, c.Criticality, "Criticality"))
|
|
||||||
}
|
|
||||||
packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, string(c.ControlValue), "Control Value"))
|
|
||||||
return packet
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a human-readable description
|
|
||||||
func (c *ControlString) String() string {
|
|
||||||
return fmt.Sprintf("Control Type: %s (%q) Criticality: %t Control Value: %s", ControlTypeMap[c.ControlType], c.ControlType, c.Criticality, c.ControlValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ControlPaging implements the paging control described in https://www.ietf.org/rfc/rfc2696.txt
|
|
||||||
type ControlPaging struct {
|
|
||||||
// PagingSize indicates the page size
|
|
||||||
PagingSize uint32
|
|
||||||
// Cookie is an opaque value returned by the server to track a paging cursor
|
|
||||||
Cookie []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetControlType returns the OID
|
|
||||||
func (c *ControlPaging) GetControlType() string {
|
|
||||||
return ControlTypePaging
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode returns the ber packet representation
|
|
||||||
func (c *ControlPaging) Encode() *ber.Packet {
|
|
||||||
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
|
|
||||||
packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypePaging, "Control Type ("+ControlTypeMap[ControlTypePaging]+")"))
|
|
||||||
|
|
||||||
p2 := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Control Value (Paging)")
|
|
||||||
seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Search Control Value")
|
|
||||||
seq.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(c.PagingSize), "Paging Size"))
|
|
||||||
cookie := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Cookie")
|
|
||||||
cookie.Value = c.Cookie
|
|
||||||
cookie.Data.Write(c.Cookie)
|
|
||||||
seq.AppendChild(cookie)
|
|
||||||
p2.AppendChild(seq)
|
|
||||||
|
|
||||||
packet.AppendChild(p2)
|
|
||||||
return packet
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a human-readable description
|
|
||||||
func (c *ControlPaging) String() string {
|
|
||||||
return fmt.Sprintf(
|
|
||||||
"Control Type: %s (%q) Criticality: %t PagingSize: %d Cookie: %q",
|
|
||||||
ControlTypeMap[ControlTypePaging],
|
|
||||||
ControlTypePaging,
|
|
||||||
false,
|
|
||||||
c.PagingSize,
|
|
||||||
c.Cookie)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetCookie stores the given cookie in the paging control
|
|
||||||
func (c *ControlPaging) SetCookie(cookie []byte) {
|
|
||||||
c.Cookie = cookie
|
|
||||||
}
|
|
||||||
|
|
||||||
// ControlBeheraPasswordPolicy implements the control described in https://tools.ietf.org/html/draft-behera-ldap-password-policy-10
|
|
||||||
type ControlBeheraPasswordPolicy struct {
|
|
||||||
// Expire contains the number of seconds before a password will expire
|
|
||||||
Expire int64
|
|
||||||
// Grace indicates the remaining number of times a user will be allowed to authenticate with an expired password
|
|
||||||
Grace int64
|
|
||||||
// Error indicates the error code
|
|
||||||
Error int8
|
|
||||||
// ErrorString is a human readable error
|
|
||||||
ErrorString string
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetControlType returns the OID
|
|
||||||
func (c *ControlBeheraPasswordPolicy) GetControlType() string {
|
|
||||||
return ControlTypeBeheraPasswordPolicy
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode returns the ber packet representation
|
|
||||||
func (c *ControlBeheraPasswordPolicy) Encode() *ber.Packet {
|
|
||||||
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
|
|
||||||
packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeBeheraPasswordPolicy, "Control Type ("+ControlTypeMap[ControlTypeBeheraPasswordPolicy]+")"))
|
|
||||||
|
|
||||||
return packet
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a human-readable description
|
|
||||||
func (c *ControlBeheraPasswordPolicy) String() string {
|
|
||||||
return fmt.Sprintf(
|
|
||||||
"Control Type: %s (%q) Criticality: %t Expire: %d Grace: %d Error: %d, ErrorString: %s",
|
|
||||||
ControlTypeMap[ControlTypeBeheraPasswordPolicy],
|
|
||||||
ControlTypeBeheraPasswordPolicy,
|
|
||||||
false,
|
|
||||||
c.Expire,
|
|
||||||
c.Grace,
|
|
||||||
c.Error,
|
|
||||||
c.ErrorString)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ControlVChuPasswordMustChange implements the control described in https://tools.ietf.org/html/draft-vchu-ldap-pwd-policy-00
|
|
||||||
type ControlVChuPasswordMustChange struct {
|
|
||||||
// MustChange indicates if the password is required to be changed
|
|
||||||
MustChange bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetControlType returns the OID
|
|
||||||
func (c *ControlVChuPasswordMustChange) GetControlType() string {
|
|
||||||
return ControlTypeVChuPasswordMustChange
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode returns the ber packet representation
|
|
||||||
func (c *ControlVChuPasswordMustChange) Encode() *ber.Packet {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a human-readable description
|
|
||||||
func (c *ControlVChuPasswordMustChange) String() string {
|
|
||||||
return fmt.Sprintf(
|
|
||||||
"Control Type: %s (%q) Criticality: %t MustChange: %v",
|
|
||||||
ControlTypeMap[ControlTypeVChuPasswordMustChange],
|
|
||||||
ControlTypeVChuPasswordMustChange,
|
|
||||||
false,
|
|
||||||
c.MustChange)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ControlVChuPasswordWarning implements the control described in https://tools.ietf.org/html/draft-vchu-ldap-pwd-policy-00
|
|
||||||
type ControlVChuPasswordWarning struct {
|
|
||||||
// Expire indicates the time in seconds until the password expires
|
|
||||||
Expire int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetControlType returns the OID
|
|
||||||
func (c *ControlVChuPasswordWarning) GetControlType() string {
|
|
||||||
return ControlTypeVChuPasswordWarning
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode returns the ber packet representation
|
|
||||||
func (c *ControlVChuPasswordWarning) Encode() *ber.Packet {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a human-readable description
|
|
||||||
func (c *ControlVChuPasswordWarning) String() string {
|
|
||||||
return fmt.Sprintf(
|
|
||||||
"Control Type: %s (%q) Criticality: %t Expire: %b",
|
|
||||||
ControlTypeMap[ControlTypeVChuPasswordWarning],
|
|
||||||
ControlTypeVChuPasswordWarning,
|
|
||||||
false,
|
|
||||||
c.Expire)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ControlManageDsaIT implements the control described in https://tools.ietf.org/html/rfc3296
|
|
||||||
type ControlManageDsaIT struct {
|
|
||||||
// Criticality indicates if this control is required
|
|
||||||
Criticality bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetControlType returns the OID
|
|
||||||
func (c *ControlManageDsaIT) GetControlType() string {
|
|
||||||
return ControlTypeManageDsaIT
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode returns the ber packet representation
|
|
||||||
func (c *ControlManageDsaIT) Encode() *ber.Packet {
|
|
||||||
//FIXME
|
|
||||||
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
|
|
||||||
packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeManageDsaIT, "Control Type ("+ControlTypeMap[ControlTypeManageDsaIT]+")"))
|
|
||||||
if c.Criticality {
|
|
||||||
packet.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, c.Criticality, "Criticality"))
|
|
||||||
}
|
|
||||||
return packet
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a human-readable description
|
|
||||||
func (c *ControlManageDsaIT) String() string {
|
|
||||||
return fmt.Sprintf(
|
|
||||||
"Control Type: %s (%q) Criticality: %t",
|
|
||||||
ControlTypeMap[ControlTypeManageDsaIT],
|
|
||||||
ControlTypeManageDsaIT,
|
|
||||||
c.Criticality)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewControlManageDsaIT returns a ControlManageDsaIT control
|
|
||||||
func NewControlManageDsaIT(Criticality bool) *ControlManageDsaIT {
|
|
||||||
return &ControlManageDsaIT{Criticality: Criticality}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindControl returns the first control of the given type in the list, or nil
|
|
||||||
func FindControl(controls []Control, controlType string) Control {
|
|
||||||
for _, c := range controls {
|
|
||||||
if c.GetControlType() == controlType {
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeControl returns a control read from the given packet, or nil if no recognized control can be made
|
|
||||||
func DecodeControl(packet *ber.Packet) Control {
|
|
||||||
var (
|
|
||||||
ControlType = ""
|
|
||||||
Criticality = false
|
|
||||||
value *ber.Packet
|
|
||||||
)
|
|
||||||
|
|
||||||
switch len(packet.Children) {
|
|
||||||
case 0:
|
|
||||||
// at least one child is required for control type
|
|
||||||
return nil
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
// just type, no criticality or value
|
|
||||||
packet.Children[0].Description = "Control Type (" + ControlTypeMap[ControlType] + ")"
|
|
||||||
ControlType = packet.Children[0].Value.(string)
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
packet.Children[0].Description = "Control Type (" + ControlTypeMap[ControlType] + ")"
|
|
||||||
ControlType = packet.Children[0].Value.(string)
|
|
||||||
|
|
||||||
// Children[1] could be criticality or value (both are optional)
|
|
||||||
// duck-type on whether this is a boolean
|
|
||||||
if _, ok := packet.Children[1].Value.(bool); ok {
|
|
||||||
packet.Children[1].Description = "Criticality"
|
|
||||||
Criticality = packet.Children[1].Value.(bool)
|
|
||||||
} else {
|
|
||||||
packet.Children[1].Description = "Control Value"
|
|
||||||
value = packet.Children[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
case 3:
|
|
||||||
packet.Children[0].Description = "Control Type (" + ControlTypeMap[ControlType] + ")"
|
|
||||||
ControlType = packet.Children[0].Value.(string)
|
|
||||||
|
|
||||||
packet.Children[1].Description = "Criticality"
|
|
||||||
Criticality = packet.Children[1].Value.(bool)
|
|
||||||
|
|
||||||
packet.Children[2].Description = "Control Value"
|
|
||||||
value = packet.Children[2]
|
|
||||||
|
|
||||||
default:
|
|
||||||
// more than 3 children is invalid
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
switch ControlType {
|
|
||||||
case ControlTypeManageDsaIT:
|
|
||||||
return NewControlManageDsaIT(Criticality)
|
|
||||||
case ControlTypePaging:
|
|
||||||
value.Description += " (Paging)"
|
|
||||||
c := new(ControlPaging)
|
|
||||||
if value.Value != nil {
|
|
||||||
valueChildren := ber.DecodePacket(value.Data.Bytes())
|
|
||||||
value.Data.Truncate(0)
|
|
||||||
value.Value = nil
|
|
||||||
value.AppendChild(valueChildren)
|
|
||||||
}
|
|
||||||
value = value.Children[0]
|
|
||||||
value.Description = "Search Control Value"
|
|
||||||
value.Children[0].Description = "Paging Size"
|
|
||||||
value.Children[1].Description = "Cookie"
|
|
||||||
c.PagingSize = uint32(value.Children[0].Value.(int64))
|
|
||||||
c.Cookie = value.Children[1].Data.Bytes()
|
|
||||||
value.Children[1].Value = c.Cookie
|
|
||||||
return c
|
|
||||||
case ControlTypeBeheraPasswordPolicy:
|
|
||||||
value.Description += " (Password Policy - Behera)"
|
|
||||||
c := NewControlBeheraPasswordPolicy()
|
|
||||||
if value.Value != nil {
|
|
||||||
valueChildren := ber.DecodePacket(value.Data.Bytes())
|
|
||||||
value.Data.Truncate(0)
|
|
||||||
value.Value = nil
|
|
||||||
value.AppendChild(valueChildren)
|
|
||||||
}
|
|
||||||
|
|
||||||
sequence := value.Children[0]
|
|
||||||
|
|
||||||
for _, child := range sequence.Children {
|
|
||||||
if child.Tag == 0 {
|
|
||||||
//Warning
|
|
||||||
warningPacket := child.Children[0]
|
|
||||||
packet := ber.DecodePacket(warningPacket.Data.Bytes())
|
|
||||||
val, ok := packet.Value.(int64)
|
|
||||||
if ok {
|
|
||||||
if warningPacket.Tag == 0 {
|
|
||||||
//timeBeforeExpiration
|
|
||||||
c.Expire = val
|
|
||||||
warningPacket.Value = c.Expire
|
|
||||||
} else if warningPacket.Tag == 1 {
|
|
||||||
//graceAuthNsRemaining
|
|
||||||
c.Grace = val
|
|
||||||
warningPacket.Value = c.Grace
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if child.Tag == 1 {
|
|
||||||
// Error
|
|
||||||
packet := ber.DecodePacket(child.Data.Bytes())
|
|
||||||
val, ok := packet.Value.(int8)
|
|
||||||
if !ok {
|
|
||||||
// what to do?
|
|
||||||
val = -1
|
|
||||||
}
|
|
||||||
c.Error = val
|
|
||||||
child.Value = c.Error
|
|
||||||
c.ErrorString = BeheraPasswordPolicyErrorMap[c.Error]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return c
|
|
||||||
case ControlTypeVChuPasswordMustChange:
|
|
||||||
c := &ControlVChuPasswordMustChange{MustChange: true}
|
|
||||||
return c
|
|
||||||
case ControlTypeVChuPasswordWarning:
|
|
||||||
c := &ControlVChuPasswordWarning{Expire: -1}
|
|
||||||
expireStr := ber.DecodeString(value.Data.Bytes())
|
|
||||||
|
|
||||||
expire, err := strconv.ParseInt(expireStr, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
c.Expire = expire
|
|
||||||
value.Value = c.Expire
|
|
||||||
|
|
||||||
return c
|
|
||||||
default:
|
|
||||||
c := new(ControlString)
|
|
||||||
c.ControlType = ControlType
|
|
||||||
c.Criticality = Criticality
|
|
||||||
if value != nil {
|
|
||||||
c.ControlValue = value.Value.(string)
|
|
||||||
}
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewControlString returns a generic control
|
|
||||||
func NewControlString(controlType string, criticality bool, controlValue string) *ControlString {
|
|
||||||
return &ControlString{
|
|
||||||
ControlType: controlType,
|
|
||||||
Criticality: criticality,
|
|
||||||
ControlValue: controlValue,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewControlPaging returns a paging control
|
|
||||||
func NewControlPaging(pagingSize uint32) *ControlPaging {
|
|
||||||
return &ControlPaging{PagingSize: pagingSize}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewControlBeheraPasswordPolicy returns a ControlBeheraPasswordPolicy
|
|
||||||
func NewControlBeheraPasswordPolicy() *ControlBeheraPasswordPolicy {
|
|
||||||
return &ControlBeheraPasswordPolicy{
|
|
||||||
Expire: -1,
|
|
||||||
Grace: -1,
|
|
||||||
Error: -1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodeControls(controls []Control) *ber.Packet {
|
|
||||||
packet := ber.Encode(ber.ClassContext, ber.TypeConstructed, 0, nil, "Controls")
|
|
||||||
for _, control := range controls {
|
|
||||||
packet.AppendChild(control.Encode())
|
|
||||||
}
|
|
||||||
return packet
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
package ldap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"gopkg.in/asn1-ber.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// debugging type
|
|
||||||
// - has a Printf method to write the debug output
|
|
||||||
type debugging bool
|
|
||||||
|
|
||||||
// write debug output
|
|
||||||
func (debug debugging) Printf(format string, args ...interface{}) {
|
|
||||||
if debug {
|
|
||||||
log.Printf(format, args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (debug debugging) PrintPacket(packet *ber.Packet) {
|
|
||||||
if debug {
|
|
||||||
ber.PrintPacket(packet)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,84 +0,0 @@
|
||||||
//
|
|
||||||
// https://tools.ietf.org/html/rfc4511
|
|
||||||
//
|
|
||||||
// DelRequest ::= [APPLICATION 10] LDAPDN
|
|
||||||
|
|
||||||
package ldap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"gopkg.in/asn1-ber.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DelRequest implements an LDAP deletion request
|
|
||||||
type DelRequest struct {
|
|
||||||
// DN is the name of the directory entry to delete
|
|
||||||
DN string
|
|
||||||
// Controls hold optional controls to send with the request
|
|
||||||
Controls []Control
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d DelRequest) encode() *ber.Packet {
|
|
||||||
request := ber.Encode(ber.ClassApplication, ber.TypePrimitive, ApplicationDelRequest, d.DN, "Del Request")
|
|
||||||
request.Data.Write([]byte(d.DN))
|
|
||||||
return request
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDelRequest creates a delete request for the given DN and controls
|
|
||||||
func NewDelRequest(DN string,
|
|
||||||
Controls []Control) *DelRequest {
|
|
||||||
return &DelRequest{
|
|
||||||
DN: DN,
|
|
||||||
Controls: Controls,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Del executes the given delete request
|
|
||||||
func (l *Conn) Del(delRequest *DelRequest) error {
|
|
||||||
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
|
|
||||||
packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID"))
|
|
||||||
packet.AppendChild(delRequest.encode())
|
|
||||||
if delRequest.Controls != nil {
|
|
||||||
packet.AppendChild(encodeControls(delRequest.Controls))
|
|
||||||
}
|
|
||||||
|
|
||||||
l.Debug.PrintPacket(packet)
|
|
||||||
|
|
||||||
msgCtx, err := l.sendMessage(packet)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer l.finishMessage(msgCtx)
|
|
||||||
|
|
||||||
l.Debug.Printf("%d: waiting for response", msgCtx.id)
|
|
||||||
packetResponse, ok := <-msgCtx.responses
|
|
||||||
if !ok {
|
|
||||||
return NewError(ErrorNetwork, errors.New("ldap: response channel closed"))
|
|
||||||
}
|
|
||||||
packet, err = packetResponse.ReadPacket()
|
|
||||||
l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if l.Debug {
|
|
||||||
if err := addLDAPDescriptions(packet); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ber.PrintPacket(packet)
|
|
||||||
}
|
|
||||||
|
|
||||||
if packet.Children[1].Tag == ApplicationDelResponse {
|
|
||||||
resultCode, resultDescription := getLDAPResultCode(packet)
|
|
||||||
if resultCode != 0 {
|
|
||||||
return NewError(resultCode, errors.New(resultDescription))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Printf("Unexpected Response: %d", packet.Children[1].Tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
l.Debug.Printf("%d: returning", msgCtx.id)
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,247 +0,0 @@
|
||||||
// Copyright 2015 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
//
|
|
||||||
// File contains DN parsing functionality
|
|
||||||
//
|
|
||||||
// https://tools.ietf.org/html/rfc4514
|
|
||||||
//
|
|
||||||
// distinguishedName = [ relativeDistinguishedName
|
|
||||||
// *( COMMA relativeDistinguishedName ) ]
|
|
||||||
// relativeDistinguishedName = attributeTypeAndValue
|
|
||||||
// *( PLUS attributeTypeAndValue )
|
|
||||||
// attributeTypeAndValue = attributeType EQUALS attributeValue
|
|
||||||
// attributeType = descr / numericoid
|
|
||||||
// attributeValue = string / hexstring
|
|
||||||
//
|
|
||||||
// ; The following characters are to be escaped when they appear
|
|
||||||
// ; in the value to be encoded: ESC, one of <escaped>, leading
|
|
||||||
// ; SHARP or SPACE, trailing SPACE, and NULL.
|
|
||||||
// string = [ ( leadchar / pair ) [ *( stringchar / pair )
|
|
||||||
// ( trailchar / pair ) ] ]
|
|
||||||
//
|
|
||||||
// leadchar = LUTF1 / UTFMB
|
|
||||||
// LUTF1 = %x01-1F / %x21 / %x24-2A / %x2D-3A /
|
|
||||||
// %x3D / %x3F-5B / %x5D-7F
|
|
||||||
//
|
|
||||||
// trailchar = TUTF1 / UTFMB
|
|
||||||
// TUTF1 = %x01-1F / %x21 / %x23-2A / %x2D-3A /
|
|
||||||
// %x3D / %x3F-5B / %x5D-7F
|
|
||||||
//
|
|
||||||
// stringchar = SUTF1 / UTFMB
|
|
||||||
// SUTF1 = %x01-21 / %x23-2A / %x2D-3A /
|
|
||||||
// %x3D / %x3F-5B / %x5D-7F
|
|
||||||
//
|
|
||||||
// pair = ESC ( ESC / special / hexpair )
|
|
||||||
// special = escaped / SPACE / SHARP / EQUALS
|
|
||||||
// escaped = DQUOTE / PLUS / COMMA / SEMI / LANGLE / RANGLE
|
|
||||||
// hexstring = SHARP 1*hexpair
|
|
||||||
// hexpair = HEX HEX
|
|
||||||
//
|
|
||||||
// where the productions <descr>, <numericoid>, <COMMA>, <DQUOTE>,
|
|
||||||
// <EQUALS>, <ESC>, <HEX>, <LANGLE>, <NULL>, <PLUS>, <RANGLE>, <SEMI>,
|
|
||||||
// <SPACE>, <SHARP>, and <UTFMB> are defined in [RFC4512].
|
|
||||||
//
|
|
||||||
|
|
||||||
package ldap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
enchex "encoding/hex"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"gopkg.in/asn1-ber.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AttributeTypeAndValue represents an attributeTypeAndValue from https://tools.ietf.org/html/rfc4514
|
|
||||||
type AttributeTypeAndValue struct {
|
|
||||||
// Type is the attribute type
|
|
||||||
Type string
|
|
||||||
// Value is the attribute value
|
|
||||||
Value string
|
|
||||||
}
|
|
||||||
|
|
||||||
// RelativeDN represents a relativeDistinguishedName from https://tools.ietf.org/html/rfc4514
|
|
||||||
type RelativeDN struct {
|
|
||||||
Attributes []*AttributeTypeAndValue
|
|
||||||
}
|
|
||||||
|
|
||||||
// DN represents a distinguishedName from https://tools.ietf.org/html/rfc4514
|
|
||||||
type DN struct {
|
|
||||||
RDNs []*RelativeDN
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseDN returns a distinguishedName or an error
|
|
||||||
func ParseDN(str string) (*DN, error) {
|
|
||||||
dn := new(DN)
|
|
||||||
dn.RDNs = make([]*RelativeDN, 0)
|
|
||||||
rdn := new(RelativeDN)
|
|
||||||
rdn.Attributes = make([]*AttributeTypeAndValue, 0)
|
|
||||||
buffer := bytes.Buffer{}
|
|
||||||
attribute := new(AttributeTypeAndValue)
|
|
||||||
escaping := false
|
|
||||||
|
|
||||||
unescapedTrailingSpaces := 0
|
|
||||||
stringFromBuffer := func() string {
|
|
||||||
s := buffer.String()
|
|
||||||
s = s[0 : len(s)-unescapedTrailingSpaces]
|
|
||||||
buffer.Reset()
|
|
||||||
unescapedTrailingSpaces = 0
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < len(str); i++ {
|
|
||||||
char := str[i]
|
|
||||||
if escaping {
|
|
||||||
unescapedTrailingSpaces = 0
|
|
||||||
escaping = false
|
|
||||||
switch char {
|
|
||||||
case ' ', '"', '#', '+', ',', ';', '<', '=', '>', '\\':
|
|
||||||
buffer.WriteByte(char)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Not a special character, assume hex encoded octet
|
|
||||||
if len(str) == i+1 {
|
|
||||||
return nil, errors.New("Got corrupted escaped character")
|
|
||||||
}
|
|
||||||
|
|
||||||
dst := []byte{0}
|
|
||||||
n, err := enchex.Decode([]byte(dst), []byte(str[i:i+2]))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Failed to decode escaped character: %s", err)
|
|
||||||
} else if n != 1 {
|
|
||||||
return nil, fmt.Errorf("Expected 1 byte when un-escaping, got %d", n)
|
|
||||||
}
|
|
||||||
buffer.WriteByte(dst[0])
|
|
||||||
i++
|
|
||||||
} else if char == '\\' {
|
|
||||||
unescapedTrailingSpaces = 0
|
|
||||||
escaping = true
|
|
||||||
} else if char == '=' {
|
|
||||||
attribute.Type = stringFromBuffer()
|
|
||||||
// Special case: If the first character in the value is # the
|
|
||||||
// following data is BER encoded so we can just fast forward
|
|
||||||
// and decode.
|
|
||||||
if len(str) > i+1 && str[i+1] == '#' {
|
|
||||||
i += 2
|
|
||||||
index := strings.IndexAny(str[i:], ",+")
|
|
||||||
data := str
|
|
||||||
if index > 0 {
|
|
||||||
data = str[i : i+index]
|
|
||||||
} else {
|
|
||||||
data = str[i:]
|
|
||||||
}
|
|
||||||
rawBER, err := enchex.DecodeString(data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Failed to decode BER encoding: %s", err)
|
|
||||||
}
|
|
||||||
packet := ber.DecodePacket(rawBER)
|
|
||||||
buffer.WriteString(packet.Data.String())
|
|
||||||
i += len(data) - 1
|
|
||||||
}
|
|
||||||
} else if char == ',' || char == '+' {
|
|
||||||
// We're done with this RDN or value, push it
|
|
||||||
if len(attribute.Type) == 0 {
|
|
||||||
return nil, errors.New("incomplete type, value pair")
|
|
||||||
}
|
|
||||||
attribute.Value = stringFromBuffer()
|
|
||||||
rdn.Attributes = append(rdn.Attributes, attribute)
|
|
||||||
attribute = new(AttributeTypeAndValue)
|
|
||||||
if char == ',' {
|
|
||||||
dn.RDNs = append(dn.RDNs, rdn)
|
|
||||||
rdn = new(RelativeDN)
|
|
||||||
rdn.Attributes = make([]*AttributeTypeAndValue, 0)
|
|
||||||
}
|
|
||||||
} else if char == ' ' && buffer.Len() == 0 {
|
|
||||||
// ignore unescaped leading spaces
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
if char == ' ' {
|
|
||||||
// Track unescaped spaces in case they are trailing and we need to remove them
|
|
||||||
unescapedTrailingSpaces++
|
|
||||||
} else {
|
|
||||||
// Reset if we see a non-space char
|
|
||||||
unescapedTrailingSpaces = 0
|
|
||||||
}
|
|
||||||
buffer.WriteByte(char)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if buffer.Len() > 0 {
|
|
||||||
if len(attribute.Type) == 0 {
|
|
||||||
return nil, errors.New("DN ended with incomplete type, value pair")
|
|
||||||
}
|
|
||||||
attribute.Value = stringFromBuffer()
|
|
||||||
rdn.Attributes = append(rdn.Attributes, attribute)
|
|
||||||
dn.RDNs = append(dn.RDNs, rdn)
|
|
||||||
}
|
|
||||||
return dn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equal returns true if the DNs are equal as defined by rfc4517 4.2.15 (distinguishedNameMatch).
|
|
||||||
// Returns true if they have the same number of relative distinguished names
|
|
||||||
// and corresponding relative distinguished names (by position) are the same.
|
|
||||||
func (d *DN) Equal(other *DN) bool {
|
|
||||||
if len(d.RDNs) != len(other.RDNs) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for i := range d.RDNs {
|
|
||||||
if !d.RDNs[i].Equal(other.RDNs[i]) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// AncestorOf returns true if the other DN consists of at least one RDN followed by all the RDNs of the current DN.
|
|
||||||
// "ou=widgets,o=acme.com" is an ancestor of "ou=sprockets,ou=widgets,o=acme.com"
|
|
||||||
// "ou=widgets,o=acme.com" is not an ancestor of "ou=sprockets,ou=widgets,o=foo.com"
|
|
||||||
// "ou=widgets,o=acme.com" is not an ancestor of "ou=widgets,o=acme.com"
|
|
||||||
func (d *DN) AncestorOf(other *DN) bool {
|
|
||||||
if len(d.RDNs) >= len(other.RDNs) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// Take the last `len(d.RDNs)` RDNs from the other DN to compare against
|
|
||||||
otherRDNs := other.RDNs[len(other.RDNs)-len(d.RDNs):]
|
|
||||||
for i := range d.RDNs {
|
|
||||||
if !d.RDNs[i].Equal(otherRDNs[i]) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equal returns true if the RelativeDNs are equal as defined by rfc4517 4.2.15 (distinguishedNameMatch).
|
|
||||||
// Relative distinguished names are the same if and only if they have the same number of AttributeTypeAndValues
|
|
||||||
// and each attribute of the first RDN is the same as the attribute of the second RDN with the same attribute type.
|
|
||||||
// The order of attributes is not significant.
|
|
||||||
// Case of attribute types is not significant.
|
|
||||||
func (r *RelativeDN) Equal(other *RelativeDN) bool {
|
|
||||||
if len(r.Attributes) != len(other.Attributes) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return r.hasAllAttributes(other.Attributes) && other.hasAllAttributes(r.Attributes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RelativeDN) hasAllAttributes(attrs []*AttributeTypeAndValue) bool {
|
|
||||||
for _, attr := range attrs {
|
|
||||||
found := false
|
|
||||||
for _, myattr := range r.Attributes {
|
|
||||||
if myattr.Equal(attr) {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equal returns true if the AttributeTypeAndValue is equivalent to the specified AttributeTypeAndValue
|
|
||||||
// Case of the attribute type is not significant
|
|
||||||
func (a *AttributeTypeAndValue) Equal(other *AttributeTypeAndValue) bool {
|
|
||||||
return strings.EqualFold(a.Type, other.Type) && a.Value == other.Value
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
/*
|
|
||||||
Package ldap provides basic LDAP v3 functionality.
|
|
||||||
*/
|
|
||||||
package ldap
|
|
|
@ -1,155 +0,0 @@
|
||||||
package ldap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"gopkg.in/asn1-ber.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// LDAP Result Codes
|
|
||||||
const (
|
|
||||||
LDAPResultSuccess = 0
|
|
||||||
LDAPResultOperationsError = 1
|
|
||||||
LDAPResultProtocolError = 2
|
|
||||||
LDAPResultTimeLimitExceeded = 3
|
|
||||||
LDAPResultSizeLimitExceeded = 4
|
|
||||||
LDAPResultCompareFalse = 5
|
|
||||||
LDAPResultCompareTrue = 6
|
|
||||||
LDAPResultAuthMethodNotSupported = 7
|
|
||||||
LDAPResultStrongAuthRequired = 8
|
|
||||||
LDAPResultReferral = 10
|
|
||||||
LDAPResultAdminLimitExceeded = 11
|
|
||||||
LDAPResultUnavailableCriticalExtension = 12
|
|
||||||
LDAPResultConfidentialityRequired = 13
|
|
||||||
LDAPResultSaslBindInProgress = 14
|
|
||||||
LDAPResultNoSuchAttribute = 16
|
|
||||||
LDAPResultUndefinedAttributeType = 17
|
|
||||||
LDAPResultInappropriateMatching = 18
|
|
||||||
LDAPResultConstraintViolation = 19
|
|
||||||
LDAPResultAttributeOrValueExists = 20
|
|
||||||
LDAPResultInvalidAttributeSyntax = 21
|
|
||||||
LDAPResultNoSuchObject = 32
|
|
||||||
LDAPResultAliasProblem = 33
|
|
||||||
LDAPResultInvalidDNSyntax = 34
|
|
||||||
LDAPResultAliasDereferencingProblem = 36
|
|
||||||
LDAPResultInappropriateAuthentication = 48
|
|
||||||
LDAPResultInvalidCredentials = 49
|
|
||||||
LDAPResultInsufficientAccessRights = 50
|
|
||||||
LDAPResultBusy = 51
|
|
||||||
LDAPResultUnavailable = 52
|
|
||||||
LDAPResultUnwillingToPerform = 53
|
|
||||||
LDAPResultLoopDetect = 54
|
|
||||||
LDAPResultNamingViolation = 64
|
|
||||||
LDAPResultObjectClassViolation = 65
|
|
||||||
LDAPResultNotAllowedOnNonLeaf = 66
|
|
||||||
LDAPResultNotAllowedOnRDN = 67
|
|
||||||
LDAPResultEntryAlreadyExists = 68
|
|
||||||
LDAPResultObjectClassModsProhibited = 69
|
|
||||||
LDAPResultAffectsMultipleDSAs = 71
|
|
||||||
LDAPResultOther = 80
|
|
||||||
|
|
||||||
ErrorNetwork = 200
|
|
||||||
ErrorFilterCompile = 201
|
|
||||||
ErrorFilterDecompile = 202
|
|
||||||
ErrorDebugging = 203
|
|
||||||
ErrorUnexpectedMessage = 204
|
|
||||||
ErrorUnexpectedResponse = 205
|
|
||||||
)
|
|
||||||
|
|
||||||
// LDAPResultCodeMap contains string descriptions for LDAP error codes
|
|
||||||
var LDAPResultCodeMap = map[uint8]string{
|
|
||||||
LDAPResultSuccess: "Success",
|
|
||||||
LDAPResultOperationsError: "Operations Error",
|
|
||||||
LDAPResultProtocolError: "Protocol Error",
|
|
||||||
LDAPResultTimeLimitExceeded: "Time Limit Exceeded",
|
|
||||||
LDAPResultSizeLimitExceeded: "Size Limit Exceeded",
|
|
||||||
LDAPResultCompareFalse: "Compare False",
|
|
||||||
LDAPResultCompareTrue: "Compare True",
|
|
||||||
LDAPResultAuthMethodNotSupported: "Auth Method Not Supported",
|
|
||||||
LDAPResultStrongAuthRequired: "Strong Auth Required",
|
|
||||||
LDAPResultReferral: "Referral",
|
|
||||||
LDAPResultAdminLimitExceeded: "Admin Limit Exceeded",
|
|
||||||
LDAPResultUnavailableCriticalExtension: "Unavailable Critical Extension",
|
|
||||||
LDAPResultConfidentialityRequired: "Confidentiality Required",
|
|
||||||
LDAPResultSaslBindInProgress: "Sasl Bind In Progress",
|
|
||||||
LDAPResultNoSuchAttribute: "No Such Attribute",
|
|
||||||
LDAPResultUndefinedAttributeType: "Undefined Attribute Type",
|
|
||||||
LDAPResultInappropriateMatching: "Inappropriate Matching",
|
|
||||||
LDAPResultConstraintViolation: "Constraint Violation",
|
|
||||||
LDAPResultAttributeOrValueExists: "Attribute Or Value Exists",
|
|
||||||
LDAPResultInvalidAttributeSyntax: "Invalid Attribute Syntax",
|
|
||||||
LDAPResultNoSuchObject: "No Such Object",
|
|
||||||
LDAPResultAliasProblem: "Alias Problem",
|
|
||||||
LDAPResultInvalidDNSyntax: "Invalid DN Syntax",
|
|
||||||
LDAPResultAliasDereferencingProblem: "Alias Dereferencing Problem",
|
|
||||||
LDAPResultInappropriateAuthentication: "Inappropriate Authentication",
|
|
||||||
LDAPResultInvalidCredentials: "Invalid Credentials",
|
|
||||||
LDAPResultInsufficientAccessRights: "Insufficient Access Rights",
|
|
||||||
LDAPResultBusy: "Busy",
|
|
||||||
LDAPResultUnavailable: "Unavailable",
|
|
||||||
LDAPResultUnwillingToPerform: "Unwilling To Perform",
|
|
||||||
LDAPResultLoopDetect: "Loop Detect",
|
|
||||||
LDAPResultNamingViolation: "Naming Violation",
|
|
||||||
LDAPResultObjectClassViolation: "Object Class Violation",
|
|
||||||
LDAPResultNotAllowedOnNonLeaf: "Not Allowed On Non Leaf",
|
|
||||||
LDAPResultNotAllowedOnRDN: "Not Allowed On RDN",
|
|
||||||
LDAPResultEntryAlreadyExists: "Entry Already Exists",
|
|
||||||
LDAPResultObjectClassModsProhibited: "Object Class Mods Prohibited",
|
|
||||||
LDAPResultAffectsMultipleDSAs: "Affects Multiple DSAs",
|
|
||||||
LDAPResultOther: "Other",
|
|
||||||
|
|
||||||
ErrorNetwork: "Network Error",
|
|
||||||
ErrorFilterCompile: "Filter Compile Error",
|
|
||||||
ErrorFilterDecompile: "Filter Decompile Error",
|
|
||||||
ErrorDebugging: "Debugging Error",
|
|
||||||
ErrorUnexpectedMessage: "Unexpected Message",
|
|
||||||
ErrorUnexpectedResponse: "Unexpected Response",
|
|
||||||
}
|
|
||||||
|
|
||||||
func getLDAPResultCode(packet *ber.Packet) (code uint8, description string) {
|
|
||||||
if packet == nil {
|
|
||||||
return ErrorUnexpectedResponse, "Empty packet"
|
|
||||||
} else if len(packet.Children) >= 2 {
|
|
||||||
response := packet.Children[1]
|
|
||||||
if response == nil {
|
|
||||||
return ErrorUnexpectedResponse, "Empty response in packet"
|
|
||||||
}
|
|
||||||
if response.ClassType == ber.ClassApplication && response.TagType == ber.TypeConstructed && len(response.Children) >= 3 {
|
|
||||||
// Children[1].Children[2] is the diagnosticMessage which is guaranteed to exist as seen here: https://tools.ietf.org/html/rfc4511#section-4.1.9
|
|
||||||
return uint8(response.Children[0].Value.(int64)), response.Children[2].Value.(string)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ErrorNetwork, "Invalid packet format"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error holds LDAP error information
|
|
||||||
type Error struct {
|
|
||||||
// Err is the underlying error
|
|
||||||
Err error
|
|
||||||
// ResultCode is the LDAP error code
|
|
||||||
ResultCode uint8
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Error) Error() string {
|
|
||||||
return fmt.Sprintf("LDAP Result Code %d %q: %s", e.ResultCode, LDAPResultCodeMap[e.ResultCode], e.Err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewError creates an LDAP error with the given code and underlying error
|
|
||||||
func NewError(resultCode uint8, err error) error {
|
|
||||||
return &Error{ResultCode: resultCode, Err: err}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsErrorWithCode returns true if the given error is an LDAP error with the given result code
|
|
||||||
func IsErrorWithCode(err error, desiredResultCode uint8) bool {
|
|
||||||
if err == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
serverError, ok := err.(*Error)
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return serverError.ResultCode == desiredResultCode
|
|
||||||
}
|
|
|
@ -1,469 +0,0 @@
|
||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package ldap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
hexpac "encoding/hex"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
"gopkg.in/asn1-ber.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Filter choices
|
|
||||||
const (
|
|
||||||
FilterAnd = 0
|
|
||||||
FilterOr = 1
|
|
||||||
FilterNot = 2
|
|
||||||
FilterEqualityMatch = 3
|
|
||||||
FilterSubstrings = 4
|
|
||||||
FilterGreaterOrEqual = 5
|
|
||||||
FilterLessOrEqual = 6
|
|
||||||
FilterPresent = 7
|
|
||||||
FilterApproxMatch = 8
|
|
||||||
FilterExtensibleMatch = 9
|
|
||||||
)
|
|
||||||
|
|
||||||
// FilterMap contains human readable descriptions of Filter choices
|
|
||||||
var FilterMap = map[uint64]string{
|
|
||||||
FilterAnd: "And",
|
|
||||||
FilterOr: "Or",
|
|
||||||
FilterNot: "Not",
|
|
||||||
FilterEqualityMatch: "Equality Match",
|
|
||||||
FilterSubstrings: "Substrings",
|
|
||||||
FilterGreaterOrEqual: "Greater Or Equal",
|
|
||||||
FilterLessOrEqual: "Less Or Equal",
|
|
||||||
FilterPresent: "Present",
|
|
||||||
FilterApproxMatch: "Approx Match",
|
|
||||||
FilterExtensibleMatch: "Extensible Match",
|
|
||||||
}
|
|
||||||
|
|
||||||
// SubstringFilter options
|
|
||||||
const (
|
|
||||||
FilterSubstringsInitial = 0
|
|
||||||
FilterSubstringsAny = 1
|
|
||||||
FilterSubstringsFinal = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
// FilterSubstringsMap contains human readable descriptions of SubstringFilter choices
|
|
||||||
var FilterSubstringsMap = map[uint64]string{
|
|
||||||
FilterSubstringsInitial: "Substrings Initial",
|
|
||||||
FilterSubstringsAny: "Substrings Any",
|
|
||||||
FilterSubstringsFinal: "Substrings Final",
|
|
||||||
}
|
|
||||||
|
|
||||||
// MatchingRuleAssertion choices
|
|
||||||
const (
|
|
||||||
MatchingRuleAssertionMatchingRule = 1
|
|
||||||
MatchingRuleAssertionType = 2
|
|
||||||
MatchingRuleAssertionMatchValue = 3
|
|
||||||
MatchingRuleAssertionDNAttributes = 4
|
|
||||||
)
|
|
||||||
|
|
||||||
// MatchingRuleAssertionMap contains human readable descriptions of MatchingRuleAssertion choices
|
|
||||||
var MatchingRuleAssertionMap = map[uint64]string{
|
|
||||||
MatchingRuleAssertionMatchingRule: "Matching Rule Assertion Matching Rule",
|
|
||||||
MatchingRuleAssertionType: "Matching Rule Assertion Type",
|
|
||||||
MatchingRuleAssertionMatchValue: "Matching Rule Assertion Match Value",
|
|
||||||
MatchingRuleAssertionDNAttributes: "Matching Rule Assertion DN Attributes",
|
|
||||||
}
|
|
||||||
|
|
||||||
// CompileFilter converts a string representation of a filter into a BER-encoded packet
|
|
||||||
func CompileFilter(filter string) (*ber.Packet, error) {
|
|
||||||
if len(filter) == 0 || filter[0] != '(' {
|
|
||||||
return nil, NewError(ErrorFilterCompile, errors.New("ldap: filter does not start with an '('"))
|
|
||||||
}
|
|
||||||
packet, pos, err := compileFilter(filter, 1)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case pos > len(filter):
|
|
||||||
return nil, NewError(ErrorFilterCompile, errors.New("ldap: unexpected end of filter"))
|
|
||||||
case pos < len(filter):
|
|
||||||
return nil, NewError(ErrorFilterCompile, errors.New("ldap: finished compiling filter with extra at end: "+fmt.Sprint(filter[pos:])))
|
|
||||||
}
|
|
||||||
return packet, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecompileFilter converts a packet representation of a filter into a string representation
|
|
||||||
func DecompileFilter(packet *ber.Packet) (ret string, err error) {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
err = NewError(ErrorFilterDecompile, errors.New("ldap: error decompiling filter"))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
ret = "("
|
|
||||||
err = nil
|
|
||||||
childStr := ""
|
|
||||||
|
|
||||||
switch packet.Tag {
|
|
||||||
case FilterAnd:
|
|
||||||
ret += "&"
|
|
||||||
for _, child := range packet.Children {
|
|
||||||
childStr, err = DecompileFilter(child)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ret += childStr
|
|
||||||
}
|
|
||||||
case FilterOr:
|
|
||||||
ret += "|"
|
|
||||||
for _, child := range packet.Children {
|
|
||||||
childStr, err = DecompileFilter(child)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ret += childStr
|
|
||||||
}
|
|
||||||
case FilterNot:
|
|
||||||
ret += "!"
|
|
||||||
childStr, err = DecompileFilter(packet.Children[0])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ret += childStr
|
|
||||||
|
|
||||||
case FilterSubstrings:
|
|
||||||
ret += ber.DecodeString(packet.Children[0].Data.Bytes())
|
|
||||||
ret += "="
|
|
||||||
for i, child := range packet.Children[1].Children {
|
|
||||||
if i == 0 && child.Tag != FilterSubstringsInitial {
|
|
||||||
ret += "*"
|
|
||||||
}
|
|
||||||
ret += EscapeFilter(ber.DecodeString(child.Data.Bytes()))
|
|
||||||
if child.Tag != FilterSubstringsFinal {
|
|
||||||
ret += "*"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case FilterEqualityMatch:
|
|
||||||
ret += ber.DecodeString(packet.Children[0].Data.Bytes())
|
|
||||||
ret += "="
|
|
||||||
ret += EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes()))
|
|
||||||
case FilterGreaterOrEqual:
|
|
||||||
ret += ber.DecodeString(packet.Children[0].Data.Bytes())
|
|
||||||
ret += ">="
|
|
||||||
ret += EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes()))
|
|
||||||
case FilterLessOrEqual:
|
|
||||||
ret += ber.DecodeString(packet.Children[0].Data.Bytes())
|
|
||||||
ret += "<="
|
|
||||||
ret += EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes()))
|
|
||||||
case FilterPresent:
|
|
||||||
ret += ber.DecodeString(packet.Data.Bytes())
|
|
||||||
ret += "=*"
|
|
||||||
case FilterApproxMatch:
|
|
||||||
ret += ber.DecodeString(packet.Children[0].Data.Bytes())
|
|
||||||
ret += "~="
|
|
||||||
ret += EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes()))
|
|
||||||
case FilterExtensibleMatch:
|
|
||||||
attr := ""
|
|
||||||
dnAttributes := false
|
|
||||||
matchingRule := ""
|
|
||||||
value := ""
|
|
||||||
|
|
||||||
for _, child := range packet.Children {
|
|
||||||
switch child.Tag {
|
|
||||||
case MatchingRuleAssertionMatchingRule:
|
|
||||||
matchingRule = ber.DecodeString(child.Data.Bytes())
|
|
||||||
case MatchingRuleAssertionType:
|
|
||||||
attr = ber.DecodeString(child.Data.Bytes())
|
|
||||||
case MatchingRuleAssertionMatchValue:
|
|
||||||
value = ber.DecodeString(child.Data.Bytes())
|
|
||||||
case MatchingRuleAssertionDNAttributes:
|
|
||||||
dnAttributes = child.Value.(bool)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(attr) > 0 {
|
|
||||||
ret += attr
|
|
||||||
}
|
|
||||||
if dnAttributes {
|
|
||||||
ret += ":dn"
|
|
||||||
}
|
|
||||||
if len(matchingRule) > 0 {
|
|
||||||
ret += ":"
|
|
||||||
ret += matchingRule
|
|
||||||
}
|
|
||||||
ret += ":="
|
|
||||||
ret += EscapeFilter(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
ret += ")"
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func compileFilterSet(filter string, pos int, parent *ber.Packet) (int, error) {
|
|
||||||
for pos < len(filter) && filter[pos] == '(' {
|
|
||||||
child, newPos, err := compileFilter(filter, pos+1)
|
|
||||||
if err != nil {
|
|
||||||
return pos, err
|
|
||||||
}
|
|
||||||
pos = newPos
|
|
||||||
parent.AppendChild(child)
|
|
||||||
}
|
|
||||||
if pos == len(filter) {
|
|
||||||
return pos, NewError(ErrorFilterCompile, errors.New("ldap: unexpected end of filter"))
|
|
||||||
}
|
|
||||||
|
|
||||||
return pos + 1, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func compileFilter(filter string, pos int) (*ber.Packet, int, error) {
|
|
||||||
var (
|
|
||||||
packet *ber.Packet
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
err = NewError(ErrorFilterCompile, errors.New("ldap: error compiling filter"))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
newPos := pos
|
|
||||||
|
|
||||||
currentRune, currentWidth := utf8.DecodeRuneInString(filter[newPos:])
|
|
||||||
|
|
||||||
switch currentRune {
|
|
||||||
case utf8.RuneError:
|
|
||||||
return nil, 0, NewError(ErrorFilterCompile, fmt.Errorf("ldap: error reading rune at position %d", newPos))
|
|
||||||
case '(':
|
|
||||||
packet, newPos, err = compileFilter(filter, pos+currentWidth)
|
|
||||||
newPos++
|
|
||||||
return packet, newPos, err
|
|
||||||
case '&':
|
|
||||||
packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterAnd, nil, FilterMap[FilterAnd])
|
|
||||||
newPos, err = compileFilterSet(filter, pos+currentWidth, packet)
|
|
||||||
return packet, newPos, err
|
|
||||||
case '|':
|
|
||||||
packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterOr, nil, FilterMap[FilterOr])
|
|
||||||
newPos, err = compileFilterSet(filter, pos+currentWidth, packet)
|
|
||||||
return packet, newPos, err
|
|
||||||
case '!':
|
|
||||||
packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterNot, nil, FilterMap[FilterNot])
|
|
||||||
var child *ber.Packet
|
|
||||||
child, newPos, err = compileFilter(filter, pos+currentWidth)
|
|
||||||
packet.AppendChild(child)
|
|
||||||
return packet, newPos, err
|
|
||||||
default:
|
|
||||||
const (
|
|
||||||
stateReadingAttr = 0
|
|
||||||
stateReadingExtensibleMatchingRule = 1
|
|
||||||
stateReadingCondition = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
state := stateReadingAttr
|
|
||||||
|
|
||||||
attribute := ""
|
|
||||||
extensibleDNAttributes := false
|
|
||||||
extensibleMatchingRule := ""
|
|
||||||
condition := ""
|
|
||||||
|
|
||||||
for newPos < len(filter) {
|
|
||||||
remainingFilter := filter[newPos:]
|
|
||||||
currentRune, currentWidth = utf8.DecodeRuneInString(remainingFilter)
|
|
||||||
if currentRune == ')' {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if currentRune == utf8.RuneError {
|
|
||||||
return packet, newPos, NewError(ErrorFilterCompile, fmt.Errorf("ldap: error reading rune at position %d", newPos))
|
|
||||||
}
|
|
||||||
|
|
||||||
switch state {
|
|
||||||
case stateReadingAttr:
|
|
||||||
switch {
|
|
||||||
// Extensible rule, with only DN-matching
|
|
||||||
case currentRune == ':' && strings.HasPrefix(remainingFilter, ":dn:="):
|
|
||||||
packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterExtensibleMatch, nil, FilterMap[FilterExtensibleMatch])
|
|
||||||
extensibleDNAttributes = true
|
|
||||||
state = stateReadingCondition
|
|
||||||
newPos += 5
|
|
||||||
|
|
||||||
// Extensible rule, with DN-matching and a matching OID
|
|
||||||
case currentRune == ':' && strings.HasPrefix(remainingFilter, ":dn:"):
|
|
||||||
packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterExtensibleMatch, nil, FilterMap[FilterExtensibleMatch])
|
|
||||||
extensibleDNAttributes = true
|
|
||||||
state = stateReadingExtensibleMatchingRule
|
|
||||||
newPos += 4
|
|
||||||
|
|
||||||
// Extensible rule, with attr only
|
|
||||||
case currentRune == ':' && strings.HasPrefix(remainingFilter, ":="):
|
|
||||||
packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterExtensibleMatch, nil, FilterMap[FilterExtensibleMatch])
|
|
||||||
state = stateReadingCondition
|
|
||||||
newPos += 2
|
|
||||||
|
|
||||||
// Extensible rule, with no DN attribute matching
|
|
||||||
case currentRune == ':':
|
|
||||||
packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterExtensibleMatch, nil, FilterMap[FilterExtensibleMatch])
|
|
||||||
state = stateReadingExtensibleMatchingRule
|
|
||||||
newPos++
|
|
||||||
|
|
||||||
// Equality condition
|
|
||||||
case currentRune == '=':
|
|
||||||
packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterEqualityMatch, nil, FilterMap[FilterEqualityMatch])
|
|
||||||
state = stateReadingCondition
|
|
||||||
newPos++
|
|
||||||
|
|
||||||
// Greater-than or equal
|
|
||||||
case currentRune == '>' && strings.HasPrefix(remainingFilter, ">="):
|
|
||||||
packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterGreaterOrEqual, nil, FilterMap[FilterGreaterOrEqual])
|
|
||||||
state = stateReadingCondition
|
|
||||||
newPos += 2
|
|
||||||
|
|
||||||
// Less-than or equal
|
|
||||||
case currentRune == '<' && strings.HasPrefix(remainingFilter, "<="):
|
|
||||||
packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterLessOrEqual, nil, FilterMap[FilterLessOrEqual])
|
|
||||||
state = stateReadingCondition
|
|
||||||
newPos += 2
|
|
||||||
|
|
||||||
// Approx
|
|
||||||
case currentRune == '~' && strings.HasPrefix(remainingFilter, "~="):
|
|
||||||
packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterApproxMatch, nil, FilterMap[FilterApproxMatch])
|
|
||||||
state = stateReadingCondition
|
|
||||||
newPos += 2
|
|
||||||
|
|
||||||
// Still reading the attribute name
|
|
||||||
default:
|
|
||||||
attribute += fmt.Sprintf("%c", currentRune)
|
|
||||||
newPos += currentWidth
|
|
||||||
}
|
|
||||||
|
|
||||||
case stateReadingExtensibleMatchingRule:
|
|
||||||
switch {
|
|
||||||
|
|
||||||
// Matching rule OID is done
|
|
||||||
case currentRune == ':' && strings.HasPrefix(remainingFilter, ":="):
|
|
||||||
state = stateReadingCondition
|
|
||||||
newPos += 2
|
|
||||||
|
|
||||||
// Still reading the matching rule oid
|
|
||||||
default:
|
|
||||||
extensibleMatchingRule += fmt.Sprintf("%c", currentRune)
|
|
||||||
newPos += currentWidth
|
|
||||||
}
|
|
||||||
|
|
||||||
case stateReadingCondition:
|
|
||||||
// append to the condition
|
|
||||||
condition += fmt.Sprintf("%c", currentRune)
|
|
||||||
newPos += currentWidth
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if newPos == len(filter) {
|
|
||||||
err = NewError(ErrorFilterCompile, errors.New("ldap: unexpected end of filter"))
|
|
||||||
return packet, newPos, err
|
|
||||||
}
|
|
||||||
if packet == nil {
|
|
||||||
err = NewError(ErrorFilterCompile, errors.New("ldap: error parsing filter"))
|
|
||||||
return packet, newPos, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case packet.Tag == FilterExtensibleMatch:
|
|
||||||
// MatchingRuleAssertion ::= SEQUENCE {
|
|
||||||
// matchingRule [1] MatchingRuleID OPTIONAL,
|
|
||||||
// type [2] AttributeDescription OPTIONAL,
|
|
||||||
// matchValue [3] AssertionValue,
|
|
||||||
// dnAttributes [4] BOOLEAN DEFAULT FALSE
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Include the matching rule oid, if specified
|
|
||||||
if len(extensibleMatchingRule) > 0 {
|
|
||||||
packet.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, MatchingRuleAssertionMatchingRule, extensibleMatchingRule, MatchingRuleAssertionMap[MatchingRuleAssertionMatchingRule]))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Include the attribute, if specified
|
|
||||||
if len(attribute) > 0 {
|
|
||||||
packet.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, MatchingRuleAssertionType, attribute, MatchingRuleAssertionMap[MatchingRuleAssertionType]))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the value (only required child)
|
|
||||||
encodedString, encodeErr := escapedStringToEncodedBytes(condition)
|
|
||||||
if encodeErr != nil {
|
|
||||||
return packet, newPos, encodeErr
|
|
||||||
}
|
|
||||||
packet.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, MatchingRuleAssertionMatchValue, encodedString, MatchingRuleAssertionMap[MatchingRuleAssertionMatchValue]))
|
|
||||||
|
|
||||||
// Defaults to false, so only include in the sequence if true
|
|
||||||
if extensibleDNAttributes {
|
|
||||||
packet.AppendChild(ber.NewBoolean(ber.ClassContext, ber.TypePrimitive, MatchingRuleAssertionDNAttributes, extensibleDNAttributes, MatchingRuleAssertionMap[MatchingRuleAssertionDNAttributes]))
|
|
||||||
}
|
|
||||||
|
|
||||||
case packet.Tag == FilterEqualityMatch && condition == "*":
|
|
||||||
packet = ber.NewString(ber.ClassContext, ber.TypePrimitive, FilterPresent, attribute, FilterMap[FilterPresent])
|
|
||||||
case packet.Tag == FilterEqualityMatch && strings.Contains(condition, "*"):
|
|
||||||
packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute, "Attribute"))
|
|
||||||
packet.Tag = FilterSubstrings
|
|
||||||
packet.Description = FilterMap[uint64(packet.Tag)]
|
|
||||||
seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Substrings")
|
|
||||||
parts := strings.Split(condition, "*")
|
|
||||||
for i, part := range parts {
|
|
||||||
if part == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var tag ber.Tag
|
|
||||||
switch i {
|
|
||||||
case 0:
|
|
||||||
tag = FilterSubstringsInitial
|
|
||||||
case len(parts) - 1:
|
|
||||||
tag = FilterSubstringsFinal
|
|
||||||
default:
|
|
||||||
tag = FilterSubstringsAny
|
|
||||||
}
|
|
||||||
encodedString, encodeErr := escapedStringToEncodedBytes(part)
|
|
||||||
if encodeErr != nil {
|
|
||||||
return packet, newPos, encodeErr
|
|
||||||
}
|
|
||||||
seq.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, tag, encodedString, FilterSubstringsMap[uint64(tag)]))
|
|
||||||
}
|
|
||||||
packet.AppendChild(seq)
|
|
||||||
default:
|
|
||||||
encodedString, encodeErr := escapedStringToEncodedBytes(condition)
|
|
||||||
if encodeErr != nil {
|
|
||||||
return packet, newPos, encodeErr
|
|
||||||
}
|
|
||||||
packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute, "Attribute"))
|
|
||||||
packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, encodedString, "Condition"))
|
|
||||||
}
|
|
||||||
|
|
||||||
newPos += currentWidth
|
|
||||||
return packet, newPos, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert from "ABC\xx\xx\xx" form to literal bytes for transport
|
|
||||||
func escapedStringToEncodedBytes(escapedString string) (string, error) {
|
|
||||||
var buffer bytes.Buffer
|
|
||||||
i := 0
|
|
||||||
for i < len(escapedString) {
|
|
||||||
currentRune, currentWidth := utf8.DecodeRuneInString(escapedString[i:])
|
|
||||||
if currentRune == utf8.RuneError {
|
|
||||||
return "", NewError(ErrorFilterCompile, fmt.Errorf("ldap: error reading rune at position %d", i))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for escaped hex characters and convert them to their literal value for transport.
|
|
||||||
if currentRune == '\\' {
|
|
||||||
// http://tools.ietf.org/search/rfc4515
|
|
||||||
// \ (%x5C) is not a valid character unless it is followed by two HEX characters due to not
|
|
||||||
// being a member of UTF1SUBSET.
|
|
||||||
if i+2 > len(escapedString) {
|
|
||||||
return "", NewError(ErrorFilterCompile, errors.New("ldap: missing characters for escape in filter"))
|
|
||||||
}
|
|
||||||
escByte, decodeErr := hexpac.DecodeString(escapedString[i+1 : i+3])
|
|
||||||
if decodeErr != nil {
|
|
||||||
return "", NewError(ErrorFilterCompile, errors.New("ldap: invalid characters for escape in filter"))
|
|
||||||
}
|
|
||||||
buffer.WriteByte(escByte[0])
|
|
||||||
i += 2 // +1 from end of loop, so 3 total for \xx.
|
|
||||||
} else {
|
|
||||||
buffer.WriteRune(currentRune)
|
|
||||||
}
|
|
||||||
|
|
||||||
i += currentWidth
|
|
||||||
}
|
|
||||||
return buffer.String(), nil
|
|
||||||
}
|
|
|
@ -1,320 +0,0 @@
|
||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package ldap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"gopkg.in/asn1-ber.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// LDAP Application Codes
|
|
||||||
const (
|
|
||||||
ApplicationBindRequest = 0
|
|
||||||
ApplicationBindResponse = 1
|
|
||||||
ApplicationUnbindRequest = 2
|
|
||||||
ApplicationSearchRequest = 3
|
|
||||||
ApplicationSearchResultEntry = 4
|
|
||||||
ApplicationSearchResultDone = 5
|
|
||||||
ApplicationModifyRequest = 6
|
|
||||||
ApplicationModifyResponse = 7
|
|
||||||
ApplicationAddRequest = 8
|
|
||||||
ApplicationAddResponse = 9
|
|
||||||
ApplicationDelRequest = 10
|
|
||||||
ApplicationDelResponse = 11
|
|
||||||
ApplicationModifyDNRequest = 12
|
|
||||||
ApplicationModifyDNResponse = 13
|
|
||||||
ApplicationCompareRequest = 14
|
|
||||||
ApplicationCompareResponse = 15
|
|
||||||
ApplicationAbandonRequest = 16
|
|
||||||
ApplicationSearchResultReference = 19
|
|
||||||
ApplicationExtendedRequest = 23
|
|
||||||
ApplicationExtendedResponse = 24
|
|
||||||
)
|
|
||||||
|
|
||||||
// ApplicationMap contains human readable descriptions of LDAP Application Codes
|
|
||||||
var ApplicationMap = map[uint8]string{
|
|
||||||
ApplicationBindRequest: "Bind Request",
|
|
||||||
ApplicationBindResponse: "Bind Response",
|
|
||||||
ApplicationUnbindRequest: "Unbind Request",
|
|
||||||
ApplicationSearchRequest: "Search Request",
|
|
||||||
ApplicationSearchResultEntry: "Search Result Entry",
|
|
||||||
ApplicationSearchResultDone: "Search Result Done",
|
|
||||||
ApplicationModifyRequest: "Modify Request",
|
|
||||||
ApplicationModifyResponse: "Modify Response",
|
|
||||||
ApplicationAddRequest: "Add Request",
|
|
||||||
ApplicationAddResponse: "Add Response",
|
|
||||||
ApplicationDelRequest: "Del Request",
|
|
||||||
ApplicationDelResponse: "Del Response",
|
|
||||||
ApplicationModifyDNRequest: "Modify DN Request",
|
|
||||||
ApplicationModifyDNResponse: "Modify DN Response",
|
|
||||||
ApplicationCompareRequest: "Compare Request",
|
|
||||||
ApplicationCompareResponse: "Compare Response",
|
|
||||||
ApplicationAbandonRequest: "Abandon Request",
|
|
||||||
ApplicationSearchResultReference: "Search Result Reference",
|
|
||||||
ApplicationExtendedRequest: "Extended Request",
|
|
||||||
ApplicationExtendedResponse: "Extended Response",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ldap Behera Password Policy Draft 10 (https://tools.ietf.org/html/draft-behera-ldap-password-policy-10)
|
|
||||||
const (
|
|
||||||
BeheraPasswordExpired = 0
|
|
||||||
BeheraAccountLocked = 1
|
|
||||||
BeheraChangeAfterReset = 2
|
|
||||||
BeheraPasswordModNotAllowed = 3
|
|
||||||
BeheraMustSupplyOldPassword = 4
|
|
||||||
BeheraInsufficientPasswordQuality = 5
|
|
||||||
BeheraPasswordTooShort = 6
|
|
||||||
BeheraPasswordTooYoung = 7
|
|
||||||
BeheraPasswordInHistory = 8
|
|
||||||
)
|
|
||||||
|
|
||||||
// BeheraPasswordPolicyErrorMap contains human readable descriptions of Behera Password Policy error codes
|
|
||||||
var BeheraPasswordPolicyErrorMap = map[int8]string{
|
|
||||||
BeheraPasswordExpired: "Password expired",
|
|
||||||
BeheraAccountLocked: "Account locked",
|
|
||||||
BeheraChangeAfterReset: "Password must be changed",
|
|
||||||
BeheraPasswordModNotAllowed: "Policy prevents password modification",
|
|
||||||
BeheraMustSupplyOldPassword: "Policy requires old password in order to change password",
|
|
||||||
BeheraInsufficientPasswordQuality: "Password fails quality checks",
|
|
||||||
BeheraPasswordTooShort: "Password is too short for policy",
|
|
||||||
BeheraPasswordTooYoung: "Password has been changed too recently",
|
|
||||||
BeheraPasswordInHistory: "New password is in list of old passwords",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adds descriptions to an LDAP Response packet for debugging
|
|
||||||
func addLDAPDescriptions(packet *ber.Packet) (err error) {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
err = NewError(ErrorDebugging, errors.New("ldap: cannot process packet to add descriptions"))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
packet.Description = "LDAP Response"
|
|
||||||
packet.Children[0].Description = "Message ID"
|
|
||||||
|
|
||||||
application := uint8(packet.Children[1].Tag)
|
|
||||||
packet.Children[1].Description = ApplicationMap[application]
|
|
||||||
|
|
||||||
switch application {
|
|
||||||
case ApplicationBindRequest:
|
|
||||||
addRequestDescriptions(packet)
|
|
||||||
case ApplicationBindResponse:
|
|
||||||
addDefaultLDAPResponseDescriptions(packet)
|
|
||||||
case ApplicationUnbindRequest:
|
|
||||||
addRequestDescriptions(packet)
|
|
||||||
case ApplicationSearchRequest:
|
|
||||||
addRequestDescriptions(packet)
|
|
||||||
case ApplicationSearchResultEntry:
|
|
||||||
packet.Children[1].Children[0].Description = "Object Name"
|
|
||||||
packet.Children[1].Children[1].Description = "Attributes"
|
|
||||||
for _, child := range packet.Children[1].Children[1].Children {
|
|
||||||
child.Description = "Attribute"
|
|
||||||
child.Children[0].Description = "Attribute Name"
|
|
||||||
child.Children[1].Description = "Attribute Values"
|
|
||||||
for _, grandchild := range child.Children[1].Children {
|
|
||||||
grandchild.Description = "Attribute Value"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(packet.Children) == 3 {
|
|
||||||
addControlDescriptions(packet.Children[2])
|
|
||||||
}
|
|
||||||
case ApplicationSearchResultDone:
|
|
||||||
addDefaultLDAPResponseDescriptions(packet)
|
|
||||||
case ApplicationModifyRequest:
|
|
||||||
addRequestDescriptions(packet)
|
|
||||||
case ApplicationModifyResponse:
|
|
||||||
case ApplicationAddRequest:
|
|
||||||
addRequestDescriptions(packet)
|
|
||||||
case ApplicationAddResponse:
|
|
||||||
case ApplicationDelRequest:
|
|
||||||
addRequestDescriptions(packet)
|
|
||||||
case ApplicationDelResponse:
|
|
||||||
case ApplicationModifyDNRequest:
|
|
||||||
addRequestDescriptions(packet)
|
|
||||||
case ApplicationModifyDNResponse:
|
|
||||||
case ApplicationCompareRequest:
|
|
||||||
addRequestDescriptions(packet)
|
|
||||||
case ApplicationCompareResponse:
|
|
||||||
case ApplicationAbandonRequest:
|
|
||||||
addRequestDescriptions(packet)
|
|
||||||
case ApplicationSearchResultReference:
|
|
||||||
case ApplicationExtendedRequest:
|
|
||||||
addRequestDescriptions(packet)
|
|
||||||
case ApplicationExtendedResponse:
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func addControlDescriptions(packet *ber.Packet) {
|
|
||||||
packet.Description = "Controls"
|
|
||||||
for _, child := range packet.Children {
|
|
||||||
var value *ber.Packet
|
|
||||||
controlType := ""
|
|
||||||
child.Description = "Control"
|
|
||||||
switch len(child.Children) {
|
|
||||||
case 0:
|
|
||||||
// at least one child is required for control type
|
|
||||||
continue
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
// just type, no criticality or value
|
|
||||||
controlType = child.Children[0].Value.(string)
|
|
||||||
child.Children[0].Description = "Control Type (" + ControlTypeMap[controlType] + ")"
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
controlType = child.Children[0].Value.(string)
|
|
||||||
child.Children[0].Description = "Control Type (" + ControlTypeMap[controlType] + ")"
|
|
||||||
// Children[1] could be criticality or value (both are optional)
|
|
||||||
// duck-type on whether this is a boolean
|
|
||||||
if _, ok := child.Children[1].Value.(bool); ok {
|
|
||||||
child.Children[1].Description = "Criticality"
|
|
||||||
} else {
|
|
||||||
child.Children[1].Description = "Control Value"
|
|
||||||
value = child.Children[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
case 3:
|
|
||||||
// criticality and value present
|
|
||||||
controlType = child.Children[0].Value.(string)
|
|
||||||
child.Children[0].Description = "Control Type (" + ControlTypeMap[controlType] + ")"
|
|
||||||
child.Children[1].Description = "Criticality"
|
|
||||||
child.Children[2].Description = "Control Value"
|
|
||||||
value = child.Children[2]
|
|
||||||
|
|
||||||
default:
|
|
||||||
// more than 3 children is invalid
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if value == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch controlType {
|
|
||||||
case ControlTypePaging:
|
|
||||||
value.Description += " (Paging)"
|
|
||||||
if value.Value != nil {
|
|
||||||
valueChildren := ber.DecodePacket(value.Data.Bytes())
|
|
||||||
value.Data.Truncate(0)
|
|
||||||
value.Value = nil
|
|
||||||
valueChildren.Children[1].Value = valueChildren.Children[1].Data.Bytes()
|
|
||||||
value.AppendChild(valueChildren)
|
|
||||||
}
|
|
||||||
value.Children[0].Description = "Real Search Control Value"
|
|
||||||
value.Children[0].Children[0].Description = "Paging Size"
|
|
||||||
value.Children[0].Children[1].Description = "Cookie"
|
|
||||||
|
|
||||||
case ControlTypeBeheraPasswordPolicy:
|
|
||||||
value.Description += " (Password Policy - Behera Draft)"
|
|
||||||
if value.Value != nil {
|
|
||||||
valueChildren := ber.DecodePacket(value.Data.Bytes())
|
|
||||||
value.Data.Truncate(0)
|
|
||||||
value.Value = nil
|
|
||||||
value.AppendChild(valueChildren)
|
|
||||||
}
|
|
||||||
sequence := value.Children[0]
|
|
||||||
for _, child := range sequence.Children {
|
|
||||||
if child.Tag == 0 {
|
|
||||||
//Warning
|
|
||||||
warningPacket := child.Children[0]
|
|
||||||
packet := ber.DecodePacket(warningPacket.Data.Bytes())
|
|
||||||
val, ok := packet.Value.(int64)
|
|
||||||
if ok {
|
|
||||||
if warningPacket.Tag == 0 {
|
|
||||||
//timeBeforeExpiration
|
|
||||||
value.Description += " (TimeBeforeExpiration)"
|
|
||||||
warningPacket.Value = val
|
|
||||||
} else if warningPacket.Tag == 1 {
|
|
||||||
//graceAuthNsRemaining
|
|
||||||
value.Description += " (GraceAuthNsRemaining)"
|
|
||||||
warningPacket.Value = val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if child.Tag == 1 {
|
|
||||||
// Error
|
|
||||||
packet := ber.DecodePacket(child.Data.Bytes())
|
|
||||||
val, ok := packet.Value.(int8)
|
|
||||||
if !ok {
|
|
||||||
val = -1
|
|
||||||
}
|
|
||||||
child.Description = "Error"
|
|
||||||
child.Value = val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func addRequestDescriptions(packet *ber.Packet) {
|
|
||||||
packet.Description = "LDAP Request"
|
|
||||||
packet.Children[0].Description = "Message ID"
|
|
||||||
packet.Children[1].Description = ApplicationMap[uint8(packet.Children[1].Tag)]
|
|
||||||
if len(packet.Children) == 3 {
|
|
||||||
addControlDescriptions(packet.Children[2])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func addDefaultLDAPResponseDescriptions(packet *ber.Packet) {
|
|
||||||
resultCode, _ := getLDAPResultCode(packet)
|
|
||||||
packet.Children[1].Children[0].Description = "Result Code (" + LDAPResultCodeMap[resultCode] + ")"
|
|
||||||
packet.Children[1].Children[1].Description = "Matched DN"
|
|
||||||
packet.Children[1].Children[2].Description = "Error Message"
|
|
||||||
if len(packet.Children[1].Children) > 3 {
|
|
||||||
packet.Children[1].Children[3].Description = "Referral"
|
|
||||||
}
|
|
||||||
if len(packet.Children) == 3 {
|
|
||||||
addControlDescriptions(packet.Children[2])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DebugBinaryFile reads and prints packets from the given filename
|
|
||||||
func DebugBinaryFile(fileName string) error {
|
|
||||||
file, err := ioutil.ReadFile(fileName)
|
|
||||||
if err != nil {
|
|
||||||
return NewError(ErrorDebugging, err)
|
|
||||||
}
|
|
||||||
ber.PrintBytes(os.Stdout, file, "")
|
|
||||||
packet := ber.DecodePacket(file)
|
|
||||||
addLDAPDescriptions(packet)
|
|
||||||
ber.PrintPacket(packet)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var hex = "0123456789abcdef"
|
|
||||||
|
|
||||||
func mustEscape(c byte) bool {
|
|
||||||
return c > 0x7f || c == '(' || c == ')' || c == '\\' || c == '*' || c == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// EscapeFilter escapes from the provided LDAP filter string the special
|
|
||||||
// characters in the set `()*\` and those out of the range 0 < c < 0x80,
|
|
||||||
// as defined in RFC4515.
|
|
||||||
func EscapeFilter(filter string) string {
|
|
||||||
escape := 0
|
|
||||||
for i := 0; i < len(filter); i++ {
|
|
||||||
if mustEscape(filter[i]) {
|
|
||||||
escape++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if escape == 0 {
|
|
||||||
return filter
|
|
||||||
}
|
|
||||||
buf := make([]byte, len(filter)+escape*2)
|
|
||||||
for i, j := 0, 0; i < len(filter); i++ {
|
|
||||||
c := filter[i]
|
|
||||||
if mustEscape(c) {
|
|
||||||
buf[j+0] = '\\'
|
|
||||||
buf[j+1] = hex[c>>4]
|
|
||||||
buf[j+2] = hex[c&0xf]
|
|
||||||
j += 3
|
|
||||||
} else {
|
|
||||||
buf[j] = c
|
|
||||||
j++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return string(buf)
|
|
||||||
}
|
|
|
@ -1,170 +0,0 @@
|
||||||
// Copyright 2014 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
//
|
|
||||||
// File contains Modify functionality
|
|
||||||
//
|
|
||||||
// https://tools.ietf.org/html/rfc4511
|
|
||||||
//
|
|
||||||
// ModifyRequest ::= [APPLICATION 6] SEQUENCE {
|
|
||||||
// object LDAPDN,
|
|
||||||
// changes SEQUENCE OF change SEQUENCE {
|
|
||||||
// operation ENUMERATED {
|
|
||||||
// add (0),
|
|
||||||
// delete (1),
|
|
||||||
// replace (2),
|
|
||||||
// ... },
|
|
||||||
// modification PartialAttribute } }
|
|
||||||
//
|
|
||||||
// PartialAttribute ::= SEQUENCE {
|
|
||||||
// type AttributeDescription,
|
|
||||||
// vals SET OF value AttributeValue }
|
|
||||||
//
|
|
||||||
// AttributeDescription ::= LDAPString
|
|
||||||
// -- Constrained to <attributedescription>
|
|
||||||
// -- [RFC4512]
|
|
||||||
//
|
|
||||||
// AttributeValue ::= OCTET STRING
|
|
||||||
//
|
|
||||||
|
|
||||||
package ldap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"gopkg.in/asn1-ber.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Change operation choices
|
|
||||||
const (
|
|
||||||
AddAttribute = 0
|
|
||||||
DeleteAttribute = 1
|
|
||||||
ReplaceAttribute = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
// PartialAttribute for a ModifyRequest as defined in https://tools.ietf.org/html/rfc4511
|
|
||||||
type PartialAttribute struct {
|
|
||||||
// Type is the type of the partial attribute
|
|
||||||
Type string
|
|
||||||
// Vals are the values of the partial attribute
|
|
||||||
Vals []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PartialAttribute) encode() *ber.Packet {
|
|
||||||
seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "PartialAttribute")
|
|
||||||
seq.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, p.Type, "Type"))
|
|
||||||
set := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSet, nil, "AttributeValue")
|
|
||||||
for _, value := range p.Vals {
|
|
||||||
set.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, value, "Vals"))
|
|
||||||
}
|
|
||||||
seq.AppendChild(set)
|
|
||||||
return seq
|
|
||||||
}
|
|
||||||
|
|
||||||
// ModifyRequest as defined in https://tools.ietf.org/html/rfc4511
|
|
||||||
type ModifyRequest struct {
|
|
||||||
// DN is the distinguishedName of the directory entry to modify
|
|
||||||
DN string
|
|
||||||
// AddAttributes contain the attributes to add
|
|
||||||
AddAttributes []PartialAttribute
|
|
||||||
// DeleteAttributes contain the attributes to delete
|
|
||||||
DeleteAttributes []PartialAttribute
|
|
||||||
// ReplaceAttributes contain the attributes to replace
|
|
||||||
ReplaceAttributes []PartialAttribute
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add inserts the given attribute to the list of attributes to add
|
|
||||||
func (m *ModifyRequest) Add(attrType string, attrVals []string) {
|
|
||||||
m.AddAttributes = append(m.AddAttributes, PartialAttribute{Type: attrType, Vals: attrVals})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete inserts the given attribute to the list of attributes to delete
|
|
||||||
func (m *ModifyRequest) Delete(attrType string, attrVals []string) {
|
|
||||||
m.DeleteAttributes = append(m.DeleteAttributes, PartialAttribute{Type: attrType, Vals: attrVals})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace inserts the given attribute to the list of attributes to replace
|
|
||||||
func (m *ModifyRequest) Replace(attrType string, attrVals []string) {
|
|
||||||
m.ReplaceAttributes = append(m.ReplaceAttributes, PartialAttribute{Type: attrType, Vals: attrVals})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m ModifyRequest) encode() *ber.Packet {
|
|
||||||
request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationModifyRequest, nil, "Modify Request")
|
|
||||||
request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, m.DN, "DN"))
|
|
||||||
changes := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Changes")
|
|
||||||
for _, attribute := range m.AddAttributes {
|
|
||||||
change := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Change")
|
|
||||||
change.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(AddAttribute), "Operation"))
|
|
||||||
change.AppendChild(attribute.encode())
|
|
||||||
changes.AppendChild(change)
|
|
||||||
}
|
|
||||||
for _, attribute := range m.DeleteAttributes {
|
|
||||||
change := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Change")
|
|
||||||
change.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(DeleteAttribute), "Operation"))
|
|
||||||
change.AppendChild(attribute.encode())
|
|
||||||
changes.AppendChild(change)
|
|
||||||
}
|
|
||||||
for _, attribute := range m.ReplaceAttributes {
|
|
||||||
change := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Change")
|
|
||||||
change.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(ReplaceAttribute), "Operation"))
|
|
||||||
change.AppendChild(attribute.encode())
|
|
||||||
changes.AppendChild(change)
|
|
||||||
}
|
|
||||||
request.AppendChild(changes)
|
|
||||||
return request
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewModifyRequest creates a modify request for the given DN
|
|
||||||
func NewModifyRequest(
|
|
||||||
dn string,
|
|
||||||
) *ModifyRequest {
|
|
||||||
return &ModifyRequest{
|
|
||||||
DN: dn,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Modify performs the ModifyRequest
|
|
||||||
func (l *Conn) Modify(modifyRequest *ModifyRequest) error {
|
|
||||||
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
|
|
||||||
packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID"))
|
|
||||||
packet.AppendChild(modifyRequest.encode())
|
|
||||||
|
|
||||||
l.Debug.PrintPacket(packet)
|
|
||||||
|
|
||||||
msgCtx, err := l.sendMessage(packet)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer l.finishMessage(msgCtx)
|
|
||||||
|
|
||||||
l.Debug.Printf("%d: waiting for response", msgCtx.id)
|
|
||||||
packetResponse, ok := <-msgCtx.responses
|
|
||||||
if !ok {
|
|
||||||
return NewError(ErrorNetwork, errors.New("ldap: response channel closed"))
|
|
||||||
}
|
|
||||||
packet, err = packetResponse.ReadPacket()
|
|
||||||
l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if l.Debug {
|
|
||||||
if err := addLDAPDescriptions(packet); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ber.PrintPacket(packet)
|
|
||||||
}
|
|
||||||
|
|
||||||
if packet.Children[1].Tag == ApplicationModifyResponse {
|
|
||||||
resultCode, resultDescription := getLDAPResultCode(packet)
|
|
||||||
if resultCode != 0 {
|
|
||||||
return NewError(resultCode, errors.New(resultDescription))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Printf("Unexpected Response: %d", packet.Children[1].Tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
l.Debug.Printf("%d: returning", msgCtx.id)
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,148 +0,0 @@
|
||||||
// This file contains the password modify extended operation as specified in rfc 3062
|
|
||||||
//
|
|
||||||
// https://tools.ietf.org/html/rfc3062
|
|
||||||
//
|
|
||||||
|
|
||||||
package ldap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"gopkg.in/asn1-ber.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
passwordModifyOID = "1.3.6.1.4.1.4203.1.11.1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PasswordModifyRequest implements the Password Modify Extended Operation as defined in https://www.ietf.org/rfc/rfc3062.txt
|
|
||||||
type PasswordModifyRequest struct {
|
|
||||||
// UserIdentity is an optional string representation of the user associated with the request.
|
|
||||||
// This string may or may not be an LDAPDN [RFC2253].
|
|
||||||
// If no UserIdentity field is present, the request acts up upon the password of the user currently associated with the LDAP session
|
|
||||||
UserIdentity string
|
|
||||||
// OldPassword, if present, contains the user's current password
|
|
||||||
OldPassword string
|
|
||||||
// NewPassword, if present, contains the desired password for this user
|
|
||||||
NewPassword string
|
|
||||||
}
|
|
||||||
|
|
||||||
// PasswordModifyResult holds the server response to a PasswordModifyRequest
|
|
||||||
type PasswordModifyResult struct {
|
|
||||||
// GeneratedPassword holds a password generated by the server, if present
|
|
||||||
GeneratedPassword string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *PasswordModifyRequest) encode() (*ber.Packet, error) {
|
|
||||||
request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationExtendedRequest, nil, "Password Modify Extended Operation")
|
|
||||||
request.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, passwordModifyOID, "Extended Request Name: Password Modify OID"))
|
|
||||||
extendedRequestValue := ber.Encode(ber.ClassContext, ber.TypePrimitive, 1, nil, "Extended Request Value: Password Modify Request")
|
|
||||||
passwordModifyRequestValue := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Password Modify Request")
|
|
||||||
if r.UserIdentity != "" {
|
|
||||||
passwordModifyRequestValue.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, r.UserIdentity, "User Identity"))
|
|
||||||
}
|
|
||||||
if r.OldPassword != "" {
|
|
||||||
passwordModifyRequestValue.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 1, r.OldPassword, "Old Password"))
|
|
||||||
}
|
|
||||||
if r.NewPassword != "" {
|
|
||||||
passwordModifyRequestValue.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 2, r.NewPassword, "New Password"))
|
|
||||||
}
|
|
||||||
|
|
||||||
extendedRequestValue.AppendChild(passwordModifyRequestValue)
|
|
||||||
request.AppendChild(extendedRequestValue)
|
|
||||||
|
|
||||||
return request, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPasswordModifyRequest creates a new PasswordModifyRequest
|
|
||||||
//
|
|
||||||
// According to the RFC 3602:
|
|
||||||
// userIdentity is a string representing the user associated with the request.
|
|
||||||
// This string may or may not be an LDAPDN (RFC 2253).
|
|
||||||
// If userIdentity is empty then the operation will act on the user associated
|
|
||||||
// with the session.
|
|
||||||
//
|
|
||||||
// oldPassword is the current user's password, it can be empty or it can be
|
|
||||||
// needed depending on the session user access rights (usually an administrator
|
|
||||||
// can change a user's password without knowing the current one) and the
|
|
||||||
// password policy (see pwdSafeModify password policy's attribute)
|
|
||||||
//
|
|
||||||
// newPassword is the desired user's password. If empty the server can return
|
|
||||||
// an error or generate a new password that will be available in the
|
|
||||||
// PasswordModifyResult.GeneratedPassword
|
|
||||||
//
|
|
||||||
func NewPasswordModifyRequest(userIdentity string, oldPassword string, newPassword string) *PasswordModifyRequest {
|
|
||||||
return &PasswordModifyRequest{
|
|
||||||
UserIdentity: userIdentity,
|
|
||||||
OldPassword: oldPassword,
|
|
||||||
NewPassword: newPassword,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PasswordModify performs the modification request
|
|
||||||
func (l *Conn) PasswordModify(passwordModifyRequest *PasswordModifyRequest) (*PasswordModifyResult, error) {
|
|
||||||
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
|
|
||||||
packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID"))
|
|
||||||
|
|
||||||
encodedPasswordModifyRequest, err := passwordModifyRequest.encode()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
packet.AppendChild(encodedPasswordModifyRequest)
|
|
||||||
|
|
||||||
l.Debug.PrintPacket(packet)
|
|
||||||
|
|
||||||
msgCtx, err := l.sendMessage(packet)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer l.finishMessage(msgCtx)
|
|
||||||
|
|
||||||
result := &PasswordModifyResult{}
|
|
||||||
|
|
||||||
l.Debug.Printf("%d: waiting for response", msgCtx.id)
|
|
||||||
packetResponse, ok := <-msgCtx.responses
|
|
||||||
if !ok {
|
|
||||||
return nil, NewError(ErrorNetwork, errors.New("ldap: response channel closed"))
|
|
||||||
}
|
|
||||||
packet, err = packetResponse.ReadPacket()
|
|
||||||
l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if packet == nil {
|
|
||||||
return nil, NewError(ErrorNetwork, errors.New("ldap: could not retrieve message"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if l.Debug {
|
|
||||||
if err := addLDAPDescriptions(packet); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ber.PrintPacket(packet)
|
|
||||||
}
|
|
||||||
|
|
||||||
if packet.Children[1].Tag == ApplicationExtendedResponse {
|
|
||||||
resultCode, resultDescription := getLDAPResultCode(packet)
|
|
||||||
if resultCode != 0 {
|
|
||||||
return nil, NewError(resultCode, errors.New(resultDescription))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil, NewError(ErrorUnexpectedResponse, fmt.Errorf("Unexpected Response: %d", packet.Children[1].Tag))
|
|
||||||
}
|
|
||||||
|
|
||||||
extendedResponse := packet.Children[1]
|
|
||||||
for _, child := range extendedResponse.Children {
|
|
||||||
if child.Tag == 11 {
|
|
||||||
passwordModifyResponseValue := ber.DecodePacket(child.Data.Bytes())
|
|
||||||
if len(passwordModifyResponseValue.Children) == 1 {
|
|
||||||
if passwordModifyResponseValue.Children[0].Tag == 0 {
|
|
||||||
result.GeneratedPassword = ber.DecodeString(passwordModifyResponseValue.Children[0].Data.Bytes())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
|
@ -1,450 +0,0 @@
|
||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
//
|
|
||||||
// File contains Search functionality
|
|
||||||
//
|
|
||||||
// https://tools.ietf.org/html/rfc4511
|
|
||||||
//
|
|
||||||
// SearchRequest ::= [APPLICATION 3] SEQUENCE {
|
|
||||||
// baseObject LDAPDN,
|
|
||||||
// scope ENUMERATED {
|
|
||||||
// baseObject (0),
|
|
||||||
// singleLevel (1),
|
|
||||||
// wholeSubtree (2),
|
|
||||||
// ... },
|
|
||||||
// derefAliases ENUMERATED {
|
|
||||||
// neverDerefAliases (0),
|
|
||||||
// derefInSearching (1),
|
|
||||||
// derefFindingBaseObj (2),
|
|
||||||
// derefAlways (3) },
|
|
||||||
// sizeLimit INTEGER (0 .. maxInt),
|
|
||||||
// timeLimit INTEGER (0 .. maxInt),
|
|
||||||
// typesOnly BOOLEAN,
|
|
||||||
// filter Filter,
|
|
||||||
// attributes AttributeSelection }
|
|
||||||
//
|
|
||||||
// AttributeSelection ::= SEQUENCE OF selector LDAPString
|
|
||||||
// -- The LDAPString is constrained to
|
|
||||||
// -- <attributeSelector> in Section 4.5.1.8
|
|
||||||
//
|
|
||||||
// Filter ::= CHOICE {
|
|
||||||
// and [0] SET SIZE (1..MAX) OF filter Filter,
|
|
||||||
// or [1] SET SIZE (1..MAX) OF filter Filter,
|
|
||||||
// not [2] Filter,
|
|
||||||
// equalityMatch [3] AttributeValueAssertion,
|
|
||||||
// substrings [4] SubstringFilter,
|
|
||||||
// greaterOrEqual [5] AttributeValueAssertion,
|
|
||||||
// lessOrEqual [6] AttributeValueAssertion,
|
|
||||||
// present [7] AttributeDescription,
|
|
||||||
// approxMatch [8] AttributeValueAssertion,
|
|
||||||
// extensibleMatch [9] MatchingRuleAssertion,
|
|
||||||
// ... }
|
|
||||||
//
|
|
||||||
// SubstringFilter ::= SEQUENCE {
|
|
||||||
// type AttributeDescription,
|
|
||||||
// substrings SEQUENCE SIZE (1..MAX) OF substring CHOICE {
|
|
||||||
// initial [0] AssertionValue, -- can occur at most once
|
|
||||||
// any [1] AssertionValue,
|
|
||||||
// final [2] AssertionValue } -- can occur at most once
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// MatchingRuleAssertion ::= SEQUENCE {
|
|
||||||
// matchingRule [1] MatchingRuleId OPTIONAL,
|
|
||||||
// type [2] AttributeDescription OPTIONAL,
|
|
||||||
// matchValue [3] AssertionValue,
|
|
||||||
// dnAttributes [4] BOOLEAN DEFAULT FALSE }
|
|
||||||
//
|
|
||||||
//
|
|
||||||
|
|
||||||
package ldap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"gopkg.in/asn1-ber.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// scope choices
|
|
||||||
const (
|
|
||||||
ScopeBaseObject = 0
|
|
||||||
ScopeSingleLevel = 1
|
|
||||||
ScopeWholeSubtree = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
// ScopeMap contains human readable descriptions of scope choices
|
|
||||||
var ScopeMap = map[int]string{
|
|
||||||
ScopeBaseObject: "Base Object",
|
|
||||||
ScopeSingleLevel: "Single Level",
|
|
||||||
ScopeWholeSubtree: "Whole Subtree",
|
|
||||||
}
|
|
||||||
|
|
||||||
// derefAliases
|
|
||||||
const (
|
|
||||||
NeverDerefAliases = 0
|
|
||||||
DerefInSearching = 1
|
|
||||||
DerefFindingBaseObj = 2
|
|
||||||
DerefAlways = 3
|
|
||||||
)
|
|
||||||
|
|
||||||
// DerefMap contains human readable descriptions of derefAliases choices
|
|
||||||
var DerefMap = map[int]string{
|
|
||||||
NeverDerefAliases: "NeverDerefAliases",
|
|
||||||
DerefInSearching: "DerefInSearching",
|
|
||||||
DerefFindingBaseObj: "DerefFindingBaseObj",
|
|
||||||
DerefAlways: "DerefAlways",
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewEntry returns an Entry object with the specified distinguished name and attribute key-value pairs.
|
|
||||||
// The map of attributes is accessed in alphabetical order of the keys in order to ensure that, for the
|
|
||||||
// same input map of attributes, the output entry will contain the same order of attributes
|
|
||||||
func NewEntry(dn string, attributes map[string][]string) *Entry {
|
|
||||||
var attributeNames []string
|
|
||||||
for attributeName := range attributes {
|
|
||||||
attributeNames = append(attributeNames, attributeName)
|
|
||||||
}
|
|
||||||
sort.Strings(attributeNames)
|
|
||||||
|
|
||||||
var encodedAttributes []*EntryAttribute
|
|
||||||
for _, attributeName := range attributeNames {
|
|
||||||
encodedAttributes = append(encodedAttributes, NewEntryAttribute(attributeName, attributes[attributeName]))
|
|
||||||
}
|
|
||||||
return &Entry{
|
|
||||||
DN: dn,
|
|
||||||
Attributes: encodedAttributes,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Entry represents a single search result entry
|
|
||||||
type Entry struct {
|
|
||||||
// DN is the distinguished name of the entry
|
|
||||||
DN string
|
|
||||||
// Attributes are the returned attributes for the entry
|
|
||||||
Attributes []*EntryAttribute
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAttributeValues returns the values for the named attribute, or an empty list
|
|
||||||
func (e *Entry) GetAttributeValues(attribute string) []string {
|
|
||||||
for _, attr := range e.Attributes {
|
|
||||||
if attr.Name == attribute {
|
|
||||||
return attr.Values
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return []string{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRawAttributeValues returns the byte values for the named attribute, or an empty list
|
|
||||||
func (e *Entry) GetRawAttributeValues(attribute string) [][]byte {
|
|
||||||
for _, attr := range e.Attributes {
|
|
||||||
if attr.Name == attribute {
|
|
||||||
return attr.ByteValues
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [][]byte{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAttributeValue returns the first value for the named attribute, or ""
|
|
||||||
func (e *Entry) GetAttributeValue(attribute string) string {
|
|
||||||
values := e.GetAttributeValues(attribute)
|
|
||||||
if len(values) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return values[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRawAttributeValue returns the first value for the named attribute, or an empty slice
|
|
||||||
func (e *Entry) GetRawAttributeValue(attribute string) []byte {
|
|
||||||
values := e.GetRawAttributeValues(attribute)
|
|
||||||
if len(values) == 0 {
|
|
||||||
return []byte{}
|
|
||||||
}
|
|
||||||
return values[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print outputs a human-readable description
|
|
||||||
func (e *Entry) Print() {
|
|
||||||
fmt.Printf("DN: %s\n", e.DN)
|
|
||||||
for _, attr := range e.Attributes {
|
|
||||||
attr.Print()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrettyPrint outputs a human-readable description indenting
|
|
||||||
func (e *Entry) PrettyPrint(indent int) {
|
|
||||||
fmt.Printf("%sDN: %s\n", strings.Repeat(" ", indent), e.DN)
|
|
||||||
for _, attr := range e.Attributes {
|
|
||||||
attr.PrettyPrint(indent + 2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewEntryAttribute returns a new EntryAttribute with the desired key-value pair
|
|
||||||
func NewEntryAttribute(name string, values []string) *EntryAttribute {
|
|
||||||
var bytes [][]byte
|
|
||||||
for _, value := range values {
|
|
||||||
bytes = append(bytes, []byte(value))
|
|
||||||
}
|
|
||||||
return &EntryAttribute{
|
|
||||||
Name: name,
|
|
||||||
Values: values,
|
|
||||||
ByteValues: bytes,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// EntryAttribute holds a single attribute
|
|
||||||
type EntryAttribute struct {
|
|
||||||
// Name is the name of the attribute
|
|
||||||
Name string
|
|
||||||
// Values contain the string values of the attribute
|
|
||||||
Values []string
|
|
||||||
// ByteValues contain the raw values of the attribute
|
|
||||||
ByteValues [][]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print outputs a human-readable description
|
|
||||||
func (e *EntryAttribute) Print() {
|
|
||||||
fmt.Printf("%s: %s\n", e.Name, e.Values)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrettyPrint outputs a human-readable description with indenting
|
|
||||||
func (e *EntryAttribute) PrettyPrint(indent int) {
|
|
||||||
fmt.Printf("%s%s: %s\n", strings.Repeat(" ", indent), e.Name, e.Values)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SearchResult holds the server's response to a search request
|
|
||||||
type SearchResult struct {
|
|
||||||
// Entries are the returned entries
|
|
||||||
Entries []*Entry
|
|
||||||
// Referrals are the returned referrals
|
|
||||||
Referrals []string
|
|
||||||
// Controls are the returned controls
|
|
||||||
Controls []Control
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print outputs a human-readable description
|
|
||||||
func (s *SearchResult) Print() {
|
|
||||||
for _, entry := range s.Entries {
|
|
||||||
entry.Print()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrettyPrint outputs a human-readable description with indenting
|
|
||||||
func (s *SearchResult) PrettyPrint(indent int) {
|
|
||||||
for _, entry := range s.Entries {
|
|
||||||
entry.PrettyPrint(indent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SearchRequest represents a search request to send to the server
|
|
||||||
type SearchRequest struct {
|
|
||||||
BaseDN string
|
|
||||||
Scope int
|
|
||||||
DerefAliases int
|
|
||||||
SizeLimit int
|
|
||||||
TimeLimit int
|
|
||||||
TypesOnly bool
|
|
||||||
Filter string
|
|
||||||
Attributes []string
|
|
||||||
Controls []Control
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SearchRequest) encode() (*ber.Packet, error) {
|
|
||||||
request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationSearchRequest, nil, "Search Request")
|
|
||||||
request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, s.BaseDN, "Base DN"))
|
|
||||||
request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(s.Scope), "Scope"))
|
|
||||||
request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(s.DerefAliases), "Deref Aliases"))
|
|
||||||
request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, uint64(s.SizeLimit), "Size Limit"))
|
|
||||||
request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, uint64(s.TimeLimit), "Time Limit"))
|
|
||||||
request.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, s.TypesOnly, "Types Only"))
|
|
||||||
// compile and encode filter
|
|
||||||
filterPacket, err := CompileFilter(s.Filter)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
request.AppendChild(filterPacket)
|
|
||||||
// encode attributes
|
|
||||||
attributesPacket := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Attributes")
|
|
||||||
for _, attribute := range s.Attributes {
|
|
||||||
attributesPacket.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute, "Attribute"))
|
|
||||||
}
|
|
||||||
request.AppendChild(attributesPacket)
|
|
||||||
return request, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSearchRequest creates a new search request
|
|
||||||
func NewSearchRequest(
|
|
||||||
BaseDN string,
|
|
||||||
Scope, DerefAliases, SizeLimit, TimeLimit int,
|
|
||||||
TypesOnly bool,
|
|
||||||
Filter string,
|
|
||||||
Attributes []string,
|
|
||||||
Controls []Control,
|
|
||||||
) *SearchRequest {
|
|
||||||
return &SearchRequest{
|
|
||||||
BaseDN: BaseDN,
|
|
||||||
Scope: Scope,
|
|
||||||
DerefAliases: DerefAliases,
|
|
||||||
SizeLimit: SizeLimit,
|
|
||||||
TimeLimit: TimeLimit,
|
|
||||||
TypesOnly: TypesOnly,
|
|
||||||
Filter: Filter,
|
|
||||||
Attributes: Attributes,
|
|
||||||
Controls: Controls,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SearchWithPaging accepts a search request and desired page size in order to execute LDAP queries to fulfill the
|
|
||||||
// search request. All paged LDAP query responses will be buffered and the final result will be returned atomically.
|
|
||||||
// The following four cases are possible given the arguments:
|
|
||||||
// - given SearchRequest missing a control of type ControlTypePaging: we will add one with the desired paging size
|
|
||||||
// - given SearchRequest contains a control of type ControlTypePaging that isn't actually a ControlPaging: fail without issuing any queries
|
|
||||||
// - given SearchRequest contains a control of type ControlTypePaging with pagingSize equal to the size requested: no change to the search request
|
|
||||||
// - given SearchRequest contains a control of type ControlTypePaging with pagingSize not equal to the size requested: fail without issuing any queries
|
|
||||||
// A requested pagingSize of 0 is interpreted as no limit by LDAP servers.
|
|
||||||
func (l *Conn) SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32) (*SearchResult, error) {
|
|
||||||
var pagingControl *ControlPaging
|
|
||||||
|
|
||||||
control := FindControl(searchRequest.Controls, ControlTypePaging)
|
|
||||||
if control == nil {
|
|
||||||
pagingControl = NewControlPaging(pagingSize)
|
|
||||||
searchRequest.Controls = append(searchRequest.Controls, pagingControl)
|
|
||||||
} else {
|
|
||||||
castControl, ok := control.(*ControlPaging)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("Expected paging control to be of type *ControlPaging, got %v", control)
|
|
||||||
}
|
|
||||||
if castControl.PagingSize != pagingSize {
|
|
||||||
return nil, fmt.Errorf("Paging size given in search request (%d) conflicts with size given in search call (%d)", castControl.PagingSize, pagingSize)
|
|
||||||
}
|
|
||||||
pagingControl = castControl
|
|
||||||
}
|
|
||||||
|
|
||||||
searchResult := new(SearchResult)
|
|
||||||
for {
|
|
||||||
result, err := l.Search(searchRequest)
|
|
||||||
l.Debug.Printf("Looking for Paging Control...")
|
|
||||||
if err != nil {
|
|
||||||
return searchResult, err
|
|
||||||
}
|
|
||||||
if result == nil {
|
|
||||||
return searchResult, NewError(ErrorNetwork, errors.New("ldap: packet not received"))
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, entry := range result.Entries {
|
|
||||||
searchResult.Entries = append(searchResult.Entries, entry)
|
|
||||||
}
|
|
||||||
for _, referral := range result.Referrals {
|
|
||||||
searchResult.Referrals = append(searchResult.Referrals, referral)
|
|
||||||
}
|
|
||||||
for _, control := range result.Controls {
|
|
||||||
searchResult.Controls = append(searchResult.Controls, control)
|
|
||||||
}
|
|
||||||
|
|
||||||
l.Debug.Printf("Looking for Paging Control...")
|
|
||||||
pagingResult := FindControl(result.Controls, ControlTypePaging)
|
|
||||||
if pagingResult == nil {
|
|
||||||
pagingControl = nil
|
|
||||||
l.Debug.Printf("Could not find paging control. Breaking...")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
cookie := pagingResult.(*ControlPaging).Cookie
|
|
||||||
if len(cookie) == 0 {
|
|
||||||
pagingControl = nil
|
|
||||||
l.Debug.Printf("Could not find cookie. Breaking...")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
pagingControl.SetCookie(cookie)
|
|
||||||
}
|
|
||||||
|
|
||||||
if pagingControl != nil {
|
|
||||||
l.Debug.Printf("Abandoning Paging...")
|
|
||||||
pagingControl.PagingSize = 0
|
|
||||||
l.Search(searchRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
return searchResult, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search performs the given search request
|
|
||||||
func (l *Conn) Search(searchRequest *SearchRequest) (*SearchResult, error) {
|
|
||||||
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
|
|
||||||
packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID"))
|
|
||||||
// encode search request
|
|
||||||
encodedSearchRequest, err := searchRequest.encode()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
packet.AppendChild(encodedSearchRequest)
|
|
||||||
// encode search controls
|
|
||||||
if searchRequest.Controls != nil {
|
|
||||||
packet.AppendChild(encodeControls(searchRequest.Controls))
|
|
||||||
}
|
|
||||||
|
|
||||||
l.Debug.PrintPacket(packet)
|
|
||||||
|
|
||||||
msgCtx, err := l.sendMessage(packet)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer l.finishMessage(msgCtx)
|
|
||||||
|
|
||||||
result := &SearchResult{
|
|
||||||
Entries: make([]*Entry, 0),
|
|
||||||
Referrals: make([]string, 0),
|
|
||||||
Controls: make([]Control, 0)}
|
|
||||||
|
|
||||||
foundSearchResultDone := false
|
|
||||||
for !foundSearchResultDone {
|
|
||||||
l.Debug.Printf("%d: waiting for response", msgCtx.id)
|
|
||||||
packetResponse, ok := <-msgCtx.responses
|
|
||||||
if !ok {
|
|
||||||
return nil, NewError(ErrorNetwork, errors.New("ldap: response channel closed"))
|
|
||||||
}
|
|
||||||
packet, err = packetResponse.ReadPacket()
|
|
||||||
l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if l.Debug {
|
|
||||||
if err := addLDAPDescriptions(packet); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ber.PrintPacket(packet)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch packet.Children[1].Tag {
|
|
||||||
case 4:
|
|
||||||
entry := new(Entry)
|
|
||||||
entry.DN = packet.Children[1].Children[0].Value.(string)
|
|
||||||
for _, child := range packet.Children[1].Children[1].Children {
|
|
||||||
attr := new(EntryAttribute)
|
|
||||||
attr.Name = child.Children[0].Value.(string)
|
|
||||||
for _, value := range child.Children[1].Children {
|
|
||||||
attr.Values = append(attr.Values, value.Value.(string))
|
|
||||||
attr.ByteValues = append(attr.ByteValues, value.ByteValue)
|
|
||||||
}
|
|
||||||
entry.Attributes = append(entry.Attributes, attr)
|
|
||||||
}
|
|
||||||
result.Entries = append(result.Entries, entry)
|
|
||||||
case 5:
|
|
||||||
resultCode, resultDescription := getLDAPResultCode(packet)
|
|
||||||
if resultCode != 0 {
|
|
||||||
return result, NewError(resultCode, errors.New(resultDescription))
|
|
||||||
}
|
|
||||||
if len(packet.Children) == 3 {
|
|
||||||
for _, child := range packet.Children[2].Children {
|
|
||||||
result.Controls = append(result.Controls, DecodeControl(child))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
foundSearchResultDone = true
|
|
||||||
case 19:
|
|
||||||
result.Referrals = append(result.Referrals, packet.Children[1].Children[0].Value.(string))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
l.Debug.Printf("%d: returning", msgCtx.id)
|
|
||||||
return result, nil
|
|
||||||
}
|
|
218
vpnserver.go
218
vpnserver.go
|
@ -3,50 +3,33 @@ package main
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
hibp "github.com/mattevans/pwned-passwords"
|
|
||||||
"github.com/pyke369/golang-support/rcache"
|
"github.com/pyke369/golang-support/rcache"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Server represents the server
|
// Server represents the server
|
||||||
type OpenVpnMgt struct {
|
type OpenVpnMgt struct {
|
||||||
port string
|
port string
|
||||||
buf map[string]*bufio.ReadWriter
|
buf map[string]*bufio.ReadWriter
|
||||||
m sync.RWMutex
|
m sync.RWMutex
|
||||||
ret chan []string
|
ret chan []string
|
||||||
ldap map[string]ldapConfig
|
syslog bool
|
||||||
clients map[string]map[int]*vpnSession
|
debug bool
|
||||||
authCa string
|
hold bool
|
||||||
vpnlogUrl string
|
|
||||||
mailRelay string
|
|
||||||
MailFrom string
|
|
||||||
CcPwnPassword string
|
|
||||||
pwnTemplate string
|
|
||||||
newAsTemplate string
|
|
||||||
cacheDir string
|
|
||||||
syslog bool
|
|
||||||
otpMasterSecrets []string
|
|
||||||
hibpClient *hibp.Client
|
|
||||||
debug bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewServer returns a pointer to a new server
|
// NewServer returns a pointer to a new server
|
||||||
func NewVPNServer(port string) *OpenVpnMgt {
|
func NewVPNServer(port string) *OpenVpnMgt {
|
||||||
return &OpenVpnMgt{
|
return &OpenVpnMgt{
|
||||||
port: port,
|
port: port,
|
||||||
ret: make(chan []string),
|
ret: make(chan []string),
|
||||||
ldap: make(map[string]ldapConfig),
|
buf: make(map[string]*bufio.ReadWriter),
|
||||||
buf: make(map[string]*bufio.ReadWriter),
|
|
||||||
clients: make(map[string]map[int]*vpnSession),
|
|
||||||
hibpClient: hibp.NewClient(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,16 +60,6 @@ func (s *OpenVpnMgt) Run() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *OpenVpnMgt) CheckPwn(c *vpnSession) error {
|
|
||||||
c.LogPrintln("checking pwn password")
|
|
||||||
pwned, err := s.hibpClient.Pwned.Compromised(c.password)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.PwnedPasswd = pwned
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// send a command to the server. Set the channel to receive the response
|
// send a command to the server. Set the channel to receive the response
|
||||||
func (s *OpenVpnMgt) sendCommand(msg []string, remote string) (error, []string) {
|
func (s *OpenVpnMgt) sendCommand(msg []string, remote string) (error, []string) {
|
||||||
if len(s.buf) == 0 {
|
if len(s.buf) == 0 {
|
||||||
|
@ -117,23 +90,6 @@ func (s *OpenVpnMgt) sendCommand(msg []string, remote string) (error, []string)
|
||||||
return nil, ret
|
return nil, ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// send the list of all connected clients
|
|
||||||
func (s *OpenVpnMgt) Stats() map[string]map[int]*vpnSession {
|
|
||||||
return s.clients
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *OpenVpnMgt) Kill(session string, id int, killer string) error {
|
|
||||||
if _, ok := s.clients[session]; !ok {
|
|
||||||
return errors.New("unknown session")
|
|
||||||
}
|
|
||||||
if _, ok := s.clients[session][id]; !ok {
|
|
||||||
return errors.New("unknown session id")
|
|
||||||
}
|
|
||||||
log.Printf("user %s's session killed from the web by %s\n", s.clients[session][id].Login, killer)
|
|
||||||
err, _ := s.sendCommand([]string{fmt.Sprintf("client-kill %d", id)}, session)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// send the help command on all vpn servers. Kind of useless
|
// send the help command on all vpn servers. Kind of useless
|
||||||
func (s *OpenVpnMgt) Help() (error, map[string]map[string]string) {
|
func (s *OpenVpnMgt) Help() (error, map[string]map[string]string) {
|
||||||
ret := make(map[string]map[string]string)
|
ret := make(map[string]map[string]string)
|
||||||
|
@ -169,133 +125,15 @@ func (s *OpenVpnMgt) Version() (error, map[string][]string) {
|
||||||
return nil, ret
|
return nil, ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// called after a client is confirmed connected and authenticated
|
|
||||||
func (s *OpenVpnMgt) ClientValidated(line, remote string) {
|
|
||||||
err, c := s.getClient(line, remote)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err, line)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Status = "success"
|
|
||||||
infos := <-s.ret
|
|
||||||
|
|
||||||
if err := c.ParseEnv(s, &infos); err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
s.Log(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// called after a client is disconnected, including for auth issues
|
|
||||||
func (s *OpenVpnMgt) ClientDisconnect(line, remote string) {
|
|
||||||
err, c := s.getClient(line, remote)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
<-s.ret
|
|
||||||
|
|
||||||
// if the disconnect is due to an auth failure, don't change the status
|
|
||||||
if c.Status == "success" {
|
|
||||||
c.Operation = "log out"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't log the initial auth failure due to absence of OTP code
|
|
||||||
// And don't log the auth failure during re auth
|
|
||||||
if c.Operation != "re auth" && c.Status != "Need OTP Code" {
|
|
||||||
s.Log(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer delete(s.clients[remote], c.cID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// called at the initial connexion
|
|
||||||
func (s *OpenVpnMgt) ClientConnect(line, remote string) {
|
|
||||||
c := NewVPNSession()
|
|
||||||
c.vpnserver = remote
|
|
||||||
c.ParseSessionId(line)
|
|
||||||
s.clients[remote][c.cID] = c
|
|
||||||
infos := <-s.ret
|
|
||||||
if err := c.ParseEnv(s, &infos); err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Auth(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *OpenVpnMgt) ClientReAuth(line, remote string) {
|
|
||||||
err, c := s.getClient(line, remote)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err, line)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.ParseSessionId(line)
|
|
||||||
infos := <-s.ret
|
|
||||||
if err := c.ParseEnv(s, &infos); err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// reset some values
|
|
||||||
c.Profile = ""
|
|
||||||
c.Status = "system failure"
|
|
||||||
c.Operation = "re auth"
|
|
||||||
|
|
||||||
c.Auth(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// find a client among all registered sessions
|
|
||||||
func (s *OpenVpnMgt) getClient(line, remote string) (error, *vpnSession) {
|
|
||||||
re := rcache.Get("^[^0-9]*,([0-9]+)[^0-9]*")
|
|
||||||
match := re.FindStringSubmatch(line)
|
|
||||||
if len(match) == 0 {
|
|
||||||
return errors.New("invalid message"), nil
|
|
||||||
}
|
|
||||||
id, err := strconv.Atoi(match[1])
|
|
||||||
if err != nil {
|
|
||||||
return err, nil
|
|
||||||
}
|
|
||||||
if _, ok := s.clients[remote]; !ok {
|
|
||||||
return errors.New("unknown vpn server"), nil
|
|
||||||
}
|
|
||||||
if c, ok := s.clients[remote][id]; ok {
|
|
||||||
return nil, c
|
|
||||||
}
|
|
||||||
return errors.New("unknown vpn client"), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// update counters
|
|
||||||
func (s *OpenVpnMgt) updateCounters(line, remote string) {
|
|
||||||
p := strings.Split(strings.Replace(line, ":", ",", 1), ",")
|
|
||||||
err, c := s.getClient(p[0]+","+p[1], remote)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err, line)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if c.BwWrite, err = strconv.Atoi(p[2]); err != nil {
|
|
||||||
c.LogPrintln(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if c.BwRead, err = strconv.Atoi(p[3]); err != nil {
|
|
||||||
c.LogPrintln(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// main loop for a given openvpn server
|
// main loop for a given openvpn server
|
||||||
func (s *OpenVpnMgt) handleConn(conn net.Conn) {
|
func (s *OpenVpnMgt) handleConn(conn net.Conn) {
|
||||||
remote := conn.RemoteAddr().String()
|
remote := conn.RemoteAddr().String()
|
||||||
|
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
defer delete(s.buf, remote)
|
defer delete(s.buf, remote)
|
||||||
defer delete(s.clients, remote)
|
|
||||||
|
|
||||||
// we store the buffer pointer in the struct, to be accessed from other methods
|
// we store the buffer pointer in the struct, to be accessed from other methods
|
||||||
s.buf[remote] = bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn))
|
s.buf[remote] = bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn))
|
||||||
s.clients[remote] = make(map[int]*vpnSession)
|
|
||||||
|
|
||||||
// most response are multilined, use response to concatenate them
|
// most response are multilined, use response to concatenate them
|
||||||
response := []string{}
|
response := []string{}
|
||||||
|
@ -311,21 +149,6 @@ func (s *OpenVpnMgt) handleConn(conn net.Conn) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// ask for statistics
|
|
||||||
if _, err := s.buf[remote].WriteString("bytecount 30\r\n"); err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := s.buf[remote].Flush(); err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if line, err := s.buf[remote].ReadString('\n'); err != nil ||
|
|
||||||
line != "SUCCESS: bytecount interval changed\r\n" {
|
|
||||||
log.Println("Bogus Client")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Valid openvpn connected from %s\n", remote)
|
log.Printf("Valid openvpn connected from %s\n", remote)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
@ -364,27 +187,6 @@ func (s *OpenVpnMgt) handleConn(conn net.Conn) {
|
||||||
// command successfull, we can ignore
|
// command successfull, we can ignore
|
||||||
case strings.HasPrefix(line, ">SUCCESS: client-deny command succeeded"):
|
case strings.HasPrefix(line, ">SUCCESS: client-deny command succeeded"):
|
||||||
|
|
||||||
// trafic stats
|
|
||||||
case strings.HasPrefix(line, ">BYTECOUNT_CLI"):
|
|
||||||
go s.updateCounters(line, remote)
|
|
||||||
|
|
||||||
// new bloc for a disconnect event.
|
|
||||||
// We start the receiving handler, which will wait for the Channel message
|
|
||||||
case strings.HasPrefix(line, ">CLIENT:DISCONNECT"):
|
|
||||||
go s.ClientDisconnect(line, remote)
|
|
||||||
|
|
||||||
// new bloc for a connect event.
|
|
||||||
// We start the receiving handler, which will wait for the Channel message
|
|
||||||
case strings.HasPrefix(line, ">CLIENT:ADDRESS"):
|
|
||||||
case strings.HasPrefix(line, ">CLIENT:ESTABLISHED"):
|
|
||||||
go s.ClientValidated(line, remote)
|
|
||||||
|
|
||||||
case strings.HasPrefix(line, ">CLIENT:CONNECT"):
|
|
||||||
go s.ClientConnect(line, remote)
|
|
||||||
|
|
||||||
case strings.HasPrefix(line, ">CLIENT:REAUTH"):
|
|
||||||
go s.ClientReAuth(line, remote)
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
response = append(response, line)
|
response = append(response, line)
|
||||||
}
|
}
|
||||||
|
|
317
vpnsession.go
317
vpnsession.go
|
@ -1,317 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"github.com/pyke369/golang-support/rcache"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type vpnSession struct {
|
|
||||||
Time time.Time `json:"time"`
|
|
||||||
Login string `json:"username"`
|
|
||||||
Operation string `json:"operation"`
|
|
||||||
Status string `json:"status"`
|
|
||||||
Profile string `json:"profile"`
|
|
||||||
TwoFA bool `json:"2fa_auth"`
|
|
||||||
IP string `json:"client_ip"`
|
|
||||||
PrivIP string `json:"private_ip"`
|
|
||||||
AsNumber string `json:"as_number"`
|
|
||||||
AsName string `json:"as_name"`
|
|
||||||
NewAS bool `json:"as_new"`
|
|
||||||
PwnedPasswd bool `json:"pwned_passwd"`
|
|
||||||
Hostname string `json:"hostname"`
|
|
||||||
TooMuchPwn bool `json:"too_much_pwn"`
|
|
||||||
BwRead int `json:"in_bytes"`
|
|
||||||
BwWrite int `json:"out_bytes"`
|
|
||||||
Mail string `json:"-"`
|
|
||||||
cID int `json:"-"`
|
|
||||||
kID int `json:"-"`
|
|
||||||
port int `json:"-"`
|
|
||||||
dev string `json:"-"`
|
|
||||||
netmask string `json:"-"`
|
|
||||||
password string `json:"-"`
|
|
||||||
otpCode string `json:"-"`
|
|
||||||
localIP string `json:"-"`
|
|
||||||
vpnserver string `json:"-"`
|
|
||||||
pwnMail string `json:"-"`
|
|
||||||
newAsMail string `json:"-"`
|
|
||||||
MailFrom string `json:"-"`
|
|
||||||
CcPwnPassword string `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewVPNSession() *vpnSession {
|
|
||||||
v := vpnSession{
|
|
||||||
Time: time.Now().Round(time.Second),
|
|
||||||
Status: "system failure",
|
|
||||||
Operation: "log in",
|
|
||||||
}
|
|
||||||
v.Hostname, _ = os.Hostname()
|
|
||||||
|
|
||||||
return &v
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *vpnSession) String() string {
|
|
||||||
if res, err := json.MarshalIndent(c, " ", " "); err == nil {
|
|
||||||
return string(res)
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *vpnSession) b64Login() string {
|
|
||||||
return base64.StdEncoding.EncodeToString([]byte(c.Login))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *vpnSession) baseHash(salt string, i int64) string {
|
|
||||||
return fmt.Sprintf("%s%s%s%s", salt, c.Login, c.IP, i)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *vpnSession) AddRoute(ip string) error {
|
|
||||||
var cmd *exec.Cmd
|
|
||||||
if os.Geteuid() == 0 {
|
|
||||||
cmd = exec.Command("/bin/ip", "route", "replace", ip+"/32", "dev", c.dev)
|
|
||||||
} else {
|
|
||||||
cmd = exec.Command("/usr/bin/sudo", "/bin/ip", "route", "replace", ip+"/32", "dev", c.dev)
|
|
||||||
}
|
|
||||||
return cmd.Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *vpnSession) ParseSessionId(line string) error {
|
|
||||||
var err error
|
|
||||||
re := rcache.Get("^>CLIENT:[^,]*,([0-9]+),([0-9]+)$")
|
|
||||||
match := re.FindStringSubmatch(line)
|
|
||||||
if len(match) == 0 {
|
|
||||||
return errors.New("invalid message")
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.cID, err = strconv.Atoi(match[1]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if c.kID, err = strconv.Atoi(match[2]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *vpnSession) ParseEnv(s *OpenVpnMgt, infos *[]string) error {
|
|
||||||
var err error
|
|
||||||
r := rcache.Get("[^a-zA-Z0-9./_@-]")
|
|
||||||
renv := rcache.Get("^>CLIENT:ENV,([^=]*)=(.*)$")
|
|
||||||
for _, line := range *infos {
|
|
||||||
p := renv.FindStringSubmatch(line)
|
|
||||||
if len(p) != 3 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
switch p[1] {
|
|
||||||
case "trusted_port":
|
|
||||||
if c.port, err = strconv.Atoi(r.ReplaceAllString(p[2], "")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case "untrusted_port":
|
|
||||||
if c.port, err = strconv.Atoi(r.ReplaceAllString(p[2], "")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case "trusted_ip":
|
|
||||||
c.IP = r.ReplaceAllString(p[2], "")
|
|
||||||
case "untrusted_ip":
|
|
||||||
c.IP = r.ReplaceAllString(p[2], "")
|
|
||||||
case "ifconfig_pool_remote_ip":
|
|
||||||
c.PrivIP = r.ReplaceAllString(p[2], "")
|
|
||||||
case "ifconfig_local":
|
|
||||||
c.localIP = r.ReplaceAllString(p[2], "")
|
|
||||||
case "bytes_received":
|
|
||||||
if c.BwWrite, err = strconv.Atoi(p[2]); err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case "bytes_sent":
|
|
||||||
if c.BwRead, err = strconv.Atoi(p[2]); err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case "password":
|
|
||||||
switch {
|
|
||||||
case strings.HasPrefix(p[2], "CRV1"):
|
|
||||||
split := strings.Split(p[2], ":")
|
|
||||||
if len(split) != 5 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
c.password = split[2]
|
|
||||||
c.otpCode = split[4]
|
|
||||||
if c.otpCode == "" {
|
|
||||||
c.otpCode = "***"
|
|
||||||
}
|
|
||||||
// don't check that password against the ibp database
|
|
||||||
case strings.HasPrefix(p[2], "SCRV1"):
|
|
||||||
split := strings.Split(p[2], ":")
|
|
||||||
if len(split) != 3 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
data, err := base64.StdEncoding.DecodeString(split[1])
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
c.password = string(data)
|
|
||||||
|
|
||||||
data, err = base64.StdEncoding.DecodeString(split[2])
|
|
||||||
if err != nil {
|
|
||||||
c.password = p[2]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
c.otpCode = string(data)
|
|
||||||
|
|
||||||
if c.otpCode == "" {
|
|
||||||
c.otpCode = "***"
|
|
||||||
}
|
|
||||||
// only check if the password is pwned on the first connection
|
|
||||||
if c.Operation == "log in" {
|
|
||||||
go s.CheckPwn(c)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
c.password = p[2]
|
|
||||||
c.otpCode = ""
|
|
||||||
// only check if the password is pwned on the first connection
|
|
||||||
if c.Operation == "log in" {
|
|
||||||
go s.CheckPwn(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case "username":
|
|
||||||
c.Login = strings.ToLower(r.ReplaceAllString(p[2], ""))
|
|
||||||
case "dev":
|
|
||||||
c.dev = r.ReplaceAllString(p[2], "")
|
|
||||||
case "ifconfig_netmask":
|
|
||||||
c.netmask = r.ReplaceAllString(p[2], "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *vpnSession) Auth(s *OpenVpnMgt) {
|
|
||||||
var cmd []string
|
|
||||||
var ip string
|
|
||||||
var errIP error
|
|
||||||
|
|
||||||
err, ok := c.auth(s)
|
|
||||||
// if auth is ok, time to get an IP address
|
|
||||||
if ok == 0 && c.PrivIP == "" {
|
|
||||||
ip, errIP = s.getIP(c)
|
|
||||||
if errIP != nil {
|
|
||||||
ok = -10
|
|
||||||
err = errIP
|
|
||||||
} else {
|
|
||||||
if err := c.AddRoute(ip); err != nil {
|
|
||||||
c.LogPrintln(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case ok == 0:
|
|
||||||
cmd = []string{
|
|
||||||
fmt.Sprintf("client-auth %d %d", c.cID, c.kID),
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.netmask == "255.255.255.255" {
|
|
||||||
cmd = append(cmd, fmt.Sprintf("ifconfig-push %s %s", ip, c.localIP))
|
|
||||||
} else {
|
|
||||||
cmd = append(cmd, fmt.Sprintf("ifconfig-push %s %s", ip, c.netmask))
|
|
||||||
}
|
|
||||||
for _, r := range s.ldap[c.Profile].routes {
|
|
||||||
cmd = append(cmd, fmt.Sprintf("push \"route %s vpn_gateway\"", r))
|
|
||||||
}
|
|
||||||
cmd = append(cmd, "END")
|
|
||||||
c.Status = "success"
|
|
||||||
|
|
||||||
case ok < 0:
|
|
||||||
cmd = []string{fmt.Sprintf("client-deny %d %d \"%s\" \"%s\"",
|
|
||||||
c.cID, c.kID, err, err)}
|
|
||||||
|
|
||||||
case ok == 1:
|
|
||||||
cmd = []string{fmt.Sprintf(
|
|
||||||
"client-deny %d %d \"Need OTP\" \"CRV1:R,E:%s:%s:OTP Code \"",
|
|
||||||
c.cID, c.kID, c.password, c.b64Login())}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err, _ := s.sendCommand(cmd, c.vpnserver); err != nil {
|
|
||||||
c.LogPrintln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// main authentication function.
|
|
||||||
// returns 0 if auth is valid
|
|
||||||
// returns 1 if an TOTP code is necessary
|
|
||||||
// returns a negative if auth is not valid
|
|
||||||
func (c *vpnSession) auth(s *OpenVpnMgt) (error, int) {
|
|
||||||
// an empty password is not good
|
|
||||||
if c.password == "" {
|
|
||||||
c.Status = "Empty Password"
|
|
||||||
return errors.New("Empty Password"), -1
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if the password is a valid token (see TOTP request)
|
|
||||||
tokenPasswordOk, tokenPassword := s.TokenPassword(c)
|
|
||||||
|
|
||||||
// password is a token. We remove it from the session object to
|
|
||||||
// avoid checking it against the ldap
|
|
||||||
if tokenPasswordOk {
|
|
||||||
c.password = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
otpSalt := ""
|
|
||||||
c.Profile, c.Mail, otpSalt, _ = s.AuthLoop("", c.Login, c.password, tokenPasswordOk)
|
|
||||||
|
|
||||||
// no profile validated, we stop here
|
|
||||||
if c.Profile == "" {
|
|
||||||
c.Status = "fail (password)"
|
|
||||||
return errors.New("Authentication Failed"), -3
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the otp is not empty, we check it against the valid codes as soon as
|
|
||||||
// possible
|
|
||||||
otpvalidated := false
|
|
||||||
if c.otpCode != "" {
|
|
||||||
codes, err := s.GenerateOTP(c.Login + otpSalt)
|
|
||||||
if err != nil {
|
|
||||||
return err, -2
|
|
||||||
}
|
|
||||||
for _, possible := range codes {
|
|
||||||
if possible == c.otpCode {
|
|
||||||
otpvalidated = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check the MFA requested by the secured profile
|
|
||||||
c.TwoFA = true
|
|
||||||
switch s.ldap[c.Profile].mfaType {
|
|
||||||
case "internal":
|
|
||||||
if otpvalidated {
|
|
||||||
return nil, 0
|
|
||||||
}
|
|
||||||
// log that the failure is due to the OTP
|
|
||||||
if c.otpCode == "" {
|
|
||||||
c.Status = "Need OTP Code"
|
|
||||||
} else {
|
|
||||||
c.Status = "fail (OTP) : "
|
|
||||||
}
|
|
||||||
c.password = tokenPassword
|
|
||||||
return errors.New("Need OTP Code"), 1
|
|
||||||
case "okta":
|
|
||||||
//TODO implement okta MFA
|
|
||||||
c.Status = "fail (Okta)"
|
|
||||||
return nil, -4
|
|
||||||
default:
|
|
||||||
c.TwoFA = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// no MFA requested, the login is valid
|
|
||||||
return nil, 0
|
|
||||||
}
|
|
Loading…
Reference in New Issue