Browse Source

cmd/syncthing: Add LDAP authentication for GUI (fixes #5163) (#5169)

Boris Rybalkin 7 years ago
parent
commit
1b1741de64
37 changed files with 4435 additions and 46 deletions
  1. 3 2
      cmd/syncthing/gui.go
  2. 68 33
      cmd/syncthing/gui_auth.go
  3. 36 0
      cmd/syncthing/gui_auth_test.go
  4. 4 0
      cmd/syncthing/mocked_config_test.go
  5. 41 0
      lib/config/authmode.go
  6. 1 0
      lib/config/config.go
  7. 16 11
      lib/config/guiconfiguration.go
  8. 18 0
      lib/config/ldapconfiguration.go
  9. 46 0
      lib/config/ldaptransport.go
  10. 6 0
      lib/config/wrapper.go
  11. 22 0
      vendor/gopkg.in/asn1-ber.v1/LICENSE
  12. 504 0
      vendor/gopkg.in/asn1-ber.v1/ber.go
  13. 25 0
      vendor/gopkg.in/asn1-ber.v1/content_int.go
  14. 29 0
      vendor/gopkg.in/asn1-ber.v1/header.go
  15. 103 0
      vendor/gopkg.in/asn1-ber.v1/identifier.go
  16. 81 0
      vendor/gopkg.in/asn1-ber.v1/length.go
  17. 24 0
      vendor/gopkg.in/asn1-ber.v1/util.go
  18. 22 0
      vendor/gopkg.in/ldap.v2/LICENSE
  19. 113 0
      vendor/gopkg.in/ldap.v2/add.go
  20. 13 0
      vendor/gopkg.in/ldap.v2/atomic_value.go
  21. 28 0
      vendor/gopkg.in/ldap.v2/atomic_value_go13.go
  22. 143 0
      vendor/gopkg.in/ldap.v2/bind.go
  23. 27 0
      vendor/gopkg.in/ldap.v2/client.go
  24. 85 0
      vendor/gopkg.in/ldap.v2/compare.go
  25. 470 0
      vendor/gopkg.in/ldap.v2/conn.go
  26. 420 0
      vendor/gopkg.in/ldap.v2/control.go
  27. 24 0
      vendor/gopkg.in/ldap.v2/debug.go
  28. 84 0
      vendor/gopkg.in/ldap.v2/del.go
  29. 247 0
      vendor/gopkg.in/ldap.v2/dn.go
  30. 4 0
      vendor/gopkg.in/ldap.v2/doc.go
  31. 155 0
      vendor/gopkg.in/ldap.v2/error.go
  32. 469 0
      vendor/gopkg.in/ldap.v2/filter.go
  33. 320 0
      vendor/gopkg.in/ldap.v2/ldap.go
  34. 170 0
      vendor/gopkg.in/ldap.v2/modify.go
  35. 148 0
      vendor/gopkg.in/ldap.v2/passwdmodify.go
  36. 450 0
      vendor/gopkg.in/ldap.v2/search.go
  37. 16 0
      vendor/manifest

+ 3 - 2
cmd/syncthing/gui.go

@@ -119,6 +119,7 @@ type modelIntf interface {
 
 type configIntf interface {
 	GUI() config.GUIConfiguration
+	LDAP() config.LDAPConfiguration
 	RawCopy() config.Configuration
 	Options() config.OptionsConfiguration
 	Replace(cfg config.Configuration) (config.Waiter, error)
@@ -343,8 +344,8 @@ func (s *apiService) Serve() {
 	handler = withDetailsMiddleware(s.id, handler)
 
 	// Wrap everything in basic auth, if user/password is set.
-	if len(guiCfg.User) > 0 && len(guiCfg.Password) > 0 {
-		handler = basicAuthAndSessionMiddleware("sessionid-"+s.id.String()[:5], guiCfg, handler)
+	if guiCfg.IsAuthEnabled() {
+		handler = basicAuthAndSessionMiddleware("sessionid-"+s.id.String()[:5], guiCfg, s.cfg.LDAP(), handler)
 	}
 
 	// Redirect to HTTPS if we are supposed to

+ 68 - 33
cmd/syncthing/gui_auth.go

@@ -8,7 +8,9 @@ package main
 
 import (
 	"bytes"
+	"crypto/tls"
 	"encoding/base64"
+	"fmt"
 	"net/http"
 	"strings"
 	"time"
@@ -18,6 +20,7 @@ import (
 	"github.com/syncthing/syncthing/lib/rand"
 	"github.com/syncthing/syncthing/lib/sync"
 	"golang.org/x/crypto/bcrypt"
+	"gopkg.in/ldap.v2"
 )
 
 var (
@@ -32,9 +35,9 @@ func emitLoginAttempt(success bool, username string) {
 	})
 }
 
-func basicAuthAndSessionMiddleware(cookieName string, cfg config.GUIConfiguration, next http.Handler) http.Handler {
+func basicAuthAndSessionMiddleware(cookieName string, guiCfg config.GUIConfiguration, ldapCfg config.LDAPConfiguration, next http.Handler) http.Handler {
 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		if cfg.IsValidAPIKey(r.Header.Get("X-API-Key")) {
+		if guiCfg.IsValidAPIKey(r.Header.Get("X-API-Key")) {
 			next.ServeHTTP(w, r)
 			return
 		}
@@ -77,42 +80,26 @@ func basicAuthAndSessionMiddleware(cookieName string, cfg config.GUIConfiguratio
 			return
 		}
 
-		// Check if the username is correct, assuming it was sent as UTF-8
+		authOk := false
 		username := string(fields[0])
-		if username == cfg.User {
-			goto usernameOK
-		}
-
-		// ... check it again, converting it from assumed ISO-8859-1 to UTF-8
-		username = string(iso88591ToUTF8(fields[0]))
-		if username == cfg.User {
-			goto usernameOK
-		}
-
-		// Neither of the possible interpretations match the configured username
-		emitLoginAttempt(false, username)
-		error()
-		return
-
-	usernameOK:
-		// Check password as given (assumes UTF-8 encoding)
-		password := fields[1]
-		if err := bcrypt.CompareHashAndPassword([]byte(cfg.Password), password); err == nil {
-			goto passwordOK
+		password := string(fields[1])
+
+		authOk = auth(username, password, guiCfg, ldapCfg)
+		if !authOk {
+			usernameIso := string(iso88591ToUTF8([]byte(username)))
+			passwordIso := string(iso88591ToUTF8([]byte(password)))
+			authOk = auth(usernameIso, passwordIso, guiCfg, ldapCfg)
+			if authOk {
+				username = usernameIso
+			}
 		}
 
-		// ... check it again, converting it from assumed ISO-8859-1 to UTF-8
-		password = iso88591ToUTF8(password)
-		if err := bcrypt.CompareHashAndPassword([]byte(cfg.Password), password); err == nil {
-			goto passwordOK
+		if !authOk {
+			emitLoginAttempt(false, username)
+			error()
+			return
 		}
 
-		// Neither of the attempts to verify the password checked out
-		emitLoginAttempt(false, username)
-		error()
-		return
-
-	passwordOK:
 		sessionid := rand.String(32)
 		sessionsMut.Lock()
 		sessions[sessionid] = true
@@ -128,6 +115,54 @@ func basicAuthAndSessionMiddleware(cookieName string, cfg config.GUIConfiguratio
 	})
 }
 
+func auth(username string, password string, guiCfg config.GUIConfiguration, ldapCfg config.LDAPConfiguration) bool {
+	if guiCfg.AuthMode == config.AuthModeLDAP {
+		return authLDAP(username, password, ldapCfg)
+	} else {
+		return authStatic(username, password, guiCfg.User, guiCfg.Password)
+	}
+}
+
+func authStatic(username string, password string, configUser string, configPassword string) bool {
+	configPasswordBytes := []byte(configPassword)
+	passwordBytes := []byte(password)
+	return bcrypt.CompareHashAndPassword(configPasswordBytes, passwordBytes) == nil && username == configUser
+}
+
+func authLDAP(username string, password string, cfg config.LDAPConfiguration) bool {
+	address := cfg.Address
+	var connection *ldap.Conn
+	var err error
+	if cfg.Transport == config.LDAPTransportTLS {
+		connection, err = ldap.DialTLS("tcp", address, &tls.Config{InsecureSkipVerify: cfg.InsecureSkipVerify})
+	} else {
+		connection, err = ldap.Dial("tcp", address)
+	}
+
+	if err != nil {
+		l.Warnln("LDAP Dial:", err)
+		return false
+	}
+
+	if cfg.Transport == config.LDAPTransportStartTLS {
+		err = connection.StartTLS(&tls.Config{InsecureSkipVerify: cfg.InsecureSkipVerify})
+		if err != nil {
+			l.Warnln("LDAP Start TLS:", err)
+			return false
+		}
+	}
+
+	defer connection.Close()
+
+	err = connection.Bind(fmt.Sprintf(cfg.BindDN, username), password)
+	if err != nil {
+		l.Warnln("LDAP Bind:", err)
+		return false
+	}
+
+	return true
+}
+
 // Convert an ISO-8859-1 encoded byte string to UTF-8. Works by the
 // principle that ISO-8859-1 bytes are equivalent to unicode code points,
 // that a rune slice is a list of code points, and that stringifying a slice

+ 36 - 0
cmd/syncthing/gui_auth_test.go

@@ -0,0 +1,36 @@
+// Copyright (C) 2014 The Syncthing Authors.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at https://mozilla.org/MPL/2.0/.
+
+package main
+
+import (
+	"golang.org/x/crypto/bcrypt"
+	"testing"
+)
+
+func TestStaticAuthOK(t *testing.T) {
+	passwordHashBytes, _ := bcrypt.GenerateFromPassword([]byte("pass"), 14)
+	ok := authStatic("user", "pass", "user", string(passwordHashBytes))
+	if !ok {
+		t.Fatalf("should pass auth")
+	}
+}
+
+func TestSimpleAuthUsernameFail(t *testing.T) {
+	passwordHashBytes, _ := bcrypt.GenerateFromPassword([]byte("pass"), 14)
+	ok := authStatic("userWRONG", "pass", "user", string(passwordHashBytes))
+	if ok {
+		t.Fatalf("should fail auth")
+	}
+}
+
+func TestStaticAuthPasswordFail(t *testing.T) {
+	passwordHashBytes, _ := bcrypt.GenerateFromPassword([]byte("passWRONG"), 14)
+	ok := authStatic("user", "pass", "user", string(passwordHashBytes))
+	if ok {
+		t.Fatalf("should fail auth")
+	}
+}

+ 4 - 0
cmd/syncthing/mocked_config_test.go

@@ -24,6 +24,10 @@ func (c *mockedConfig) ListenAddresses() []string {
 	return nil
 }
 
+func (c *mockedConfig) LDAP() config.LDAPConfiguration {
+	return config.LDAPConfiguration{}
+}
+
 func (c *mockedConfig) RawCopy() config.Configuration {
 	cfg := config.Configuration{}
 	util.SetDefaults(&cfg.Options)

+ 41 - 0
lib/config/authmode.go

@@ -0,0 +1,41 @@
+// Copyright (C) 2018 The Syncthing Authors.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at https://mozilla.org/MPL/2.0/.
+
+package config
+
+type AuthMode int
+
+const (
+	AuthModeStatic AuthMode = iota // default is static
+	AuthModeLDAP
+)
+
+func (t AuthMode) String() string {
+	switch t {
+	case AuthModeStatic:
+		return "static"
+	case AuthModeLDAP:
+		return "ldap"
+	default:
+		return "unknown"
+	}
+}
+
+func (t AuthMode) MarshalText() ([]byte, error) {
+	return []byte(t.String()), nil
+}
+
+func (t *AuthMode) UnmarshalText(bs []byte) error {
+	switch string(bs) {
+	case "ldap":
+		*t = AuthModeLDAP
+	case "static":
+		*t = AuthModeStatic
+	default:
+		*t = AuthModeStatic
+	}
+	return nil
+}

+ 1 - 0
lib/config/config.go

@@ -129,6 +129,7 @@ type Configuration struct {
 	Folders        []FolderConfiguration `xml:"folder" json:"folders"`
 	Devices        []DeviceConfiguration `xml:"device" json:"devices"`
 	GUI            GUIConfiguration      `xml:"gui" json:"gui"`
+	LDAP           LDAPConfiguration     `xml:"ldap" json:"ldap"`
 	Options        OptionsConfiguration  `xml:"options" json:"options"`
 	IgnoredDevices []ObservedDevice      `xml:"remoteIgnoredDevice" json:"remoteIgnoredDevices"`
 	PendingDevices []ObservedDevice      `xml:"pendingDevice" json:"pendingDevices"`

+ 16 - 11
lib/config/guiconfiguration.go

@@ -13,17 +13,22 @@ import (
 )
 
 type GUIConfiguration struct {
-	Enabled                   bool   `xml:"enabled,attr" json:"enabled" default:"true"`
-	RawAddress                string `xml:"address" json:"address" default:"127.0.0.1:8384"`
-	User                      string `xml:"user,omitempty" json:"user"`
-	Password                  string `xml:"password,omitempty" json:"password"`
-	RawUseTLS                 bool   `xml:"tls,attr" json:"useTLS"`
-	APIKey                    string `xml:"apikey,omitempty" json:"apiKey"`
-	InsecureAdminAccess       bool   `xml:"insecureAdminAccess,omitempty" json:"insecureAdminAccess"`
-	Theme                     string `xml:"theme" json:"theme" default:"default"`
-	Debugging                 bool   `xml:"debugging,attr" json:"debugging"`
-	InsecureSkipHostCheck     bool   `xml:"insecureSkipHostcheck,omitempty" json:"insecureSkipHostcheck"`
-	InsecureAllowFrameLoading bool   `xml:"insecureAllowFrameLoading,omitempty" json:"insecureAllowFrameLoading"`
+	Enabled                   bool     `xml:"enabled,attr" json:"enabled" default:"true"`
+	RawAddress                string   `xml:"address" json:"address" default:"127.0.0.1:8384"`
+	User                      string   `xml:"user,omitempty" json:"user"`
+	Password                  string   `xml:"password,omitempty" json:"password"`
+	AuthMode                  AuthMode `xml:"authMode,omitempty" json:"authMode"`
+	RawUseTLS                 bool     `xml:"tls,attr" json:"useTLS"`
+	APIKey                    string   `xml:"apikey,omitempty" json:"apiKey"`
+	InsecureAdminAccess       bool     `xml:"insecureAdminAccess,omitempty" json:"insecureAdminAccess"`
+	Theme                     string   `xml:"theme" json:"theme" default:"default"`
+	Debugging                 bool     `xml:"debugging,attr" json:"debugging"`
+	InsecureSkipHostCheck     bool     `xml:"insecureSkipHostcheck,omitempty" json:"insecureSkipHostcheck"`
+	InsecureAllowFrameLoading bool     `xml:"insecureAllowFrameLoading,omitempty" json:"insecureAllowFrameLoading"`
+}
+
+func (c GUIConfiguration) IsAuthEnabled() bool {
+	return c.AuthMode == AuthModeLDAP || (len(c.User) > 0 && len(c.Password) > 0)
 }
 
 func (c GUIConfiguration) Address() string {

+ 18 - 0
lib/config/ldapconfiguration.go

@@ -0,0 +1,18 @@
+// Copyright (C) 2018 The Syncthing Authors.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at https://mozilla.org/MPL/2.0/.
+
+package config
+
+type LDAPConfiguration struct {
+	Address            string        `xml:"address,omitempty" json:"addresd"`
+	BindDN             string        `xml:"bindDN,omitempty" json:"bindDN"`
+	Transport          LDAPTransport `xml:"transport,omitempty" json:"transport"`
+	InsecureSkipVerify bool          `xml:"insecureSkipVerify,omitempty" json:"insecureSkipVerify" default:"false"`
+}
+
+func (c LDAPConfiguration) Copy() LDAPConfiguration {
+	return c
+}

+ 46 - 0
lib/config/ldaptransport.go

@@ -0,0 +1,46 @@
+// Copyright (C) 2018 The Syncthing Authors.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at https://mozilla.org/MPL/2.0/.
+
+package config
+
+type LDAPTransport int
+
+const (
+	LDAPTransportPlain LDAPTransport = iota // default is plain
+	LDAPTransportTLS
+	LDAPTransportStartTLS
+)
+
+func (t LDAPTransport) String() string {
+	switch t {
+	case LDAPTransportPlain:
+		return "plain"
+	case LDAPTransportTLS:
+		return "tls"
+	case LDAPTransportStartTLS:
+		return "starttls"
+	default:
+		return "unknown"
+	}
+}
+
+func (t LDAPTransport) MarshalText() ([]byte, error) {
+	return []byte(t.String()), nil
+}
+
+func (t *LDAPTransport) UnmarshalText(bs []byte) error {
+	switch string(bs) {
+	case "plain":
+		*t = LDAPTransportPlain
+	case "tls":
+		*t = LDAPTransportTLS
+	case "starttls":
+		*t = LDAPTransportStartTLS
+	default:
+		*t = LDAPTransportPlain
+	}
+	return nil
+}

+ 6 - 0
lib/config/wrapper.go

@@ -305,6 +305,12 @@ func (w *Wrapper) SetOptions(opts OptionsConfiguration) (Waiter, error) {
 	return w.replaceLocked(newCfg)
 }
 
+func (w *Wrapper) LDAP() LDAPConfiguration {
+	w.mut.Lock()
+	defer w.mut.Unlock()
+	return w.cfg.LDAP.Copy()
+}
+
 // GUI returns the current GUI configuration object.
 func (w *Wrapper) GUI() GUIConfiguration {
 	w.mut.Lock()

+ 22 - 0
vendor/gopkg.in/asn1-ber.v1/LICENSE

@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2011-2015 Michael Mitton ([email protected])
+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.

+ 504 - 0
vendor/gopkg.in/asn1-ber.v1/ber.go

@@ -0,0 +1,504 @@
+package ber
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	"io"
+	"os"
+	"reflect"
+)
+
+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
+	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
+}

+ 25 - 0
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
+}

+ 29 - 0
vendor/gopkg.in/asn1-ber.v1/header.go

@@ -0,0 +1,29 @@
+package ber
+
+import (
+	"errors"
+	"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")
+	}
+
+	return identifier, length, read, nil
+}

+ 103 - 0
vendor/gopkg.in/asn1-ber.v1/identifier.go

@@ -0,0 +1,103 @@
+package ber
+
+import (
+	"errors"
+	"fmt"
+	"io"
+	"math"
+)
+
+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
+
+		highBit := uint(63)
+		for {
+			if tag&(1<<highBit) != 0 {
+				break
+			}
+			highBit--
+		}
+
+		tagBytes := int(math.Ceil(float64(highBit) / 7.0))
+		for i := tagBytes - 1; i >= 0; i-- {
+			offset := uint(i) * 7
+			mask := Tag(0x7f) << offset
+			tagByte := (tag & mask) >> offset
+			if i != 0 {
+				tagByte |= 0x80
+			}
+			b = append(b, byte(tagByte))
+		}
+	}
+	return b
+}

+ 81 - 0
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
+}

+ 24 - 0
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
+}

+ 22 - 0
vendor/gopkg.in/ldap.v2/LICENSE

@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2011-2015 Michael Mitton ([email protected])
+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.

+ 113 - 0
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
+}

+ 13 - 0
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
+}

+ 28 - 0
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
+}

+ 143 - 0
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
+}

+ 27 - 0
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)
+}

+ 85 - 0
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 <attributedescription>
+//                         -- [RFC4512]
+//
+// AttributeValue ::= OCTET STRING
+//
+
+package ldap
+
+import (
+	"errors"
+	"fmt"
+
+	"gopkg.in/asn1-ber.v1"
+)
+
+// Compare checks to see if the attribute of the dn matches value. Returns true if it does otherwise
+// false with any error that occurs if any.
+func (l *Conn) Compare(dn, attribute, value string) (bool, error) {
+	packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
+	packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID"))
+
+	request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationCompareRequest, nil, "Compare Request")
+	request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, dn, "DN"))
+
+	ava := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "AttributeValueAssertion")
+	ava.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute, "AttributeDesc"))
+	ava.AppendChild(ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagOctetString, value, "AssertionValue"))
+	request.AppendChild(ava)
+	packet.AppendChild(request)
+
+	l.Debug.PrintPacket(packet)
+
+	msgCtx, err := l.sendMessage(packet)
+	if err != nil {
+		return false, err
+	}
+	defer l.finishMessage(msgCtx)
+
+	l.Debug.Printf("%d: waiting for response", msgCtx.id)
+	packetResponse, ok := <-msgCtx.responses
+	if !ok {
+		return false, NewError(ErrorNetwork, errors.New("ldap: response channel closed"))
+	}
+	packet, err = packetResponse.ReadPacket()
+	l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
+	if err != nil {
+		return false, err
+	}
+
+	if l.Debug {
+		if err := addLDAPDescriptions(packet); err != nil {
+			return false, err
+		}
+		ber.PrintPacket(packet)
+	}
+
+	if packet.Children[1].Tag == ApplicationCompareResponse {
+		resultCode, resultDescription := getLDAPResultCode(packet)
+		if resultCode == LDAPResultCompareTrue {
+			return true, nil
+		} else if resultCode == LDAPResultCompareFalse {
+			return false, nil
+		} else {
+			return false, NewError(resultCode, errors.New(resultDescription))
+		}
+	}
+	return false, fmt.Errorf("Unexpected Response: %d", packet.Children[1].Tag)
+}

+ 470 - 0
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
+		}
+	}
+}

+ 420 - 0
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
+}

+ 24 - 0
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)
+	}
+}

+ 84 - 0
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
+}

+ 247 - 0
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 <escaped>, leading
+//     ; SHARP or SPACE, trailing SPACE, and NULL.
+//     string =   [ ( leadchar / pair ) [ *( stringchar / pair )
+//        ( trailchar / pair ) ] ]
+//
+//     leadchar = LUTF1 / UTFMB
+//     LUTF1 = %x01-1F / %x21 / %x24-2A / %x2D-3A /
+//        %x3D / %x3F-5B / %x5D-7F
+//
+//     trailchar  = TUTF1 / UTFMB
+//     TUTF1 = %x01-1F / %x21 / %x23-2A / %x2D-3A /
+//        %x3D / %x3F-5B / %x5D-7F
+//
+//     stringchar = SUTF1 / UTFMB
+//     SUTF1 = %x01-21 / %x23-2A / %x2D-3A /
+//        %x3D / %x3F-5B / %x5D-7F
+//
+//     pair = ESC ( ESC / special / hexpair )
+//     special = escaped / SPACE / SHARP / EQUALS
+//     escaped = DQUOTE / PLUS / COMMA / SEMI / LANGLE / RANGLE
+//     hexstring = SHARP 1*hexpair
+//     hexpair = HEX HEX
+//
+//  where the productions <descr>, <numericoid>, <COMMA>, <DQUOTE>,
+//  <EQUALS>, <ESC>, <HEX>, <LANGLE>, <NULL>, <PLUS>, <RANGLE>, <SEMI>,
+//  <SPACE>, <SHARP>, and <UTFMB> are defined in [RFC4512].
+//
+
+package ldap
+
+import (
+	"bytes"
+	enchex "encoding/hex"
+	"errors"
+	"fmt"
+	"strings"
+
+	"gopkg.in/asn1-ber.v1"
+)
+
+// AttributeTypeAndValue represents an attributeTypeAndValue from https://tools.ietf.org/html/rfc4514
+type AttributeTypeAndValue struct {
+	// Type is the attribute type
+	Type string
+	// Value is the attribute value
+	Value string
+}
+
+// RelativeDN represents a relativeDistinguishedName from https://tools.ietf.org/html/rfc4514
+type RelativeDN struct {
+	Attributes []*AttributeTypeAndValue
+}
+
+// DN represents a distinguishedName from https://tools.ietf.org/html/rfc4514
+type DN struct {
+	RDNs []*RelativeDN
+}
+
+// ParseDN returns a distinguishedName or an error
+func ParseDN(str string) (*DN, error) {
+	dn := new(DN)
+	dn.RDNs = make([]*RelativeDN, 0)
+	rdn := new(RelativeDN)
+	rdn.Attributes = make([]*AttributeTypeAndValue, 0)
+	buffer := bytes.Buffer{}
+	attribute := new(AttributeTypeAndValue)
+	escaping := false
+
+	unescapedTrailingSpaces := 0
+	stringFromBuffer := func() string {
+		s := buffer.String()
+		s = s[0 : len(s)-unescapedTrailingSpaces]
+		buffer.Reset()
+		unescapedTrailingSpaces = 0
+		return s
+	}
+
+	for i := 0; i < len(str); i++ {
+		char := str[i]
+		if escaping {
+			unescapedTrailingSpaces = 0
+			escaping = false
+			switch char {
+			case ' ', '"', '#', '+', ',', ';', '<', '=', '>', '\\':
+				buffer.WriteByte(char)
+				continue
+			}
+			// Not a special character, assume hex encoded octet
+			if len(str) == i+1 {
+				return nil, errors.New("Got corrupted escaped character")
+			}
+
+			dst := []byte{0}
+			n, err := enchex.Decode([]byte(dst), []byte(str[i:i+2]))
+			if err != nil {
+				return nil, fmt.Errorf("Failed to decode escaped character: %s", err)
+			} else if n != 1 {
+				return nil, fmt.Errorf("Expected 1 byte when un-escaping, got %d", n)
+			}
+			buffer.WriteByte(dst[0])
+			i++
+		} else if char == '\\' {
+			unescapedTrailingSpaces = 0
+			escaping = true
+		} else if char == '=' {
+			attribute.Type = stringFromBuffer()
+			// Special case: If the first character in the value is # the
+			// following data is BER encoded so we can just fast forward
+			// and decode.
+			if len(str) > i+1 && str[i+1] == '#' {
+				i += 2
+				index := strings.IndexAny(str[i:], ",+")
+				data := str
+				if index > 0 {
+					data = str[i : i+index]
+				} else {
+					data = str[i:]
+				}
+				rawBER, err := enchex.DecodeString(data)
+				if err != nil {
+					return nil, fmt.Errorf("Failed to decode BER encoding: %s", err)
+				}
+				packet := ber.DecodePacket(rawBER)
+				buffer.WriteString(packet.Data.String())
+				i += len(data) - 1
+			}
+		} else if char == ',' || char == '+' {
+			// We're done with this RDN or value, push it
+			if len(attribute.Type) == 0 {
+				return nil, errors.New("incomplete type, value pair")
+			}
+			attribute.Value = stringFromBuffer()
+			rdn.Attributes = append(rdn.Attributes, attribute)
+			attribute = new(AttributeTypeAndValue)
+			if char == ',' {
+				dn.RDNs = append(dn.RDNs, rdn)
+				rdn = new(RelativeDN)
+				rdn.Attributes = make([]*AttributeTypeAndValue, 0)
+			}
+		} else if char == ' ' && buffer.Len() == 0 {
+			// ignore unescaped leading spaces
+			continue
+		} else {
+			if char == ' ' {
+				// Track unescaped spaces in case they are trailing and we need to remove them
+				unescapedTrailingSpaces++
+			} else {
+				// Reset if we see a non-space char
+				unescapedTrailingSpaces = 0
+			}
+			buffer.WriteByte(char)
+		}
+	}
+	if buffer.Len() > 0 {
+		if len(attribute.Type) == 0 {
+			return nil, errors.New("DN ended with incomplete type, value pair")
+		}
+		attribute.Value = stringFromBuffer()
+		rdn.Attributes = append(rdn.Attributes, attribute)
+		dn.RDNs = append(dn.RDNs, rdn)
+	}
+	return dn, nil
+}
+
+// Equal returns true if the DNs are equal as defined by rfc4517 4.2.15 (distinguishedNameMatch).
+// Returns true if they have the same number of relative distinguished names
+// and corresponding relative distinguished names (by position) are the same.
+func (d *DN) Equal(other *DN) bool {
+	if len(d.RDNs) != len(other.RDNs) {
+		return false
+	}
+	for i := range d.RDNs {
+		if !d.RDNs[i].Equal(other.RDNs[i]) {
+			return false
+		}
+	}
+	return true
+}
+
+// AncestorOf returns true if the other DN consists of at least one RDN followed by all the RDNs of the current DN.
+// "ou=widgets,o=acme.com" is an ancestor of "ou=sprockets,ou=widgets,o=acme.com"
+// "ou=widgets,o=acme.com" is not an ancestor of "ou=sprockets,ou=widgets,o=foo.com"
+// "ou=widgets,o=acme.com" is not an ancestor of "ou=widgets,o=acme.com"
+func (d *DN) AncestorOf(other *DN) bool {
+	if len(d.RDNs) >= len(other.RDNs) {
+		return false
+	}
+	// Take the last `len(d.RDNs)` RDNs from the other DN to compare against
+	otherRDNs := other.RDNs[len(other.RDNs)-len(d.RDNs):]
+	for i := range d.RDNs {
+		if !d.RDNs[i].Equal(otherRDNs[i]) {
+			return false
+		}
+	}
+	return true
+}
+
+// Equal returns true if the RelativeDNs are equal as defined by rfc4517 4.2.15 (distinguishedNameMatch).
+// Relative distinguished names are the same if and only if they have the same number of AttributeTypeAndValues
+// and each attribute of the first RDN is the same as the attribute of the second RDN with the same attribute type.
+// The order of attributes is not significant.
+// Case of attribute types is not significant.
+func (r *RelativeDN) Equal(other *RelativeDN) bool {
+	if len(r.Attributes) != len(other.Attributes) {
+		return false
+	}
+	return r.hasAllAttributes(other.Attributes) && other.hasAllAttributes(r.Attributes)
+}
+
+func (r *RelativeDN) hasAllAttributes(attrs []*AttributeTypeAndValue) bool {
+	for _, attr := range attrs {
+		found := false
+		for _, myattr := range r.Attributes {
+			if myattr.Equal(attr) {
+				found = true
+				break
+			}
+		}
+		if !found {
+			return false
+		}
+	}
+	return true
+}
+
+// Equal returns true if the AttributeTypeAndValue is equivalent to the specified AttributeTypeAndValue
+// Case of the attribute type is not significant
+func (a *AttributeTypeAndValue) Equal(other *AttributeTypeAndValue) bool {
+	return strings.EqualFold(a.Type, other.Type) && a.Value == other.Value
+}

+ 4 - 0
vendor/gopkg.in/ldap.v2/doc.go

@@ -0,0 +1,4 @@
+/*
+Package ldap provides basic LDAP v3 functionality.
+*/
+package ldap

+ 155 - 0
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
+}

+ 469 - 0
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
+}

+ 320 - 0
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)
+}

+ 170 - 0
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 <attributedescription>
+//                         -- [RFC4512]
+//
+// AttributeValue ::= OCTET STRING
+//
+
+package ldap
+
+import (
+	"errors"
+	"log"
+
+	"gopkg.in/asn1-ber.v1"
+)
+
+// Change operation choices
+const (
+	AddAttribute     = 0
+	DeleteAttribute  = 1
+	ReplaceAttribute = 2
+)
+
+// PartialAttribute for a ModifyRequest as defined in https://tools.ietf.org/html/rfc4511
+type PartialAttribute struct {
+	// Type is the type of the partial attribute
+	Type string
+	// Vals are the values of the partial attribute
+	Vals []string
+}
+
+func (p *PartialAttribute) encode() *ber.Packet {
+	seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "PartialAttribute")
+	seq.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, p.Type, "Type"))
+	set := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSet, nil, "AttributeValue")
+	for _, value := range p.Vals {
+		set.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, value, "Vals"))
+	}
+	seq.AppendChild(set)
+	return seq
+}
+
+// ModifyRequest as defined in https://tools.ietf.org/html/rfc4511
+type ModifyRequest struct {
+	// DN is the distinguishedName of the directory entry to modify
+	DN string
+	// AddAttributes contain the attributes to add
+	AddAttributes []PartialAttribute
+	// DeleteAttributes contain the attributes to delete
+	DeleteAttributes []PartialAttribute
+	// ReplaceAttributes contain the attributes to replace
+	ReplaceAttributes []PartialAttribute
+}
+
+// Add inserts the given attribute to the list of attributes to add
+func (m *ModifyRequest) Add(attrType string, attrVals []string) {
+	m.AddAttributes = append(m.AddAttributes, PartialAttribute{Type: attrType, Vals: attrVals})
+}
+
+// Delete inserts the given attribute to the list of attributes to delete
+func (m *ModifyRequest) Delete(attrType string, attrVals []string) {
+	m.DeleteAttributes = append(m.DeleteAttributes, PartialAttribute{Type: attrType, Vals: attrVals})
+}
+
+// Replace inserts the given attribute to the list of attributes to replace
+func (m *ModifyRequest) Replace(attrType string, attrVals []string) {
+	m.ReplaceAttributes = append(m.ReplaceAttributes, PartialAttribute{Type: attrType, Vals: attrVals})
+}
+
+func (m ModifyRequest) encode() *ber.Packet {
+	request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationModifyRequest, nil, "Modify Request")
+	request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, m.DN, "DN"))
+	changes := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Changes")
+	for _, attribute := range m.AddAttributes {
+		change := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Change")
+		change.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(AddAttribute), "Operation"))
+		change.AppendChild(attribute.encode())
+		changes.AppendChild(change)
+	}
+	for _, attribute := range m.DeleteAttributes {
+		change := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Change")
+		change.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(DeleteAttribute), "Operation"))
+		change.AppendChild(attribute.encode())
+		changes.AppendChild(change)
+	}
+	for _, attribute := range m.ReplaceAttributes {
+		change := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Change")
+		change.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(ReplaceAttribute), "Operation"))
+		change.AppendChild(attribute.encode())
+		changes.AppendChild(change)
+	}
+	request.AppendChild(changes)
+	return request
+}
+
+// NewModifyRequest creates a modify request for the given DN
+func NewModifyRequest(
+	dn string,
+) *ModifyRequest {
+	return &ModifyRequest{
+		DN: dn,
+	}
+}
+
+// Modify performs the ModifyRequest
+func (l *Conn) Modify(modifyRequest *ModifyRequest) error {
+	packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
+	packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID"))
+	packet.AppendChild(modifyRequest.encode())
+
+	l.Debug.PrintPacket(packet)
+
+	msgCtx, err := l.sendMessage(packet)
+	if err != nil {
+		return err
+	}
+	defer l.finishMessage(msgCtx)
+
+	l.Debug.Printf("%d: waiting for response", msgCtx.id)
+	packetResponse, ok := <-msgCtx.responses
+	if !ok {
+		return NewError(ErrorNetwork, errors.New("ldap: response channel closed"))
+	}
+	packet, err = packetResponse.ReadPacket()
+	l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
+	if err != nil {
+		return err
+	}
+
+	if l.Debug {
+		if err := addLDAPDescriptions(packet); err != nil {
+			return err
+		}
+		ber.PrintPacket(packet)
+	}
+
+	if packet.Children[1].Tag == ApplicationModifyResponse {
+		resultCode, resultDescription := getLDAPResultCode(packet)
+		if resultCode != 0 {
+			return NewError(resultCode, errors.New(resultDescription))
+		}
+	} else {
+		log.Printf("Unexpected Response: %d", packet.Children[1].Tag)
+	}
+
+	l.Debug.Printf("%d: returning", msgCtx.id)
+	return nil
+}

+ 148 - 0
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
+}

+ 450 - 0
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
+//                         -- <attributeSelector> in Section 4.5.1.8
+//
+//         Filter ::= CHOICE {
+//              and             [0] SET SIZE (1..MAX) OF filter Filter,
+//              or              [1] SET SIZE (1..MAX) OF filter Filter,
+//              not             [2] Filter,
+//              equalityMatch   [3] AttributeValueAssertion,
+//              substrings      [4] SubstringFilter,
+//              greaterOrEqual  [5] AttributeValueAssertion,
+//              lessOrEqual     [6] AttributeValueAssertion,
+//              present         [7] AttributeDescription,
+//              approxMatch     [8] AttributeValueAssertion,
+//              extensibleMatch [9] MatchingRuleAssertion,
+//              ...  }
+//
+//         SubstringFilter ::= SEQUENCE {
+//              type           AttributeDescription,
+//              substrings     SEQUENCE SIZE (1..MAX) OF substring CHOICE {
+//                   initial [0] AssertionValue,  -- can occur at most once
+//                   any     [1] AssertionValue,
+//                   final   [2] AssertionValue } -- can occur at most once
+//              }
+//
+//         MatchingRuleAssertion ::= SEQUENCE {
+//              matchingRule    [1] MatchingRuleId OPTIONAL,
+//              type            [2] AttributeDescription OPTIONAL,
+//              matchValue      [3] AssertionValue,
+//              dnAttributes    [4] BOOLEAN DEFAULT FALSE }
+//
+//
+
+package ldap
+
+import (
+	"errors"
+	"fmt"
+	"sort"
+	"strings"
+
+	"gopkg.in/asn1-ber.v1"
+)
+
+// scope choices
+const (
+	ScopeBaseObject   = 0
+	ScopeSingleLevel  = 1
+	ScopeWholeSubtree = 2
+)
+
+// ScopeMap contains human readable descriptions of scope choices
+var ScopeMap = map[int]string{
+	ScopeBaseObject:   "Base Object",
+	ScopeSingleLevel:  "Single Level",
+	ScopeWholeSubtree: "Whole Subtree",
+}
+
+// derefAliases
+const (
+	NeverDerefAliases   = 0
+	DerefInSearching    = 1
+	DerefFindingBaseObj = 2
+	DerefAlways         = 3
+)
+
+// DerefMap contains human readable descriptions of derefAliases choices
+var DerefMap = map[int]string{
+	NeverDerefAliases:   "NeverDerefAliases",
+	DerefInSearching:    "DerefInSearching",
+	DerefFindingBaseObj: "DerefFindingBaseObj",
+	DerefAlways:         "DerefAlways",
+}
+
+// NewEntry returns an Entry object with the specified distinguished name and attribute key-value pairs.
+// The map of attributes is accessed in alphabetical order of the keys in order to ensure that, for the
+// same input map of attributes, the output entry will contain the same order of attributes
+func NewEntry(dn string, attributes map[string][]string) *Entry {
+	var attributeNames []string
+	for attributeName := range attributes {
+		attributeNames = append(attributeNames, attributeName)
+	}
+	sort.Strings(attributeNames)
+
+	var encodedAttributes []*EntryAttribute
+	for _, attributeName := range attributeNames {
+		encodedAttributes = append(encodedAttributes, NewEntryAttribute(attributeName, attributes[attributeName]))
+	}
+	return &Entry{
+		DN:         dn,
+		Attributes: encodedAttributes,
+	}
+}
+
+// Entry represents a single search result entry
+type Entry struct {
+	// DN is the distinguished name of the entry
+	DN string
+	// Attributes are the returned attributes for the entry
+	Attributes []*EntryAttribute
+}
+
+// GetAttributeValues returns the values for the named attribute, or an empty list
+func (e *Entry) GetAttributeValues(attribute string) []string {
+	for _, attr := range e.Attributes {
+		if attr.Name == attribute {
+			return attr.Values
+		}
+	}
+	return []string{}
+}
+
+// GetRawAttributeValues returns the byte values for the named attribute, or an empty list
+func (e *Entry) GetRawAttributeValues(attribute string) [][]byte {
+	for _, attr := range e.Attributes {
+		if attr.Name == attribute {
+			return attr.ByteValues
+		}
+	}
+	return [][]byte{}
+}
+
+// GetAttributeValue returns the first value for the named attribute, or ""
+func (e *Entry) GetAttributeValue(attribute string) string {
+	values := e.GetAttributeValues(attribute)
+	if len(values) == 0 {
+		return ""
+	}
+	return values[0]
+}
+
+// GetRawAttributeValue returns the first value for the named attribute, or an empty slice
+func (e *Entry) GetRawAttributeValue(attribute string) []byte {
+	values := e.GetRawAttributeValues(attribute)
+	if len(values) == 0 {
+		return []byte{}
+	}
+	return values[0]
+}
+
+// Print outputs a human-readable description
+func (e *Entry) Print() {
+	fmt.Printf("DN: %s\n", e.DN)
+	for _, attr := range e.Attributes {
+		attr.Print()
+	}
+}
+
+// PrettyPrint outputs a human-readable description indenting
+func (e *Entry) PrettyPrint(indent int) {
+	fmt.Printf("%sDN: %s\n", strings.Repeat(" ", indent), e.DN)
+	for _, attr := range e.Attributes {
+		attr.PrettyPrint(indent + 2)
+	}
+}
+
+// NewEntryAttribute returns a new EntryAttribute with the desired key-value pair
+func NewEntryAttribute(name string, values []string) *EntryAttribute {
+	var bytes [][]byte
+	for _, value := range values {
+		bytes = append(bytes, []byte(value))
+	}
+	return &EntryAttribute{
+		Name:       name,
+		Values:     values,
+		ByteValues: bytes,
+	}
+}
+
+// EntryAttribute holds a single attribute
+type EntryAttribute struct {
+	// Name is the name of the attribute
+	Name string
+	// Values contain the string values of the attribute
+	Values []string
+	// ByteValues contain the raw values of the attribute
+	ByteValues [][]byte
+}
+
+// Print outputs a human-readable description
+func (e *EntryAttribute) Print() {
+	fmt.Printf("%s: %s\n", e.Name, e.Values)
+}
+
+// PrettyPrint outputs a human-readable description with indenting
+func (e *EntryAttribute) PrettyPrint(indent int) {
+	fmt.Printf("%s%s: %s\n", strings.Repeat(" ", indent), e.Name, e.Values)
+}
+
+// SearchResult holds the server's response to a search request
+type SearchResult struct {
+	// Entries are the returned entries
+	Entries []*Entry
+	// Referrals are the returned referrals
+	Referrals []string
+	// Controls are the returned controls
+	Controls []Control
+}
+
+// Print outputs a human-readable description
+func (s *SearchResult) Print() {
+	for _, entry := range s.Entries {
+		entry.Print()
+	}
+}
+
+// PrettyPrint outputs a human-readable description with indenting
+func (s *SearchResult) PrettyPrint(indent int) {
+	for _, entry := range s.Entries {
+		entry.PrettyPrint(indent)
+	}
+}
+
+// SearchRequest represents a search request to send to the server
+type SearchRequest struct {
+	BaseDN       string
+	Scope        int
+	DerefAliases int
+	SizeLimit    int
+	TimeLimit    int
+	TypesOnly    bool
+	Filter       string
+	Attributes   []string
+	Controls     []Control
+}
+
+func (s *SearchRequest) encode() (*ber.Packet, error) {
+	request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationSearchRequest, nil, "Search Request")
+	request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, s.BaseDN, "Base DN"))
+	request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(s.Scope), "Scope"))
+	request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(s.DerefAliases), "Deref Aliases"))
+	request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, uint64(s.SizeLimit), "Size Limit"))
+	request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, uint64(s.TimeLimit), "Time Limit"))
+	request.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, s.TypesOnly, "Types Only"))
+	// compile and encode filter
+	filterPacket, err := CompileFilter(s.Filter)
+	if err != nil {
+		return nil, err
+	}
+	request.AppendChild(filterPacket)
+	// encode attributes
+	attributesPacket := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Attributes")
+	for _, attribute := range s.Attributes {
+		attributesPacket.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute, "Attribute"))
+	}
+	request.AppendChild(attributesPacket)
+	return request, nil
+}
+
+// NewSearchRequest creates a new search request
+func NewSearchRequest(
+	BaseDN string,
+	Scope, DerefAliases, SizeLimit, TimeLimit int,
+	TypesOnly bool,
+	Filter string,
+	Attributes []string,
+	Controls []Control,
+) *SearchRequest {
+	return &SearchRequest{
+		BaseDN:       BaseDN,
+		Scope:        Scope,
+		DerefAliases: DerefAliases,
+		SizeLimit:    SizeLimit,
+		TimeLimit:    TimeLimit,
+		TypesOnly:    TypesOnly,
+		Filter:       Filter,
+		Attributes:   Attributes,
+		Controls:     Controls,
+	}
+}
+
+// SearchWithPaging accepts a search request and desired page size in order to execute LDAP queries to fulfill the
+// search request. All paged LDAP query responses will be buffered and the final result will be returned atomically.
+// The following four cases are possible given the arguments:
+//  - given SearchRequest missing a control of type ControlTypePaging: we will add one with the desired paging size
+//  - given SearchRequest contains a control of type ControlTypePaging that isn't actually a ControlPaging: fail without issuing any queries
+//  - given SearchRequest contains a control of type ControlTypePaging with pagingSize equal to the size requested: no change to the search request
+//  - given SearchRequest contains a control of type ControlTypePaging with pagingSize not equal to the size requested: fail without issuing any queries
+// A requested pagingSize of 0 is interpreted as no limit by LDAP servers.
+func (l *Conn) SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32) (*SearchResult, error) {
+	var pagingControl *ControlPaging
+
+	control := FindControl(searchRequest.Controls, ControlTypePaging)
+	if control == nil {
+		pagingControl = NewControlPaging(pagingSize)
+		searchRequest.Controls = append(searchRequest.Controls, pagingControl)
+	} else {
+		castControl, ok := control.(*ControlPaging)
+		if !ok {
+			return nil, fmt.Errorf("Expected paging control to be of type *ControlPaging, got %v", control)
+		}
+		if castControl.PagingSize != pagingSize {
+			return nil, fmt.Errorf("Paging size given in search request (%d) conflicts with size given in search call (%d)", castControl.PagingSize, pagingSize)
+		}
+		pagingControl = castControl
+	}
+
+	searchResult := new(SearchResult)
+	for {
+		result, err := l.Search(searchRequest)
+		l.Debug.Printf("Looking for Paging Control...")
+		if err != nil {
+			return searchResult, err
+		}
+		if result == nil {
+			return searchResult, NewError(ErrorNetwork, errors.New("ldap: packet not received"))
+		}
+
+		for _, entry := range result.Entries {
+			searchResult.Entries = append(searchResult.Entries, entry)
+		}
+		for _, referral := range result.Referrals {
+			searchResult.Referrals = append(searchResult.Referrals, referral)
+		}
+		for _, control := range result.Controls {
+			searchResult.Controls = append(searchResult.Controls, control)
+		}
+
+		l.Debug.Printf("Looking for Paging Control...")
+		pagingResult := FindControl(result.Controls, ControlTypePaging)
+		if pagingResult == nil {
+			pagingControl = nil
+			l.Debug.Printf("Could not find paging control.  Breaking...")
+			break
+		}
+
+		cookie := pagingResult.(*ControlPaging).Cookie
+		if len(cookie) == 0 {
+			pagingControl = nil
+			l.Debug.Printf("Could not find cookie.  Breaking...")
+			break
+		}
+		pagingControl.SetCookie(cookie)
+	}
+
+	if pagingControl != nil {
+		l.Debug.Printf("Abandoning Paging...")
+		pagingControl.PagingSize = 0
+		l.Search(searchRequest)
+	}
+
+	return searchResult, nil
+}
+
+// Search performs the given search request
+func (l *Conn) Search(searchRequest *SearchRequest) (*SearchResult, error) {
+	packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
+	packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID"))
+	// encode search request
+	encodedSearchRequest, err := searchRequest.encode()
+	if err != nil {
+		return nil, err
+	}
+	packet.AppendChild(encodedSearchRequest)
+	// encode search controls
+	if searchRequest.Controls != nil {
+		packet.AppendChild(encodeControls(searchRequest.Controls))
+	}
+
+	l.Debug.PrintPacket(packet)
+
+	msgCtx, err := l.sendMessage(packet)
+	if err != nil {
+		return nil, err
+	}
+	defer l.finishMessage(msgCtx)
+
+	result := &SearchResult{
+		Entries:   make([]*Entry, 0),
+		Referrals: make([]string, 0),
+		Controls:  make([]Control, 0)}
+
+	foundSearchResultDone := false
+	for !foundSearchResultDone {
+		l.Debug.Printf("%d: waiting for response", msgCtx.id)
+		packetResponse, ok := <-msgCtx.responses
+		if !ok {
+			return nil, NewError(ErrorNetwork, errors.New("ldap: response channel closed"))
+		}
+		packet, err = packetResponse.ReadPacket()
+		l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
+		if err != nil {
+			return nil, err
+		}
+
+		if l.Debug {
+			if err := addLDAPDescriptions(packet); err != nil {
+				return nil, err
+			}
+			ber.PrintPacket(packet)
+		}
+
+		switch packet.Children[1].Tag {
+		case 4:
+			entry := new(Entry)
+			entry.DN = packet.Children[1].Children[0].Value.(string)
+			for _, child := range packet.Children[1].Children[1].Children {
+				attr := new(EntryAttribute)
+				attr.Name = child.Children[0].Value.(string)
+				for _, value := range child.Children[1].Children {
+					attr.Values = append(attr.Values, value.Value.(string))
+					attr.ByteValues = append(attr.ByteValues, value.ByteValue)
+				}
+				entry.Attributes = append(entry.Attributes, attr)
+			}
+			result.Entries = append(result.Entries, entry)
+		case 5:
+			resultCode, resultDescription := getLDAPResultCode(packet)
+			if resultCode != 0 {
+				return result, NewError(resultCode, errors.New(resultDescription))
+			}
+			if len(packet.Children) == 3 {
+				for _, child := range packet.Children[2].Children {
+					result.Controls = append(result.Controls, DecodeControl(child))
+				}
+			}
+			foundSearchResultDone = true
+		case 19:
+			result.Referrals = append(result.Referrals, packet.Children[1].Children[0].Value.(string))
+		}
+	}
+	l.Debug.Printf("%d: returning", msgCtx.id)
+	return result, nil
+}

+ 16 - 0
vendor/manifest

@@ -763,6 +763,22 @@
 			"path": "/rate",
 			"notests": true
 		},
+		{
+			"importpath": "gopkg.in/asn1-ber.v1",
+			"repository": "https://gopkg.in/asn1-ber.v1",
+			"vcs": "git",
+			"revision": "379148ca0225df7a432012b8df0355c2a2063ac0",
+			"branch": "master",
+			"notests": true
+		},
+		{
+			"importpath": "gopkg.in/ldap.v2",
+			"repository": "https://gopkg.in/ldap.v2",
+			"vcs": "git",
+			"revision": "bb7a9ca6e4fbc2129e3db588a34bc970ffe811a9",
+			"branch": "master",
+			"notests": true
+		},
 		{
 			"importpath": "gopkg.in/urfave/cli.v1",
 			"repository": "https://gopkg.in/urfave/cli.v1",