From 55ae63dc1dbd7ecd566c913935f20f8579821ef3 Mon Sep 17 00:00:00 2001 From: Xavier Henner Date: Tue, 9 Jul 2019 09:53:46 +0200 Subject: [PATCH] vendor and go mod. add ldap file --- go.mod | 8 +- go.sum | 6 + ldap.go | 172 ++++ utils.go | 34 + .../pyke369/golang-support/bslab/bslab.go | 89 -- .../pyke369/golang-support/chash/README.md | 0 .../pyke369/golang-support/chash/chash.go | 356 -------- .../golang-support/dynacert/dynacert.go | 54 -- .../pyke369/golang-support/fqdn/README.md | 0 .../pyke369/golang-support/fqdn/fqdn.go | 28 - .../golang-support/listener/listener.go | 60 -- .../pyke369/golang-support/prefixdb/README.md | 34 - .../golang-support/prefixdb/cmd/Makefile | 11 - .../golang-support/prefixdb/cmd/go.mod | 5 - .../golang-support/prefixdb/cmd/go.sum | 2 - .../golang-support/prefixdb/cmd/prefixdb.go | 367 -------- .../golang-support/prefixdb/prefixdb.go | 827 ------------------ .../pyke369/golang-support/rcache/rcache.go | 23 +- .../pyke369/golang-support/rpack/README.md | 0 .../pyke369/golang-support/rpack/cmd/rpack.go | 38 - .../pyke369/golang-support/rpack/rpack.go | 155 ---- .../pyke369/golang-support/uconfig/uconfig.go | 176 ++-- .../pyke369/golang-support/ulog/README.md | 0 .../pyke369/golang-support/ulog/ulog.go | 545 ------------ .../pyke369/golang-support/uuid/README.md | 0 .../pyke369/golang-support/uuid/uuid.go | 30 - .../pyke369/golang-support/whohas/README.md | 0 .../pyke369/golang-support/whohas/whohas.go | 244 ------ vendor/gopkg.in/asn1-ber.v1/.travis.yml | 36 + vendor/gopkg.in/asn1-ber.v1/LICENSE | 22 + vendor/gopkg.in/asn1-ber.v1/README.md | 24 + vendor/gopkg.in/asn1-ber.v1/ber.go | 512 +++++++++++ vendor/gopkg.in/asn1-ber.v1/content_int.go | 25 + vendor/gopkg.in/asn1-ber.v1/header.go | 35 + vendor/gopkg.in/asn1-ber.v1/identifier.go | 112 +++ vendor/gopkg.in/asn1-ber.v1/length.go | 81 ++ vendor/gopkg.in/asn1-ber.v1/util.go | 24 + .../README.md => gopkg.in/ldap.v2/.gitignore} | 0 vendor/gopkg.in/ldap.v2/.travis.yml | 31 + vendor/gopkg.in/ldap.v2/LICENSE | 22 + vendor/gopkg.in/ldap.v2/Makefile | 52 ++ vendor/gopkg.in/ldap.v2/README.md | 53 ++ vendor/gopkg.in/ldap.v2/add.go | 113 +++ vendor/gopkg.in/ldap.v2/atomic_value.go | 13 + vendor/gopkg.in/ldap.v2/atomic_value_go13.go | 28 + vendor/gopkg.in/ldap.v2/bind.go | 143 +++ vendor/gopkg.in/ldap.v2/client.go | 27 + vendor/gopkg.in/ldap.v2/compare.go | 85 ++ vendor/gopkg.in/ldap.v2/conn.go | 470 ++++++++++ vendor/gopkg.in/ldap.v2/control.go | 420 +++++++++ vendor/gopkg.in/ldap.v2/debug.go | 24 + vendor/gopkg.in/ldap.v2/del.go | 84 ++ vendor/gopkg.in/ldap.v2/dn.go | 247 ++++++ vendor/gopkg.in/ldap.v2/doc.go | 4 + vendor/gopkg.in/ldap.v2/error.go | 155 ++++ vendor/gopkg.in/ldap.v2/filter.go | 469 ++++++++++ vendor/gopkg.in/ldap.v2/ldap.go | 320 +++++++ vendor/gopkg.in/ldap.v2/modify.go | 170 ++++ vendor/gopkg.in/ldap.v2/passwdmodify.go | 148 ++++ vendor/gopkg.in/ldap.v2/search.go | 450 ++++++++++ vendor/modules.txt | 7 + 61 files changed, 4696 insertions(+), 2974 deletions(-) create mode 100644 go.sum create mode 100644 ldap.go create mode 100644 utils.go delete mode 100644 vendor/github.com/pyke369/golang-support/bslab/bslab.go delete mode 100644 vendor/github.com/pyke369/golang-support/chash/README.md delete mode 100644 vendor/github.com/pyke369/golang-support/chash/chash.go delete mode 100644 vendor/github.com/pyke369/golang-support/dynacert/dynacert.go delete mode 100644 vendor/github.com/pyke369/golang-support/fqdn/README.md delete mode 100644 vendor/github.com/pyke369/golang-support/fqdn/fqdn.go delete mode 100644 vendor/github.com/pyke369/golang-support/listener/listener.go delete mode 100644 vendor/github.com/pyke369/golang-support/prefixdb/README.md delete mode 100644 vendor/github.com/pyke369/golang-support/prefixdb/cmd/Makefile delete mode 100644 vendor/github.com/pyke369/golang-support/prefixdb/cmd/go.mod delete mode 100644 vendor/github.com/pyke369/golang-support/prefixdb/cmd/go.sum delete mode 100644 vendor/github.com/pyke369/golang-support/prefixdb/cmd/prefixdb.go delete mode 100644 vendor/github.com/pyke369/golang-support/prefixdb/prefixdb.go delete mode 100644 vendor/github.com/pyke369/golang-support/rpack/README.md delete mode 100644 vendor/github.com/pyke369/golang-support/rpack/cmd/rpack.go delete mode 100644 vendor/github.com/pyke369/golang-support/rpack/rpack.go delete mode 100644 vendor/github.com/pyke369/golang-support/ulog/README.md delete mode 100644 vendor/github.com/pyke369/golang-support/ulog/ulog.go delete mode 100644 vendor/github.com/pyke369/golang-support/uuid/README.md delete mode 100644 vendor/github.com/pyke369/golang-support/uuid/uuid.go delete mode 100644 vendor/github.com/pyke369/golang-support/whohas/README.md delete mode 100644 vendor/github.com/pyke369/golang-support/whohas/whohas.go create mode 100644 vendor/gopkg.in/asn1-ber.v1/.travis.yml create mode 100644 vendor/gopkg.in/asn1-ber.v1/LICENSE create mode 100644 vendor/gopkg.in/asn1-ber.v1/README.md create mode 100644 vendor/gopkg.in/asn1-ber.v1/ber.go create mode 100644 vendor/gopkg.in/asn1-ber.v1/content_int.go create mode 100644 vendor/gopkg.in/asn1-ber.v1/header.go create mode 100644 vendor/gopkg.in/asn1-ber.v1/identifier.go create mode 100644 vendor/gopkg.in/asn1-ber.v1/length.go create mode 100644 vendor/gopkg.in/asn1-ber.v1/util.go rename vendor/{github.com/pyke369/golang-support/README.md => gopkg.in/ldap.v2/.gitignore} (100%) create mode 100644 vendor/gopkg.in/ldap.v2/.travis.yml create mode 100644 vendor/gopkg.in/ldap.v2/LICENSE create mode 100644 vendor/gopkg.in/ldap.v2/Makefile create mode 100644 vendor/gopkg.in/ldap.v2/README.md create mode 100644 vendor/gopkg.in/ldap.v2/add.go create mode 100644 vendor/gopkg.in/ldap.v2/atomic_value.go create mode 100644 vendor/gopkg.in/ldap.v2/atomic_value_go13.go create mode 100644 vendor/gopkg.in/ldap.v2/bind.go create mode 100644 vendor/gopkg.in/ldap.v2/client.go create mode 100644 vendor/gopkg.in/ldap.v2/compare.go create mode 100644 vendor/gopkg.in/ldap.v2/conn.go create mode 100644 vendor/gopkg.in/ldap.v2/control.go create mode 100644 vendor/gopkg.in/ldap.v2/debug.go create mode 100644 vendor/gopkg.in/ldap.v2/del.go create mode 100644 vendor/gopkg.in/ldap.v2/dn.go create mode 100644 vendor/gopkg.in/ldap.v2/doc.go create mode 100644 vendor/gopkg.in/ldap.v2/error.go create mode 100644 vendor/gopkg.in/ldap.v2/filter.go create mode 100644 vendor/gopkg.in/ldap.v2/ldap.go create mode 100644 vendor/gopkg.in/ldap.v2/modify.go create mode 100644 vendor/gopkg.in/ldap.v2/passwdmodify.go create mode 100644 vendor/gopkg.in/ldap.v2/search.go create mode 100644 vendor/modules.txt diff --git a/go.mod b/go.mod index ebd4f0c..fe129c9 100644 --- a/go.mod +++ b/go.mod @@ -1 +1,7 @@ -module github.com/pyke369/golang-support/uconfig +module gitlab.dm.gg/vwf/openvpn-dm-mgt-server + +require ( + github.com/pyke369/golang-support v0.0.0-20190703174728-34ca97aa79e9 + gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect + gopkg.in/ldap.v2 v2.5.1 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e57c09c --- /dev/null +++ b/go.sum @@ -0,0 +1,6 @@ +github.com/pyke369/golang-support v0.0.0-20190703174728-34ca97aa79e9 h1:H1vjQ+Mfc8dFAOTuF541/tScdKoynzll9iKuWgaLLxM= +github.com/pyke369/golang-support v0.0.0-20190703174728-34ca97aa79e9/go.mod h1:0XGrzgrEp0fa/+JSV8XZePUwyjnU6C3bMc7Xz2bHHKI= +gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM= +gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= +gopkg.in/ldap.v2 v2.5.1 h1:wiu0okdNfjlBzg6UWvd1Hn8Y+Ux17/u/4nlk4CQr6tU= +gopkg.in/ldap.v2 v2.5.1/go.mod h1:oI0cpe/D7HRtBQl8aTg+ZmzFUAvu4lsv3eLXMLGFxWk= diff --git a/ldap.go b/ldap.go new file mode 100644 index 0000000..9351e8c --- /dev/null +++ b/ldap.go @@ -0,0 +1,172 @@ +package main + +import ( + "crypto/tls" + "errors" + "fmt" + "log" + "net" + "strings" + "time" + + "gopkg.in/ldap.v2" +) + +type ldapConfig struct { + servers []string + baseDN string + bindCn string + bindPw string + searchFilter string + primaryAttribute string + secondaryAttribute string + validGroups []string + otpType string + certAuth string + ipMin net.IP + ipMax net.IP + upgradeFrom string +} + +func (l *ldapConfig) addIPRange(s string) error { + ips := strings.Split(s, "-") + if len(ips) != 2 { + return errors.New("invalid IPs") + } + if ip := net.ParseIP(ips[0]); ip != nil { + l.ipMin = ip + } + if ip := net.ParseIP(ips[1]); ip != nil { + l.ipMax = ip + } + return nil +} + +// 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) { + var primary, secondary []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, logins + } + } + + if len(logins) != 1 { + return errors.New("invalid login"), false, false, nil + } + attributes = 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 := []string{"dn", conf.primaryAttribute} + if conf.secondaryAttribute != "" { + search = append(search, conf.secondaryAttribute) + } + + // 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 { + log.Println("User does not exist or too many entries returned") + return nil, false, false, nil + } + + // check the attributes requested in the search + // a valid account must be part of the correct group (per instance) + for _, attribute := range sr.Entries[0].Attributes { + if (*attribute).Name == conf.primaryAttribute { + primary = attribute.Values + } + if (*attribute).Name == conf.secondaryAttribute { + secondary = attribute.Values + } + } + + // user must have both primary and secondary attributes + if len(primary) == 0 { + log.Printf("User has no %s attribute", conf.primaryAttribute) + return nil, false, false, nil + } + + if len(secondary) == 0 { + log.Printf("User has no %s attribute", conf.secondaryAttribute) + return nil, false, false, nil + } + + // check if the primary attributes are in the validGroups list + if len(conf.validGroups) > 0 && !inArray(conf.validGroups, primary) { + return nil, false, false, nil + } + + // if there is no validGroups check, pass the primary attributes to the + // next level + if len(conf.validGroups) == 0 { + attributes = primary + } else { + attributes = secondary + } + + log.Printf("User has a valid account on %s", 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 has a valid password on %s", 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 +} diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..d09882c --- /dev/null +++ b/utils.go @@ -0,0 +1,34 @@ +package main + +import ( + "sort" + + "github.com/pyke369/golang-support/uconfig" +) + +// check if there is a member of "search" in "list". sort list to be more +// efficiant. adapt with sorting "search" too +func inArray(search, list []string) bool { + sort.Strings(list) + for _, g := range search { + i := sort.Search(len(list), func(i int) bool { return list[i] >= g }) + if i < len(list) && list[i] == g { + return true + break + } + } + return false +} + +// parse a uconf array +func parseConfigArray(config *uconfig.UConfig, configpath string) []string { + result := []string{} + for _, i := range config.GetPaths(configpath) { + if s := config.GetString(i, ""); s == "" { + continue + } else { + result = append(result, s) + } + } + return result +} diff --git a/vendor/github.com/pyke369/golang-support/bslab/bslab.go b/vendor/github.com/pyke369/golang-support/bslab/bslab.go deleted file mode 100644 index 4b59742..0000000 --- a/vendor/github.com/pyke369/golang-support/bslab/bslab.go +++ /dev/null @@ -1,89 +0,0 @@ -package bslab - -import "sync/atomic" - -type slab struct { - queue chan []byte - get, put, alloc, lost int64 -} - -var ( - slabs = map[int]*slab{} -) - -func init() { - slabs[0] = &slab{} - for size := uint(9); size <= 26; size++ { - slabs[1<= size { - return item[:0] - } - Put(item) - } - bits, power := uint(0), uint(0) - if size&(size-1) == 0 { - power = 1 - } - for size != 0 { - size >>= 1 - bits++ - } - size = 1 << (bits - power) - if slab, ok := slabs[size]; ok { - atomic.AddInt64(&(slab.get), 1) - select { - case item := <-slab.queue: - return item[:0] - default: - atomic.AddInt64(&(slab.alloc), 1) - return make([]byte, 0, size) - } - } - atomic.AddInt64(&(slabs[0].get), 1) - atomic.AddInt64(&(slabs[0].alloc), int64(size)) - return make([]byte, 0, size) -} - -func Put(item []byte) { - if item == nil || cap(item) <= 0 { - return - } - size, bits := cap(item), uint(0) - for size != 0 { - size >>= 1 - bits++ - } - size = 1 << (bits - 1) - if size > 0 && float64(cap(item))/float64(size) <= 1.2 { - if slab, ok := slabs[size]; ok { - atomic.AddInt64(&(slab.put), 1) - select { - case slab.queue <- item: - default: - atomic.AddInt64(&(slab.lost), 1) - } - } else { - atomic.AddInt64(&(slabs[0].put), 1) - atomic.AddInt64(&(slabs[0].lost), int64(cap(item))) - } - } else { - atomic.AddInt64(&(slabs[0].put), 1) - atomic.AddInt64(&(slabs[0].lost), int64(cap(item))) - } -} diff --git a/vendor/github.com/pyke369/golang-support/chash/README.md b/vendor/github.com/pyke369/golang-support/chash/README.md deleted file mode 100644 index e69de29..0000000 diff --git a/vendor/github.com/pyke369/golang-support/chash/chash.go b/vendor/github.com/pyke369/golang-support/chash/chash.go deleted file mode 100644 index 604fc65..0000000 --- a/vendor/github.com/pyke369/golang-support/chash/chash.go +++ /dev/null @@ -1,356 +0,0 @@ -package chash - -import ( - "math/rand" - "os" - "runtime" - "sort" - "strconv" - "sync" - "time" -) - -const ( - CHASH_MAGIC uint32 = 0x48414843 - CHASH_REPLICAS = 128 -) - -type item struct { - hash uint32 - target uint16 -} - -type CHash struct { - targets map[string]uint8 - names []string - ring []item - ringSize uint32 - replicas uint8 - frozen bool - sync.RWMutex -} - -var cores int - -func init() { - rand.Seed(time.Now().UnixNano() + int64(os.Getpid())) -} - -func mmhash2(key []byte, keySize int) uint32 { - var magic, hash, current, value uint32 = 0x5bd1e995, uint32(0x4d4d4832 ^ keySize), 0, 0 - - if keySize < 0 { - keySize = len(key) - } - for keySize >= 4 { - value = uint32(key[current]) | uint32(key[current+1])<<8 | - uint32(key[current+2])<<16 | uint32(key[current+3])<<24 - value *= magic - value ^= value >> 24 - value *= magic - hash *= magic - hash ^= value - current += 4 - keySize -= 4 - } - if keySize >= 3 { - hash ^= uint32(key[current+2]) << 16 - } - if keySize >= 2 { - hash ^= uint32(key[current+1]) << 8 - } - if keySize >= 1 { - hash ^= uint32(key[current]) - } - if keySize != 0 { - hash *= magic - } - hash ^= hash >> 13 - hash *= magic - hash ^= hash >> 15 - return hash -} - -type ByHash []item - -func (a ByHash) Len() int { return len(a) } -func (a ByHash) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a ByHash) Less(i, j int) bool { return a[i].hash < a[j].hash } - -func (this *CHash) freeze() { - if cores > 1 { - this.Lock() - defer this.Unlock() - } - if this.frozen { - return - } - this.ringSize = 0 - for _, tweight := range this.targets { - this.ringSize += uint32(tweight) * uint32(this.replicas) - } - if this.ringSize == 0 { - this.frozen = true - return - } - - var ( - target uint16 = 0 - offset uint32 = 0 - key []byte = make([]byte, 128) - ) - this.names = make([]string, len(this.targets)) - this.ring = make([]item, this.ringSize) - for tname, tweight := range this.targets { - this.names[target] = tname - for weight := uint8(0); weight < tweight; weight++ { - for replica := uint8(0); replica < this.replicas; replica++ { - key = append(key[:0], tname...) - key = strconv.AppendInt(key, int64(weight), 10) - key = strconv.AppendInt(key, int64(replica), 10) - this.ring[offset] = item{mmhash2(key, -1), target} - offset++ - } - } - target++ - } - sort.Sort(ByHash(this.ring)) - this.frozen = true -} - -func New(replicas ...uint8) *CHash { - if cores == 0 { - cores = runtime.NumCPU() - } - chash := &CHash{ - targets: make(map[string]uint8), - names: nil, - ring: nil, - ringSize: 0, - replicas: CHASH_REPLICAS, - frozen: false, - } - if len(replicas) > 0 { - chash.replicas = replicas[0] - } - if chash.replicas < 1 { - chash.replicas = 1 - } - if chash.replicas > CHASH_REPLICAS { - chash.replicas = CHASH_REPLICAS - } - return chash -} - -func (this *CHash) AddTarget(name string, weight uint8) bool { - if weight > 0 && weight <= 100 && len(name) <= 128 && this.targets[name] != weight { - if cores > 1 { - this.Lock() - defer this.Unlock() - } - this.targets[name] = weight - this.frozen = false - return true - } - return false -} -func (this *CHash) RemoveTarget(name string) bool { - if cores > 1 { - this.Lock() - defer this.Unlock() - } - delete(this.targets, name) - this.frozen = false - return true -} -func (this *CHash) ClearTargets() bool { - if cores > 1 { - this.Lock() - defer this.Unlock() - } - this.targets = make(map[string]uint8) - this.frozen = false - return true -} - -func (this *CHash) Serialize() []byte { - this.freeze() - if cores > 1 { - this.RLock() - defer this.RUnlock() - } - size := uint32(4) + 4 + 1 + 2 + 4 - for _, name := range this.names { - size += 1 + 1 + uint32(len(name)) - } - size += (this.ringSize * 6) - serialized := make([]byte, size) - offset := uint32(0) - serialized[offset] = byte(CHASH_MAGIC & 0xff) - serialized[offset+1] = byte((CHASH_MAGIC >> 8) & 0xff) - serialized[offset+2] = byte((CHASH_MAGIC >> 16) & 0xff) - serialized[offset+3] = byte((CHASH_MAGIC >> 24) & 0xff) - serialized[offset+4] = byte(size & 0xff) - serialized[offset+5] = byte((size >> 8) & 0xff) - serialized[offset+6] = byte((size >> 16) & 0xff) - serialized[offset+7] = byte((size >> 24) & 0xff) - serialized[offset+8] = this.replicas - serialized[offset+9] = byte(uint16(len(this.names)) & 0xff) - serialized[offset+10] = byte(((uint16(len(this.names))) >> 8) & 0xff) - serialized[offset+11] = byte(this.ringSize & 0xff) - serialized[offset+12] = byte((this.ringSize >> 8) & 0xff) - serialized[offset+13] = byte((this.ringSize >> 16) & 0xff) - serialized[offset+14] = byte((this.ringSize >> 24) & 0xff) - offset += 15 - for _, name := range this.names { - serialized[offset] = this.targets[name] - serialized[offset+1] = byte(len(name) & 0xff) - copy(serialized[offset+2:offset+2+uint32(serialized[offset+1])], []byte(name)) - offset += 2 + uint32(serialized[offset+1]) - } - for _, item := range this.ring { - serialized[offset] = byte(item.hash & 0xff) - serialized[offset+1] = byte((item.hash >> 8) & 0xff) - serialized[offset+2] = byte((item.hash >> 16) & 0xff) - serialized[offset+3] = byte((item.hash >> 24) & 0xff) - serialized[offset+4] = byte(item.target & 0xff) - serialized[offset+5] = byte((item.target >> 8) & 0xff) - offset += 6 - } - return serialized -} -func (this *CHash) FileSerialize(path string) bool { - handle, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) - if err != nil { - return false - } - defer handle.Close() - if _, err := handle.Write(this.Serialize()); err != nil { - return false - } - return true -} - -func (this *CHash) Unserialize(serialized []byte) bool { - if cores > 1 { - this.Lock() - defer this.Unlock() - } - if len(serialized) < 15 { - return false - } - magic := uint32(serialized[0]) + (uint32(serialized[1]) << 8) + (uint32(serialized[2]) << 16) + (uint32(serialized[3]) << 24) - size := uint32(serialized[4]) + (uint32(serialized[5]) << 8) + (uint32(serialized[6]) << 16) + (uint32(serialized[7]) << 24) - replicas := serialized[8] - names := uint16(serialized[9]) + (uint16(serialized[10]) << 8) - ringSize := uint32(serialized[11]) + (uint32(serialized[12]) << 8) + (uint32(serialized[13]) << 16) + (uint32(serialized[14]) << 24) - if magic != CHASH_MAGIC || size != uint32(len(serialized)) { - return false - } - this.targets = make(map[string]uint8) - this.names = make([]string, names) - this.ring = make([]item, ringSize) - this.ringSize = ringSize - this.replicas = replicas - offset := uint32(15) - for index := uint16(0); index < names && offset < size; index++ { - len := uint32(serialized[offset+1]) - this.names[index] = string(serialized[offset+2 : offset+2+len]) - this.targets[this.names[index]] = serialized[offset] - offset += 2 + len - } - if offset > size { - return false - } - for item := uint32(0); item < ringSize && offset < size; item++ { - this.ring[item].hash = uint32(serialized[offset]) + (uint32(serialized[offset+1]) << 8) + (uint32(serialized[offset+2]) << 16) + (uint32(serialized[offset+3]) << 24) - this.ring[item].target = uint16(serialized[offset+4]) + (uint16(serialized[offset+5]) << 8) - offset += 6 - } - if offset != size { - return false - } - this.frozen = true - return true -} -func (this *CHash) FileUnserialize(path string) bool { - handle, err := os.OpenFile(path, os.O_RDONLY, 0) - if err != nil { - return false - } - defer handle.Close() - info, err := handle.Stat() - if err != nil { - return false - } - if info.Size() > 128*1024*1024 { - return false - } - serialized := make([]byte, info.Size()) - read, err := handle.Read(serialized) - if int64(read) != info.Size() || err != nil { - return false - } - return this.Unserialize(serialized) -} - -func (this *CHash) Lookup(candidate string, count int) []string { - var start uint32 = 0 - - this.freeze() - if cores > 1 { - this.RLock() - defer this.RUnlock() - } - if count > len(this.targets) { - count = len(this.targets) - } - if this.ringSize == 0 || count < 1 { - return []string{} - } - hash := mmhash2([]byte(candidate), -1) - if hash > this.ring[0].hash && hash <= this.ring[this.ringSize-1].hash { - start = this.ringSize / 2 - span := start / 2 - for { - if hash > this.ring[start].hash && hash <= this.ring[start+1].hash { - break - } - if hash > this.ring[start].hash { - start += span - } else { - start -= span - } - span /= 2 - if span < 1 { - span = 1 - } - } - } - result := make([]string, count) - rank := 0 - for rank < count { - index := 0 - for index = 0; index < rank; index++ { - if result[index] == this.names[this.ring[start].target] { - break - } - } - if index >= rank { - result[rank] = this.names[this.ring[start].target] - rank++ - } - start++ - if start >= this.ringSize { - start = 0 - } - } - return result -} -func (this *CHash) LookupBalance(candidate string, count int) string { - result := this.Lookup(candidate, count) - if len(result) > 0 { - return result[rand.Intn(len(result))] - } - return "" -} diff --git a/vendor/github.com/pyke369/golang-support/dynacert/dynacert.go b/vendor/github.com/pyke369/golang-support/dynacert/dynacert.go deleted file mode 100644 index 801dca2..0000000 --- a/vendor/github.com/pyke369/golang-support/dynacert/dynacert.go +++ /dev/null @@ -1,54 +0,0 @@ -package dynacert - -import ( - "crypto/tls" - "os" - "runtime" - "sync" - "time" -) - -type DYNACERT struct { - Public, Key string - Certificate *tls.Certificate - Last, Modified time.Time - sync.RWMutex -} - -var cores int - -func (this *DYNACERT) GetCertificate(*tls.ClientHelloInfo) (cert *tls.Certificate, err error) { - var info os.FileInfo - - if cores == 0 { - cores = runtime.NumCPU() - } - if this.Certificate == nil || time.Now().Sub(this.Last) >= 10*time.Second { - this.Last = time.Now() - if info, err = os.Stat(this.Public); err != nil { - return nil, err - } - if _, err = os.Stat(this.Key); err != nil { - return nil, err - } - if this.Certificate == nil || info.ModTime().Sub(this.Modified) != 0 { - if certificate, err := tls.LoadX509KeyPair(this.Public, this.Key); err != nil { - return nil, err - } else { - if cores > 1 { - this.Lock() - } - this.Modified = info.ModTime() - this.Certificate = &certificate - if cores > 1 { - this.Unlock() - } - } - } - } - if cores > 1 { - this.RLock() - defer this.RUnlock() - } - return this.Certificate, nil -} diff --git a/vendor/github.com/pyke369/golang-support/fqdn/README.md b/vendor/github.com/pyke369/golang-support/fqdn/README.md deleted file mode 100644 index e69de29..0000000 diff --git a/vendor/github.com/pyke369/golang-support/fqdn/fqdn.go b/vendor/github.com/pyke369/golang-support/fqdn/fqdn.go deleted file mode 100644 index c761d66..0000000 --- a/vendor/github.com/pyke369/golang-support/fqdn/fqdn.go +++ /dev/null @@ -1,28 +0,0 @@ -package fqdn - -import ( - "net" - "os" - "strings" -) - -func FQDN() (string, string) { - if hostname, err := os.Hostname(); err == nil { - if addresses, err := net.LookupHost(hostname); err != nil { - return hostname, "*" - } else { - for _, address := range addresses { - if hostnames, err := net.LookupAddr(address); err == nil && len(hostnames) > 0 { - for _, hostname := range hostnames { - if strings.Count(hostname, ".") > 1 { - hostname = strings.TrimSuffix(hostname, ".") - addresses, _ = net.LookupHost(hostname) - return hostname, addresses[0] - } - } - } - } - } - } - return "unknown", "*" -} diff --git a/vendor/github.com/pyke369/golang-support/listener/listener.go b/vendor/github.com/pyke369/golang-support/listener/listener.go deleted file mode 100644 index 343257f..0000000 --- a/vendor/github.com/pyke369/golang-support/listener/listener.go +++ /dev/null @@ -1,60 +0,0 @@ -package listener - -import ( - "context" - "net" - "syscall" - - "golang.org/x/sys/unix" -) - -type TCPListener struct { - *net.TCPListener - ReadBufferSize int - WriteBufferSize int -} - -func (this *TCPListener) Accept() (net.Conn, error) { - connection, err := this.AcceptTCP() - if err != nil { - return nil, err - } - if rconnection, err := connection.SyscallConn(); err == nil { - rconnection.Control( - func(handle uintptr) { - syscall.SetsockoptInt(int(handle), syscall.SOL_SOCKET, syscall.SO_KEEPALIVE, 1) - syscall.SetsockoptInt(int(handle), syscall.IPPROTO_TCP, syscall.TCP_KEEPIDLE, 60) - syscall.SetsockoptInt(int(handle), syscall.IPPROTO_TCP, syscall.TCP_KEEPINTVL, 10) - syscall.SetsockoptInt(int(handle), syscall.IPPROTO_TCP, syscall.TCP_KEEPCNT, 3) - }) - } - if this.ReadBufferSize > 0 { - connection.SetReadBuffer(this.ReadBufferSize) - } - if this.WriteBufferSize > 0 { - connection.SetWriteBuffer(this.WriteBufferSize) - } - return connection, nil -} - -func NewTCPListener(network, address string, reuseport bool, read, write int) (listener *TCPListener, err error) { - config := net.ListenConfig{ - Control: func(network, address string, connection syscall.RawConn) error { - connection.Control(func(handle uintptr) { - if err := syscall.SetsockoptInt(int(handle), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil { - return - } - if reuseport { - if err := syscall.SetsockoptInt(int(handle), syscall.SOL_SOCKET, unix.SO_REUSEPORT, 1); err != nil { - return - } - } - }) - return nil - }} - if clistener, err := config.Listen(context.Background(), network, address); err != nil { - return nil, err - } else { - return &TCPListener{clistener.(*net.TCPListener), read, write}, nil - } -} diff --git a/vendor/github.com/pyke369/golang-support/prefixdb/README.md b/vendor/github.com/pyke369/golang-support/prefixdb/README.md deleted file mode 100644 index d29a76b..0000000 --- a/vendor/github.com/pyke369/golang-support/prefixdb/README.md +++ /dev/null @@ -1,34 +0,0 @@ -PFDB binary layout ------------------- -"PFDB" -VVVV (4) -HHHHHHHHHHHHHHHH (16) -"DESC" -"DDDDDDDDDDDDDDDDDDDD" (20) -"STRS" -W (1) -SSSS (4) -CCCC (4) -... -"NUMS" -SSSS (4) -CCCC (4) -... -"PAIR" -SSSS (4) -CCCC (4) -... -"CLUS" -W (1) -SSSS (4) -CCCC (4) -... -"MAPS" -SSSS (4) -CCCC (4) -... -"NODE" -W (1) -SSSS (4) -CCCC (4) -... diff --git a/vendor/github.com/pyke369/golang-support/prefixdb/cmd/Makefile b/vendor/github.com/pyke369/golang-support/prefixdb/cmd/Makefile deleted file mode 100644 index b4d8d5c..0000000 --- a/vendor/github.com/pyke369/golang-support/prefixdb/cmd/Makefile +++ /dev/null @@ -1,11 +0,0 @@ -prefixdb: prefixdb.go - @go build prefixdb.go && strip prefixdb - -run: prefixdb - @#./prefixdb city city.pfdb@'MMCITY 20190402' GeoIP2-City-Locations-en.csv GeoIP2-City-Blocks-IPv4.csv GeoIP2-City-Blocks-IPv6.csv - @#./prefixdb asn asn.pfdb@'MMASN 20190402' GeoLite2-ASN-Blocks-IPv4.csv GeoLite2-ASN-Blocks-IPv6.csv - @./prefixdb lookup city.pfdb asn.pfdb 78.193.67.63 188.65.124.26 - @#./prefixdb server *:8000 city.pfdb asn.pfdb - -clean: - @rm -f prefixdb *.pfdb diff --git a/vendor/github.com/pyke369/golang-support/prefixdb/cmd/go.mod b/vendor/github.com/pyke369/golang-support/prefixdb/cmd/go.mod deleted file mode 100644 index 7dfafbe..0000000 --- a/vendor/github.com/pyke369/golang-support/prefixdb/cmd/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module . - -go 1.12 - -require github.com/pyke369/golang-support v0.0.0-20190428173758-fae1fcd33c43 // indirect diff --git a/vendor/github.com/pyke369/golang-support/prefixdb/cmd/go.sum b/vendor/github.com/pyke369/golang-support/prefixdb/cmd/go.sum deleted file mode 100644 index b89598d..0000000 --- a/vendor/github.com/pyke369/golang-support/prefixdb/cmd/go.sum +++ /dev/null @@ -1,2 +0,0 @@ -github.com/pyke369/golang-support v0.0.0-20190428173758-fae1fcd33c43 h1:638A4GSCbTc/Z8N1TyymmC8iWQOE3BWnWJv9fzZeHJc= -github.com/pyke369/golang-support v0.0.0-20190428173758-fae1fcd33c43/go.mod h1:0XGrzgrEp0fa/+JSV8XZePUwyjnU6C3bMc7Xz2bHHKI= diff --git a/vendor/github.com/pyke369/golang-support/prefixdb/cmd/prefixdb.go b/vendor/github.com/pyke369/golang-support/prefixdb/cmd/prefixdb.go deleted file mode 100644 index 57bc683..0000000 --- a/vendor/github.com/pyke369/golang-support/prefixdb/cmd/prefixdb.go +++ /dev/null @@ -1,367 +0,0 @@ -package main - -import ( - "bufio" - "encoding/json" - "fmt" - "net" - "net/http" - "os" - "regexp" - "strconv" - "strings" - "time" - - "github.com/pyke369/golang-support/prefixdb" -) - -type LOCATION struct { - ContinentCode string - ContinentName string - CountryCode string - CountryName string - RegionCode string - RegionName string - StateCode string - StateName string - CityName string - TimeZone string - InEurope bool -} - -var ( - csvMatcher = regexp.MustCompile(`(?:,|\n|^)("(?:(?:"")*[^"]*)*"|[^",\n]*|(?:\n|$))`) - jsonMatcher = regexp.MustCompile(`^(\S+)(?:\s(\{.+?\}))?$`) - pfdb = prefixdb.New() -) - -func size(input int) string { - if input < 1024*1024 { - return fmt.Sprintf("%.1fkB", float64(input)/1024) - } - return fmt.Sprintf("%.1fMB", float64(input)/(1024*1024)) -} - -func mkjson() { - for index := 3; index < len(os.Args); index++ { - count := 0 - if handle, err := os.Open(os.Args[index]); err == nil { - reader := bufio.NewReader(handle) - last := time.Now() - start := last - for { - if line, err := reader.ReadString('\n'); err != nil { - break - } else { - if fields := jsonMatcher.FindStringSubmatch(strings.TrimSpace(line)); fields != nil { - if _, prefix, err := net.ParseCIDR(fields[1]); err == nil { - data := map[string]interface{}{} - json.Unmarshal([]byte(fields[2]), &data) - pfdb.Add(*prefix, data, [][]string{[]string{"key1", "key2"}}) - count++ - } - } - } - if now := time.Now(); now.Sub(last) >= 250*time.Millisecond { - last = now - fmt.Fprintf(os.Stderr, "\r- adding prefixes [%s] %d", os.Args[index], count) - } - } - handle.Close() - fmt.Fprintf(os.Stderr, "\r- added prefixes [%s] (%.3fs - %d entries)\n", os.Args[index], float64(time.Now().Sub(start))/float64(time.Second), count) - } - } - start := time.Now() - description := "" - if index := strings.Index(os.Args[2], "@"); index > 0 { - description = os.Args[2][index+1:] - os.Args[2] = os.Args[2][:index] - } - fmt.Fprintf(os.Stderr, "\r- saving database [%s]... ", os.Args[2]) - if _, err := pfdb.Save(os.Args[2], description); err != nil { - fmt.Fprintf(os.Stderr, "\r- saving database [%s] failed (%v)\n", os.Args[2], err) - } else { - fmt.Fprintf(os.Stderr, "\r- saved database [%s] (%.3fs - total[%s] strings[%s] numbers[%s] pairs[%s] clusters[%s] maps[%s] nodes[%s])\n", - os.Args[2], float64(time.Now().Sub(start))/float64(time.Second), size(pfdb.Total), size(pfdb.Strings[0]), - size(pfdb.Numbers[0]), size(pfdb.Pairs[0]), size(pfdb.Clusters[0]), size(pfdb.Maps[0]), size(pfdb.Nodes[0]), - ) - } -} - -func mkcity() { - locations := map[int]*LOCATION{} - if handle, err := os.Open(os.Args[3]); err == nil { - reader := bufio.NewReader(handle) - last := time.Now() - start := last - for { - if line, err := reader.ReadString('\n'); err != nil { - break - } else { - if fields := csvMatcher.FindAllStringSubmatch(strings.TrimSpace(line), -1); len(fields) == 14 { - for index := 0; index < len(fields); index++ { - fields[index][1] = strings.Trim(fields[index][1], `"`) - } - if id, err := strconv.Atoi(fields[0][1]); err == nil { - locations[id] = &LOCATION{ - ContinentCode: fields[2][1], - ContinentName: fields[3][1], - CountryCode: fields[4][1], - CountryName: fields[5][1], - RegionCode: fields[6][1], - RegionName: fields[7][1], - StateCode: fields[8][1], - StateName: fields[9][1], - CityName: fields[10][1], - TimeZone: fields[12][1], - InEurope: fields[13][1] == "1", - } - } - } - } - if now := time.Now(); now.Sub(last) >= 250*time.Millisecond { - last = now - fmt.Fprintf(os.Stderr, "\r- loading locations [%s] %d", os.Args[3], len(locations)) - } - } - handle.Close() - fmt.Fprintf(os.Stderr, "\r- loaded locations [%s] (%.3fs - %d entries)\n", os.Args[3], float64(time.Now().Sub(start))/float64(time.Second), len(locations)) - } - - clusters := [][]string{ - []string{"continent_code", "continent_name", "country_code", "country_name", "region_code", "region_name", "state_code", "state_name", "timezone", "in_europe"}, - []string{"city_name", "postal_code", "latitude", "longitude"}, - } - for index := 4; index < len(os.Args); index++ { - count := 0 - if handle, err := os.Open(os.Args[index]); err == nil { - reader := bufio.NewReader(handle) - last := time.Now() - start := last - for { - if line, err := reader.ReadString('\n'); err != nil { - break - } else { - if fields := strings.Split(strings.TrimSpace(line), ","); len(fields) == 10 { - id := 0 - if id, _ = strconv.Atoi(fields[1]); id == 0 { - id, _ = strconv.Atoi(fields[2]) - } - if id != 0 && locations[id] != nil { - if _, prefix, err := net.ParseCIDR(fields[0]); err == nil { - latitude, _ := strconv.ParseFloat(fields[7], 64) - longitude, _ := strconv.ParseFloat(fields[8], 64) - pfdb.Add(*prefix, map[string]interface{}{ - "continent_code": locations[id].ContinentCode, - "continent_name": locations[id].ContinentName, - "country_code": locations[id].CountryCode, - "country_name": locations[id].CountryName, - "region_code": locations[id].RegionCode, - "region_name": locations[id].RegionName, - "state_code": locations[id].StateCode, - "state_name": locations[id].StateName, - "city_name": locations[id].CityName, - "in_europe": locations[id].InEurope, - "timezone": locations[id].TimeZone, - "postal_code": fields[6], - "latitude": latitude, - "longitude": longitude, - }, clusters) - count++ - } - } - } - } - if now := time.Now(); now.Sub(last) >= 250*time.Millisecond { - last = now - fmt.Fprintf(os.Stderr, "\r- adding prefixes [%s] %d", os.Args[index], count) - } - } - handle.Close() - fmt.Fprintf(os.Stderr, "\r- added prefixes [%s] (%.3fs - %d entries)\n", os.Args[index], float64(time.Now().Sub(start))/float64(time.Second), count) - } - } - - start := time.Now() - description := "" - if index := strings.Index(os.Args[2], "@"); index > 0 { - description = os.Args[2][index+1:] - os.Args[2] = os.Args[2][:index] - } - fmt.Fprintf(os.Stderr, "\r- saving database [%s]... ", os.Args[2]) - if _, err := pfdb.Save(os.Args[2], description); err != nil { - fmt.Fprintf(os.Stderr, "\r- saving database [%s] failed (%v)\n", os.Args[2], err) - } else { - fmt.Fprintf(os.Stderr, "\r- saved database [%s] (%.3fs - total[%s] strings[%s] numbers[%s] pairs[%s] clusters[%s] maps[%s] nodes[%s])\n", - os.Args[2], float64(time.Now().Sub(start))/float64(time.Second), size(pfdb.Total), size(pfdb.Strings[0]), - size(pfdb.Numbers[0]), size(pfdb.Pairs[0]), size(pfdb.Clusters[0]), size(pfdb.Maps[0]), size(pfdb.Nodes[0]), - ) - } -} - -func mkasn() { - for index := 3; index < len(os.Args); index++ { - count := 0 - if handle, err := os.Open(os.Args[index]); err == nil { - reader := bufio.NewReader(handle) - last := time.Now() - start := last - for { - if line, err := reader.ReadString('\n'); err != nil { - break - } else { - if fields := csvMatcher.FindAllStringSubmatch(strings.TrimSpace(line), -1); len(fields) == 3 { - for index := 0; index < len(fields); index++ { - fields[index][1] = strings.Trim(fields[index][1], `"`) - } - if asnum, _ := strconv.Atoi(fields[1][1]); asnum != 0 { - if _, prefix, err := net.ParseCIDR(fields[0][1]); err == nil { - pfdb.Add(*prefix, map[string]interface{}{ - "as_number": fmt.Sprintf("AS%d", asnum), - "as_name": fields[2][1], - }, nil) - count++ - } - } - } - } - if now := time.Now(); now.Sub(last) >= 250*time.Millisecond { - last = now - fmt.Fprintf(os.Stderr, "\r- adding prefixes [%s] %d", os.Args[index], count) - } - } - handle.Close() - fmt.Fprintf(os.Stderr, "\r- added prefixes [%s] (%.3fs - %d entries)\n", os.Args[index], float64(time.Now().Sub(start))/float64(time.Second), count) - } - } - - start := time.Now() - description := "" - if index := strings.Index(os.Args[2], "@"); index > 0 { - description = os.Args[2][index+1:] - os.Args[2] = os.Args[2][:index] - } - fmt.Fprintf(os.Stderr, "\r- saving database [%s]... ", os.Args[2]) - if _, err := pfdb.Save(os.Args[2], description); err != nil { - fmt.Fprintf(os.Stderr, "\r- saving database [%s] failed (%v)\n", os.Args[2], err) - } else { - fmt.Fprintf(os.Stderr, "\r- saved database [%s] (%.3fs - total[%s] strings[%s] numbers[%s] pairs[%s] clusters[%s] maps[%s] nodes[%s])\n", - os.Args[2], float64(time.Now().Sub(start))/float64(time.Second), size(pfdb.Total), size(pfdb.Strings[0]), - size(pfdb.Numbers[0]), size(pfdb.Pairs[0]), size(pfdb.Clusters[0]), size(pfdb.Maps[0]), size(pfdb.Nodes[0]), - ) - } -} - -func lookup() { - databases := []*prefixdb.PrefixDB{} - for index := 2; index < len(os.Args); index++ { - if strings.HasSuffix(os.Args[index], `.pfdb`) { - fmt.Fprintf(os.Stderr, "\r- loading database [%s]...", os.Args[index]) - database := prefixdb.New() - if err := database.Load(os.Args[index]); err == nil { - fmt.Fprintf(os.Stderr, "\r- loaded database [%s] (total[%s] version[%d.%d.%d] description[%s])\n", - os.Args[index], size(database.Total), (database.Version>>16)&0xff, (database.Version>>8)&0xff, database.Version&0xff, database.Description) - databases = append(databases, database) - } else { - fmt.Fprintf(os.Stderr, "\r- loading database [%s] failed (%v)\n", os.Args[index], err) - } - } else { - if ip := net.ParseIP(os.Args[index]); ip == nil { - fmt.Fprintf(os.Stderr, "- lookup [%s] failed (not a valid IP address)", os.Args[index]) - } else { - fmt.Fprintf(os.Stderr, "- lookup [%s] ", os.Args[index]) - lookup := map[string]interface{}{} - for _, database := range databases { - lookup, _ = database.Lookup(ip, lookup) - } - data, _ := json.Marshal(lookup) - fmt.Printf("%s\n", data) - } - } - } -} - -func server() { - databases := []*prefixdb.PrefixDB{} - for index := 3; index < len(os.Args); index++ { - fmt.Fprintf(os.Stderr, "\r- loading database [%s]...", os.Args[index]) - database := prefixdb.New() - if err := database.Load(os.Args[index]); err == nil { - fmt.Fprintf(os.Stderr, "\r- loaded database [%s] (total[%s] version[%d.%d.%d] description[%s])\n", - os.Args[index], size(database.Total), (database.Version>>16)&0xff, (database.Version>>8)&0xff, database.Version&0xff, database.Description) - databases = append(databases, database) - } else { - fmt.Fprintf(os.Stderr, "\r- loading database [%s] failed (%v)\n", os.Args[index], err) - } - } - http.HandleFunc("/", func(response http.ResponseWriter, request *http.Request) { - response.Header().Set("Content-Type", "application/json") - remote, _, _ := net.SplitHostPort(request.RemoteAddr) - if value := request.Header.Get("X-Forwarded-For"); value != "" { - remote = strings.Split(value, ",")[0] - } - parameters := request.URL.Query() - if value := parameters.Get("remote"); value != "" { - remote = value - } - lookup := map[string]interface{}{} - if ip := net.ParseIP(remote); ip != nil { - lookup["ip"] = fmt.Sprintf("%s", ip) - for _, database := range databases { - lookup, _ = database.Lookup(ip, lookup) - } - data, _ := json.Marshal(lookup) - response.Write(data) - } - }) - parts := strings.Split(os.Args[2], ",") - server := &http.Server{Addr: strings.TrimLeft(parts[0], "*"), ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second} - fmt.Fprintf(os.Stderr, "\r- listening to [%s]\n", os.Args[2]) - if len(parts) > 1 { - server.ListenAndServeTLS(parts[1], parts[2]) - } else { - server.ListenAndServe() - } -} - -func usage(status int) { - fmt.Fprintf(os.Stderr, "usage: prefixdb [parameters...]\n\n"+ - "help show this help screen\n"+ - "json ]> ... build database from generic JSON-formatted prefixes lists\n"+ - "city ]> ... build database from MaxMind GeoIP2 cities lists\n"+ - "asn ]> ... build database from MaxMind GeoLite2 asnums lists\n"+ - "lookup ...
... lookup entries in databases\n"+ - "server ... spawn an HTTP(S) server for entries lookup\n") - os.Exit(status) -} - -func main() { - if len(os.Args) < 2 { - usage(1) - } - switch os.Args[1] { - case "help": - usage(0) - case "json": - if len(os.Args) < 3 { - usage(1) - } - mkjson() - case "city": - if len(os.Args) < 5 { - usage(1) - } - mkcity() - case "asn": - if len(os.Args) < 3 { - usage(1) - } - mkasn() - case "lookup": - lookup() - case "server": - server() - default: - usage(2) - } -} diff --git a/vendor/github.com/pyke369/golang-support/prefixdb/prefixdb.go b/vendor/github.com/pyke369/golang-support/prefixdb/prefixdb.go deleted file mode 100644 index 14b9a25..0000000 --- a/vendor/github.com/pyke369/golang-support/prefixdb/prefixdb.go +++ /dev/null @@ -1,827 +0,0 @@ -package prefixdb - -import ( - "bytes" - "crypto/md5" - "encoding/binary" - "errors" - "fmt" - "io/ioutil" - "math" - "net" - "os" - "runtime" - "sort" - "strings" - "sync" - "time" -) - -const VERSION = 0x00010000 - -type fame struct { - fame int - value interface{} -} -type byfame []*fame - -func (a byfame) Len() int { return len(a) } -func (a byfame) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a byfame) Less(i, j int) bool { return a[i].fame > a[j].fame } - -type node struct { - down [2]*node - up *node - data []uint64 - offset int - explored [4]bool - emitted bool - id int -} -type cluster struct { - values [3]int // fame / initial index / final index - pairs []uint64 // cluster pairs - data []byte // reduced cluster pairs -} -type PrefixDB struct { - sync.RWMutex - tree node - strings map[string]*[3]int // fame / initial index / final index - numbers map[float64]*[3]int // fame / initial index / final index - pairs map[uint64]*[3]int // fame / initial index / final index - clusters map[[16]byte]*cluster - data []byte - Total int - Version uint32 - Description string - Strings [4]int // size / count / offset / strings index width (bytes) - Numbers [3]int // size / count / offset - Pairs [3]int // size / count / offset - Clusters [4]int // size / count / offset / clusters index width (bytes) - Maps [3]int // size / count / offset - Nodes [4]int // size / count / offset / nodes width (bits) -} - -var cores int - -func New() *PrefixDB { - if cores == 0 { - cores = runtime.NumCPU() - } - return &PrefixDB{strings: map[string]*[3]int{}, numbers: map[float64]*[3]int{}, pairs: map[uint64]*[3]int{}, clusters: map[[16]byte]*cluster{}} -} - -func (this *PrefixDB) Add(prefix net.IPNet, data map[string]interface{}, clusters [][]string) { - prefix.IP = prefix.IP.To16() - ones, bits := prefix.Mask.Size() - if bits == 32 { - ones += 96 - prefix.Mask = net.CIDRMask(ones, bits+96) - } - if cores > 1 { - this.Lock() - } - pnode := &this.tree - for bit := 0; bit < ones; bit++ { - down := 0 - if (prefix.IP[bit/8] & (1 << (7 - (byte(bit) % 8)))) != 0 { - down = 1 - } - if pnode.down[down] == nil { - pnode.down[down] = &node{} - pnode.down[down].up = pnode - } - if len(pnode.data) != 0 { - pnode.data = []uint64{} - } - pnode = pnode.down[down] - } - - skeys, ckeys, lkeys := "", [][]string{}, []string{} - for _, cluster := range clusters { - skeys += strings.Join(cluster, ` `) + ` ` - ckeys = append(ckeys, cluster) - } - for key, _ := range data { - if strings.Index(skeys, key) < 0 { - lkeys = append(lkeys, key) - } - } - ckeys = append(ckeys, lkeys) - for cindex, keys := range ckeys { - cpairs := []uint64{} - for _, key := range keys { - if len(key) > 255 { - continue - } - if value, ok := data[key]; ok { - index := 0 - if _, ok := this.strings[key]; !ok { - index = len(this.strings) - this.strings[key] = &[3]int{1, index} - } else { - index = this.strings[key][1] - this.strings[key][0]++ - } - pair := uint64((uint32(index)&0x0fffffff)|0x10000000) << 32 - if tvalue, ok := value.(string); ok { - if len(tvalue) <= 255 { - index = 0 - if _, ok := this.strings[tvalue]; !ok { - index = len(this.strings) - this.strings[tvalue] = &[3]int{1, index} - } else { - index = this.strings[tvalue][1] - this.strings[tvalue][0]++ - } - pair |= uint64((uint32(index) & 0x0fffffff) | 0x10000000) - } else { - pair |= uint64(0x50000000) - } - } else if tvalue, ok := value.(float64); ok { - index = 0 - if _, ok := this.numbers[tvalue]; !ok { - index = len(this.numbers) - this.numbers[tvalue] = &[3]int{1, index} - } else { - index = this.numbers[tvalue][1] - this.numbers[tvalue][0]++ - } - pair |= uint64((uint32(index) & 0x0fffffff) | 0x20000000) - } else if tvalue, ok := value.(bool); ok { - if tvalue { - pair |= uint64(0x30000000) - } else { - pair |= uint64(0x40000000) - } - } else { - pair |= uint64(0x50000000) - } - if _, ok := this.pairs[pair]; !ok { - index = len(this.pairs) - this.pairs[pair] = &[3]int{1, index} - } else { - this.pairs[pair][0]++ - } - if cindex < len(ckeys)-1 { - cpairs = append(cpairs, pair) - } else { - pnode.data = append(pnode.data, pair) - } - } - } - if len(cpairs) != 0 { - buffer := make([]byte, len(cpairs)*8) - for index, value := range cpairs { - binary.BigEndian.PutUint64(buffer[index*8:], value) - } - key := md5.Sum(buffer) - index := 0 - if _, ok := this.clusters[key]; !ok { - index = len(this.clusters) - this.clusters[key] = &cluster{pairs: cpairs, values: [3]int{1, index}} - } else { - index = this.clusters[key].values[1] - this.clusters[key].values[0]++ - } - pnode.data = append(pnode.data, 0x7000000000000000|((uint64(index)<<32)&0x0fffffff00000000)) - } - } - if cores > 1 { - this.Unlock() - } -} - -func wbytes(bytes, value int, data []byte) { - if len(data) >= bytes { - for index := bytes - 1; index >= 0; index-- { - data[bytes-index-1] = byte(value >> (uint(index * 8))) - } - } -} -func wpbits(prefix byte, value int) []byte { - if value <= 7 { - return []byte{prefix | (byte(value) & 0x07)} - } - bytes := int(math.Ceil(math.Ceil(math.Log2(float64(value+1))) / 8)) - data := []byte{prefix | 0x08 | byte(bytes)} - for nibble := bytes - 1; nibble >= 0; nibble-- { - data = append(data, byte(value>>(uint(nibble*8)))) - } - return data -} -func wnbits(bits, value0, value1 int, data []byte) { - if bits >= 8 && bits <= 32 && bits%4 == 0 && len(data) >= bits/4 { - switch bits { - case 8: - data[0], data[1] = byte(value0), byte(value1) - case 12: - data[0], data[1], data[2] = byte(value0>>4), byte(value0<<4)|(byte(value1>>8)&0x0f), byte(value1) - case 16: - binary.BigEndian.PutUint16(data[0:], uint16(value0)) - binary.BigEndian.PutUint16(data[2:], uint16(value1)) - case 20: - data[0], data[1] = byte(value0>>12), byte(value0>>4) - data[2] = byte(value0<<4) | (byte(value1>>16) & 0x0f) - data[3], data[4] = byte(value1>>8), byte(value1) - case 24: - data[0], data[1], data[2] = byte(value0>>16), byte(value0>>8), byte(value0) - data[3], data[4], data[5] = byte(value1>>16), byte(value1>>8), byte(value1) - case 28: - data[0], data[1], data[2] = byte(value0>>20), byte(value0>>12), byte(value0>>4) - data[3] = byte(value0<<4) | (byte(value1>>24) & 0x0f) - data[4], data[5], data[6] = byte(value1>>16), byte(value1>>8), byte(value1) - case 32: - binary.BigEndian.PutUint32(data[0:], uint32(value0)) - binary.BigEndian.PutUint32(data[4:], uint32(value1)) - } - } -} -func (this *PrefixDB) Save(path, description string) (content []byte, err error) { - // layout header + signature placeholder + description - if cores > 1 { - this.Lock() - } - this.data = []byte{'P', 'F', 'D', 'B', 0, (VERSION >> 16) & 0xff, (VERSION >> 8) & 0xff, (VERSION & 0xff), - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'D', 'E', 'S', 'C', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} - if description == "" { - description = time.Now().Format(`20060102150405`) - } - this.Description = description - copy(this.data[28:47], []byte(description)) - - // layout strings dictionary (ordered by fame) - this.Strings[0] = 0 - for key, _ := range this.strings { - this.Strings[0] += len(key) - } - this.Strings[3] = int(math.Ceil(math.Ceil(math.Log2(float64(this.Strings[0]+1))) / 8)) - this.data = append(this.data, []byte{'S', 'T', 'R', 'S', byte(this.Strings[3]), 0, 0, 0, 0, 0, 0, 0, 0}...) - this.Strings[2] = len(this.data) - this.Strings[1] = len(this.strings) - this.Strings[0] += this.Strings[1] * this.Strings[3] - flist := make([]*fame, this.Strings[1]) - for key, values := range this.strings { - flist[values[1]] = &fame{values[0], key} - } - sort.Sort(byfame(flist)) - this.data = append(this.data, make([]byte, this.Strings[1]*this.Strings[3])...) - offset := 0 - for index, item := range flist { - this.strings[item.value.(string)][2] = index - this.data = append(this.data, []byte(item.value.(string))...) - wbytes(this.Strings[3], offset, this.data[this.Strings[2]+(index*this.Strings[3]):]) - offset += len(item.value.(string)) - } - binary.BigEndian.PutUint32(this.data[this.Strings[2]-8:], uint32(this.Strings[0])) - binary.BigEndian.PutUint32(this.data[this.Strings[2]-4:], uint32(this.Strings[1])) - strings := make([]*fame, this.Strings[1]) - for key, values := range this.strings { - strings[values[1]] = &fame{values[0], key} - } - - // layout numbers dictionary (ordered by fame) - this.data = append(this.data, []byte{'N', 'U', 'M', 'S', 0, 0, 0, 0, 0, 0, 0, 0}...) - this.Numbers[2] = len(this.data) - this.Numbers[1] = len(this.numbers) - this.Numbers[0] = this.Numbers[1] * 8 - flist = make([]*fame, this.Numbers[1]) - for key, values := range this.numbers { - flist[values[1]] = &fame{values[0], key} - } - sort.Sort(byfame(flist)) - this.data = append(this.data, make([]byte, this.Numbers[1]*8)...) - for index, item := range flist { - this.numbers[item.value.(float64)][2] = index - binary.BigEndian.PutUint64(this.data[this.Numbers[2]+(index*8):], math.Float64bits(item.value.(float64))) - } - binary.BigEndian.PutUint32(this.data[this.Numbers[2]-8:], uint32(this.Numbers[0])) - binary.BigEndian.PutUint32(this.data[this.Numbers[2]-4:], uint32(this.Numbers[1])) - numbers := make([]*fame, this.Numbers[1]) - for key, values := range this.numbers { - numbers[values[1]] = &fame{values[0], key} - } - - // layout pairs dictionary (ordered by fame) - this.data = append(this.data, []byte{'P', 'A', 'I', 'R', 0, 0, 0, 0, 0, 0, 0, 0}...) - this.Pairs[2] = len(this.data) - flist = make([]*fame, len(this.pairs)) - for key, values := range this.pairs { - flist[values[1]] = &fame{values[0], key} - } - sort.Sort(byfame(flist)) - for index, item := range flist { - if item.fame > 1 { - this.pairs[item.value.(uint64)][2] = index - } else { - delete(this.pairs, item.value.(uint64)) - } - } - this.Pairs[1] = len(this.pairs) - this.Pairs[0] = this.Pairs[1] * 8 - this.data = append(this.data, make([]byte, this.Pairs[0])...) - for index, item := range flist { - if item.fame <= 1 { - break - } - pair := 0x1000000000000000 | (uint64(this.strings[strings[(item.value.(uint64)>>32)&0x0fffffff].value.(string)][2]) << 32) - switch (item.value.(uint64) & 0xf0000000) >> 28 { - case 1: - pair |= 0x10000000 | uint64(this.strings[strings[item.value.(uint64)&0x0fffffff].value.(string)][2]) - case 2: - pair |= 0x20000000 | uint64(this.numbers[numbers[item.value.(uint64)&0x0fffffff].value.(float64)][2]) - default: - pair |= item.value.(uint64) & 0xf0000000 - } - binary.BigEndian.PutUint64(this.data[this.Pairs[2]+(index*8):], pair) - } - binary.BigEndian.PutUint32(this.data[this.Pairs[2]-8:], uint32(this.Pairs[0])) - binary.BigEndian.PutUint32(this.data[this.Pairs[2]-4:], uint32(this.Pairs[1])) - - // layout clusters dictionary (ordered by fame, and reduced for strings, numbers and pairs) - this.Clusters[0] = 0 - for _, cluster := range this.clusters { - for _, pair := range cluster.pairs { - if _, ok := this.pairs[pair]; ok { - cluster.data = append(cluster.data, wpbits(0x60, this.pairs[pair][2])...) - } else { - cluster.data = append(cluster.data, wpbits(0x10, this.strings[strings[(pair>>32)&0x0fffffff].value.(string)][2])...) - switch (pair & 0xf0000000) >> 28 { - case 1: - cluster.data = append(cluster.data, wpbits(0x10, this.strings[strings[pair&0x0fffffff].value.(string)][2])...) - case 2: - cluster.data = append(cluster.data, wpbits(0x20, this.numbers[numbers[pair&0x0fffffff].value.(float64)][2])...) - default: - cluster.data = append(cluster.data, byte((pair&0xf0000000)>>24)) - } - } - } - this.Clusters[0] += len(cluster.data) - } - this.Clusters[3] = int(math.Ceil(math.Ceil(math.Log2(float64(this.Clusters[0]+1))) / 8)) - this.data = append(this.data, []byte{'C', 'L', 'U', 'S', byte(this.Clusters[3]), 0, 0, 0, 0, 0, 0, 0, 0}...) - this.Clusters[2] = len(this.data) - this.Clusters[1] = len(this.clusters) - this.Clusters[0] += this.Clusters[1] * this.Clusters[3] - flist = make([]*fame, this.Clusters[1]) - for key, cluster := range this.clusters { - flist[cluster.values[1]] = &fame{cluster.values[0], key} - } - sort.Sort(byfame(flist)) - this.data = append(this.data, make([]byte, this.Clusters[1]*this.Clusters[3])...) - offset = 0 - for index, item := range flist { - this.clusters[item.value.([16]byte)].values[2] = index - this.data = append(this.data, this.clusters[item.value.([16]byte)].data...) - wbytes(this.Clusters[3], offset, this.data[this.Clusters[2]+(index*this.Clusters[3]):]) - offset += len(this.clusters[item.value.([16]byte)].data) - } - binary.BigEndian.PutUint32(this.data[this.Clusters[2]-8:], uint32(this.Clusters[0])) - binary.BigEndian.PutUint32(this.data[this.Clusters[2]-4:], uint32(this.Clusters[1])) - clusters := make([]*fame, this.Clusters[1]) - for key, cluster := range this.clusters { - clusters[cluster.values[1]] = &fame{cluster.values[0], key} - } - - // layout maps dictionary (reduced for strings, numbers, pairs and clusters) - this.Nodes[1] = 1 - this.data = append(this.data, []byte{'M', 'A', 'P', 'S', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x80}...) - pnode := &this.tree - this.Maps[2] = len(this.data) - 2 - this.Maps[1] = 1 - this.Maps[0] = 2 - for { - if pnode.down[0] != nil && !pnode.explored[0] { - pnode.explored[0] = true - pnode = pnode.down[0] - } else if pnode.down[1] != nil && !pnode.explored[1] { - pnode.explored[1] = true - pnode = pnode.down[1] - } else if pnode.up != nil { - pnode = pnode.up - } - if pnode.up == nil { - break - } - if pnode.down[0] == nil && pnode.down[1] == nil { - if len(pnode.data) == 0 { - pnode.offset = 1 - } else { - data := []byte{} - for index := 0; index < len(pnode.data); index++ { - last := byte(0x00) - if index == len(pnode.data)-1 { - last = 0x80 - } - if ((pnode.data[index]>>32)&0xf0000000)>>28 == 7 { - data = append(data, wpbits(last|0x70, this.clusters[clusters[(pnode.data[index]>>32)&0x0fffffff].value.([16]byte)].values[2])...) - } else { - if _, ok := this.pairs[pnode.data[index]]; ok { - data = append(data, wpbits(last|0x60, this.pairs[pnode.data[index]][2])...) - } else { - data = append(data, wpbits(0x10, this.strings[strings[(pnode.data[index]>>32)&0x0fffffff].value.(string)][2])...) - switch (pnode.data[index] & 0xf0000000) >> 28 { - case 1: - data = append(data, wpbits(last|0x10, this.strings[strings[pnode.data[index]&0x0fffffff].value.(string)][2])...) - case 2: - data = append(data, wpbits(last|0x20, this.numbers[numbers[pnode.data[index]&0x0fffffff].value.(float64)][2])...) - default: - data = append(data, last|byte((pnode.data[index]&0xf0000000)>>24)) - } - } - } - } - this.data = append(this.data, data...) - pnode.offset = this.Maps[0] - this.Maps[0] += len(data) - this.Maps[1]++ - } - } else if pnode.id == 0 { - pnode.id = this.Nodes[1] - this.Nodes[1]++ - } - } - binary.BigEndian.PutUint32(this.data[this.Maps[2]-8:], uint32(this.Maps[0])) - binary.BigEndian.PutUint32(this.data[this.Maps[2]-4:], uint32(this.Maps[1])) - - // layout nodes tree - this.Nodes[3] = int(math.Ceil(math.Ceil(math.Log2(float64(this.Nodes[1]+this.Maps[0]+1)))/4) * 4) - this.data = append(this.data, []byte{'N', 'O', 'D', 'E', byte(this.Nodes[3]), 0, 0, 0, 0, 0, 0, 0, 0}...) - this.Nodes[2] = len(this.data) - this.Nodes[0] = this.Nodes[1] * ((2 * this.Nodes[3]) / 8) - this.data = append(this.data, make([]byte, this.Nodes[0])...) - pnode = &this.tree - next := [2]int{} - for { - if (pnode == &this.tree || pnode.id != 0) && !pnode.emitted { - pnode.emitted = true - for index := 0; index <= 1; index++ { - next[index] = this.Nodes[1] - if pnode.down[index] != nil { - if pnode.down[index].id != 0 { - next[index] = pnode.down[index].id - } else { - next[index] += pnode.down[index].offset - } - } - } - wnbits(this.Nodes[3], next[0], next[1], this.data[this.Nodes[2]+(pnode.id*((2*this.Nodes[3])/8)):]) - } - if pnode.down[0] != nil && !pnode.explored[2] { - pnode.explored[2] = true - pnode = pnode.down[0] - } else if pnode.down[1] != nil && !pnode.explored[3] { - pnode.explored[3] = true - pnode = pnode.down[1] - } else if pnode.up != nil { - pnode = pnode.up - } - if pnode.up == nil { - break - } - } - binary.BigEndian.PutUint32(this.data[this.Nodes[2]-8:], uint32(this.Nodes[0])) - binary.BigEndian.PutUint32(this.data[this.Nodes[2]-4:], uint32(this.Nodes[1])) - - // finalize header - this.tree, this.strings, this.numbers, this.pairs, this.clusters = node{}, map[string]*[3]int{}, map[float64]*[3]int{}, map[uint64]*[3]int{}, map[[16]byte]*cluster{} - hash := md5.Sum(this.data[24:]) - copy(this.data[8:], hash[:]) - this.Total = len(this.data) - - // save database - if path != "" { - if path == "-" { - _, err = os.Stdout.Write(this.data) - } else { - err = ioutil.WriteFile(path, this.data, 0644) - } - } - - if cores > 1 { - this.Unlock() - } - return this.data, err -} - -func (this *PrefixDB) Load(path string) error { - if data, err := ioutil.ReadFile(path); err != nil { - return err - } else { - if len(data) < 8 || string(data[0:4]) != "PFDB" { - return errors.New(`invalid preamble`) - } - if version := (uint32(data[5]) << 16) + (uint32(data[6]) << 8) + uint32(data[7]); (version & 0xff0000) > (VERSION & 0xff0000) { - return errors.New(fmt.Sprintf(`library major version %d is incompatible with database major version %d`, (VERSION&0xff0000)>>16, (version&0xff0000)>>16)) - } else { - if len(data) < 24 || fmt.Sprintf("%x", md5.Sum(data[24:])) != fmt.Sprintf("%x", data[8:24]) { - return errors.New(`database checksum is invalid`) - } - if cores > 1 { - this.Lock() - } - this.data = data - this.Total = len(data) - this.Version = version - offset := 24 - if this.Total >= offset+4 && string(data[offset:offset+4]) == "DESC" { - offset += 4 - if this.Total >= offset+20 { - index := 0 - if index = bytes.Index(data[offset:offset+20], []byte{0}); index < 0 { - index = 20 - } - this.Description = fmt.Sprintf("%s", data[offset:offset+index]) - offset += 20 - if this.Total >= offset+4 && string(data[offset:offset+4]) == "STRS" { - offset += 4 - if this.Total >= offset+9 { - this.Strings[3] = int(data[offset]) - this.Strings[2] = offset + 9 - this.Strings[1] = int(binary.BigEndian.Uint32(this.data[offset+5:])) - this.Strings[0] = int(binary.BigEndian.Uint32(this.data[offset+1:])) - offset += 9 + this.Strings[0] - if this.Total >= offset+4 && string(data[offset:offset+4]) == "NUMS" { - offset += 4 - if this.Total >= offset+8 { - this.Numbers[2] = offset + 8 - this.Numbers[1] = int(binary.BigEndian.Uint32(this.data[offset+4:])) - this.Numbers[0] = int(binary.BigEndian.Uint32(this.data[offset:])) - offset += 8 + this.Numbers[0] - if this.Total >= offset+4 && string(data[offset:offset+4]) == "PAIR" { - offset += 4 - if this.Total >= offset+8 { - this.Pairs[2] = offset + 8 - this.Pairs[1] = int(binary.BigEndian.Uint32(this.data[offset+4:])) - this.Pairs[0] = int(binary.BigEndian.Uint32(this.data[offset:])) - offset += 8 + this.Pairs[0] - if this.Total >= offset+4 && string(data[offset:offset+4]) == "CLUS" { - offset += 4 - this.Clusters[3] = int(data[offset]) - this.Clusters[2] = offset + 9 - this.Clusters[1] = int(binary.BigEndian.Uint32(this.data[offset+5:])) - this.Clusters[0] = int(binary.BigEndian.Uint32(this.data[offset+1:])) - offset += 9 + this.Clusters[0] - if this.Total >= offset+4 && string(data[offset:offset+4]) == "MAPS" { - offset += 4 - if this.Total >= offset+8 { - this.Maps[2] = offset + 8 - this.Maps[1] = int(binary.BigEndian.Uint32(this.data[offset+4:])) - this.Maps[0] = int(binary.BigEndian.Uint32(this.data[offset:])) - offset += 8 + this.Maps[0] - if this.Total >= offset+9 && string(data[offset:offset+4]) == "NODE" { - offset += 4 - this.Nodes[3] = int(data[offset]) - this.Nodes[2] = offset + 9 - this.Nodes[1] = int(binary.BigEndian.Uint32(this.data[offset+5:])) - this.Nodes[0] = int(binary.BigEndian.Uint32(this.data[offset+1:])) - if offset+9+this.Nodes[0] != this.Total { - this.Nodes[2] = 0 - } - } - } - } - } - } - } - } - } - } - } - } - } - if cores > 1 { - this.Unlock() - } - if this.Strings[2] == 0 || this.Numbers[2] == 0 || this.Pairs[2] == 0 || this.Clusters[2] == 0 || this.Maps[2] == 0 || this.Nodes[2] == 0 { - return errors.New(`database structure is invalid`) - } - } - } - return nil -} -func rpbits(data []byte) (section, index, size int, last bool) { - section = int((data[0] & 0x70) >> 4) - if data[0]&0x80 != 0 || section == 0 { - last = true - } - if section == 1 || section == 2 || section == 6 || section == 7 { - if data[0]&0x08 != 0 { - size = int(data[0] & 0x07) - for nibble := 1; nibble <= size; nibble++ { - index |= int(data[nibble]) << (uint(size-nibble) * 8) - } - } else { - index = int(data[0] & 0x07) - } - } - size++ - return section, index, size, last -} -func rnbits(bits, index, down int, data []byte) int { - if bits >= 8 && bits <= 32 && bits%4 == 0 && (down == 0 || down == 1) && len(data) >= (index+1)*(bits/4) { - offset := index * (bits / 4) - switch bits { - case 8: - return int(data[offset+down]) - case 12: - if down == 0 { - return (int(data[offset]) << 4) | ((int(data[offset+1]) >> 4) & 0x0f) - } else { - return ((int(data[offset+1]) & 0x0f) << 8) | int(data[offset+2]) - } - case 16: - return int(binary.BigEndian.Uint16(data[offset+(down*2):])) - case 20: - if down == 0 { - return (int(data[offset]) << 12) | (int(data[offset+1]) << 4) | ((int(data[offset+2]) >> 4) & 0x0f) - } else { - return ((int(data[offset+2]) & 0x0f) << 16) | (int(data[offset+3]) << 8) | int(data[offset+4]) - } - case 24: - if down == 0 { - return (int(data[offset]) << 16) | (int(data[offset+1]) << 8) | int(data[offset+2]) - } else { - return (int(data[offset+3]) << 16) | (int(data[offset+4]) << 8) | int(data[offset+5]) - } - case 28: - if down == 0 { - return (int(data[offset]) << 20) | (int(data[offset+1]) << 12) | (int(data[offset+2]) << 4) | ((int(data[offset+3]) >> 4) & 0x0f) - } else { - return ((int(data[offset+3]) & 0x0f) << 24) | (int(data[offset+4]) << 16) | (int(data[offset+5]) << 8) | int(data[offset+6]) - } - case 32: - return int(binary.BigEndian.Uint32(data[offset+(down*4):])) - } - } - return index -} -func rbytes(width int, data []byte) (value int) { - for index := 0; index < width; index++ { - value |= int(data[index]) << (uint(width-1-index) * 8) - } - return value -} -func (this *PrefixDB) rstring(index int) string { - count, offset, width := this.Strings[1], this.Strings[2], this.Strings[3] - if index >= count { - return "" - } - start, end := rbytes(width, this.data[offset+(index*width):]), 0 - if index < count-1 { - end = rbytes(width, this.data[offset+(index+1)*width:]) - } else { - end = this.Strings[0] - (count * width) - } - return string(this.data[offset+(count*width)+start : offset+(count*width)+end]) -} -func (this *PrefixDB) rnumber(index int) float64 { - if index >= this.Numbers[1] { - return 0.0 - } - return math.Float64frombits(binary.BigEndian.Uint64(this.data[this.Numbers[2]+(index*8):])) -} -func (this *PrefixDB) rpair(index int, pairs map[string]interface{}) { - if index < this.Pairs[1] { - pair := binary.BigEndian.Uint64(this.data[this.Pairs[2]+(index*8):]) - if key := this.rstring(int((pair >> 32) & 0x0fffffff)); key != "" { - switch (pair & 0xf0000000) >> 28 { - case 1: - pairs[key] = this.rstring(int(pair & 0x0fffffff)) - case 2: - pairs[key] = this.rnumber(int(pair & 0x0fffffff)) - case 3: - pairs[key] = true - case 4: - pairs[key] = false - } - } - } -} -func (this *PrefixDB) rcluster(index int, pairs map[string]interface{}) { - count, offset, width := this.Clusters[1], this.Clusters[2], this.Clusters[3] - if index < count { - start, end := rbytes(width, this.data[offset+(index*width):]), 0 - if index < count-1 { - end = rbytes(width, this.data[offset+(index+1)*width:]) - } else { - end = this.Clusters[0] - (count * width) - } - start += offset + (count * width) - end += offset + (count * width) - key := "" - for start < end { - section, index, size, _ := rpbits(this.data[start:]) - switch section { - case 1: - if key != "" { - pairs[key] = this.rstring(index) - key = "" - } else { - key = this.rstring(index) - } - case 2: - if key != "" { - pairs[key] = this.rnumber(index) - key = "" - } - case 3: - if key != "" { - pairs[key] = true - key = "" - } - case 4: - if key != "" { - pairs[key] = false - key = "" - } - case 5: - if key != "" { - pairs[key] = nil - key = "" - } - case 6: - this.rpair(index, pairs) - } - start += size - } - } -} -func (this *PrefixDB) Lookup(address net.IP, input map[string]interface{}) (output map[string]interface{}, err error) { - output = input - if this.data == nil || this.Total == 0 || this.Version == 0 || this.Strings[2] == 0 || this.Numbers[2] == 0 || - this.Pairs[2] == 0 || this.Clusters[2] == 0 || this.Maps[2] == 0 || this.Nodes[2] == 0 || address == nil { - err = errors.New("record not found") - } else { - address = address.To16() - offset := 0 - if cores > 1 { - this.RLock() - } - for bit := 0; bit < 128; bit++ { - down := 0 - if (address[bit/8] & (1 << (7 - (byte(bit) % 8)))) != 0 { - down = 1 - } - offset = rnbits(this.Nodes[3], offset, down, this.data[this.Nodes[2]:]) - if offset == this.Nodes[1] || offset == 0 { - break - } - if output == nil { - output = map[string]interface{}{} - } - if offset > this.Nodes[1] { - offset -= this.Nodes[1] - if offset < this.Maps[0] { - offset += this.Maps[2] - key := "" - for offset < this.Maps[2]+this.Maps[0] { - section, index, size, last := rpbits(this.data[offset:]) - switch section { - case 1: - if key != "" { - output[key] = this.rstring(index) - key = "" - } else { - key = this.rstring(index) - } - case 2: - if key != "" { - output[key] = this.rnumber(index) - key = "" - } - case 3: - if key != "" { - output[key] = true - key = "" - } - case 4: - if key != "" { - output[key] = false - key = "" - } - case 5: - if key != "" { - output[key] = nil - key = "" - } - case 6: - this.rpair(index, output) - case 7: - this.rcluster(index, output) - } - if last { - break - } - offset += size - } - } - break - } - } - if cores > 1 { - this.RUnlock() - } - } - return output, err -} diff --git a/vendor/github.com/pyke369/golang-support/rcache/rcache.go b/vendor/github.com/pyke369/golang-support/rcache/rcache.go index e02ed73..f5c5f17 100644 --- a/vendor/github.com/pyke369/golang-support/rcache/rcache.go +++ b/vendor/github.com/pyke369/golang-support/rcache/rcache.go @@ -3,38 +3,25 @@ package rcache import ( "crypto/md5" "regexp" - "runtime" "sync" ) var ( - cores int cache map[[16]byte]*regexp.Regexp = map[[16]byte]*regexp.Regexp{} lock sync.RWMutex ) func Get(expression string) *regexp.Regexp { - if cores == 0 { - cores = runtime.NumCPU() - } key := md5.Sum([]byte(expression)) - if cores > 1 { - lock.RLock() - } + lock.RLock() if cache[key] != nil { - if cores > 1 { - defer lock.RUnlock() - } + defer lock.RUnlock() return cache[key].Copy() } - if cores > 1 { - lock.RUnlock() - } + lock.RUnlock() if regex, err := regexp.Compile(expression); err == nil { - if cores > 1 { - lock.Lock() - defer lock.Unlock() - } + lock.Lock() + defer lock.Unlock() cache[key] = regex return cache[key].Copy() } diff --git a/vendor/github.com/pyke369/golang-support/rpack/README.md b/vendor/github.com/pyke369/golang-support/rpack/README.md deleted file mode 100644 index e69de29..0000000 diff --git a/vendor/github.com/pyke369/golang-support/rpack/cmd/rpack.go b/vendor/github.com/pyke369/golang-support/rpack/cmd/rpack.go deleted file mode 100644 index 0799c04..0000000 --- a/vendor/github.com/pyke369/golang-support/rpack/cmd/rpack.go +++ /dev/null @@ -1,38 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "os" - "path/filepath" - - "github.com/pyke369/golang-support/rpack" -) - -func main() { - var ( - options flag.FlagSet - usemain bool - ) - - options = flag.FlagSet{Usage: func() { - fmt.Fprintf(os.Stderr, "usage: %s [options] rootdir\n\noptions are:\n\n", filepath.Base(os.Args[0])) - options.PrintDefaults() - }, - } - options.String("output", "static.go", "the generated output file path") - options.String("pkgname", "main", "the package name to use in the generated output") - options.String("funcname", "resources", "the function name to use in the generated output") - options.Bool("main", false, "whether to generate a main func or not") - if err := options.Parse(os.Args[1:]); err != nil { - os.Exit(1) - } - if options.NArg() == 0 { - options.Usage() - os.Exit(1) - } - if options.Lookup("main").Value.String() == "true" { - usemain = true - } - rpack.Pack(options.Arg(0), options.Lookup("output").Value.String(), options.Lookup("pkgname").Value.String(), options.Lookup("funcname").Value.String(), usemain) -} diff --git a/vendor/github.com/pyke369/golang-support/rpack/rpack.go b/vendor/github.com/pyke369/golang-support/rpack/rpack.go deleted file mode 100644 index 998802a..0000000 --- a/vendor/github.com/pyke369/golang-support/rpack/rpack.go +++ /dev/null @@ -1,155 +0,0 @@ -package rpack - -import ( - "bytes" - "compress/gzip" - "crypto/md5" - "encoding/base64" - "fmt" - "io/ioutil" - "math/rand" - "mime" - "net/http" - "os" - "path/filepath" - "strings" - "sync" - "time" -) - -type RPACK struct { - Compressed bool - Modified int64 - Mime string - Content string - raw []byte -} - -var guzpool = sync.Pool{ - New: func() interface{} { - return &gzip.Reader{} - }} - -func Pack(root, output, pkgname, funcname string, main bool) { - root = strings.TrimSuffix(root, "/") - if root == "" || output == "" { - return - } - if pkgname == "" || main { - pkgname = "main" - } - if funcname == "" { - funcname = "resources" - } - funcname = strings.ToUpper(funcname[:1]) + funcname[1:] - entries := map[string]*RPACK{} - compressor, _ := gzip.NewWriterLevel(nil, gzip.BestCompression) - count := 0 - size := int64(0) - start := time.Now() - filepath.Walk(root, func(path string, info os.FileInfo, err error) error { - rpath := strings.TrimPrefix(path, root+"/") - if info.Mode()&os.ModeType == 0 { - for _, part := range strings.Split(rpath, "/") { - if len(part) > 0 && part[0] == '.' { - return nil - } - } - pack := &RPACK{Modified: info.ModTime().Unix(), Mime: "text/plain"} - if mime := mime.TypeByExtension(filepath.Ext(rpath)); mime != "" { - pack.Mime = mime - } - content, _ := ioutil.ReadFile(path) - compressed := bytes.Buffer{} - compressor.Reset(&compressed) - compressor.Write(content) - compressor.Close() - if compressed.Len() < len(content) { - pack.Content = base64.StdEncoding.EncodeToString(compressed.Bytes()) - pack.Compressed = true - } else { - pack.Content = base64.StdEncoding.EncodeToString(content) - } - entries[rpath] = pack - fmt.Fprintf(os.Stderr, "\r%-120.120s ", rpath) - count++ - size += info.Size() - } - return nil - }) - fmt.Fprintf(os.Stderr, "\r%-120.120s\rpacked %d file(s) %d byte(s) in %v\n", "", count, size, time.Now().Sub(start)) - if handle, err := os.OpenFile(output, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644); err == nil { - random := make([]byte, 64) - rand.Seed(time.Now().UnixNano() + int64(os.Getpid())) - rand.Read(random) - uid := fmt.Sprintf("rpack%8.8x", md5.Sum(random)) - fmt.Fprintf(handle, - `package %s - -import ( - "net/http" - "github.com/pyke369/golang-support/rpack" -) - -var %s map[string]*rpack.RPACK = map[string]*rpack.RPACK { -`, - pkgname, uid) - for path, entry := range entries { - fmt.Fprintf(handle, - ` "%s": &rpack.RPACK{Compressed:%t, Modified:%d, Mime:"%s", Content:"%s"}, -`, path, entry.Compressed, entry.Modified, entry.Mime, entry.Content) - } - fmt.Fprintf(handle, - `} - -func %s() http.Handler { - return rpack.Serve(%s) -} -`, funcname, uid) - if main { - fmt.Fprintf(handle, - ` -func main() { - http.Handle("/resources/", http.StripPrefix("/resources/", %s())) - http.ListenAndServe(":8000", nil) -} -`, funcname) - } - handle.Close() - } -} - -func Serve(pack map[string]*RPACK) http.Handler { - return http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) { - var err error - if pack == nil || pack[request.URL.Path] == nil { - response.WriteHeader(http.StatusNotFound) - return - } - if pack[request.URL.Path].raw == nil { - if pack[request.URL.Path].raw, err = base64.StdEncoding.DecodeString(pack[request.URL.Path].Content); err != nil { - response.WriteHeader(http.StatusNotFound) - return - } - } - resource := pack[request.URL.Path] - response.Header().Set("Content-Type", resource.Mime) - if strings.Index(request.Header.Get("Accept-Encoding"), "gzip") >= 0 && request.Header.Get("Range") == "" && resource.Compressed { - response.Header().Set("Content-Encoding", "gzip") - response.Header().Set("Content-Length", fmt.Sprintf("%d", len(resource.raw))) - http.ServeContent(response, request, request.URL.Path, time.Unix(resource.Modified, 0), bytes.NewReader(resource.raw)) - } else { - if resource.Compressed { - decompressor := guzpool.Get().(*gzip.Reader) - decompressor.Reset(bytes.NewReader(resource.raw)) - if raw, err := ioutil.ReadAll(decompressor); err == nil { - http.ServeContent(response, request, request.URL.Path, time.Unix(resource.Modified, 0), bytes.NewReader(raw)) - } - decompressor.Close() - guzpool.Put(decompressor) - } else { - http.ServeContent(response, request, request.URL.Path, time.Unix(resource.Modified, 0), bytes.NewReader(resource.raw)) - } - } - }) -} diff --git a/vendor/github.com/pyke369/golang-support/uconfig/uconfig.go b/vendor/github.com/pyke369/golang-support/uconfig/uconfig.go index ab52086..4a94b4f 100644 --- a/vendor/github.com/pyke369/golang-support/uconfig/uconfig.go +++ b/vendor/github.com/pyke369/golang-support/uconfig/uconfig.go @@ -14,11 +14,12 @@ import ( "path/filepath" "reflect" "regexp" - "runtime" "strconv" "strings" "sync" "time" + + "github.com/pyke369/golang-support/rcache" ) type UConfig struct { @@ -36,7 +37,6 @@ type replacer struct { } var ( - cores int escaped string unescaper *regexp.Regexp requoter *regexp.Regexp @@ -49,27 +49,27 @@ var ( func init() { escaped = "{}[],#/*;:= " // match characters within quotes to escape - unescaper = regexp.MustCompile("@\\d+@") // match escaped characters (to reverse previous escaping) - expander = regexp.MustCompile("{{([<|@&!\\-\\+])\\s*([^{}]*?)\\s*}}") // match external content macros - sizer = regexp.MustCompile("^(\\d+(?:\\.\\d*)?)\\s*([KMGTP]?)(B?)$") // match size value - duration1 = regexp.MustCompile("(\\d+)(Y|MO|D|H|MN|S|MS|US)?") // match duration value form1 (free) - duration2 = regexp.MustCompile("^(?:(\\d+):)?(\\d{2}):(\\d{2})(?:\\.(\\d{1,3}))?$") // match duration value form2 (timecode) - replacers[0] = replacer{regexp.MustCompile("(?m)^(.*?)(?:#|//).*?$"), "$1", false} // remove # and // commented portions - replacers[1] = replacer{regexp.MustCompile("/\\*[^\\*]*\\*/"), "", true} // remove /* */ commented portions - replacers[2] = replacer{regexp.MustCompile("(?m)^\\s+"), "", false} // trim leading spaces - replacers[3] = replacer{regexp.MustCompile("(?m)\\s+$"), "", false} // trim trailing spaces + unescaper = regexp.MustCompile(`@\d+@`) // match escaped characters (to reverse previous escaping) + expander = regexp.MustCompile(`{{([<|@&!\-\+])\s*([^{}]*?)\s*}}`) // match external content macros + sizer = regexp.MustCompile(`^(\d+(?:\.\d*)?)\s*([KMGTP]?)(B?)$`) // match size value + duration1 = regexp.MustCompile(`(\d+)(Y|MO|D|H|MN|S|MS|US)?`) // match duration value form1 (free) + duration2 = regexp.MustCompile(`^(?:(\d+):)?(\d{2}):(\d{2})(?:\.(\d{1,3}))?$`) // match duration value form2 (timecode) + replacers[0] = replacer{regexp.MustCompile("(?m)^(.*?)(?:#|//).*?$"), `$1`, false} // remove # and // commented portions + replacers[1] = replacer{regexp.MustCompile(`/\*[^\*]*\*/`), ``, true} // remove /* */ commented portions + replacers[2] = replacer{regexp.MustCompile(`(?m)^\s+`), ``, false} // trim leading spaces + replacers[3] = replacer{regexp.MustCompile(`(?m)\s+$`), ``, false} // trim trailing spaces replacers[4] = replacer{regexp.MustCompile("(?s)(^|[\r\n]+)\\[([^\\]\r\n]+?)\\](.+?)((?:[\r\n]+\\[)|$)"), "$1$2\n{$3\n}$4", true} // convert INI sections into JSON objects - replacers[5] = replacer{regexp.MustCompile("(?m)^(\\S+)\\s+([^{}\\[\\],;:=]+);$"), "$1 = $2;", false} // add missing key-value separators - replacers[6] = replacer{regexp.MustCompile("(?m);$"), ",", false} // replace ; line terminators by , - replacers[7] = replacer{regexp.MustCompile("(\\S+?)\\s*[:=]"), "$1:", false} // replace = key-value separators by : - replacers[8] = replacer{regexp.MustCompile("([}\\]])(\\s*)([^,}\\]\\s])"), "$1,$2$3", false} // add missing objects/arrays , separators - replacers[9] = replacer{regexp.MustCompile("(?m)(^[^:]+:.+?[^,])$"), "$1,", false} // add missing values trailing , seperators - replacers[10] = replacer{regexp.MustCompile("(^|[,{\\[]+\\s*)([^:{\\[]+?)(\\s*[{\\[])"), "$1$2:$3", true} // add missing key-(object/array-)value separator - replacers[11] = replacer{regexp.MustCompile("(?m)^([^\":{}\\[\\]]+)"), "\"$1\"", false} // add missing quotes around keys - replacers[12] = replacer{regexp.MustCompile("([:,\\[\\s]+)([^\",\\[\\]{}\n\r]+?)(\\s*[,\\]}])"), "$1\"$2\"$3", false} // add missing quotes around values + replacers[5] = replacer{regexp.MustCompile(`(?m)^(\S+)\s+([^{}\[\],;:=]+);$`), "$1 = $2;", false} // add missing key-value separators + replacers[6] = replacer{regexp.MustCompile(`(?m);$`), `,`, false} // replace ; line terminators by , + replacers[7] = replacer{regexp.MustCompile(`(\S+?)\s*[:=]`), `$1:`, false} // replace = key-value separators by : + replacers[8] = replacer{regexp.MustCompile(`([}\]])(\s*)([^,}\]\s])`), `$1,$2$3`, false} // add missing objects/arrays , separators + replacers[9] = replacer{regexp.MustCompile("(?m)(^[^:]+:.+?[^,])$"), `$1,`, false} // add missing values trailing , seperators + replacers[10] = replacer{regexp.MustCompile(`(^|[,{\[]+\s*)([^:{\[]+?)(\s*[{\[])`), `$1$2:$3`, true} // add missing key-(object/array-)value separator + replacers[11] = replacer{regexp.MustCompile(`(?m)^([^":{}\[\]]+)`), `"$1"`, false} // add missing quotes around keys + replacers[12] = replacer{regexp.MustCompile("([:,\\[\\s]+)([^\",\\[\\]{}\n\r]+?)(\\s*[,\\]}])"), `$1"$2"$3`, false} // add missing quotes around values replacers[13] = replacer{regexp.MustCompile("\"[\r\n]"), "\",\n", false} // add still issing objects/arrays , separators - replacers[14] = replacer{regexp.MustCompile("\"\\s*(.+?)\\s*\""), "\"$1\"", false} // trim leading and trailing spaces in quoted strings - replacers[15] = replacer{regexp.MustCompile(",+(\\s*[}\\]])"), "$1", false} // remove objets/arrays last element extra , + replacers[14] = replacer{regexp.MustCompile(`"\s*(.+?)\s*"`), `"$1"`, false} // trim leading and trailing spaces in quoted strings + replacers[15] = replacer{regexp.MustCompile(`,+(\s*[}\]])`), `$1`, false} // remove objets/arrays last element extra , } func escape(input string) string { @@ -77,7 +77,7 @@ func escape(input string) string { instring := false for index := 0; index < len(input); index++ { - if input[index:index+1] == "\"" && (index == 0 || input[index-1:index] != "\\") { + if input[index:index+1] == `"` && (index == 0 || input[index-1:index] != `\`) { instring = !instring } if instring == true { @@ -135,9 +135,6 @@ func reduce(input interface{}) { } func New(input string, inline ...bool) (*UConfig, error) { - if cores == 0 { - cores = runtime.NumCPU() - } config := &UConfig{ config: nil, } @@ -145,11 +142,7 @@ func New(input string, inline ...bool) (*UConfig, error) { } func (this *UConfig) Load(input string, inline ...bool) error { - if cores > 1 { - this.Lock() - defer this.Unlock() - } - + this.Lock() this.cache = map[string]interface{}{} base, _ := os.Getwd() content := fmt.Sprintf("/*base:%s*/\n", base) @@ -272,30 +265,29 @@ func (this *UConfig) Load(input string, inline ...bool) error { if syntax, ok := err.(*json.SyntaxError); ok && syntax.Offset < int64(len(content)) { if start := strings.LastIndex(content[:syntax.Offset], "\n") + 1; start >= 0 { line := strings.Count(content[:start], "\n") + 1 + this.Unlock() return errors.New(fmt.Sprintf("%s at line %d near %s", syntax, line, content[start:syntax.Offset])) } } + this.Unlock() return err } } reduce(this.config) + this.Unlock() return nil } func (this *UConfig) Loaded() bool { - if cores > 1 { - this.RLock() - defer this.RUnlock() - } + this.RLock() + defer this.RUnlock() return !(this.config == nil) } func (this *UConfig) Hash() string { - if cores > 1 { - this.RLock() - defer this.RUnlock() - } + this.RLock() + defer this.RUnlock() return this.hash } @@ -312,54 +304,41 @@ func (this *UConfig) GetPaths(path string) []string { paths []string = []string{} ) - if cores > 1 { - this.RLock() - defer this.RUnlock() - } + this.RLock() prefix := "" if current == nil || path == "" { + this.RUnlock() return paths } - if cores > 1 { - this.cacheLock.RLock() - } + this.cacheLock.RLock() if this.cache[path] != nil { if paths, ok := this.cache[path].([]string); ok { - if cores > 1 { - this.cacheLock.RUnlock() - } + this.cacheLock.RUnlock() + this.RUnlock() return paths } } - if cores > 1 { - this.cacheLock.RUnlock() - } + this.cacheLock.RUnlock() if path != "" { prefix = "." for _, part := range strings.Split(path, ".") { kind := reflect.TypeOf(current).Kind() index, err := strconv.Atoi(part) if (kind == reflect.Slice && (err != nil || index < 0 || index >= len(current.([]interface{})))) || (kind != reflect.Slice && kind != reflect.Map) { - if cores > 1 { - this.cacheLock.Lock() - } + this.cacheLock.Lock() this.cache[path] = paths - if cores > 1 { - this.cacheLock.Unlock() - } + this.cacheLock.Unlock() + this.RUnlock() return paths } if kind == reflect.Slice { current = current.([]interface{})[index] } else { if current = current.(map[string]interface{})[strings.TrimSpace(part)]; current == nil { - if cores > 1 { - this.cacheLock.Lock() - } + this.cacheLock.Lock() this.cache[path] = paths - if cores > 1 { - this.cacheLock.Unlock() - } + this.cacheLock.Unlock() + this.RUnlock() return paths } } @@ -375,91 +354,68 @@ func (this *UConfig) GetPaths(path string) []string { paths = append(paths, fmt.Sprintf("%s%s%s", path, prefix, key)) } } - if cores > 1 { - this.cacheLock.Lock() - } + this.cacheLock.Lock() this.cache[path] = paths - if cores > 1 { - this.cacheLock.Unlock() - } + this.cacheLock.Unlock() + this.RUnlock() return paths } func (this *UConfig) value(path string) (string, error) { var current interface{} = this.config - if cores > 1 { - this.RLock() - defer this.RUnlock() - } + this.RLock() if current == nil || path == "" { + this.RUnlock() return "", fmt.Errorf("invalid parameter") } - if cores > 1 { - this.cacheLock.RLock() - } + this.cacheLock.RLock() if this.cache[path] != nil { if current, ok := this.cache[path].(bool); ok && !current { - if cores > 1 { - this.cacheLock.RUnlock() - } + this.cacheLock.RUnlock() + this.RUnlock() return "", fmt.Errorf("invalid path") } if current, ok := this.cache[path].(string); ok { - if cores > 1 { - this.cacheLock.RUnlock() - } + this.cacheLock.RUnlock() + this.RUnlock() return current, nil } } - if cores > 1 { - this.cacheLock.RUnlock() - } + this.cacheLock.RUnlock() for _, part := range strings.Split(path, ".") { kind := reflect.TypeOf(current).Kind() index, err := strconv.Atoi(part) if (kind == reflect.Slice && (err != nil || index < 0 || index >= len(current.([]interface{})))) || (kind != reflect.Slice && kind != reflect.Map) { - if cores > 1 { - this.cacheLock.Lock() - } + this.cacheLock.Lock() this.cache[path] = false - if cores > 1 { - this.cacheLock.Unlock() - } + this.cacheLock.Unlock() + this.RUnlock() return "", fmt.Errorf("invalid path") } if kind == reflect.Slice { current = current.([]interface{})[index] } else { if current = current.(map[string]interface{})[strings.TrimSpace(part)]; current == nil { - if cores > 1 { - this.cacheLock.Lock() - } + this.cacheLock.Lock() this.cache[path] = false - if cores > 1 { - this.cacheLock.Unlock() - } + this.cacheLock.Unlock() + this.RUnlock() return "", fmt.Errorf("invalid path") } } } if reflect.TypeOf(current).Kind() == reflect.String { - if cores > 1 { - this.cacheLock.Lock() - } + this.cacheLock.Lock() this.cache[path] = current.(string) - if cores > 1 { - this.cacheLock.Unlock() - } + this.cacheLock.Unlock() + this.RUnlock() return current.(string), nil } - if cores > 1 { - this.cacheLock.Lock() - } + this.cacheLock.Lock() this.cache[path] = false - if cores > 1 { - this.cacheLock.Unlock() - } + this.cacheLock.Unlock() + this.RUnlock() return "", fmt.Errorf("invalid path") } @@ -486,7 +442,7 @@ func (this *UConfig) GetStringMatchCaptures(path string, fallback, match string) return []string{fallback} } if match != "" { - if matcher, err := regexp.Compile(match); err == nil { + if matcher := rcache.Get(match); matcher != nil { if matches := matcher.FindStringSubmatch(value); matches != nil { return matches } else { diff --git a/vendor/github.com/pyke369/golang-support/ulog/README.md b/vendor/github.com/pyke369/golang-support/ulog/README.md deleted file mode 100644 index e69de29..0000000 diff --git a/vendor/github.com/pyke369/golang-support/ulog/ulog.go b/vendor/github.com/pyke369/golang-support/ulog/ulog.go deleted file mode 100644 index 59033f3..0000000 --- a/vendor/github.com/pyke369/golang-support/ulog/ulog.go +++ /dev/null @@ -1,545 +0,0 @@ -package ulog - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "log/syslog" - "os" - "path/filepath" - "reflect" - "regexp" - "runtime" - "strings" - "sync" - "syscall" - "time" - "unsafe" -) - -const ( - TIME_NONE int = iota - TIME_DATETIME - TIME_MSDATETIME - TIME_TIMESTAMP - TIME_MSTIMESTAMP -) - -var ( - cores int - facilities = map[string]syslog.Priority{ - "user": syslog.LOG_USER, - "daemon": syslog.LOG_DAEMON, - "local0": syslog.LOG_LOCAL0, - "local1": syslog.LOG_LOCAL1, - "local2": syslog.LOG_LOCAL2, - "local3": syslog.LOG_LOCAL3, - "local4": syslog.LOG_LOCAL4, - "local5": syslog.LOG_LOCAL5, - "local6": syslog.LOG_LOCAL6, - "local7": syslog.LOG_LOCAL7, - } - severities = map[string]syslog.Priority{ - "error": syslog.LOG_ERR, - "warning": syslog.LOG_WARNING, - "info": syslog.LOG_INFO, - "debug": syslog.LOG_DEBUG, - } - severityLabels = map[syslog.Priority]string{ - syslog.LOG_ERR: "ERRO ", - syslog.LOG_WARNING: "WARN ", - syslog.LOG_INFO: "INFO ", - syslog.LOG_DEBUG: "DBUG ", - } - severityColors = map[syslog.Priority]string{ - syslog.LOG_ERR: "\x1b[31m", - syslog.LOG_WARNING: "\x1b[33m", - syslog.LOG_INFO: "\x1b[36m", - syslog.LOG_DEBUG: "\x1b[32m", - } -) - -type FileOutput struct { - handle *os.File - last time.Time -} -type ULog struct { - file, console, syslog bool - fileOutputs map[string]*FileOutput - filePath string - fileTime int - fileLast time.Time - fileSeverity bool - consoleHandle io.Writer - consoleTime int - consoleSeverity bool - consoleColors bool - syslogHandle *syslog.Writer - syslogRemote string - syslogName string - syslogFacility syslog.Priority - optionUTC bool - level syslog.Priority - sync.Mutex -} - -func New(target string) *ULog { - if cores == 0 { - cores = runtime.NumCPU() - } - log := &ULog{ - fileOutputs: map[string]*FileOutput{}, - syslogHandle: nil, - } - return log.Load(target) -} - -func (this *ULog) Load(target string) *ULog { - this.Close() - if cores > 1 { - this.Lock() - defer this.Unlock() - } - - this.file = false - this.filePath = "" - this.fileTime = TIME_DATETIME - this.fileSeverity = true - this.console = false - this.consoleTime = TIME_DATETIME - this.consoleSeverity = true - this.consoleColors = true - this.consoleHandle = os.Stderr - this.syslog = false - this.syslogRemote = "" - this.syslogName = filepath.Base(os.Args[0]) - this.syslogFacility = syslog.LOG_DAEMON - this.optionUTC = false - this.level = syslog.LOG_INFO - for _, target := range regexp.MustCompile("(file|console|syslog|option)\\s*\\(([^\\)]*)\\)").FindAllStringSubmatch(target, -1) { - switch strings.ToLower(target[1]) { - case "file": - this.file = true - for _, option := range regexp.MustCompile("([^:=,\\s]+)\\s*[:=]\\s*([^,\\s]+)").FindAllStringSubmatch(target[2], -1) { - switch strings.ToLower(option[1]) { - case "path": - this.filePath = option[2] - case "time": - option[2] = strings.ToLower(option[2]) - switch { - case option[2] == "datetime": - this.fileTime = TIME_DATETIME - case option[2] == "msdatetime": - this.fileTime = TIME_MSDATETIME - case option[2] == "stamp" || option[2] == "timestamp": - this.fileTime = TIME_TIMESTAMP - case option[2] == "msstamp" || option[2] == "mstimestamp": - this.fileTime = TIME_MSTIMESTAMP - case option[2] != "1" && option[2] != "true" && option[2] != "on" && option[2] != "yes": - this.fileTime = TIME_NONE - } - case "severity": - option[2] = strings.ToLower(option[2]) - if option[2] != "1" && option[2] != "true" && option[2] != "on" && option[2] != "yes" { - this.fileSeverity = false - } - } - } - if this.filePath == "" { - this.file = false - } - case "console": - this.console = true - for _, option := range regexp.MustCompile("([^:=,\\s]+)\\s*[:=]\\s*([^,\\s]+)").FindAllStringSubmatch(target[2], -1) { - option[2] = strings.ToLower(option[2]) - switch strings.ToLower(option[1]) { - case "output": - if option[2] == "stdout" { - this.consoleHandle = os.Stdout - } - case "time": - switch { - case option[2] == "datetime": - this.consoleTime = TIME_DATETIME - case option[2] == "msdatetime": - this.consoleTime = TIME_MSDATETIME - case option[2] == "stamp" || option[2] == "timestamp": - this.consoleTime = TIME_TIMESTAMP - case option[2] == "msstamp" || option[2] == "mstimestamp": - this.consoleTime = TIME_MSTIMESTAMP - case option[2] != "1" && option[2] != "true" && option[2] != "on" && option[2] != "yes": - this.consoleTime = TIME_NONE - } - case "severity": - if option[2] != "1" && option[2] != "true" && option[2] != "on" && option[2] != "yes" { - this.consoleSeverity = false - } - case "colors": - if option[2] != "1" && option[2] != "true" && option[2] != "on" && option[2] != "yes" { - this.consoleColors = false - } - } - } - case "syslog": - this.syslog = true - for _, option := range regexp.MustCompile("([^:=,\\s]+)\\s*[:=]\\s*([^,\\s]+)").FindAllStringSubmatch(target[2], -1) { - switch strings.ToLower(option[1]) { - case "remote": - this.syslogRemote = option[2] - if !regexp.MustCompile(":\\d+$").MatchString(this.syslogRemote) { - this.syslogRemote += ":514" - } - case "name": - this.syslogName = option[2] - case "facility": - this.syslogFacility = facilities[strings.ToLower(option[2])] - } - } - case "option": - for _, option := range regexp.MustCompile("([^:=,\\s]+)\\s*[:=]\\s*([^,\\s]+)").FindAllStringSubmatch(target[2], -1) { - option[2] = strings.ToLower(option[2]) - switch strings.ToLower(option[1]) { - case "utc": - if option[2] == "1" || option[2] == "true" || option[2] == "on" || option[2] == "yes" { - this.optionUTC = true - } - case "level": - this.level = severities[strings.ToLower(option[2])] - } - } - } - } - - var info syscall.Termios - if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, this.consoleHandle.(*os.File).Fd(), syscall.TCGETS, uintptr(unsafe.Pointer(&info)), 0, 0, 0); err != 0 { - this.consoleColors = false - } - return this -} - -func (this *ULog) Close() { - if cores > 1 { - this.Lock() - defer this.Unlock() - } - if this.syslogHandle != nil { - this.syslogHandle.Close() - this.syslogHandle = nil - } - for path, output := range this.fileOutputs { - if output.handle != nil { - output.handle.Close() - } - delete(this.fileOutputs, path) - } -} - -func (this *ULog) SetLevel(level string) { - level = strings.ToLower(level) - switch level { - case "error": - this.level = syslog.LOG_ERR - case "warning": - this.level = syslog.LOG_WARNING - case "info": - this.level = syslog.LOG_INFO - case "debug": - this.level = syslog.LOG_DEBUG - } -} - -func strftime(layout string, base time.Time) string { - var output []string - - length := len(layout) - for index := 0; index < length; index++ { - switch layout[index] { - case '%': - if index < length-1 { - switch layout[index+1] { - case 'a': - output = append(output, base.Format("Mon")) - case 'A': - output = append(output, base.Format("Monday")) - case 'b': - output = append(output, base.Format("Jan")) - case 'B': - output = append(output, base.Format("January")) - case 'c': - output = append(output, base.Format("Mon Jan 2 15:04:05 2006")) - case 'C': - output = append(output, fmt.Sprintf("%02d", base.Year()/100)) - case 'd': - output = append(output, fmt.Sprintf("%02d", base.Day())) - case 'D': - output = append(output, fmt.Sprintf("%02d/%02d/%02d", base.Month(), base.Day(), base.Year()%100)) - case 'e': - output = append(output, fmt.Sprintf("%2d", base.Day())) - case 'f': - output = append(output, fmt.Sprintf("%06d", base.Nanosecond()/1000)) - case 'F': - output = append(output, fmt.Sprintf("%04d-%02d-%02d", base.Year(), base.Month(), base.Day())) - case 'g': - year, _ := base.ISOWeek() - output = append(output, fmt.Sprintf("%02d", year%100)) - case 'G': - year, _ := base.ISOWeek() - output = append(output, fmt.Sprintf("%04d", year)) - case 'h': - output = append(output, base.Format("Jan")) - case 'H': - output = append(output, fmt.Sprintf("%02d", base.Hour())) - case 'I': - if base.Hour() == 0 || base.Hour() == 12 { - output = append(output, "12") - } else { - output = append(output, fmt.Sprintf("%02d", base.Hour()%12)) - } - case 'j': - output = append(output, fmt.Sprintf("%03d", base.YearDay())) - case 'k': - output = append(output, fmt.Sprintf("%2d", base.Hour())) - case 'l': - if base.Hour() == 0 || base.Hour() == 12 { - output = append(output, "12") - } else { - output = append(output, fmt.Sprintf("%2d", base.Hour()%12)) - } - case 'm': - output = append(output, fmt.Sprintf("%02d", base.Month())) - case 'M': - output = append(output, fmt.Sprintf("%02d", base.Minute())) - case 'n': - output = append(output, "\n") - case 'p': - if base.Hour() < 12 { - output = append(output, "AM") - } else { - output = append(output, "PM") - } - case 'P': - if base.Hour() < 12 { - output = append(output, "am") - } else { - output = append(output, "pm") - } - case 'r': - if base.Hour() == 0 || base.Hour() == 12 { - output = append(output, "12") - } else { - output = append(output, fmt.Sprintf("%02d", base.Hour()%12)) - } - output = append(output, fmt.Sprintf(":%02d:%02d", base.Minute(), base.Second())) - if base.Hour() < 12 { - output = append(output, " AM") - } else { - output = append(output, " PM") - } - case 'R': - output = append(output, fmt.Sprintf("%02d:%02d", base.Hour(), base.Minute())) - case 's': - output = append(output, fmt.Sprintf("%d", base.Unix())) - case 'S': - output = append(output, fmt.Sprintf("%02d", base.Second())) - case 't': - output = append(output, "\t") - case 'T': - output = append(output, fmt.Sprintf("%02d:%02d:%02d", base.Hour(), base.Minute(), base.Second())) - case 'u': - day := base.Weekday() - if day == 0 { - day = 7 - } - output = append(output, fmt.Sprintf("%d", day)) - case 'U': - output = append(output, fmt.Sprintf("%d", (base.YearDay()+6-int(base.Weekday()))/7)) - case 'V': - _, week := base.ISOWeek() - output = append(output, fmt.Sprintf("%02d", week)) - case 'w': - output = append(output, fmt.Sprintf("%d", base.Weekday())) - case 'W': - day := int(base.Weekday()) - if day == 0 { - day = 6 - } else { - day -= 1 - } - output = append(output, fmt.Sprintf("%d", (base.YearDay()+6-day)/7)) - case 'x': - output = append(output, fmt.Sprintf("%02d/%02d/%02d", base.Month(), base.Day(), base.Year()%100)) - case 'X': - output = append(output, fmt.Sprintf("%02d:%02d:%02d", base.Hour(), base.Minute(), base.Second())) - case 'y': - output = append(output, fmt.Sprintf("%02d", base.Year()%100)) - case 'Y': - output = append(output, fmt.Sprintf("%04d", base.Year())) - case 'z': - output = append(output, base.Format("-0700")) - case 'Z': - output = append(output, base.Format("MST")) - case '%': - output = append(output, "%") - } - index++ - } - default: - output = append(output, string(layout[index])) - } - } - return strings.Join(output, "") -} - -func (this *ULog) log(now time.Time, severity syslog.Priority, xlayout interface{}, a ...interface{}) { - var err error - if this.level < severity || (!this.syslog && !this.file && !this.console) { - return - } - layout := "" - switch reflect.TypeOf(xlayout).Kind() { - case reflect.Map: - var buffer bytes.Buffer - - encoder := json.NewEncoder(&buffer) - encoder.SetEscapeHTML(false) - if err := encoder.Encode(xlayout); err == nil { - layout = "%s" - a = []interface{}{bytes.TrimSpace(buffer.Bytes())} - } - case reflect.String: - layout = xlayout.(string) - } - layout = strings.TrimSpace(layout) - if this.syslog { - if this.syslogHandle == nil { - if cores > 1 { - this.Lock() - } - if this.syslogHandle == nil { - protocol := "" - if this.syslogRemote != "" { - protocol = "udp" - } - if this.syslogHandle, err = syslog.Dial(protocol, this.syslogRemote, this.syslogFacility, this.syslogName); err != nil { - this.syslogHandle = nil - } - } - if cores > 1 { - this.Unlock() - } - } - if this.syslogHandle != nil { - switch severity { - case syslog.LOG_ERR: - this.syslogHandle.Err(fmt.Sprintf(layout, a...)) - case syslog.LOG_WARNING: - this.syslogHandle.Warning(fmt.Sprintf(layout, a...)) - case syslog.LOG_INFO: - this.syslogHandle.Info(fmt.Sprintf(layout, a...)) - case syslog.LOG_DEBUG: - this.syslogHandle.Debug(fmt.Sprintf(layout, a...)) - } - } - } - if this.optionUTC { - now = now.UTC() - } else { - now = now.Local() - } - if this.file { - path := strftime(this.filePath, now) - if cores > 1 { - this.Lock() - } - if this.fileOutputs[path] == nil { - os.MkdirAll(filepath.Dir(path), 0755) - if handle, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644); err == nil { - this.fileOutputs[path] = &FileOutput{handle: handle} - } - } - if this.fileOutputs[path] != nil && this.fileOutputs[path].handle != nil { - prefix := "" - switch this.fileTime { - case TIME_DATETIME: - prefix = fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d ", now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second()) - case TIME_MSDATETIME: - prefix = fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d.%03d ", now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second(), now.Nanosecond()/int(time.Millisecond)) - case TIME_TIMESTAMP: - prefix = fmt.Sprintf("%d ", now.Unix()) - case TIME_MSTIMESTAMP: - prefix = fmt.Sprintf("%d ", now.UnixNano()/int64(time.Millisecond)) - } - if this.fileSeverity { - prefix += severityLabels[severity] - } - this.fileOutputs[path].handle.WriteString(fmt.Sprintf(prefix+layout+"\n", a...)) - this.fileOutputs[path].last = now - } - if now.Sub(this.fileLast) >= 5*time.Second { - this.fileLast = now - for path, output := range this.fileOutputs { - if now.Sub(output.last) >= 5*time.Second { - output.handle.Close() - delete(this.fileOutputs, path) - } - } - } - if cores > 1 { - this.Unlock() - } - } - if this.console { - prefix := "" - switch this.consoleTime { - case TIME_DATETIME: - prefix = fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d ", now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second()) - case TIME_MSDATETIME: - prefix = fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d.%03d ", now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second(), now.Nanosecond()/int(time.Millisecond)) - case TIME_TIMESTAMP: - prefix = fmt.Sprintf("%d ", now.Unix()) - case TIME_MSTIMESTAMP: - prefix = fmt.Sprintf("%d ", now.UnixNano()/int64(time.Millisecond)) - } - if this.consoleSeverity { - if this.consoleColors { - prefix += fmt.Sprintf("%s%s\x1b[0m", severityColors[severity], severityLabels[severity]) - } else { - prefix += severityLabels[severity] - } - } - if cores > 1 { - this.Lock() - } - fmt.Fprintf(this.consoleHandle, prefix+layout+"\n", a...) - if cores > 1 { - this.Unlock() - } - } -} - -func (this *ULog) Error(layout interface{}, a ...interface{}) { - this.log(time.Now(), syslog.LOG_ERR, layout, a...) -} -func (this *ULog) Warn(layout interface{}, a ...interface{}) { - this.log(time.Now(), syslog.LOG_WARNING, layout, a...) -} -func (this *ULog) Info(layout interface{}, a ...interface{}) { - this.log(time.Now(), syslog.LOG_INFO, layout, a...) -} -func (this *ULog) Debug(layout interface{}, a ...interface{}) { - this.log(time.Now(), syslog.LOG_DEBUG, layout, a...) -} - -func (this *ULog) ErrorTime(now time.Time, layout interface{}, a ...interface{}) { - this.log(now, syslog.LOG_ERR, layout, a...) -} -func (this *ULog) WarnTime(now time.Time, layout interface{}, a ...interface{}) { - this.log(now, syslog.LOG_WARNING, layout, a...) -} -func (this *ULog) InfoTime(now time.Time, layout interface{}, a ...interface{}) { - this.log(now, syslog.LOG_INFO, layout, a...) -} -func (this *ULog) DebugTime(now time.Time, layout interface{}, a ...interface{}) { - this.log(now, syslog.LOG_DEBUG, layout, a...) -} diff --git a/vendor/github.com/pyke369/golang-support/uuid/README.md b/vendor/github.com/pyke369/golang-support/uuid/README.md deleted file mode 100644 index e69de29..0000000 diff --git a/vendor/github.com/pyke369/golang-support/uuid/uuid.go b/vendor/github.com/pyke369/golang-support/uuid/uuid.go deleted file mode 100644 index 5814371..0000000 --- a/vendor/github.com/pyke369/golang-support/uuid/uuid.go +++ /dev/null @@ -1,30 +0,0 @@ -package uuid - -import ( - "fmt" - "math/rand" - "os" - "time" -) - -var initialized bool - -func init() { - if !initialized { - rand.Seed(time.Now().UnixNano() + int64(os.Getpid())) - initialized = true - } -} - -func UUID() string { - var entropy = make([]byte, 16) - - if !initialized { - rand.Seed(time.Now().UnixNano() + int64(os.Getpid())) - initialized = true - } - rand.Read(entropy) - entropy[6] = (entropy[6] & 0x0f) | 0x40 - entropy[8] = (entropy[8] & 0x3f) | 0x80 - return fmt.Sprintf("%08x-%04x-%04x-%04x-%12x", entropy[0:4], entropy[4:6], entropy[6:8], entropy[8:10], entropy[10:16]) -} diff --git a/vendor/github.com/pyke369/golang-support/whohas/README.md b/vendor/github.com/pyke369/golang-support/whohas/README.md deleted file mode 100644 index e69de29..0000000 diff --git a/vendor/github.com/pyke369/golang-support/whohas/whohas.go b/vendor/github.com/pyke369/golang-support/whohas/whohas.go deleted file mode 100644 index a40892a..0000000 --- a/vendor/github.com/pyke369/golang-support/whohas/whohas.go +++ /dev/null @@ -1,244 +0,0 @@ -package whohas - -import ( - "context" - "mime" - "net/http" - "path/filepath" - "runtime" - "strconv" - "strings" - "sync" - "time" -) - -type BACKEND struct { - Host string - Secure bool - Path string - Headers map[string]string - Penalty time.Duration -} -type CACHE struct { - TTL time.Duration - last time.Time - items map[string]*LOOKUP - sync.RWMutex -} -type LOOKUP struct { - index int - deadline time.Time - Protocol string - Host string - Headers map[string]string - Size int64 - Mime string - Ranges bool - Date time.Time - Modified time.Time - Expires time.Time -} - -var cores int - -func Lookup(path string, backends []BACKEND, timeout time.Duration, cache *CACHE, ckey string) (lookup *LOOKUP) { - if cores == 0 { - cores = runtime.NumCPU() - } - if path == "" || backends == nil || len(backends) < 1 || timeout < 100*time.Millisecond { - return - } - - cpath := path - if index := strings.Index(path, "?"); index >= 0 { - cpath = path[:index] - } - cbackends := backends - if cache != nil && cache.items != nil { - now := time.Now() - if cores > 1 { - cache.RLock() - } - if cache.items[cpath] != nil && now.Sub(cache.items[cpath].deadline) < 0 { - lookup = cache.items[cpath] - if cache.items[cpath].Host != "" { - cbackends = []BACKEND{} - for _, backend := range backends { - if backend.Host == cache.items[cpath].Host { - cbackends = append(cbackends, backend) - break - } - } - if len(cbackends) < 1 { - cbackends = backends - } - } - } - if len(cbackends) == len(backends) && ckey != "" && cache.items["k"+ckey] != nil && now.Sub(cache.items["k"+ckey].deadline) < 0 { - cbackends = []BACKEND{} - for _, backend := range backends { - if backend.Host == cache.items["k"+ckey].Host { - cbackends = append(cbackends, backend) - break - } - } - if len(cbackends) < 1 { - cbackends = backends - } - } - if cores > 1 { - cache.RUnlock() - } - } - - if lookup == nil { - inflight := len(cbackends) - sink := make(chan LOOKUP, inflight+1) - cancels := make([]context.CancelFunc, inflight) - for index, backend := range cbackends { - var ctx context.Context - - ctx, cancels[index] = context.WithCancel(context.Background()) - go func(index int, backend BACKEND, ctx context.Context) { - lookup := LOOKUP{index: index} - if backend.Penalty != 0 && len(cbackends) > 1 { - select { - case <-time.After(backend.Penalty): - case <-ctx.Done(): - sink <- lookup - return - } - } - lookup.Protocol = "http" - if backend.Secure { - lookup.Protocol = "https" - } - rpath := path - if backend.Path != "" { - rpath = backend.Path - } - if request, err := http.NewRequest(http.MethodHead, lookup.Protocol+"://"+backend.Host+rpath, nil); err == nil { - request = request.WithContext(ctx) - request.Header.Set("User-Agent", "whohas") - if backend.Headers != nil { - lookup.Headers = map[string]string{} - for name, value := range backend.Headers { - lookup.Headers[name] = value - request.Header.Set(name, value) - } - } - if response, err := http.DefaultClient.Do(request); err == nil { - if response.StatusCode == 200 { - lookup.Host = backend.Host - lookup.Size, _ = strconv.ParseInt(response.Header.Get("Content-Length"), 10, 64) - lookup.Mime = response.Header.Get("Content-Type") - if lookup.Mime == "" || lookup.Mime == "application/octet-stream" || lookup.Mime == "text/plain" { - if extension := filepath.Ext(path); extension != "" { - lookup.Mime = mime.TypeByExtension(extension) - } - } - if response.Header.Get("Accept-Ranges") != "" { - lookup.Ranges = true - } - if header := response.Header.Get("Date"); header != "" { - lookup.Date, _ = http.ParseTime(header) - } else { - lookup.Date = time.Now() - } - if header := response.Header.Get("Last-Modified"); header != "" { - lookup.Modified, _ = http.ParseTime(header) - } - if header := response.Header.Get("Expires"); header != "" { - lookup.Expires, _ = http.ParseTime(header) - } else { - lookup.Expires = lookup.Date.Add(time.Hour) - } - if lookup.Expires.Sub(lookup.Date) < 2*time.Second { - lookup.Expires = lookup.Date.Add(2 * time.Second) - } - } - response.Body.Close() - } - } - sink <- lookup - }(index, backend, ctx) - } - - for inflight > 0 { - select { - case result := <-sink: - inflight-- - cancels[result.index] = nil - if result.Host != "" { - lookup = &result - for index, cancel := range cancels { - if cancels[index] != nil && index != result.index { - cancel() - cancels[index] = nil - } - } - } - case <-time.After(timeout): - for index, cancel := range cancels { - if cancels[index] != nil { - cancel() - } - } - } - } - close(sink) - } - - if cache != nil { - now := time.Now() - if cores > 1 { - cache.Lock() - } - if cache.items == nil { - cache.items = map[string]*LOOKUP{} - } - if now.Sub(cache.last) >= 5*time.Second { - cache.last = now - for key, item := range cache.items { - if now.Sub(item.deadline) >= 0 { - delete(cache.items, key) - } - } - } - if lookup == nil || lookup.Host == "" { - if ckey != "" { - delete(cache.items, "k"+ckey) - } - if cache.items[cpath] == nil { - cache.items[cpath] = &LOOKUP{deadline: now.Add(5 * time.Second)} - } - lookup = nil - } else { - if cache.TTL < 2*time.Second { - cache.TTL = 2 * time.Second - } - if ckey != "" { - cache.items["k"+ckey] = &LOOKUP{Host: lookup.Host, deadline: now.Add(cache.TTL)} - } - if cache.items[cpath] == nil { - ttl := lookup.Expires.Sub(lookup.Date) - if ttl < 2*time.Second { - ttl = 2 * time.Second - } - if ttl > cache.TTL { - ttl = cache.TTL - } - if ttl > 10*time.Minute { - ttl = 10 * time.Minute - } - lookup.deadline = now.Add(ttl) - cache.items[cpath] = lookup - } - } - if cores > 1 { - cache.Unlock() - } - } - - return -} diff --git a/vendor/gopkg.in/asn1-ber.v1/.travis.yml b/vendor/gopkg.in/asn1-ber.v1/.travis.yml new file mode 100644 index 0000000..ecf4132 --- /dev/null +++ b/vendor/gopkg.in/asn1-ber.v1/.travis.yml @@ -0,0 +1,36 @@ +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 ./... diff --git a/vendor/gopkg.in/asn1-ber.v1/LICENSE b/vendor/gopkg.in/asn1-ber.v1/LICENSE new file mode 100644 index 0000000..23f9425 --- /dev/null +++ b/vendor/gopkg.in/asn1-ber.v1/LICENSE @@ -0,0 +1,22 @@ +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. diff --git a/vendor/gopkg.in/asn1-ber.v1/README.md b/vendor/gopkg.in/asn1-ber.v1/README.md new file mode 100644 index 0000000..e3a9560 --- /dev/null +++ b/vendor/gopkg.in/asn1-ber.v1/README.md @@ -0,0 +1,24 @@ +[![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 diff --git a/vendor/gopkg.in/asn1-ber.v1/ber.go b/vendor/gopkg.in/asn1-ber.v1/ber.go new file mode 100644 index 0000000..6153f46 --- /dev/null +++ b/vendor/gopkg.in/asn1-ber.v1/ber.go @@ -0,0 +1,512 @@ +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 +} diff --git a/vendor/gopkg.in/asn1-ber.v1/content_int.go b/vendor/gopkg.in/asn1-ber.v1/content_int.go new file mode 100644 index 0000000..1858b74 --- /dev/null +++ b/vendor/gopkg.in/asn1-ber.v1/content_int.go @@ -0,0 +1,25 @@ +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 +} diff --git a/vendor/gopkg.in/asn1-ber.v1/header.go b/vendor/gopkg.in/asn1-ber.v1/header.go new file mode 100644 index 0000000..7161562 --- /dev/null +++ b/vendor/gopkg.in/asn1-ber.v1/header.go @@ -0,0 +1,35 @@ +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 +} diff --git a/vendor/gopkg.in/asn1-ber.v1/identifier.go b/vendor/gopkg.in/asn1-ber.v1/identifier.go new file mode 100644 index 0000000..e8c4357 --- /dev/null +++ b/vendor/gopkg.in/asn1-ber.v1/identifier.go @@ -0,0 +1,112 @@ +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 +} diff --git a/vendor/gopkg.in/asn1-ber.v1/length.go b/vendor/gopkg.in/asn1-ber.v1/length.go new file mode 100644 index 0000000..750e8f4 --- /dev/null +++ b/vendor/gopkg.in/asn1-ber.v1/length.go @@ -0,0 +1,81 @@ +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 +} diff --git a/vendor/gopkg.in/asn1-ber.v1/util.go b/vendor/gopkg.in/asn1-ber.v1/util.go new file mode 100644 index 0000000..3e56b66 --- /dev/null +++ b/vendor/gopkg.in/asn1-ber.v1/util.go @@ -0,0 +1,24 @@ +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 +} diff --git a/vendor/github.com/pyke369/golang-support/README.md b/vendor/gopkg.in/ldap.v2/.gitignore similarity index 100% rename from vendor/github.com/pyke369/golang-support/README.md rename to vendor/gopkg.in/ldap.v2/.gitignore diff --git a/vendor/gopkg.in/ldap.v2/.travis.yml b/vendor/gopkg.in/ldap.v2/.travis.yml new file mode 100644 index 0000000..9782c9b --- /dev/null +++ b/vendor/gopkg.in/ldap.v2/.travis.yml @@ -0,0 +1,31 @@ +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 diff --git a/vendor/gopkg.in/ldap.v2/LICENSE b/vendor/gopkg.in/ldap.v2/LICENSE new file mode 100644 index 0000000..6c0ed4b --- /dev/null +++ b/vendor/gopkg.in/ldap.v2/LICENSE @@ -0,0 +1,22 @@ +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. diff --git a/vendor/gopkg.in/ldap.v2/Makefile b/vendor/gopkg.in/ldap.v2/Makefile new file mode 100644 index 0000000..a9d351c --- /dev/null +++ b/vendor/gopkg.in/ldap.v2/Makefile @@ -0,0 +1,52 @@ +.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 diff --git a/vendor/gopkg.in/ldap.v2/README.md b/vendor/gopkg.in/ldap.v2/README.md new file mode 100644 index 0000000..a26ed2d --- /dev/null +++ b/vendor/gopkg.in/ldap.v2/README.md @@ -0,0 +1,53 @@ +[![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 diff --git a/vendor/gopkg.in/ldap.v2/add.go b/vendor/gopkg.in/ldap.v2/add.go new file mode 100644 index 0000000..0e5f6cd --- /dev/null +++ b/vendor/gopkg.in/ldap.v2/add.go @@ -0,0 +1,113 @@ +// +// 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 +} diff --git a/vendor/gopkg.in/ldap.v2/atomic_value.go b/vendor/gopkg.in/ldap.v2/atomic_value.go new file mode 100644 index 0000000..bccf757 --- /dev/null +++ b/vendor/gopkg.in/ldap.v2/atomic_value.go @@ -0,0 +1,13 @@ +// +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 +} diff --git a/vendor/gopkg.in/ldap.v2/atomic_value_go13.go b/vendor/gopkg.in/ldap.v2/atomic_value_go13.go new file mode 100644 index 0000000..04920bb --- /dev/null +++ b/vendor/gopkg.in/ldap.v2/atomic_value_go13.go @@ -0,0 +1,28 @@ +// +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 +} diff --git a/vendor/gopkg.in/ldap.v2/bind.go b/vendor/gopkg.in/ldap.v2/bind.go new file mode 100644 index 0000000..26b3cc7 --- /dev/null +++ b/vendor/gopkg.in/ldap.v2/bind.go @@ -0,0 +1,143 @@ +// 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 +} diff --git a/vendor/gopkg.in/ldap.v2/client.go b/vendor/gopkg.in/ldap.v2/client.go new file mode 100644 index 0000000..055b27b --- /dev/null +++ b/vendor/gopkg.in/ldap.v2/client.go @@ -0,0 +1,27 @@ +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) +} diff --git a/vendor/gopkg.in/ldap.v2/compare.go b/vendor/gopkg.in/ldap.v2/compare.go new file mode 100644 index 0000000..cc6d2af --- /dev/null +++ b/vendor/gopkg.in/ldap.v2/compare.go @@ -0,0 +1,85 @@ +// 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 +// -- [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) +} diff --git a/vendor/gopkg.in/ldap.v2/conn.go b/vendor/gopkg.in/ldap.v2/conn.go new file mode 100644 index 0000000..eb28eb4 --- /dev/null +++ b/vendor/gopkg.in/ldap.v2/conn.go @@ -0,0 +1,470 @@ +// 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 + } + } +} diff --git a/vendor/gopkg.in/ldap.v2/control.go b/vendor/gopkg.in/ldap.v2/control.go new file mode 100644 index 0000000..342f325 --- /dev/null +++ b/vendor/gopkg.in/ldap.v2/control.go @@ -0,0 +1,420 @@ +// 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 +} diff --git a/vendor/gopkg.in/ldap.v2/debug.go b/vendor/gopkg.in/ldap.v2/debug.go new file mode 100644 index 0000000..7279fc2 --- /dev/null +++ b/vendor/gopkg.in/ldap.v2/debug.go @@ -0,0 +1,24 @@ +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) + } +} diff --git a/vendor/gopkg.in/ldap.v2/del.go b/vendor/gopkg.in/ldap.v2/del.go new file mode 100644 index 0000000..4fd63dc --- /dev/null +++ b/vendor/gopkg.in/ldap.v2/del.go @@ -0,0 +1,84 @@ +// +// 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 +} diff --git a/vendor/gopkg.in/ldap.v2/dn.go b/vendor/gopkg.in/ldap.v2/dn.go new file mode 100644 index 0000000..34e9023 --- /dev/null +++ b/vendor/gopkg.in/ldap.v2/dn.go @@ -0,0 +1,247 @@ +// 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 , 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 , , , , +// , , , , , , , , +// , , and 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 +} diff --git a/vendor/gopkg.in/ldap.v2/doc.go b/vendor/gopkg.in/ldap.v2/doc.go new file mode 100644 index 0000000..f20d39b --- /dev/null +++ b/vendor/gopkg.in/ldap.v2/doc.go @@ -0,0 +1,4 @@ +/* +Package ldap provides basic LDAP v3 functionality. +*/ +package ldap diff --git a/vendor/gopkg.in/ldap.v2/error.go b/vendor/gopkg.in/ldap.v2/error.go new file mode 100644 index 0000000..4cccb53 --- /dev/null +++ b/vendor/gopkg.in/ldap.v2/error.go @@ -0,0 +1,155 @@ +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 +} diff --git a/vendor/gopkg.in/ldap.v2/filter.go b/vendor/gopkg.in/ldap.v2/filter.go new file mode 100644 index 0000000..3858a28 --- /dev/null +++ b/vendor/gopkg.in/ldap.v2/filter.go @@ -0,0 +1,469 @@ +// 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 +} diff --git a/vendor/gopkg.in/ldap.v2/ldap.go b/vendor/gopkg.in/ldap.v2/ldap.go new file mode 100644 index 0000000..4969247 --- /dev/null +++ b/vendor/gopkg.in/ldap.v2/ldap.go @@ -0,0 +1,320 @@ +// 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) +} diff --git a/vendor/gopkg.in/ldap.v2/modify.go b/vendor/gopkg.in/ldap.v2/modify.go new file mode 100644 index 0000000..e4ab6ce --- /dev/null +++ b/vendor/gopkg.in/ldap.v2/modify.go @@ -0,0 +1,170 @@ +// 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 +// -- [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 +} diff --git a/vendor/gopkg.in/ldap.v2/passwdmodify.go b/vendor/gopkg.in/ldap.v2/passwdmodify.go new file mode 100644 index 0000000..7d8246f --- /dev/null +++ b/vendor/gopkg.in/ldap.v2/passwdmodify.go @@ -0,0 +1,148 @@ +// 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 +} diff --git a/vendor/gopkg.in/ldap.v2/search.go b/vendor/gopkg.in/ldap.v2/search.go new file mode 100644 index 0000000..2a99894 --- /dev/null +++ b/vendor/gopkg.in/ldap.v2/search.go @@ -0,0 +1,450 @@ +// 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 +// -- 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 +} diff --git a/vendor/modules.txt b/vendor/modules.txt new file mode 100644 index 0000000..5a483e9 --- /dev/null +++ b/vendor/modules.txt @@ -0,0 +1,7 @@ +# github.com/pyke369/golang-support v0.0.0-20190703174728-34ca97aa79e9 +github.com/pyke369/golang-support/uconfig +github.com/pyke369/golang-support/rcache +# gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d +gopkg.in/asn1-ber.v1 +# gopkg.in/ldap.v2 v2.5.1 +gopkg.in/ldap.v2