You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by ro...@apache.org on 2018/05/17 22:41:23 UTC

[incubator-trafficcontrol] 04/19: vendor ldap.v2 for use in login

This is an automated email from the ASF dual-hosted git repository.

rob pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-trafficcontrol.git

commit 6592c5464173324b8e9fe2eacc8c9a42997f456a
Author: Dylan Volz <Dy...@comcast.com>
AuthorDate: Wed Mar 21 14:48:29 2018 -0600

    vendor ldap.v2 for use in login
---
 .../vendor/gopkg.in/ldap.v2/.travis.yml            |  31 ++
 .../vendor/gopkg.in/ldap.v2/LICENSE                |  22 +
 .../vendor/gopkg.in/ldap.v2/Makefile               |  52 +++
 .../vendor/gopkg.in/ldap.v2/README.md              |  53 +++
 .../vendor/gopkg.in/ldap.v2/add.go                 | 113 +++++
 .../vendor/gopkg.in/ldap.v2/atomic_value.go        |  13 +
 .../vendor/gopkg.in/ldap.v2/atomic_value_go13.go   |  28 ++
 .../vendor/gopkg.in/ldap.v2/bind.go                | 143 +++++++
 .../vendor/gopkg.in/ldap.v2/client.go              |  27 ++
 .../vendor/gopkg.in/ldap.v2/compare.go             |  85 ++++
 .../vendor/gopkg.in/ldap.v2/conn.go                | 470 +++++++++++++++++++++
 .../vendor/gopkg.in/ldap.v2/conn_test.go           | 336 +++++++++++++++
 .../vendor/gopkg.in/ldap.v2/control.go             | 420 ++++++++++++++++++
 .../vendor/gopkg.in/ldap.v2/control_test.go        |  97 +++++
 .../vendor/gopkg.in/ldap.v2/debug.go               |  24 ++
 .../vendor/gopkg.in/ldap.v2/del.go                 |  84 ++++
 .../vendor/gopkg.in/ldap.v2/dn.go                  | 247 +++++++++++
 .../vendor/gopkg.in/ldap.v2/dn_test.go             | 211 +++++++++
 .../vendor/gopkg.in/ldap.v2/doc.go                 |   4 +
 .../vendor/gopkg.in/ldap.v2/error.go               | 155 +++++++
 .../vendor/gopkg.in/ldap.v2/error_test.go          | 102 +++++
 .../vendor/gopkg.in/ldap.v2/example_test.go        | 305 +++++++++++++
 .../vendor/gopkg.in/ldap.v2/filter.go              | 469 ++++++++++++++++++++
 .../vendor/gopkg.in/ldap.v2/filter_test.go         | 254 +++++++++++
 .../vendor/gopkg.in/ldap.v2/ldap.go                | 320 ++++++++++++++
 .../vendor/gopkg.in/ldap.v2/ldap_test.go           | 275 ++++++++++++
 .../vendor/gopkg.in/ldap.v2/modify.go              | 170 ++++++++
 .../vendor/gopkg.in/ldap.v2/passwdmodify.go        | 148 +++++++
 .../vendor/gopkg.in/ldap.v2/search.go              | 450 ++++++++++++++++++++
 .../vendor/gopkg.in/ldap.v2/search_test.go         |  31 ++
 30 files changed, 5139 insertions(+)

diff --git a/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/.travis.yml b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/.travis.yml
new file mode 100644
index 0000000..9782c9b
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/.travis.yml
@@ -0,0 +1,31 @@
+language: go
+env:
+    global:
+        - VET_VERSIONS="1.6 1.7 1.8 1.9 tip"
+        - LINT_VERSIONS="1.6 1.7 1.8 1.9 tip"
+go:
+    - 1.2
+    - 1.3
+    - 1.4
+    - 1.5
+    - 1.6
+    - 1.7
+    - 1.8
+    - 1.9
+    - tip
+matrix:
+    fast_finish: true
+    allow_failures:
+        - go: tip
+go_import_path: gopkg.in/ldap.v2
+install:
+    - go get gopkg.in/asn1-ber.v1
+    - go get gopkg.in/ldap.v2
+    - go get code.google.com/p/go.tools/cmd/cover || go get golang.org/x/tools/cmd/cover
+    - go get github.com/golang/lint/golint || true
+    - go build -v ./...
+script:
+    - make test
+    - make fmt
+    - if [[ "$VET_VERSIONS"  == *"$TRAVIS_GO_VERSION"* ]]; then make vet; fi
+    - if [[ "$LINT_VERSIONS" == *"$TRAVIS_GO_VERSION"* ]]; then make lint; fi
diff --git a/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/LICENSE b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/LICENSE
new file mode 100644
index 0000000..6c0ed4b
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2011-2015 Michael Mitton (mmitton@gmail.com)
+Portions copyright (c) 2015-2016 go-ldap Authors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/Makefile b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/Makefile
new file mode 100644
index 0000000..a9d351c
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/Makefile
@@ -0,0 +1,52 @@
+.PHONY: default install build test quicktest fmt vet lint 
+
+GO_VERSION := $(shell go version | cut -d' ' -f3 | cut -d. -f2)
+
+# Only use the `-race` flag on newer versions of Go
+IS_OLD_GO := $(shell test $(GO_VERSION) -le 2 && echo true)
+ifeq ($(IS_OLD_GO),true)
+	RACE_FLAG :=
+else
+	RACE_FLAG := -race -cpu 1,2,4
+endif
+
+default: fmt vet lint build quicktest
+
+install:
+	go get -t -v ./...
+
+build:
+	go build -v ./...
+
+test:
+	go test -v $(RACE_FLAG) -cover ./...
+
+quicktest:
+	go test ./...
+
+# Capture output and force failure when there is non-empty output
+fmt:
+	@echo gofmt -l .
+	@OUTPUT=`gofmt -l . 2>&1`; \
+	if [ "$$OUTPUT" ]; then \
+		echo "gofmt must be run on the following files:"; \
+		echo "$$OUTPUT"; \
+		exit 1; \
+	fi
+
+# Only run on go1.5+
+vet:
+	go tool vet -atomic -bool -copylocks -nilfunc -printf -shadow -rangeloops -unreachable -unsafeptr -unusedresult .
+
+# https://github.com/golang/lint
+# go get github.com/golang/lint/golint
+# Capture output and force failure when there is non-empty output
+# Only run on go1.5+
+lint:
+	@echo golint ./...
+	@OUTPUT=`golint ./... 2>&1`; \
+	if [ "$$OUTPUT" ]; then \
+		echo "golint errors:"; \
+		echo "$$OUTPUT"; \
+		exit 1; \
+	fi
diff --git a/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/README.md b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/README.md
new file mode 100644
index 0000000..a26ed2d
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/README.md
@@ -0,0 +1,53 @@
+[![GoDoc](https://godoc.org/gopkg.in/ldap.v2?status.svg)](https://godoc.org/gopkg.in/ldap.v2)
+[![Build Status](https://travis-ci.org/go-ldap/ldap.svg)](https://travis-ci.org/go-ldap/ldap)
+
+# Basic LDAP v3 functionality for the GO programming language.
+
+## Install
+
+For the latest version use:
+
+    go get gopkg.in/ldap.v2
+
+Import the latest version with:
+
+    import "gopkg.in/ldap.v2"
+
+## Required Libraries:
+
+ - gopkg.in/asn1-ber.v1
+
+## Features:
+
+ - Connecting to LDAP server (non-TLS, TLS, STARTTLS)
+ - Binding to LDAP server
+ - Searching for entries
+ - Filter Compile / Decompile
+ - Paging Search Results
+ - Modify Requests / Responses
+ - Add Requests / Responses
+ - Delete Requests / Responses
+
+## Examples:
+
+ - search
+ - modify
+
+## Contributing:
+
+Bug reports and pull requests are welcome!
+
+Before submitting a pull request, please make sure tests and verification scripts pass:
+```
+make all
+```
+
+To set up a pre-push hook to run the tests and verify scripts before pushing:
+```
+ln -s ../../.githooks/pre-push .git/hooks/pre-push
+```
+
+---
+The Go gopher was designed by Renee French. (http://reneefrench.blogspot.com/)
+The design is licensed under the Creative Commons 3.0 Attributions license.
+Read this article for more details: http://blog.golang.org/gopher
diff --git a/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/add.go b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/add.go
new file mode 100644
index 0000000..0e5f6cd
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/add.go
@@ -0,0 +1,113 @@
+//
+// https://tools.ietf.org/html/rfc4511
+//
+// AddRequest ::= [APPLICATION 8] SEQUENCE {
+//      entry           LDAPDN,
+//      attributes      AttributeList }
+//
+// AttributeList ::= SEQUENCE OF attribute Attribute
+
+package ldap
+
+import (
+	"errors"
+	"log"
+
+	"gopkg.in/asn1-ber.v1"
+)
+
+// Attribute represents an LDAP attribute
+type Attribute struct {
+	// Type is the name of the LDAP attribute
+	Type string
+	// Vals are the LDAP attribute values
+	Vals []string
+}
+
+func (a *Attribute) encode() *ber.Packet {
+	seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Attribute")
+	seq.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, a.Type, "Type"))
+	set := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSet, nil, "AttributeValue")
+	for _, value := range a.Vals {
+		set.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, value, "Vals"))
+	}
+	seq.AppendChild(set)
+	return seq
+}
+
+// AddRequest represents an LDAP AddRequest operation
+type AddRequest struct {
+	// DN identifies the entry being added
+	DN string
+	// Attributes list the attributes of the new entry
+	Attributes []Attribute
+}
+
+func (a AddRequest) encode() *ber.Packet {
+	request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationAddRequest, nil, "Add Request")
+	request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, a.DN, "DN"))
+	attributes := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Attributes")
+	for _, attribute := range a.Attributes {
+		attributes.AppendChild(attribute.encode())
+	}
+	request.AppendChild(attributes)
+	return request
+}
+
+// Attribute adds an attribute with the given type and values
+func (a *AddRequest) Attribute(attrType string, attrVals []string) {
+	a.Attributes = append(a.Attributes, Attribute{Type: attrType, Vals: attrVals})
+}
+
+// NewAddRequest returns an AddRequest for the given DN, with no attributes
+func NewAddRequest(dn string) *AddRequest {
+	return &AddRequest{
+		DN: dn,
+	}
+
+}
+
+// Add performs the given AddRequest
+func (l *Conn) Add(addRequest *AddRequest) error {
+	packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
+	packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID"))
+	packet.AppendChild(addRequest.encode())
+
+	l.Debug.PrintPacket(packet)
+
+	msgCtx, err := l.sendMessage(packet)
+	if err != nil {
+		return err
+	}
+	defer l.finishMessage(msgCtx)
+
+	l.Debug.Printf("%d: waiting for response", msgCtx.id)
+	packetResponse, ok := <-msgCtx.responses
+	if !ok {
+		return NewError(ErrorNetwork, errors.New("ldap: response channel closed"))
+	}
+	packet, err = packetResponse.ReadPacket()
+	l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
+	if err != nil {
+		return err
+	}
+
+	if l.Debug {
+		if err := addLDAPDescriptions(packet); err != nil {
+			return err
+		}
+		ber.PrintPacket(packet)
+	}
+
+	if packet.Children[1].Tag == ApplicationAddResponse {
+		resultCode, resultDescription := getLDAPResultCode(packet)
+		if resultCode != 0 {
+			return NewError(resultCode, errors.New(resultDescription))
+		}
+	} else {
+		log.Printf("Unexpected Response: %d", packet.Children[1].Tag)
+	}
+
+	l.Debug.Printf("%d: returning", msgCtx.id)
+	return nil
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/atomic_value.go b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/atomic_value.go
new file mode 100644
index 0000000..bccf757
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/atomic_value.go
@@ -0,0 +1,13 @@
+// +build go1.4
+
+package ldap
+
+import (
+	"sync/atomic"
+)
+
+// For compilers that support it, we just use the underlying sync/atomic.Value
+// type.
+type atomicValue struct {
+	atomic.Value
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/atomic_value_go13.go b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/atomic_value_go13.go
new file mode 100644
index 0000000..04920bb
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/atomic_value_go13.go
@@ -0,0 +1,28 @@
+// +build !go1.4
+
+package ldap
+
+import (
+	"sync"
+)
+
+// This is a helper type that emulates the use of the "sync/atomic.Value"
+// struct that's available in Go 1.4 and up.
+type atomicValue struct {
+	value interface{}
+	lock  sync.RWMutex
+}
+
+func (av *atomicValue) Store(val interface{}) {
+	av.lock.Lock()
+	av.value = val
+	av.lock.Unlock()
+}
+
+func (av *atomicValue) Load() interface{} {
+	av.lock.RLock()
+	ret := av.value
+	av.lock.RUnlock()
+
+	return ret
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/bind.go b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/bind.go
new file mode 100644
index 0000000..26b3cc7
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/bind.go
@@ -0,0 +1,143 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ldap
+
+import (
+	"errors"
+
+	"gopkg.in/asn1-ber.v1"
+)
+
+// SimpleBindRequest represents a username/password bind operation
+type SimpleBindRequest struct {
+	// Username is the name of the Directory object that the client wishes to bind as
+	Username string
+	// Password is the credentials to bind with
+	Password string
+	// Controls are optional controls to send with the bind request
+	Controls []Control
+}
+
+// SimpleBindResult contains the response from the server
+type SimpleBindResult struct {
+	Controls []Control
+}
+
+// NewSimpleBindRequest returns a bind request
+func NewSimpleBindRequest(username string, password string, controls []Control) *SimpleBindRequest {
+	return &SimpleBindRequest{
+		Username: username,
+		Password: password,
+		Controls: controls,
+	}
+}
+
+func (bindRequest *SimpleBindRequest) encode() *ber.Packet {
+	request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request")
+	request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version"))
+	request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, bindRequest.Username, "User Name"))
+	request.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, bindRequest.Password, "Password"))
+
+	request.AppendChild(encodeControls(bindRequest.Controls))
+
+	return request
+}
+
+// SimpleBind performs the simple bind operation defined in the given request
+func (l *Conn) SimpleBind(simpleBindRequest *SimpleBindRequest) (*SimpleBindResult, error) {
+	packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
+	packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID"))
+	encodedBindRequest := simpleBindRequest.encode()
+	packet.AppendChild(encodedBindRequest)
+
+	if l.Debug {
+		ber.PrintPacket(packet)
+	}
+
+	msgCtx, err := l.sendMessage(packet)
+	if err != nil {
+		return nil, err
+	}
+	defer l.finishMessage(msgCtx)
+
+	packetResponse, ok := <-msgCtx.responses
+	if !ok {
+		return nil, NewError(ErrorNetwork, errors.New("ldap: response channel closed"))
+	}
+	packet, err = packetResponse.ReadPacket()
+	l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
+	if err != nil {
+		return nil, err
+	}
+
+	if l.Debug {
+		if err := addLDAPDescriptions(packet); err != nil {
+			return nil, err
+		}
+		ber.PrintPacket(packet)
+	}
+
+	result := &SimpleBindResult{
+		Controls: make([]Control, 0),
+	}
+
+	if len(packet.Children) == 3 {
+		for _, child := range packet.Children[2].Children {
+			result.Controls = append(result.Controls, DecodeControl(child))
+		}
+	}
+
+	resultCode, resultDescription := getLDAPResultCode(packet)
+	if resultCode != 0 {
+		return result, NewError(resultCode, errors.New(resultDescription))
+	}
+
+	return result, nil
+}
+
+// Bind performs a bind with the given username and password
+func (l *Conn) Bind(username, password string) error {
+	packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
+	packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID"))
+	bindRequest := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request")
+	bindRequest.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version"))
+	bindRequest.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, username, "User Name"))
+	bindRequest.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, password, "Password"))
+	packet.AppendChild(bindRequest)
+
+	if l.Debug {
+		ber.PrintPacket(packet)
+	}
+
+	msgCtx, err := l.sendMessage(packet)
+	if err != nil {
+		return err
+	}
+	defer l.finishMessage(msgCtx)
+
+	packetResponse, ok := <-msgCtx.responses
+	if !ok {
+		return NewError(ErrorNetwork, errors.New("ldap: response channel closed"))
+	}
+	packet, err = packetResponse.ReadPacket()
+	l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
+	if err != nil {
+		return err
+	}
+
+	if l.Debug {
+		if err := addLDAPDescriptions(packet); err != nil {
+			return err
+		}
+		ber.PrintPacket(packet)
+	}
+
+	resultCode, resultDescription := getLDAPResultCode(packet)
+	if resultCode != 0 {
+		return NewError(resultCode, errors.New(resultDescription))
+	}
+
+	return nil
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/client.go b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/client.go
new file mode 100644
index 0000000..055b27b
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/client.go
@@ -0,0 +1,27 @@
+package ldap
+
+import (
+	"crypto/tls"
+	"time"
+)
+
+// Client knows how to interact with an LDAP server
+type Client interface {
+	Start()
+	StartTLS(config *tls.Config) error
+	Close()
+	SetTimeout(time.Duration)
+
+	Bind(username, password string) error
+	SimpleBind(simpleBindRequest *SimpleBindRequest) (*SimpleBindResult, error)
+
+	Add(addRequest *AddRequest) error
+	Del(delRequest *DelRequest) error
+	Modify(modifyRequest *ModifyRequest) error
+
+	Compare(dn, attribute, value string) (bool, error)
+	PasswordModify(passwordModifyRequest *PasswordModifyRequest) (*PasswordModifyResult, error)
+
+	Search(searchRequest *SearchRequest) (*SearchResult, error)
+	SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32) (*SearchResult, error)
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/compare.go b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/compare.go
new file mode 100644
index 0000000..cc6d2af
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/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)
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/conn.go b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/conn.go
new file mode 100644
index 0000000..eb28eb4
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/conn.go
@@ -0,0 +1,470 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ldap
+
+import (
+	"crypto/tls"
+	"errors"
+	"fmt"
+	"log"
+	"net"
+	"sync"
+	"sync/atomic"
+	"time"
+
+	"gopkg.in/asn1-ber.v1"
+)
+
+const (
+	// MessageQuit causes the processMessages loop to exit
+	MessageQuit = 0
+	// MessageRequest sends a request to the server
+	MessageRequest = 1
+	// MessageResponse receives a response from the server
+	MessageResponse = 2
+	// MessageFinish indicates the client considers a particular message ID to be finished
+	MessageFinish = 3
+	// MessageTimeout indicates the client-specified timeout for a particular message ID has been reached
+	MessageTimeout = 4
+)
+
+// PacketResponse contains the packet or error encountered reading a response
+type PacketResponse struct {
+	// Packet is the packet read from the server
+	Packet *ber.Packet
+	// Error is an error encountered while reading
+	Error error
+}
+
+// ReadPacket returns the packet or an error
+func (pr *PacketResponse) ReadPacket() (*ber.Packet, error) {
+	if (pr == nil) || (pr.Packet == nil && pr.Error == nil) {
+		return nil, NewError(ErrorNetwork, errors.New("ldap: could not retrieve response"))
+	}
+	return pr.Packet, pr.Error
+}
+
+type messageContext struct {
+	id int64
+	// close(done) should only be called from finishMessage()
+	done chan struct{}
+	// close(responses) should only be called from processMessages(), and only sent to from sendResponse()
+	responses chan *PacketResponse
+}
+
+// sendResponse should only be called within the processMessages() loop which
+// is also responsible for closing the responses channel.
+func (msgCtx *messageContext) sendResponse(packet *PacketResponse) {
+	select {
+	case msgCtx.responses <- packet:
+		// Successfully sent packet to message handler.
+	case <-msgCtx.done:
+		// The request handler is done and will not receive more
+		// packets.
+	}
+}
+
+type messagePacket struct {
+	Op        int
+	MessageID int64
+	Packet    *ber.Packet
+	Context   *messageContext
+}
+
+type sendMessageFlags uint
+
+const (
+	startTLS sendMessageFlags = 1 << iota
+)
+
+// Conn represents an LDAP Connection
+type Conn struct {
+	conn                net.Conn
+	isTLS               bool
+	closing             uint32
+	closeErr            atomicValue
+	isStartingTLS       bool
+	Debug               debugging
+	chanConfirm         chan struct{}
+	messageContexts     map[int64]*messageContext
+	chanMessage         chan *messagePacket
+	chanMessageID       chan int64
+	wgClose             sync.WaitGroup
+	outstandingRequests uint
+	messageMutex        sync.Mutex
+	requestTimeout      int64
+}
+
+var _ Client = &Conn{}
+
+// DefaultTimeout is a package-level variable that sets the timeout value
+// used for the Dial and DialTLS methods.
+//
+// WARNING: since this is a package-level variable, setting this value from
+// multiple places will probably result in undesired behaviour.
+var DefaultTimeout = 60 * time.Second
+
+// Dial connects to the given address on the given network using net.Dial
+// and then returns a new Conn for the connection.
+func Dial(network, addr string) (*Conn, error) {
+	c, err := net.DialTimeout(network, addr, DefaultTimeout)
+	if err != nil {
+		return nil, NewError(ErrorNetwork, err)
+	}
+	conn := NewConn(c, false)
+	conn.Start()
+	return conn, nil
+}
+
+// DialTLS connects to the given address on the given network using tls.Dial
+// and then returns a new Conn for the connection.
+func DialTLS(network, addr string, config *tls.Config) (*Conn, error) {
+	dc, err := net.DialTimeout(network, addr, DefaultTimeout)
+	if err != nil {
+		return nil, NewError(ErrorNetwork, err)
+	}
+	c := tls.Client(dc, config)
+	err = c.Handshake()
+	if err != nil {
+		// Handshake error, close the established connection before we return an error
+		dc.Close()
+		return nil, NewError(ErrorNetwork, err)
+	}
+	conn := NewConn(c, true)
+	conn.Start()
+	return conn, nil
+}
+
+// NewConn returns a new Conn using conn for network I/O.
+func NewConn(conn net.Conn, isTLS bool) *Conn {
+	return &Conn{
+		conn:            conn,
+		chanConfirm:     make(chan struct{}),
+		chanMessageID:   make(chan int64),
+		chanMessage:     make(chan *messagePacket, 10),
+		messageContexts: map[int64]*messageContext{},
+		requestTimeout:  0,
+		isTLS:           isTLS,
+	}
+}
+
+// Start initializes goroutines to read responses and process messages
+func (l *Conn) Start() {
+	go l.reader()
+	go l.processMessages()
+	l.wgClose.Add(1)
+}
+
+// isClosing returns whether or not we're currently closing.
+func (l *Conn) isClosing() bool {
+	return atomic.LoadUint32(&l.closing) == 1
+}
+
+// setClosing sets the closing value to true
+func (l *Conn) setClosing() bool {
+	return atomic.CompareAndSwapUint32(&l.closing, 0, 1)
+}
+
+// Close closes the connection.
+func (l *Conn) Close() {
+	l.messageMutex.Lock()
+	defer l.messageMutex.Unlock()
+
+	if l.setClosing() {
+		l.Debug.Printf("Sending quit message and waiting for confirmation")
+		l.chanMessage <- &messagePacket{Op: MessageQuit}
+		<-l.chanConfirm
+		close(l.chanMessage)
+
+		l.Debug.Printf("Closing network connection")
+		if err := l.conn.Close(); err != nil {
+			log.Println(err)
+		}
+
+		l.wgClose.Done()
+	}
+	l.wgClose.Wait()
+}
+
+// SetTimeout sets the time after a request is sent that a MessageTimeout triggers
+func (l *Conn) SetTimeout(timeout time.Duration) {
+	if timeout > 0 {
+		atomic.StoreInt64(&l.requestTimeout, int64(timeout))
+	}
+}
+
+// Returns the next available messageID
+func (l *Conn) nextMessageID() int64 {
+	if messageID, ok := <-l.chanMessageID; ok {
+		return messageID
+	}
+	return 0
+}
+
+// StartTLS sends the command to start a TLS session and then creates a new TLS Client
+func (l *Conn) StartTLS(config *tls.Config) error {
+	if l.isTLS {
+		return NewError(ErrorNetwork, errors.New("ldap: already encrypted"))
+	}
+
+	packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
+	packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID"))
+	request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationExtendedRequest, nil, "Start TLS")
+	request.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, "1.3.6.1.4.1.1466.20037", "TLS Extended Command"))
+	packet.AppendChild(request)
+	l.Debug.PrintPacket(packet)
+
+	msgCtx, err := l.sendMessageWithFlags(packet, startTLS)
+	if err != nil {
+		return err
+	}
+	defer l.finishMessage(msgCtx)
+
+	l.Debug.Printf("%d: waiting for response", msgCtx.id)
+
+	packetResponse, ok := <-msgCtx.responses
+	if !ok {
+		return NewError(ErrorNetwork, errors.New("ldap: response channel closed"))
+	}
+	packet, err = packetResponse.ReadPacket()
+	l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
+	if err != nil {
+		return err
+	}
+
+	if l.Debug {
+		if err := addLDAPDescriptions(packet); err != nil {
+			l.Close()
+			return err
+		}
+		ber.PrintPacket(packet)
+	}
+
+	if resultCode, message := getLDAPResultCode(packet); resultCode == LDAPResultSuccess {
+		conn := tls.Client(l.conn, config)
+
+		if err := conn.Handshake(); err != nil {
+			l.Close()
+			return NewError(ErrorNetwork, fmt.Errorf("TLS handshake failed (%v)", err))
+		}
+
+		l.isTLS = true
+		l.conn = conn
+	} else {
+		return NewError(resultCode, fmt.Errorf("ldap: cannot StartTLS (%s)", message))
+	}
+	go l.reader()
+
+	return nil
+}
+
+func (l *Conn) sendMessage(packet *ber.Packet) (*messageContext, error) {
+	return l.sendMessageWithFlags(packet, 0)
+}
+
+func (l *Conn) sendMessageWithFlags(packet *ber.Packet, flags sendMessageFlags) (*messageContext, error) {
+	if l.isClosing() {
+		return nil, NewError(ErrorNetwork, errors.New("ldap: connection closed"))
+	}
+	l.messageMutex.Lock()
+	l.Debug.Printf("flags&startTLS = %d", flags&startTLS)
+	if l.isStartingTLS {
+		l.messageMutex.Unlock()
+		return nil, NewError(ErrorNetwork, errors.New("ldap: connection is in startls phase"))
+	}
+	if flags&startTLS != 0 {
+		if l.outstandingRequests != 0 {
+			l.messageMutex.Unlock()
+			return nil, NewError(ErrorNetwork, errors.New("ldap: cannot StartTLS with outstanding requests"))
+		}
+		l.isStartingTLS = true
+	}
+	l.outstandingRequests++
+
+	l.messageMutex.Unlock()
+
+	responses := make(chan *PacketResponse)
+	messageID := packet.Children[0].Value.(int64)
+	message := &messagePacket{
+		Op:        MessageRequest,
+		MessageID: messageID,
+		Packet:    packet,
+		Context: &messageContext{
+			id:        messageID,
+			done:      make(chan struct{}),
+			responses: responses,
+		},
+	}
+	l.sendProcessMessage(message)
+	return message.Context, nil
+}
+
+func (l *Conn) finishMessage(msgCtx *messageContext) {
+	close(msgCtx.done)
+
+	if l.isClosing() {
+		return
+	}
+
+	l.messageMutex.Lock()
+	l.outstandingRequests--
+	if l.isStartingTLS {
+		l.isStartingTLS = false
+	}
+	l.messageMutex.Unlock()
+
+	message := &messagePacket{
+		Op:        MessageFinish,
+		MessageID: msgCtx.id,
+	}
+	l.sendProcessMessage(message)
+}
+
+func (l *Conn) sendProcessMessage(message *messagePacket) bool {
+	l.messageMutex.Lock()
+	defer l.messageMutex.Unlock()
+	if l.isClosing() {
+		return false
+	}
+	l.chanMessage <- message
+	return true
+}
+
+func (l *Conn) processMessages() {
+	defer func() {
+		if err := recover(); err != nil {
+			log.Printf("ldap: recovered panic in processMessages: %v", err)
+		}
+		for messageID, msgCtx := range l.messageContexts {
+			// If we are closing due to an error, inform anyone who
+			// is waiting about the error.
+			if l.isClosing() && l.closeErr.Load() != nil {
+				msgCtx.sendResponse(&PacketResponse{Error: l.closeErr.Load().(error)})
+			}
+			l.Debug.Printf("Closing channel for MessageID %d", messageID)
+			close(msgCtx.responses)
+			delete(l.messageContexts, messageID)
+		}
+		close(l.chanMessageID)
+		close(l.chanConfirm)
+	}()
+
+	var messageID int64 = 1
+	for {
+		select {
+		case l.chanMessageID <- messageID:
+			messageID++
+		case message := <-l.chanMessage:
+			switch message.Op {
+			case MessageQuit:
+				l.Debug.Printf("Shutting down - quit message received")
+				return
+			case MessageRequest:
+				// Add to message list and write to network
+				l.Debug.Printf("Sending message %d", message.MessageID)
+
+				buf := message.Packet.Bytes()
+				_, err := l.conn.Write(buf)
+				if err != nil {
+					l.Debug.Printf("Error Sending Message: %s", err.Error())
+					message.Context.sendResponse(&PacketResponse{Error: fmt.Errorf("unable to send request: %s", err)})
+					close(message.Context.responses)
+					break
+				}
+
+				// Only add to messageContexts if we were able to
+				// successfully write the message.
+				l.messageContexts[message.MessageID] = message.Context
+
+				// Add timeout if defined
+				requestTimeout := time.Duration(atomic.LoadInt64(&l.requestTimeout))
+				if requestTimeout > 0 {
+					go func() {
+						defer func() {
+							if err := recover(); err != nil {
+								log.Printf("ldap: recovered panic in RequestTimeout: %v", err)
+							}
+						}()
+						time.Sleep(requestTimeout)
+						timeoutMessage := &messagePacket{
+							Op:        MessageTimeout,
+							MessageID: message.MessageID,
+						}
+						l.sendProcessMessage(timeoutMessage)
+					}()
+				}
+			case MessageResponse:
+				l.Debug.Printf("Receiving message %d", message.MessageID)
+				if msgCtx, ok := l.messageContexts[message.MessageID]; ok {
+					msgCtx.sendResponse(&PacketResponse{message.Packet, nil})
+				} else {
+					log.Printf("Received unexpected message %d, %v", message.MessageID, l.isClosing())
+					ber.PrintPacket(message.Packet)
+				}
+			case MessageTimeout:
+				// Handle the timeout by closing the channel
+				// All reads will return immediately
+				if msgCtx, ok := l.messageContexts[message.MessageID]; ok {
+					l.Debug.Printf("Receiving message timeout for %d", message.MessageID)
+					msgCtx.sendResponse(&PacketResponse{message.Packet, errors.New("ldap: connection timed out")})
+					delete(l.messageContexts, message.MessageID)
+					close(msgCtx.responses)
+				}
+			case MessageFinish:
+				l.Debug.Printf("Finished message %d", message.MessageID)
+				if msgCtx, ok := l.messageContexts[message.MessageID]; ok {
+					delete(l.messageContexts, message.MessageID)
+					close(msgCtx.responses)
+				}
+			}
+		}
+	}
+}
+
+func (l *Conn) reader() {
+	cleanstop := false
+	defer func() {
+		if err := recover(); err != nil {
+			log.Printf("ldap: recovered panic in reader: %v", err)
+		}
+		if !cleanstop {
+			l.Close()
+		}
+	}()
+
+	for {
+		if cleanstop {
+			l.Debug.Printf("reader clean stopping (without closing the connection)")
+			return
+		}
+		packet, err := ber.ReadPacket(l.conn)
+		if err != nil {
+			// A read error is expected here if we are closing the connection...
+			if !l.isClosing() {
+				l.closeErr.Store(fmt.Errorf("unable to read LDAP response packet: %s", err))
+				l.Debug.Printf("reader error: %s", err.Error())
+			}
+			return
+		}
+		addLDAPDescriptions(packet)
+		if len(packet.Children) == 0 {
+			l.Debug.Printf("Received bad ldap packet")
+			continue
+		}
+		l.messageMutex.Lock()
+		if l.isStartingTLS {
+			cleanstop = true
+		}
+		l.messageMutex.Unlock()
+		message := &messagePacket{
+			Op:        MessageResponse,
+			MessageID: packet.Children[0].Value.(int64),
+			Packet:    packet,
+		}
+		if !l.sendProcessMessage(message) {
+			return
+		}
+	}
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/conn_test.go b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/conn_test.go
new file mode 100644
index 0000000..488754d
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/conn_test.go
@@ -0,0 +1,336 @@
+package ldap
+
+import (
+	"bytes"
+	"errors"
+	"io"
+	"net"
+	"net/http"
+	"net/http/httptest"
+	"runtime"
+	"sync"
+	"testing"
+	"time"
+
+	"gopkg.in/asn1-ber.v1"
+)
+
+func TestUnresponsiveConnection(t *testing.T) {
+	// The do-nothing server that accepts requests and does nothing
+	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+	}))
+	defer ts.Close()
+	c, err := net.Dial(ts.Listener.Addr().Network(), ts.Listener.Addr().String())
+	if err != nil {
+		t.Fatalf("error connecting to localhost tcp: %v", err)
+	}
+
+	// Create an Ldap connection
+	conn := NewConn(c, false)
+	conn.SetTimeout(time.Millisecond)
+	conn.Start()
+	defer conn.Close()
+
+	// Mock a packet
+	packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
+	packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, conn.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"))
+	packet.AppendChild(bindRequest)
+
+	// Send packet and test response
+	msgCtx, err := conn.sendMessage(packet)
+	if err != nil {
+		t.Fatalf("error sending message: %v", err)
+	}
+	defer conn.finishMessage(msgCtx)
+
+	packetResponse, ok := <-msgCtx.responses
+	if !ok {
+		t.Fatalf("no PacketResponse in response channel")
+	}
+	packet, err = packetResponse.ReadPacket()
+	if err == nil {
+		t.Fatalf("expected timeout error")
+	}
+	if err.Error() != "ldap: connection timed out" {
+		t.Fatalf("unexpected error: %v", err)
+	}
+}
+
+// TestFinishMessage tests that we do not enter deadlock when a goroutine makes
+// a request but does not handle all responses from the server.
+func TestFinishMessage(t *testing.T) {
+	ptc := newPacketTranslatorConn()
+	defer ptc.Close()
+
+	conn := NewConn(ptc, false)
+	conn.Start()
+
+	// Test sending 5 different requests in series. Ensure that we can
+	// get a response packet from the underlying connection and also
+	// ensure that we can gracefully ignore unhandled responses.
+	for i := 0; i < 5; i++ {
+		t.Logf("serial request %d", i)
+		// Create a message and make sure we can receive responses.
+		msgCtx := testSendRequest(t, ptc, conn)
+		testReceiveResponse(t, ptc, msgCtx)
+
+		// Send a few unhandled responses and finish the message.
+		testSendUnhandledResponsesAndFinish(t, ptc, conn, msgCtx, 5)
+		t.Logf("serial request %d done", i)
+	}
+
+	// Test sending 5 different requests in parallel.
+	var wg sync.WaitGroup
+	for i := 0; i < 5; i++ {
+		wg.Add(1)
+		go func(i int) {
+			defer wg.Done()
+			t.Logf("parallel request %d", i)
+			// Create a message and make sure we can receive responses.
+			msgCtx := testSendRequest(t, ptc, conn)
+			testReceiveResponse(t, ptc, msgCtx)
+
+			// Send a few unhandled responses and finish the message.
+			testSendUnhandledResponsesAndFinish(t, ptc, conn, msgCtx, 5)
+			t.Logf("parallel request %d done", i)
+		}(i)
+	}
+	wg.Wait()
+
+	// We cannot run Close() in a defer because t.FailNow() will run it and
+	// it will block if the processMessage Loop is in a deadlock.
+	conn.Close()
+}
+
+func testSendRequest(t *testing.T, ptc *packetTranslatorConn, conn *Conn) (msgCtx *messageContext) {
+	var msgID int64
+	runWithTimeout(t, time.Second, func() {
+		msgID = conn.nextMessageID()
+	})
+
+	requestPacket := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
+	requestPacket.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, msgID, "MessageID"))
+
+	var err error
+
+	runWithTimeout(t, time.Second, func() {
+		msgCtx, err = conn.sendMessage(requestPacket)
+		if err != nil {
+			t.Fatalf("unable to send request message: %s", err)
+		}
+	})
+
+	// We should now be able to get this request packet out from the other
+	// side.
+	runWithTimeout(t, time.Second, func() {
+		if _, err = ptc.ReceiveRequest(); err != nil {
+			t.Fatalf("unable to receive request packet: %s", err)
+		}
+	})
+
+	return msgCtx
+}
+
+func testReceiveResponse(t *testing.T, ptc *packetTranslatorConn, msgCtx *messageContext) {
+	// Send a mock response packet.
+	responsePacket := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Response")
+	responsePacket.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, msgCtx.id, "MessageID"))
+
+	runWithTimeout(t, time.Second, func() {
+		if err := ptc.SendResponse(responsePacket); err != nil {
+			t.Fatalf("unable to send response packet: %s", err)
+		}
+	})
+
+	// We should be able to receive the packet from the connection.
+	runWithTimeout(t, time.Second, func() {
+		if _, ok := <-msgCtx.responses; !ok {
+			t.Fatal("response channel closed")
+		}
+	})
+}
+
+func testSendUnhandledResponsesAndFinish(t *testing.T, ptc *packetTranslatorConn, conn *Conn, msgCtx *messageContext, numResponses int) {
+	// Send a mock response packet.
+	responsePacket := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Response")
+	responsePacket.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, msgCtx.id, "MessageID"))
+
+	// Send extra responses but do not attempt to receive them on the
+	// client side.
+	for i := 0; i < numResponses; i++ {
+		runWithTimeout(t, time.Second, func() {
+			if err := ptc.SendResponse(responsePacket); err != nil {
+				t.Fatalf("unable to send response packet: %s", err)
+			}
+		})
+	}
+
+	// Finally, attempt to finish this message.
+	runWithTimeout(t, time.Second, func() {
+		conn.finishMessage(msgCtx)
+	})
+}
+
+func runWithTimeout(t *testing.T, timeout time.Duration, f func()) {
+	done := make(chan struct{})
+	go func() {
+		f()
+		close(done)
+	}()
+
+	select {
+	case <-done: // Success!
+	case <-time.After(timeout):
+		_, file, line, _ := runtime.Caller(1)
+		t.Fatalf("%s:%d timed out", file, line)
+	}
+}
+
+// packetTranslatorConn is a helpful type which can be used with various tests
+// in this package. It implements the net.Conn interface to be used as an
+// underlying connection for a *ldap.Conn. Most methods are no-ops but the
+// Read() and Write() methods are able to translate ber-encoded packets for
+// testing LDAP requests and responses.
+//
+// Test cases can simulate an LDAP server sending a response by calling the
+// SendResponse() method with a ber-encoded LDAP response packet. Test cases
+// can simulate an LDAP server receiving a request from a client by calling the
+// ReceiveRequest() method which returns a ber-encoded LDAP request packet.
+type packetTranslatorConn struct {
+	lock     sync.Mutex
+	isClosed bool
+
+	responseCond sync.Cond
+	requestCond  sync.Cond
+
+	responseBuf bytes.Buffer
+	requestBuf  bytes.Buffer
+}
+
+var errPacketTranslatorConnClosed = errors.New("connection closed")
+
+func newPacketTranslatorConn() *packetTranslatorConn {
+	conn := &packetTranslatorConn{}
+	conn.responseCond = sync.Cond{L: &conn.lock}
+	conn.requestCond = sync.Cond{L: &conn.lock}
+
+	return conn
+}
+
+// Read is called by the reader() loop to receive response packets. It will
+// block until there are more packet bytes available or this connection is
+// closed.
+func (c *packetTranslatorConn) Read(b []byte) (n int, err error) {
+	c.lock.Lock()
+	defer c.lock.Unlock()
+
+	for !c.isClosed {
+		// Attempt to read data from the response buffer. If it fails
+		// with an EOF, wait and try again.
+		n, err = c.responseBuf.Read(b)
+		if err != io.EOF {
+			return n, err
+		}
+
+		c.responseCond.Wait()
+	}
+
+	return 0, errPacketTranslatorConnClosed
+}
+
+// SendResponse writes the given response packet to the response buffer for
+// this connection, signalling any goroutine waiting to read a response.
+func (c *packetTranslatorConn) SendResponse(packet *ber.Packet) error {
+	c.lock.Lock()
+	defer c.lock.Unlock()
+
+	if c.isClosed {
+		return errPacketTranslatorConnClosed
+	}
+
+	// Signal any goroutine waiting to read a response.
+	defer c.responseCond.Broadcast()
+
+	// Writes to the buffer should always succeed.
+	c.responseBuf.Write(packet.Bytes())
+
+	return nil
+}
+
+// Write is called by the processMessages() loop to send request packets.
+func (c *packetTranslatorConn) Write(b []byte) (n int, err error) {
+	c.lock.Lock()
+	defer c.lock.Unlock()
+
+	if c.isClosed {
+		return 0, errPacketTranslatorConnClosed
+	}
+
+	// Signal any goroutine waiting to read a request.
+	defer c.requestCond.Broadcast()
+
+	// Writes to the buffer should always succeed.
+	return c.requestBuf.Write(b)
+}
+
+// ReceiveRequest attempts to read a request packet from this connection. It
+// will block until it is able to read a full request packet or until this
+// connection is closed.
+func (c *packetTranslatorConn) ReceiveRequest() (*ber.Packet, error) {
+	c.lock.Lock()
+	defer c.lock.Unlock()
+
+	for !c.isClosed {
+		// Attempt to parse a request packet from the request buffer.
+		// If it fails with an unexpected EOF, wait and try again.
+		requestReader := bytes.NewReader(c.requestBuf.Bytes())
+		packet, err := ber.ReadPacket(requestReader)
+		switch err {
+		case io.EOF, io.ErrUnexpectedEOF:
+			c.requestCond.Wait()
+		case nil:
+			// Advance the request buffer by the number of bytes
+			// read to decode the request packet.
+			c.requestBuf.Next(c.requestBuf.Len() - requestReader.Len())
+			return packet, nil
+		default:
+			return nil, err
+		}
+	}
+
+	return nil, errPacketTranslatorConnClosed
+}
+
+// Close closes this connection causing Read() and Write() calls to fail.
+func (c *packetTranslatorConn) Close() error {
+	c.lock.Lock()
+	defer c.lock.Unlock()
+
+	c.isClosed = true
+	c.responseCond.Broadcast()
+	c.requestCond.Broadcast()
+
+	return nil
+}
+
+func (c *packetTranslatorConn) LocalAddr() net.Addr {
+	return (*net.TCPAddr)(nil)
+}
+
+func (c *packetTranslatorConn) RemoteAddr() net.Addr {
+	return (*net.TCPAddr)(nil)
+}
+
+func (c *packetTranslatorConn) SetDeadline(t time.Time) error {
+	return nil
+}
+
+func (c *packetTranslatorConn) SetReadDeadline(t time.Time) error {
+	return nil
+}
+
+func (c *packetTranslatorConn) SetWriteDeadline(t time.Time) error {
+	return nil
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/control.go b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/control.go
new file mode 100644
index 0000000..342f325
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/control.go
@@ -0,0 +1,420 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ldap
+
+import (
+	"fmt"
+	"strconv"
+
+	"gopkg.in/asn1-ber.v1"
+)
+
+const (
+	// ControlTypePaging - https://www.ietf.org/rfc/rfc2696.txt
+	ControlTypePaging = "1.2.840.113556.1.4.319"
+	// ControlTypeBeheraPasswordPolicy - https://tools.ietf.org/html/draft-behera-ldap-password-policy-10
+	ControlTypeBeheraPasswordPolicy = "1.3.6.1.4.1.42.2.27.8.5.1"
+	// ControlTypeVChuPasswordMustChange - https://tools.ietf.org/html/draft-vchu-ldap-pwd-policy-00
+	ControlTypeVChuPasswordMustChange = "2.16.840.1.113730.3.4.4"
+	// ControlTypeVChuPasswordWarning - https://tools.ietf.org/html/draft-vchu-ldap-pwd-policy-00
+	ControlTypeVChuPasswordWarning = "2.16.840.1.113730.3.4.5"
+	// ControlTypeManageDsaIT - https://tools.ietf.org/html/rfc3296
+	ControlTypeManageDsaIT = "2.16.840.1.113730.3.4.2"
+)
+
+// ControlTypeMap maps controls to text descriptions
+var ControlTypeMap = map[string]string{
+	ControlTypePaging:               "Paging",
+	ControlTypeBeheraPasswordPolicy: "Password Policy - Behera Draft",
+	ControlTypeManageDsaIT:          "Manage DSA IT",
+}
+
+// Control defines an interface controls provide to encode and describe themselves
+type Control interface {
+	// GetControlType returns the OID
+	GetControlType() string
+	// Encode returns the ber packet representation
+	Encode() *ber.Packet
+	// String returns a human-readable description
+	String() string
+}
+
+// ControlString implements the Control interface for simple controls
+type ControlString struct {
+	ControlType  string
+	Criticality  bool
+	ControlValue string
+}
+
+// GetControlType returns the OID
+func (c *ControlString) GetControlType() string {
+	return c.ControlType
+}
+
+// Encode returns the ber packet representation
+func (c *ControlString) Encode() *ber.Packet {
+	packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
+	packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, c.ControlType, "Control Type ("+ControlTypeMap[c.ControlType]+")"))
+	if c.Criticality {
+		packet.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, c.Criticality, "Criticality"))
+	}
+	packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, string(c.ControlValue), "Control Value"))
+	return packet
+}
+
+// String returns a human-readable description
+func (c *ControlString) String() string {
+	return fmt.Sprintf("Control Type: %s (%q)  Criticality: %t  Control Value: %s", ControlTypeMap[c.ControlType], c.ControlType, c.Criticality, c.ControlValue)
+}
+
+// ControlPaging implements the paging control described in https://www.ietf.org/rfc/rfc2696.txt
+type ControlPaging struct {
+	// PagingSize indicates the page size
+	PagingSize uint32
+	// Cookie is an opaque value returned by the server to track a paging cursor
+	Cookie []byte
+}
+
+// GetControlType returns the OID
+func (c *ControlPaging) GetControlType() string {
+	return ControlTypePaging
+}
+
+// Encode returns the ber packet representation
+func (c *ControlPaging) Encode() *ber.Packet {
+	packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
+	packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypePaging, "Control Type ("+ControlTypeMap[ControlTypePaging]+")"))
+
+	p2 := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Control Value (Paging)")
+	seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Search Control Value")
+	seq.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(c.PagingSize), "Paging Size"))
+	cookie := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Cookie")
+	cookie.Value = c.Cookie
+	cookie.Data.Write(c.Cookie)
+	seq.AppendChild(cookie)
+	p2.AppendChild(seq)
+
+	packet.AppendChild(p2)
+	return packet
+}
+
+// String returns a human-readable description
+func (c *ControlPaging) String() string {
+	return fmt.Sprintf(
+		"Control Type: %s (%q)  Criticality: %t  PagingSize: %d  Cookie: %q",
+		ControlTypeMap[ControlTypePaging],
+		ControlTypePaging,
+		false,
+		c.PagingSize,
+		c.Cookie)
+}
+
+// SetCookie stores the given cookie in the paging control
+func (c *ControlPaging) SetCookie(cookie []byte) {
+	c.Cookie = cookie
+}
+
+// ControlBeheraPasswordPolicy implements the control described in https://tools.ietf.org/html/draft-behera-ldap-password-policy-10
+type ControlBeheraPasswordPolicy struct {
+	// Expire contains the number of seconds before a password will expire
+	Expire int64
+	// Grace indicates the remaining number of times a user will be allowed to authenticate with an expired password
+	Grace int64
+	// Error indicates the error code
+	Error int8
+	// ErrorString is a human readable error
+	ErrorString string
+}
+
+// GetControlType returns the OID
+func (c *ControlBeheraPasswordPolicy) GetControlType() string {
+	return ControlTypeBeheraPasswordPolicy
+}
+
+// Encode returns the ber packet representation
+func (c *ControlBeheraPasswordPolicy) Encode() *ber.Packet {
+	packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
+	packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeBeheraPasswordPolicy, "Control Type ("+ControlTypeMap[ControlTypeBeheraPasswordPolicy]+")"))
+
+	return packet
+}
+
+// String returns a human-readable description
+func (c *ControlBeheraPasswordPolicy) String() string {
+	return fmt.Sprintf(
+		"Control Type: %s (%q)  Criticality: %t  Expire: %d  Grace: %d  Error: %d, ErrorString: %s",
+		ControlTypeMap[ControlTypeBeheraPasswordPolicy],
+		ControlTypeBeheraPasswordPolicy,
+		false,
+		c.Expire,
+		c.Grace,
+		c.Error,
+		c.ErrorString)
+}
+
+// ControlVChuPasswordMustChange implements the control described in https://tools.ietf.org/html/draft-vchu-ldap-pwd-policy-00
+type ControlVChuPasswordMustChange struct {
+	// MustChange indicates if the password is required to be changed
+	MustChange bool
+}
+
+// GetControlType returns the OID
+func (c *ControlVChuPasswordMustChange) GetControlType() string {
+	return ControlTypeVChuPasswordMustChange
+}
+
+// Encode returns the ber packet representation
+func (c *ControlVChuPasswordMustChange) Encode() *ber.Packet {
+	return nil
+}
+
+// String returns a human-readable description
+func (c *ControlVChuPasswordMustChange) String() string {
+	return fmt.Sprintf(
+		"Control Type: %s (%q)  Criticality: %t  MustChange: %v",
+		ControlTypeMap[ControlTypeVChuPasswordMustChange],
+		ControlTypeVChuPasswordMustChange,
+		false,
+		c.MustChange)
+}
+
+// ControlVChuPasswordWarning implements the control described in https://tools.ietf.org/html/draft-vchu-ldap-pwd-policy-00
+type ControlVChuPasswordWarning struct {
+	// Expire indicates the time in seconds until the password expires
+	Expire int64
+}
+
+// GetControlType returns the OID
+func (c *ControlVChuPasswordWarning) GetControlType() string {
+	return ControlTypeVChuPasswordWarning
+}
+
+// Encode returns the ber packet representation
+func (c *ControlVChuPasswordWarning) Encode() *ber.Packet {
+	return nil
+}
+
+// String returns a human-readable description
+func (c *ControlVChuPasswordWarning) String() string {
+	return fmt.Sprintf(
+		"Control Type: %s (%q)  Criticality: %t  Expire: %b",
+		ControlTypeMap[ControlTypeVChuPasswordWarning],
+		ControlTypeVChuPasswordWarning,
+		false,
+		c.Expire)
+}
+
+// ControlManageDsaIT implements the control described in https://tools.ietf.org/html/rfc3296
+type ControlManageDsaIT struct {
+	// Criticality indicates if this control is required
+	Criticality bool
+}
+
+// GetControlType returns the OID
+func (c *ControlManageDsaIT) GetControlType() string {
+	return ControlTypeManageDsaIT
+}
+
+// Encode returns the ber packet representation
+func (c *ControlManageDsaIT) Encode() *ber.Packet {
+	//FIXME
+	packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
+	packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeManageDsaIT, "Control Type ("+ControlTypeMap[ControlTypeManageDsaIT]+")"))
+	if c.Criticality {
+		packet.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, c.Criticality, "Criticality"))
+	}
+	return packet
+}
+
+// String returns a human-readable description
+func (c *ControlManageDsaIT) String() string {
+	return fmt.Sprintf(
+		"Control Type: %s (%q)  Criticality: %t",
+		ControlTypeMap[ControlTypeManageDsaIT],
+		ControlTypeManageDsaIT,
+		c.Criticality)
+}
+
+// NewControlManageDsaIT returns a ControlManageDsaIT control
+func NewControlManageDsaIT(Criticality bool) *ControlManageDsaIT {
+	return &ControlManageDsaIT{Criticality: Criticality}
+}
+
+// FindControl returns the first control of the given type in the list, or nil
+func FindControl(controls []Control, controlType string) Control {
+	for _, c := range controls {
+		if c.GetControlType() == controlType {
+			return c
+		}
+	}
+	return nil
+}
+
+// DecodeControl returns a control read from the given packet, or nil if no recognized control can be made
+func DecodeControl(packet *ber.Packet) Control {
+	var (
+		ControlType = ""
+		Criticality = false
+		value       *ber.Packet
+	)
+
+	switch len(packet.Children) {
+	case 0:
+		// at least one child is required for control type
+		return nil
+
+	case 1:
+		// just type, no criticality or value
+		packet.Children[0].Description = "Control Type (" + ControlTypeMap[ControlType] + ")"
+		ControlType = packet.Children[0].Value.(string)
+
+	case 2:
+		packet.Children[0].Description = "Control Type (" + ControlTypeMap[ControlType] + ")"
+		ControlType = packet.Children[0].Value.(string)
+
+		// Children[1] could be criticality or value (both are optional)
+		// duck-type on whether this is a boolean
+		if _, ok := packet.Children[1].Value.(bool); ok {
+			packet.Children[1].Description = "Criticality"
+			Criticality = packet.Children[1].Value.(bool)
+		} else {
+			packet.Children[1].Description = "Control Value"
+			value = packet.Children[1]
+		}
+
+	case 3:
+		packet.Children[0].Description = "Control Type (" + ControlTypeMap[ControlType] + ")"
+		ControlType = packet.Children[0].Value.(string)
+
+		packet.Children[1].Description = "Criticality"
+		Criticality = packet.Children[1].Value.(bool)
+
+		packet.Children[2].Description = "Control Value"
+		value = packet.Children[2]
+
+	default:
+		// more than 3 children is invalid
+		return nil
+	}
+
+	switch ControlType {
+	case ControlTypeManageDsaIT:
+		return NewControlManageDsaIT(Criticality)
+	case ControlTypePaging:
+		value.Description += " (Paging)"
+		c := new(ControlPaging)
+		if value.Value != nil {
+			valueChildren := ber.DecodePacket(value.Data.Bytes())
+			value.Data.Truncate(0)
+			value.Value = nil
+			value.AppendChild(valueChildren)
+		}
+		value = value.Children[0]
+		value.Description = "Search Control Value"
+		value.Children[0].Description = "Paging Size"
+		value.Children[1].Description = "Cookie"
+		c.PagingSize = uint32(value.Children[0].Value.(int64))
+		c.Cookie = value.Children[1].Data.Bytes()
+		value.Children[1].Value = c.Cookie
+		return c
+	case ControlTypeBeheraPasswordPolicy:
+		value.Description += " (Password Policy - Behera)"
+		c := NewControlBeheraPasswordPolicy()
+		if value.Value != nil {
+			valueChildren := ber.DecodePacket(value.Data.Bytes())
+			value.Data.Truncate(0)
+			value.Value = nil
+			value.AppendChild(valueChildren)
+		}
+
+		sequence := value.Children[0]
+
+		for _, child := range sequence.Children {
+			if child.Tag == 0 {
+				//Warning
+				warningPacket := child.Children[0]
+				packet := ber.DecodePacket(warningPacket.Data.Bytes())
+				val, ok := packet.Value.(int64)
+				if ok {
+					if warningPacket.Tag == 0 {
+						//timeBeforeExpiration
+						c.Expire = val
+						warningPacket.Value = c.Expire
+					} else if warningPacket.Tag == 1 {
+						//graceAuthNsRemaining
+						c.Grace = val
+						warningPacket.Value = c.Grace
+					}
+				}
+			} else if child.Tag == 1 {
+				// Error
+				packet := ber.DecodePacket(child.Data.Bytes())
+				val, ok := packet.Value.(int8)
+				if !ok {
+					// what to do?
+					val = -1
+				}
+				c.Error = val
+				child.Value = c.Error
+				c.ErrorString = BeheraPasswordPolicyErrorMap[c.Error]
+			}
+		}
+		return c
+	case ControlTypeVChuPasswordMustChange:
+		c := &ControlVChuPasswordMustChange{MustChange: true}
+		return c
+	case ControlTypeVChuPasswordWarning:
+		c := &ControlVChuPasswordWarning{Expire: -1}
+		expireStr := ber.DecodeString(value.Data.Bytes())
+
+		expire, err := strconv.ParseInt(expireStr, 10, 64)
+		if err != nil {
+			return nil
+		}
+		c.Expire = expire
+		value.Value = c.Expire
+
+		return c
+	default:
+		c := new(ControlString)
+		c.ControlType = ControlType
+		c.Criticality = Criticality
+		if value != nil {
+			c.ControlValue = value.Value.(string)
+		}
+		return c
+	}
+}
+
+// NewControlString returns a generic control
+func NewControlString(controlType string, criticality bool, controlValue string) *ControlString {
+	return &ControlString{
+		ControlType:  controlType,
+		Criticality:  criticality,
+		ControlValue: controlValue,
+	}
+}
+
+// NewControlPaging returns a paging control
+func NewControlPaging(pagingSize uint32) *ControlPaging {
+	return &ControlPaging{PagingSize: pagingSize}
+}
+
+// NewControlBeheraPasswordPolicy returns a ControlBeheraPasswordPolicy
+func NewControlBeheraPasswordPolicy() *ControlBeheraPasswordPolicy {
+	return &ControlBeheraPasswordPolicy{
+		Expire: -1,
+		Grace:  -1,
+		Error:  -1,
+	}
+}
+
+func encodeControls(controls []Control) *ber.Packet {
+	packet := ber.Encode(ber.ClassContext, ber.TypeConstructed, 0, nil, "Controls")
+	for _, control := range controls {
+		packet.AppendChild(control.Encode())
+	}
+	return packet
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/control_test.go b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/control_test.go
new file mode 100644
index 0000000..1152746
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/control_test.go
@@ -0,0 +1,97 @@
+package ldap
+
+import (
+	"bytes"
+	"fmt"
+	"reflect"
+	"runtime"
+	"testing"
+
+	"gopkg.in/asn1-ber.v1"
+)
+
+func TestControlPaging(t *testing.T) {
+	runControlTest(t, NewControlPaging(0))
+	runControlTest(t, NewControlPaging(100))
+}
+
+func TestControlManageDsaIT(t *testing.T) {
+	runControlTest(t, NewControlManageDsaIT(true))
+	runControlTest(t, NewControlManageDsaIT(false))
+}
+
+func TestControlString(t *testing.T) {
+	runControlTest(t, NewControlString("x", true, "y"))
+	runControlTest(t, NewControlString("x", true, ""))
+	runControlTest(t, NewControlString("x", false, "y"))
+	runControlTest(t, NewControlString("x", false, ""))
+}
+
+func runControlTest(t *testing.T, originalControl Control) {
+	header := ""
+	if callerpc, _, line, ok := runtime.Caller(1); ok {
+		if caller := runtime.FuncForPC(callerpc); caller != nil {
+			header = fmt.Sprintf("%s:%d: ", caller.Name(), line)
+		}
+	}
+
+	encodedPacket := originalControl.Encode()
+	encodedBytes := encodedPacket.Bytes()
+
+	// Decode directly from the encoded packet (ensures Value is correct)
+	fromPacket := DecodeControl(encodedPacket)
+	if !bytes.Equal(encodedBytes, fromPacket.Encode().Bytes()) {
+		t.Errorf("%sround-trip from encoded packet failed", header)
+	}
+	if reflect.TypeOf(originalControl) != reflect.TypeOf(fromPacket) {
+		t.Errorf("%sgot different type decoding from encoded packet: %T vs %T", header, fromPacket, originalControl)
+	}
+
+	// Decode from the wire bytes (ensures ber-encoding is correct)
+	fromBytes := DecodeControl(ber.DecodePacket(encodedBytes))
+	if !bytes.Equal(encodedBytes, fromBytes.Encode().Bytes()) {
+		t.Errorf("%sround-trip from encoded bytes failed", header)
+	}
+	if reflect.TypeOf(originalControl) != reflect.TypeOf(fromPacket) {
+		t.Errorf("%sgot different type decoding from encoded bytes: %T vs %T", header, fromBytes, originalControl)
+	}
+}
+
+func TestDescribeControlManageDsaIT(t *testing.T) {
+	runAddControlDescriptions(t, NewControlManageDsaIT(false), "Control Type (Manage DSA IT)")
+	runAddControlDescriptions(t, NewControlManageDsaIT(true), "Control Type (Manage DSA IT)", "Criticality")
+}
+
+func TestDescribeControlPaging(t *testing.T) {
+	runAddControlDescriptions(t, NewControlPaging(100), "Control Type (Paging)", "Control Value (Paging)")
+	runAddControlDescriptions(t, NewControlPaging(0), "Control Type (Paging)", "Control Value (Paging)")
+}
+
+func TestDescribeControlString(t *testing.T) {
+	runAddControlDescriptions(t, NewControlString("x", true, "y"), "Control Type ()", "Criticality", "Control Value")
+	runAddControlDescriptions(t, NewControlString("x", true, ""), "Control Type ()", "Criticality", "Control Value")
+	runAddControlDescriptions(t, NewControlString("x", false, "y"), "Control Type ()", "Control Value")
+	runAddControlDescriptions(t, NewControlString("x", false, ""), "Control Type ()", "Control Value")
+}
+
+func runAddControlDescriptions(t *testing.T, originalControl Control, childDescriptions ...string) {
+	header := ""
+	if callerpc, _, line, ok := runtime.Caller(1); ok {
+		if caller := runtime.FuncForPC(callerpc); caller != nil {
+			header = fmt.Sprintf("%s:%d: ", caller.Name(), line)
+		}
+	}
+
+	encodedControls := encodeControls([]Control{originalControl})
+	addControlDescriptions(encodedControls)
+	encodedPacket := encodedControls.Children[0]
+	if len(encodedPacket.Children) != len(childDescriptions) {
+		t.Errorf("%sinvalid number of children: %d != %d", header, len(encodedPacket.Children), len(childDescriptions))
+	}
+	for i, desc := range childDescriptions {
+		if encodedPacket.Children[i].Description != desc {
+			t.Errorf("%sdescription not as expected: %s != %s", header, encodedPacket.Children[i].Description, desc)
+		}
+	}
+
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/debug.go b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/debug.go
new file mode 100644
index 0000000..7279fc2
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/debug.go
@@ -0,0 +1,24 @@
+package ldap
+
+import (
+	"log"
+
+	"gopkg.in/asn1-ber.v1"
+)
+
+// debugging type
+//     - has a Printf method to write the debug output
+type debugging bool
+
+// write debug output
+func (debug debugging) Printf(format string, args ...interface{}) {
+	if debug {
+		log.Printf(format, args...)
+	}
+}
+
+func (debug debugging) PrintPacket(packet *ber.Packet) {
+	if debug {
+		ber.PrintPacket(packet)
+	}
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/del.go b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/del.go
new file mode 100644
index 0000000..4fd63dc
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/del.go
@@ -0,0 +1,84 @@
+//
+// https://tools.ietf.org/html/rfc4511
+//
+// DelRequest ::= [APPLICATION 10] LDAPDN
+
+package ldap
+
+import (
+	"errors"
+	"log"
+
+	"gopkg.in/asn1-ber.v1"
+)
+
+// DelRequest implements an LDAP deletion request
+type DelRequest struct {
+	// DN is the name of the directory entry to delete
+	DN string
+	// Controls hold optional controls to send with the request
+	Controls []Control
+}
+
+func (d DelRequest) encode() *ber.Packet {
+	request := ber.Encode(ber.ClassApplication, ber.TypePrimitive, ApplicationDelRequest, d.DN, "Del Request")
+	request.Data.Write([]byte(d.DN))
+	return request
+}
+
+// NewDelRequest creates a delete request for the given DN and controls
+func NewDelRequest(DN string,
+	Controls []Control) *DelRequest {
+	return &DelRequest{
+		DN:       DN,
+		Controls: Controls,
+	}
+}
+
+// Del executes the given delete request
+func (l *Conn) Del(delRequest *DelRequest) error {
+	packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
+	packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID"))
+	packet.AppendChild(delRequest.encode())
+	if delRequest.Controls != nil {
+		packet.AppendChild(encodeControls(delRequest.Controls))
+	}
+
+	l.Debug.PrintPacket(packet)
+
+	msgCtx, err := l.sendMessage(packet)
+	if err != nil {
+		return err
+	}
+	defer l.finishMessage(msgCtx)
+
+	l.Debug.Printf("%d: waiting for response", msgCtx.id)
+	packetResponse, ok := <-msgCtx.responses
+	if !ok {
+		return NewError(ErrorNetwork, errors.New("ldap: response channel closed"))
+	}
+	packet, err = packetResponse.ReadPacket()
+	l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
+	if err != nil {
+		return err
+	}
+
+	if l.Debug {
+		if err := addLDAPDescriptions(packet); err != nil {
+			return err
+		}
+		ber.PrintPacket(packet)
+	}
+
+	if packet.Children[1].Tag == ApplicationDelResponse {
+		resultCode, resultDescription := getLDAPResultCode(packet)
+		if resultCode != 0 {
+			return NewError(resultCode, errors.New(resultDescription))
+		}
+	} else {
+		log.Printf("Unexpected Response: %d", packet.Children[1].Tag)
+	}
+
+	l.Debug.Printf("%d: returning", msgCtx.id)
+	return nil
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/dn.go b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/dn.go
new file mode 100644
index 0000000..34e9023
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/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
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/dn_test.go b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/dn_test.go
new file mode 100644
index 0000000..af5fc14
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/dn_test.go
@@ -0,0 +1,211 @@
+package ldap_test
+
+import (
+	"reflect"
+	"testing"
+
+	"gopkg.in/ldap.v2"
+)
+
+func TestSuccessfulDNParsing(t *testing.T) {
+	testcases := map[string]ldap.DN{
+		"": ldap.DN{[]*ldap.RelativeDN{}},
+		"cn=Jim\\2C \\22Hasse Hö\\22 Hansson!,dc=dummy,dc=com": ldap.DN{[]*ldap.RelativeDN{
+			&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"cn", "Jim, \"Hasse Hö\" Hansson!"}}},
+			&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"dc", "dummy"}}},
+			&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"dc", "com"}}}}},
+		"UID=jsmith,DC=example,DC=net": ldap.DN{[]*ldap.RelativeDN{
+			&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"UID", "jsmith"}}},
+			&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"DC", "example"}}},
+			&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"DC", "net"}}}}},
+		"OU=Sales+CN=J. Smith,DC=example,DC=net": ldap.DN{[]*ldap.RelativeDN{
+			&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{
+				&ldap.AttributeTypeAndValue{"OU", "Sales"},
+				&ldap.AttributeTypeAndValue{"CN", "J. Smith"}}},
+			&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"DC", "example"}}},
+			&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"DC", "net"}}}}},
+		"1.3.6.1.4.1.1466.0=#04024869": ldap.DN{[]*ldap.RelativeDN{
+			&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"1.3.6.1.4.1.1466.0", "Hi"}}}}},
+		"1.3.6.1.4.1.1466.0=#04024869,DC=net": ldap.DN{[]*ldap.RelativeDN{
+			&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"1.3.6.1.4.1.1466.0", "Hi"}}},
+			&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"DC", "net"}}}}},
+		"CN=Lu\\C4\\8Di\\C4\\87": ldap.DN{[]*ldap.RelativeDN{
+			&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"CN", "Lučić"}}}}},
+		"  CN  =  Lu\\C4\\8Di\\C4\\87  ": ldap.DN{[]*ldap.RelativeDN{
+			&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"CN", "Lučić"}}}}},
+		`   A   =   1   ,   B   =   2   `: ldap.DN{[]*ldap.RelativeDN{
+			&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"A", "1"}}},
+			&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"B", "2"}}}}},
+		`   A   =   1   +   B   =   2   `: ldap.DN{[]*ldap.RelativeDN{
+			&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{
+				&ldap.AttributeTypeAndValue{"A", "1"},
+				&ldap.AttributeTypeAndValue{"B", "2"}}}}},
+		`   \ \ A\ \    =   \ \ 1\ \    ,   \ \ B\ \    =   \ \ 2\ \    `: ldap.DN{[]*ldap.RelativeDN{
+			&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"  A  ", "  1  "}}},
+			&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"  B  ", "  2  "}}}}},
+		`   \ \ A\ \    =   \ \ 1\ \    +   \ \ B\ \    =   \ \ 2\ \    `: ldap.DN{[]*ldap.RelativeDN{
+			&ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{
+				&ldap.AttributeTypeAndValue{"  A  ", "  1  "},
+				&ldap.AttributeTypeAndValue{"  B  ", "  2  "}}}}},
+	}
+
+	for test, answer := range testcases {
+		dn, err := ldap.ParseDN(test)
+		if err != nil {
+			t.Errorf(err.Error())
+			continue
+		}
+		if !reflect.DeepEqual(dn, &answer) {
+			t.Errorf("Parsed DN %s is not equal to the expected structure", test)
+			t.Logf("Expected:")
+			for _, rdn := range answer.RDNs {
+				for _, attribs := range rdn.Attributes {
+					t.Logf("#%v\n", attribs)
+				}
+			}
+			t.Logf("Actual:")
+			for _, rdn := range dn.RDNs {
+				for _, attribs := range rdn.Attributes {
+					t.Logf("#%v\n", attribs)
+				}
+			}
+		}
+	}
+}
+
+func TestErrorDNParsing(t *testing.T) {
+	testcases := map[string]string{
+		"*":                       "DN ended with incomplete type, value pair",
+		"cn=Jim\\0Test":           "Failed to decode escaped character: encoding/hex: invalid byte: U+0054 'T'",
+		"cn=Jim\\0":               "Got corrupted escaped character",
+		"DC=example,=net":         "DN ended with incomplete type, value pair",
+		"1=#0402486":              "Failed to decode BER encoding: encoding/hex: odd length hex string",
+		"test,DC=example,DC=com":  "incomplete type, value pair",
+		"=test,DC=example,DC=com": "incomplete type, value pair",
+	}
+
+	for test, answer := range testcases {
+		_, err := ldap.ParseDN(test)
+		if err == nil {
+			t.Errorf("Expected %s to fail parsing but succeeded\n", test)
+		} else if err.Error() != answer {
+			t.Errorf("Unexpected error on %s:\n%s\nvs.\n%s\n", test, answer, err.Error())
+		}
+	}
+}
+
+func TestDNEqual(t *testing.T) {
+	testcases := []struct {
+		A     string
+		B     string
+		Equal bool
+	}{
+		// Exact match
+		{"", "", true},
+		{"o=A", "o=A", true},
+		{"o=A", "o=B", false},
+
+		{"o=A,o=B", "o=A,o=B", true},
+		{"o=A,o=B", "o=A,o=C", false},
+
+		{"o=A+o=B", "o=A+o=B", true},
+		{"o=A+o=B", "o=A+o=C", false},
+
+		// Case mismatch in type is ignored
+		{"o=A", "O=A", true},
+		{"o=A,o=B", "o=A,O=B", true},
+		{"o=A+o=B", "o=A+O=B", true},
+
+		// Case mismatch in value is significant
+		{"o=a", "O=A", false},
+		{"o=a,o=B", "o=A,O=B", false},
+		{"o=a+o=B", "o=A+O=B", false},
+
+		// Multi-valued RDN order mismatch is ignored
+		{"o=A+o=B", "O=B+o=A", true},
+		// Number of RDN attributes is significant
+		{"o=A+o=B", "O=B+o=A+O=B", false},
+
+		// Missing values are significant
+		{"o=A+o=B", "O=B+o=A+O=C", false}, // missing values matter
+		{"o=A+o=B+o=C", "O=B+o=A", false}, // missing values matter
+
+		// Whitespace tests
+		// Matching
+		{
+			"cn=John Doe, ou=People, dc=sun.com",
+			"cn=John Doe, ou=People, dc=sun.com",
+			true,
+		},
+		// Difference in leading/trailing chars is ignored
+		{
+			"cn=John Doe, ou=People, dc=sun.com",
+			"cn=John Doe,ou=People,dc=sun.com",
+			true,
+		},
+		// Difference in values is significant
+		{
+			"cn=John Doe, ou=People, dc=sun.com",
+			"cn=John  Doe, ou=People, dc=sun.com",
+			false,
+		},
+	}
+
+	for i, tc := range testcases {
+		a, err := ldap.ParseDN(tc.A)
+		if err != nil {
+			t.Errorf("%d: %v", i, err)
+			continue
+		}
+		b, err := ldap.ParseDN(tc.B)
+		if err != nil {
+			t.Errorf("%d: %v", i, err)
+			continue
+		}
+		if expected, actual := tc.Equal, a.Equal(b); expected != actual {
+			t.Errorf("%d: when comparing '%s' and '%s' expected %v, got %v", i, tc.A, tc.B, expected, actual)
+			continue
+		}
+		if expected, actual := tc.Equal, b.Equal(a); expected != actual {
+			t.Errorf("%d: when comparing '%s' and '%s' expected %v, got %v", i, tc.A, tc.B, expected, actual)
+			continue
+		}
+	}
+}
+
+func TestDNAncestor(t *testing.T) {
+	testcases := []struct {
+		A        string
+		B        string
+		Ancestor bool
+	}{
+		// Exact match returns false
+		{"", "", false},
+		{"o=A", "o=A", false},
+		{"o=A,o=B", "o=A,o=B", false},
+		{"o=A+o=B", "o=A+o=B", false},
+
+		// Mismatch
+		{"ou=C,ou=B,o=A", "ou=E,ou=D,ou=B,o=A", false},
+
+		// Descendant
+		{"ou=C,ou=B,o=A", "ou=E,ou=C,ou=B,o=A", true},
+	}
+
+	for i, tc := range testcases {
+		a, err := ldap.ParseDN(tc.A)
+		if err != nil {
+			t.Errorf("%d: %v", i, err)
+			continue
+		}
+		b, err := ldap.ParseDN(tc.B)
+		if err != nil {
+			t.Errorf("%d: %v", i, err)
+			continue
+		}
+		if expected, actual := tc.Ancestor, a.AncestorOf(b); expected != actual {
+			t.Errorf("%d: when comparing '%s' and '%s' expected %v, got %v", i, tc.A, tc.B, expected, actual)
+			continue
+		}
+	}
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/doc.go b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/doc.go
new file mode 100644
index 0000000..f20d39b
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/doc.go
@@ -0,0 +1,4 @@
+/*
+Package ldap provides basic LDAP v3 functionality.
+*/
+package ldap
diff --git a/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/error.go b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/error.go
new file mode 100644
index 0000000..4cccb53
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/error.go
@@ -0,0 +1,155 @@
+package ldap
+
+import (
+	"fmt"
+
+	"gopkg.in/asn1-ber.v1"
+)
+
+// LDAP Result Codes
+const (
+	LDAPResultSuccess                      = 0
+	LDAPResultOperationsError              = 1
+	LDAPResultProtocolError                = 2
+	LDAPResultTimeLimitExceeded            = 3
+	LDAPResultSizeLimitExceeded            = 4
+	LDAPResultCompareFalse                 = 5
+	LDAPResultCompareTrue                  = 6
+	LDAPResultAuthMethodNotSupported       = 7
+	LDAPResultStrongAuthRequired           = 8
+	LDAPResultReferral                     = 10
+	LDAPResultAdminLimitExceeded           = 11
+	LDAPResultUnavailableCriticalExtension = 12
+	LDAPResultConfidentialityRequired      = 13
+	LDAPResultSaslBindInProgress           = 14
+	LDAPResultNoSuchAttribute              = 16
+	LDAPResultUndefinedAttributeType       = 17
+	LDAPResultInappropriateMatching        = 18
+	LDAPResultConstraintViolation          = 19
+	LDAPResultAttributeOrValueExists       = 20
+	LDAPResultInvalidAttributeSyntax       = 21
+	LDAPResultNoSuchObject                 = 32
+	LDAPResultAliasProblem                 = 33
+	LDAPResultInvalidDNSyntax              = 34
+	LDAPResultAliasDereferencingProblem    = 36
+	LDAPResultInappropriateAuthentication  = 48
+	LDAPResultInvalidCredentials           = 49
+	LDAPResultInsufficientAccessRights     = 50
+	LDAPResultBusy                         = 51
+	LDAPResultUnavailable                  = 52
+	LDAPResultUnwillingToPerform           = 53
+	LDAPResultLoopDetect                   = 54
+	LDAPResultNamingViolation              = 64
+	LDAPResultObjectClassViolation         = 65
+	LDAPResultNotAllowedOnNonLeaf          = 66
+	LDAPResultNotAllowedOnRDN              = 67
+	LDAPResultEntryAlreadyExists           = 68
+	LDAPResultObjectClassModsProhibited    = 69
+	LDAPResultAffectsMultipleDSAs          = 71
+	LDAPResultOther                        = 80
+
+	ErrorNetwork            = 200
+	ErrorFilterCompile      = 201
+	ErrorFilterDecompile    = 202
+	ErrorDebugging          = 203
+	ErrorUnexpectedMessage  = 204
+	ErrorUnexpectedResponse = 205
+)
+
+// LDAPResultCodeMap contains string descriptions for LDAP error codes
+var LDAPResultCodeMap = map[uint8]string{
+	LDAPResultSuccess:                      "Success",
+	LDAPResultOperationsError:              "Operations Error",
+	LDAPResultProtocolError:                "Protocol Error",
+	LDAPResultTimeLimitExceeded:            "Time Limit Exceeded",
+	LDAPResultSizeLimitExceeded:            "Size Limit Exceeded",
+	LDAPResultCompareFalse:                 "Compare False",
+	LDAPResultCompareTrue:                  "Compare True",
+	LDAPResultAuthMethodNotSupported:       "Auth Method Not Supported",
+	LDAPResultStrongAuthRequired:           "Strong Auth Required",
+	LDAPResultReferral:                     "Referral",
+	LDAPResultAdminLimitExceeded:           "Admin Limit Exceeded",
+	LDAPResultUnavailableCriticalExtension: "Unavailable Critical Extension",
+	LDAPResultConfidentialityRequired:      "Confidentiality Required",
+	LDAPResultSaslBindInProgress:           "Sasl Bind In Progress",
+	LDAPResultNoSuchAttribute:              "No Such Attribute",
+	LDAPResultUndefinedAttributeType:       "Undefined Attribute Type",
+	LDAPResultInappropriateMatching:        "Inappropriate Matching",
+	LDAPResultConstraintViolation:          "Constraint Violation",
+	LDAPResultAttributeOrValueExists:       "Attribute Or Value Exists",
+	LDAPResultInvalidAttributeSyntax:       "Invalid Attribute Syntax",
+	LDAPResultNoSuchObject:                 "No Such Object",
+	LDAPResultAliasProblem:                 "Alias Problem",
+	LDAPResultInvalidDNSyntax:              "Invalid DN Syntax",
+	LDAPResultAliasDereferencingProblem:    "Alias Dereferencing Problem",
+	LDAPResultInappropriateAuthentication:  "Inappropriate Authentication",
+	LDAPResultInvalidCredentials:           "Invalid Credentials",
+	LDAPResultInsufficientAccessRights:     "Insufficient Access Rights",
+	LDAPResultBusy:                         "Busy",
+	LDAPResultUnavailable:                  "Unavailable",
+	LDAPResultUnwillingToPerform:           "Unwilling To Perform",
+	LDAPResultLoopDetect:                   "Loop Detect",
+	LDAPResultNamingViolation:              "Naming Violation",
+	LDAPResultObjectClassViolation:         "Object Class Violation",
+	LDAPResultNotAllowedOnNonLeaf:          "Not Allowed On Non Leaf",
+	LDAPResultNotAllowedOnRDN:              "Not Allowed On RDN",
+	LDAPResultEntryAlreadyExists:           "Entry Already Exists",
+	LDAPResultObjectClassModsProhibited:    "Object Class Mods Prohibited",
+	LDAPResultAffectsMultipleDSAs:          "Affects Multiple DSAs",
+	LDAPResultOther:                        "Other",
+
+	ErrorNetwork:            "Network Error",
+	ErrorFilterCompile:      "Filter Compile Error",
+	ErrorFilterDecompile:    "Filter Decompile Error",
+	ErrorDebugging:          "Debugging Error",
+	ErrorUnexpectedMessage:  "Unexpected Message",
+	ErrorUnexpectedResponse: "Unexpected Response",
+}
+
+func getLDAPResultCode(packet *ber.Packet) (code uint8, description string) {
+	if packet == nil {
+		return ErrorUnexpectedResponse, "Empty packet"
+	} else if len(packet.Children) >= 2 {
+		response := packet.Children[1]
+		if response == nil {
+			return ErrorUnexpectedResponse, "Empty response in packet"
+		}
+		if response.ClassType == ber.ClassApplication && response.TagType == ber.TypeConstructed && len(response.Children) >= 3 {
+			// Children[1].Children[2] is the diagnosticMessage which is guaranteed to exist as seen here: https://tools.ietf.org/html/rfc4511#section-4.1.9
+			return uint8(response.Children[0].Value.(int64)), response.Children[2].Value.(string)
+		}
+	}
+
+	return ErrorNetwork, "Invalid packet format"
+}
+
+// Error holds LDAP error information
+type Error struct {
+	// Err is the underlying error
+	Err error
+	// ResultCode is the LDAP error code
+	ResultCode uint8
+}
+
+func (e *Error) Error() string {
+	return fmt.Sprintf("LDAP Result Code %d %q: %s", e.ResultCode, LDAPResultCodeMap[e.ResultCode], e.Err.Error())
+}
+
+// NewError creates an LDAP error with the given code and underlying error
+func NewError(resultCode uint8, err error) error {
+	return &Error{ResultCode: resultCode, Err: err}
+}
+
+// IsErrorWithCode returns true if the given error is an LDAP error with the given result code
+func IsErrorWithCode(err error, desiredResultCode uint8) bool {
+	if err == nil {
+		return false
+	}
+
+	serverError, ok := err.(*Error)
+	if !ok {
+		return false
+	}
+
+	return serverError.ResultCode == desiredResultCode
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/error_test.go b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/error_test.go
new file mode 100644
index 0000000..e456431
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/error_test.go
@@ -0,0 +1,102 @@
+package ldap
+
+import (
+	"errors"
+	"net"
+	"strings"
+	"testing"
+	"time"
+
+	"gopkg.in/asn1-ber.v1"
+)
+
+// TestNilPacket tests that nil packets don't cause a panic.
+func TestNilPacket(t *testing.T) {
+	// Test for nil packet
+	code, _ := getLDAPResultCode(nil)
+	if code != ErrorUnexpectedResponse {
+		t.Errorf("Should have an 'ErrorUnexpectedResponse' error in nil packets, got: %v", code)
+	}
+
+	// Test for nil result
+	kids := []*ber.Packet{
+		{},  // Unused
+		nil, // Can't be nil
+	}
+	pack := &ber.Packet{Children: kids}
+	code, _ = getLDAPResultCode(pack)
+
+	if code != ErrorUnexpectedResponse {
+		t.Errorf("Should have an 'ErrorUnexpectedResponse' error in nil packets, got: %v", code)
+	}
+}
+
+// TestConnReadErr tests that an unexpected error reading from underlying
+// connection bubbles up to the goroutine which makes a request.
+func TestConnReadErr(t *testing.T) {
+	conn := &signalErrConn{
+		signals: make(chan error),
+	}
+
+	ldapConn := NewConn(conn, false)
+	ldapConn.Start()
+
+	// Make a dummy search request.
+	searchReq := NewSearchRequest("dc=example,dc=com", ScopeWholeSubtree, DerefAlways, 0, 0, false, "(objectClass=*)", nil, nil)
+
+	expectedError := errors.New("this is the error you are looking for")
+
+	// Send the signal after a short amount of time.
+	time.AfterFunc(10*time.Millisecond, func() { conn.signals <- expectedError })
+
+	// This should block until the underlying conn gets the error signal
+	// which should bubble up through the reader() goroutine, close the
+	// connection, and
+	_, err := ldapConn.Search(searchReq)
+	if err == nil || !strings.Contains(err.Error(), expectedError.Error()) {
+		t.Errorf("not the expected error: %s", err)
+	}
+}
+
+// signalErrConn is a helpful type used with TestConnReadErr. It implements the
+// net.Conn interface to be used as a connection for the test. Most methods are
+// no-ops but the Read() method blocks until it receives a signal which it
+// returns as an error.
+type signalErrConn struct {
+	signals chan error
+}
+
+// Read blocks until an error is sent on the internal signals channel. That
+// error is returned.
+func (c *signalErrConn) Read(b []byte) (n int, err error) {
+	return 0, <-c.signals
+}
+
+func (c *signalErrConn) Write(b []byte) (n int, err error) {
+	return len(b), nil
+}
+
+func (c *signalErrConn) Close() error {
+	close(c.signals)
+	return nil
+}
+
+func (c *signalErrConn) LocalAddr() net.Addr {
+	return (*net.TCPAddr)(nil)
+}
+
+func (c *signalErrConn) RemoteAddr() net.Addr {
+	return (*net.TCPAddr)(nil)
+}
+
+func (c *signalErrConn) SetDeadline(t time.Time) error {
+	return nil
+}
+
+func (c *signalErrConn) SetReadDeadline(t time.Time) error {
+	return nil
+}
+
+func (c *signalErrConn) SetWriteDeadline(t time.Time) error {
+	return nil
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/example_test.go b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/example_test.go
new file mode 100644
index 0000000..650af0a
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/example_test.go
@@ -0,0 +1,305 @@
+package ldap_test
+
+import (
+	"crypto/tls"
+	"fmt"
+	"log"
+
+	"gopkg.in/ldap.v2"
+)
+
+// ExampleConn_Bind demonstrates how to bind a connection to an ldap user
+// allowing access to restricted attributes that user has access to
+func ExampleConn_Bind() {
+	l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", "ldap.example.com", 389))
+	if err != nil {
+		log.Fatal(err)
+	}
+	defer l.Close()
+
+	err = l.Bind("cn=read-only-admin,dc=example,dc=com", "password")
+	if err != nil {
+		log.Fatal(err)
+	}
+}
+
+// ExampleConn_Search demonstrates how to use the search interface
+func ExampleConn_Search() {
+	l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", "ldap.example.com", 389))
+	if err != nil {
+		log.Fatal(err)
+	}
+	defer l.Close()
+
+	searchRequest := ldap.NewSearchRequest(
+		"dc=example,dc=com", // The base dn to search
+		ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
+		"(&(objectClass=organizationalPerson))", // The filter to apply
+		[]string{"dn", "cn"},                    // A list attributes to retrieve
+		nil,
+	)
+
+	sr, err := l.Search(searchRequest)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	for _, entry := range sr.Entries {
+		fmt.Printf("%s: %v\n", entry.DN, entry.GetAttributeValue("cn"))
+	}
+}
+
+// ExampleStartTLS demonstrates how to start a TLS connection
+func ExampleConn_StartTLS() {
+	l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", "ldap.example.com", 389))
+	if err != nil {
+		log.Fatal(err)
+	}
+	defer l.Close()
+
+	// Reconnect with TLS
+	err = l.StartTLS(&tls.Config{InsecureSkipVerify: true})
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	// Operations via l are now encrypted
+}
+
+// ExampleConn_Compare demonstrates how to compare an attribute with a value
+func ExampleConn_Compare() {
+	l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", "ldap.example.com", 389))
+	if err != nil {
+		log.Fatal(err)
+	}
+	defer l.Close()
+
+	matched, err := l.Compare("cn=user,dc=example,dc=com", "uid", "someuserid")
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	fmt.Println(matched)
+}
+
+func ExampleConn_PasswordModify_admin() {
+	l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", "ldap.example.com", 389))
+	if err != nil {
+		log.Fatal(err)
+	}
+	defer l.Close()
+
+	err = l.Bind("cn=admin,dc=example,dc=com", "password")
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	passwordModifyRequest := ldap.NewPasswordModifyRequest("cn=user,dc=example,dc=com", "", "NewPassword")
+	_, err = l.PasswordModify(passwordModifyRequest)
+
+	if err != nil {
+		log.Fatalf("Password could not be changed: %s", err.Error())
+	}
+}
+
+func ExampleConn_PasswordModify_generatedPassword() {
+	l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", "ldap.example.com", 389))
+	if err != nil {
+		log.Fatal(err)
+	}
+	defer l.Close()
+
+	err = l.Bind("cn=user,dc=example,dc=com", "password")
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	passwordModifyRequest := ldap.NewPasswordModifyRequest("", "OldPassword", "")
+	passwordModifyResponse, err := l.PasswordModify(passwordModifyRequest)
+	if err != nil {
+		log.Fatalf("Password could not be changed: %s", err.Error())
+	}
+
+	generatedPassword := passwordModifyResponse.GeneratedPassword
+	log.Printf("Generated password: %s\n", generatedPassword)
+}
+
+func ExampleConn_PasswordModify_setNewPassword() {
+	l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", "ldap.example.com", 389))
+	if err != nil {
+		log.Fatal(err)
+	}
+	defer l.Close()
+
+	err = l.Bind("cn=user,dc=example,dc=com", "password")
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	passwordModifyRequest := ldap.NewPasswordModifyRequest("", "OldPassword", "NewPassword")
+	_, err = l.PasswordModify(passwordModifyRequest)
+
+	if err != nil {
+		log.Fatalf("Password could not be changed: %s", err.Error())
+	}
+}
+
+func ExampleConn_Modify() {
+	l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", "ldap.example.com", 389))
+	if err != nil {
+		log.Fatal(err)
+	}
+	defer l.Close()
+
+	// Add a description, and replace the mail attributes
+	modify := ldap.NewModifyRequest("cn=user,dc=example,dc=com")
+	modify.Add("description", []string{"An example user"})
+	modify.Replace("mail", []string{"user@example.org"})
+
+	err = l.Modify(modify)
+	if err != nil {
+		log.Fatal(err)
+	}
+}
+
+// Example User Authentication shows how a typical application can verify a login attempt
+func Example_userAuthentication() {
+	// The username and password we want to check
+	username := "someuser"
+	password := "userpassword"
+
+	bindusername := "readonly"
+	bindpassword := "password"
+
+	l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", "ldap.example.com", 389))
+	if err != nil {
+		log.Fatal(err)
+	}
+	defer l.Close()
+
+	// Reconnect with TLS
+	err = l.StartTLS(&tls.Config{InsecureSkipVerify: true})
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	// First bind with a read only user
+	err = l.Bind(bindusername, bindpassword)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	// Search for the given username
+	searchRequest := ldap.NewSearchRequest(
+		"dc=example,dc=com",
+		ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
+		fmt.Sprintf("(&(objectClass=organizationalPerson)(uid=%s))", username),
+		[]string{"dn"},
+		nil,
+	)
+
+	sr, err := l.Search(searchRequest)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	if len(sr.Entries) != 1 {
+		log.Fatal("User does not exist or too many entries returned")
+	}
+
+	userdn := sr.Entries[0].DN
+
+	// Bind as the user to verify their password
+	err = l.Bind(userdn, password)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	// Rebind as the read only user for any further queries
+	err = l.Bind(bindusername, bindpassword)
+	if err != nil {
+		log.Fatal(err)
+	}
+}
+
+func Example_beherappolicy() {
+	l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", "ldap.example.com", 389))
+	if err != nil {
+		log.Fatal(err)
+	}
+	defer l.Close()
+
+	controls := []ldap.Control{}
+	controls = append(controls, ldap.NewControlBeheraPasswordPolicy())
+	bindRequest := ldap.NewSimpleBindRequest("cn=admin,dc=example,dc=com", "password", controls)
+
+	r, err := l.SimpleBind(bindRequest)
+	ppolicyControl := ldap.FindControl(r.Controls, ldap.ControlTypeBeheraPasswordPolicy)
+
+	var ppolicy *ldap.ControlBeheraPasswordPolicy
+	if ppolicyControl != nil {
+		ppolicy = ppolicyControl.(*ldap.ControlBeheraPasswordPolicy)
+	} else {
+		log.Printf("ppolicyControl response not available.\n")
+	}
+	if err != nil {
+		errStr := "ERROR: Cannot bind: " + err.Error()
+		if ppolicy != nil && ppolicy.Error >= 0 {
+			errStr += ":" + ppolicy.ErrorString
+		}
+		log.Print(errStr)
+	} else {
+		logStr := "Login Ok"
+		if ppolicy != nil {
+			if ppolicy.Expire >= 0 {
+				logStr += fmt.Sprintf(". Password expires in %d seconds\n", ppolicy.Expire)
+			} else if ppolicy.Grace >= 0 {
+				logStr += fmt.Sprintf(". Password expired, %d grace logins remain\n", ppolicy.Grace)
+			}
+		}
+		log.Print(logStr)
+	}
+}
+
+func Example_vchuppolicy() {
+	l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", "ldap.example.com", 389))
+	if err != nil {
+		log.Fatal(err)
+	}
+	defer l.Close()
+	l.Debug = true
+
+	bindRequest := ldap.NewSimpleBindRequest("cn=admin,dc=example,dc=com", "password", nil)
+
+	r, err := l.SimpleBind(bindRequest)
+
+	passwordMustChangeControl := ldap.FindControl(r.Controls, ldap.ControlTypeVChuPasswordMustChange)
+	var passwordMustChange *ldap.ControlVChuPasswordMustChange
+	if passwordMustChangeControl != nil {
+		passwordMustChange = passwordMustChangeControl.(*ldap.ControlVChuPasswordMustChange)
+	}
+
+	if passwordMustChange != nil && passwordMustChange.MustChange {
+		log.Printf("Password Must be changed.\n")
+	}
+
+	passwordWarningControl := ldap.FindControl(r.Controls, ldap.ControlTypeVChuPasswordWarning)
+
+	var passwordWarning *ldap.ControlVChuPasswordWarning
+	if passwordWarningControl != nil {
+		passwordWarning = passwordWarningControl.(*ldap.ControlVChuPasswordWarning)
+	} else {
+		log.Printf("ppolicyControl response not available.\n")
+	}
+	if err != nil {
+		log.Print("ERROR: Cannot bind: " + err.Error())
+	} else {
+		logStr := "Login Ok"
+		if passwordWarning != nil {
+			if passwordWarning.Expire >= 0 {
+				logStr += fmt.Sprintf(". Password expires in %d seconds\n", passwordWarning.Expire)
+			}
+		}
+		log.Print(logStr)
+	}
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/filter.go b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/filter.go
new file mode 100644
index 0000000..3858a28
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/filter.go
@@ -0,0 +1,469 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ldap
+
+import (
+	"bytes"
+	hexpac "encoding/hex"
+	"errors"
+	"fmt"
+	"strings"
+	"unicode/utf8"
+
+	"gopkg.in/asn1-ber.v1"
+)
+
+// Filter choices
+const (
+	FilterAnd             = 0
+	FilterOr              = 1
+	FilterNot             = 2
+	FilterEqualityMatch   = 3
+	FilterSubstrings      = 4
+	FilterGreaterOrEqual  = 5
+	FilterLessOrEqual     = 6
+	FilterPresent         = 7
+	FilterApproxMatch     = 8
+	FilterExtensibleMatch = 9
+)
+
+// FilterMap contains human readable descriptions of Filter choices
+var FilterMap = map[uint64]string{
+	FilterAnd:             "And",
+	FilterOr:              "Or",
+	FilterNot:             "Not",
+	FilterEqualityMatch:   "Equality Match",
+	FilterSubstrings:      "Substrings",
+	FilterGreaterOrEqual:  "Greater Or Equal",
+	FilterLessOrEqual:     "Less Or Equal",
+	FilterPresent:         "Present",
+	FilterApproxMatch:     "Approx Match",
+	FilterExtensibleMatch: "Extensible Match",
+}
+
+// SubstringFilter options
+const (
+	FilterSubstringsInitial = 0
+	FilterSubstringsAny     = 1
+	FilterSubstringsFinal   = 2
+)
+
+// FilterSubstringsMap contains human readable descriptions of SubstringFilter choices
+var FilterSubstringsMap = map[uint64]string{
+	FilterSubstringsInitial: "Substrings Initial",
+	FilterSubstringsAny:     "Substrings Any",
+	FilterSubstringsFinal:   "Substrings Final",
+}
+
+// MatchingRuleAssertion choices
+const (
+	MatchingRuleAssertionMatchingRule = 1
+	MatchingRuleAssertionType         = 2
+	MatchingRuleAssertionMatchValue   = 3
+	MatchingRuleAssertionDNAttributes = 4
+)
+
+// MatchingRuleAssertionMap contains human readable descriptions of MatchingRuleAssertion choices
+var MatchingRuleAssertionMap = map[uint64]string{
+	MatchingRuleAssertionMatchingRule: "Matching Rule Assertion Matching Rule",
+	MatchingRuleAssertionType:         "Matching Rule Assertion Type",
+	MatchingRuleAssertionMatchValue:   "Matching Rule Assertion Match Value",
+	MatchingRuleAssertionDNAttributes: "Matching Rule Assertion DN Attributes",
+}
+
+// CompileFilter converts a string representation of a filter into a BER-encoded packet
+func CompileFilter(filter string) (*ber.Packet, error) {
+	if len(filter) == 0 || filter[0] != '(' {
+		return nil, NewError(ErrorFilterCompile, errors.New("ldap: filter does not start with an '('"))
+	}
+	packet, pos, err := compileFilter(filter, 1)
+	if err != nil {
+		return nil, err
+	}
+	switch {
+	case pos > len(filter):
+		return nil, NewError(ErrorFilterCompile, errors.New("ldap: unexpected end of filter"))
+	case pos < len(filter):
+		return nil, NewError(ErrorFilterCompile, errors.New("ldap: finished compiling filter with extra at end: "+fmt.Sprint(filter[pos:])))
+	}
+	return packet, nil
+}
+
+// DecompileFilter converts a packet representation of a filter into a string representation
+func DecompileFilter(packet *ber.Packet) (ret string, err error) {
+	defer func() {
+		if r := recover(); r != nil {
+			err = NewError(ErrorFilterDecompile, errors.New("ldap: error decompiling filter"))
+		}
+	}()
+	ret = "("
+	err = nil
+	childStr := ""
+
+	switch packet.Tag {
+	case FilterAnd:
+		ret += "&"
+		for _, child := range packet.Children {
+			childStr, err = DecompileFilter(child)
+			if err != nil {
+				return
+			}
+			ret += childStr
+		}
+	case FilterOr:
+		ret += "|"
+		for _, child := range packet.Children {
+			childStr, err = DecompileFilter(child)
+			if err != nil {
+				return
+			}
+			ret += childStr
+		}
+	case FilterNot:
+		ret += "!"
+		childStr, err = DecompileFilter(packet.Children[0])
+		if err != nil {
+			return
+		}
+		ret += childStr
+
+	case FilterSubstrings:
+		ret += ber.DecodeString(packet.Children[0].Data.Bytes())
+		ret += "="
+		for i, child := range packet.Children[1].Children {
+			if i == 0 && child.Tag != FilterSubstringsInitial {
+				ret += "*"
+			}
+			ret += EscapeFilter(ber.DecodeString(child.Data.Bytes()))
+			if child.Tag != FilterSubstringsFinal {
+				ret += "*"
+			}
+		}
+	case FilterEqualityMatch:
+		ret += ber.DecodeString(packet.Children[0].Data.Bytes())
+		ret += "="
+		ret += EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes()))
+	case FilterGreaterOrEqual:
+		ret += ber.DecodeString(packet.Children[0].Data.Bytes())
+		ret += ">="
+		ret += EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes()))
+	case FilterLessOrEqual:
+		ret += ber.DecodeString(packet.Children[0].Data.Bytes())
+		ret += "<="
+		ret += EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes()))
+	case FilterPresent:
+		ret += ber.DecodeString(packet.Data.Bytes())
+		ret += "=*"
+	case FilterApproxMatch:
+		ret += ber.DecodeString(packet.Children[0].Data.Bytes())
+		ret += "~="
+		ret += EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes()))
+	case FilterExtensibleMatch:
+		attr := ""
+		dnAttributes := false
+		matchingRule := ""
+		value := ""
+
+		for _, child := range packet.Children {
+			switch child.Tag {
+			case MatchingRuleAssertionMatchingRule:
+				matchingRule = ber.DecodeString(child.Data.Bytes())
+			case MatchingRuleAssertionType:
+				attr = ber.DecodeString(child.Data.Bytes())
+			case MatchingRuleAssertionMatchValue:
+				value = ber.DecodeString(child.Data.Bytes())
+			case MatchingRuleAssertionDNAttributes:
+				dnAttributes = child.Value.(bool)
+			}
+		}
+
+		if len(attr) > 0 {
+			ret += attr
+		}
+		if dnAttributes {
+			ret += ":dn"
+		}
+		if len(matchingRule) > 0 {
+			ret += ":"
+			ret += matchingRule
+		}
+		ret += ":="
+		ret += EscapeFilter(value)
+	}
+
+	ret += ")"
+	return
+}
+
+func compileFilterSet(filter string, pos int, parent *ber.Packet) (int, error) {
+	for pos < len(filter) && filter[pos] == '(' {
+		child, newPos, err := compileFilter(filter, pos+1)
+		if err != nil {
+			return pos, err
+		}
+		pos = newPos
+		parent.AppendChild(child)
+	}
+	if pos == len(filter) {
+		return pos, NewError(ErrorFilterCompile, errors.New("ldap: unexpected end of filter"))
+	}
+
+	return pos + 1, nil
+}
+
+func compileFilter(filter string, pos int) (*ber.Packet, int, error) {
+	var (
+		packet *ber.Packet
+		err    error
+	)
+
+	defer func() {
+		if r := recover(); r != nil {
+			err = NewError(ErrorFilterCompile, errors.New("ldap: error compiling filter"))
+		}
+	}()
+	newPos := pos
+
+	currentRune, currentWidth := utf8.DecodeRuneInString(filter[newPos:])
+
+	switch currentRune {
+	case utf8.RuneError:
+		return nil, 0, NewError(ErrorFilterCompile, fmt.Errorf("ldap: error reading rune at position %d", newPos))
+	case '(':
+		packet, newPos, err = compileFilter(filter, pos+currentWidth)
+		newPos++
+		return packet, newPos, err
+	case '&':
+		packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterAnd, nil, FilterMap[FilterAnd])
+		newPos, err = compileFilterSet(filter, pos+currentWidth, packet)
+		return packet, newPos, err
+	case '|':
+		packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterOr, nil, FilterMap[FilterOr])
+		newPos, err = compileFilterSet(filter, pos+currentWidth, packet)
+		return packet, newPos, err
+	case '!':
+		packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterNot, nil, FilterMap[FilterNot])
+		var child *ber.Packet
+		child, newPos, err = compileFilter(filter, pos+currentWidth)
+		packet.AppendChild(child)
+		return packet, newPos, err
+	default:
+		const (
+			stateReadingAttr                   = 0
+			stateReadingExtensibleMatchingRule = 1
+			stateReadingCondition              = 2
+		)
+
+		state := stateReadingAttr
+
+		attribute := ""
+		extensibleDNAttributes := false
+		extensibleMatchingRule := ""
+		condition := ""
+
+		for newPos < len(filter) {
+			remainingFilter := filter[newPos:]
+			currentRune, currentWidth = utf8.DecodeRuneInString(remainingFilter)
+			if currentRune == ')' {
+				break
+			}
+			if currentRune == utf8.RuneError {
+				return packet, newPos, NewError(ErrorFilterCompile, fmt.Errorf("ldap: error reading rune at position %d", newPos))
+			}
+
+			switch state {
+			case stateReadingAttr:
+				switch {
+				// Extensible rule, with only DN-matching
+				case currentRune == ':' && strings.HasPrefix(remainingFilter, ":dn:="):
+					packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterExtensibleMatch, nil, FilterMap[FilterExtensibleMatch])
+					extensibleDNAttributes = true
+					state = stateReadingCondition
+					newPos += 5
+
+				// Extensible rule, with DN-matching and a matching OID
+				case currentRune == ':' && strings.HasPrefix(remainingFilter, ":dn:"):
+					packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterExtensibleMatch, nil, FilterMap[FilterExtensibleMatch])
+					extensibleDNAttributes = true
+					state = stateReadingExtensibleMatchingRule
+					newPos += 4
+
+				// Extensible rule, with attr only
+				case currentRune == ':' && strings.HasPrefix(remainingFilter, ":="):
+					packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterExtensibleMatch, nil, FilterMap[FilterExtensibleMatch])
+					state = stateReadingCondition
+					newPos += 2
+
+				// Extensible rule, with no DN attribute matching
+				case currentRune == ':':
+					packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterExtensibleMatch, nil, FilterMap[FilterExtensibleMatch])
+					state = stateReadingExtensibleMatchingRule
+					newPos++
+
+				// Equality condition
+				case currentRune == '=':
+					packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterEqualityMatch, nil, FilterMap[FilterEqualityMatch])
+					state = stateReadingCondition
+					newPos++
+
+				// Greater-than or equal
+				case currentRune == '>' && strings.HasPrefix(remainingFilter, ">="):
+					packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterGreaterOrEqual, nil, FilterMap[FilterGreaterOrEqual])
+					state = stateReadingCondition
+					newPos += 2
+
+				// Less-than or equal
+				case currentRune == '<' && strings.HasPrefix(remainingFilter, "<="):
+					packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterLessOrEqual, nil, FilterMap[FilterLessOrEqual])
+					state = stateReadingCondition
+					newPos += 2
+
+				// Approx
+				case currentRune == '~' && strings.HasPrefix(remainingFilter, "~="):
+					packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterApproxMatch, nil, FilterMap[FilterApproxMatch])
+					state = stateReadingCondition
+					newPos += 2
+
+				// Still reading the attribute name
+				default:
+					attribute += fmt.Sprintf("%c", currentRune)
+					newPos += currentWidth
+				}
+
+			case stateReadingExtensibleMatchingRule:
+				switch {
+
+				// Matching rule OID is done
+				case currentRune == ':' && strings.HasPrefix(remainingFilter, ":="):
+					state = stateReadingCondition
+					newPos += 2
+
+				// Still reading the matching rule oid
+				default:
+					extensibleMatchingRule += fmt.Sprintf("%c", currentRune)
+					newPos += currentWidth
+				}
+
+			case stateReadingCondition:
+				// append to the condition
+				condition += fmt.Sprintf("%c", currentRune)
+				newPos += currentWidth
+			}
+		}
+
+		if newPos == len(filter) {
+			err = NewError(ErrorFilterCompile, errors.New("ldap: unexpected end of filter"))
+			return packet, newPos, err
+		}
+		if packet == nil {
+			err = NewError(ErrorFilterCompile, errors.New("ldap: error parsing filter"))
+			return packet, newPos, err
+		}
+
+		switch {
+		case packet.Tag == FilterExtensibleMatch:
+			// MatchingRuleAssertion ::= SEQUENCE {
+			//         matchingRule    [1] MatchingRuleID OPTIONAL,
+			//         type            [2] AttributeDescription OPTIONAL,
+			//         matchValue      [3] AssertionValue,
+			//         dnAttributes    [4] BOOLEAN DEFAULT FALSE
+			// }
+
+			// Include the matching rule oid, if specified
+			if len(extensibleMatchingRule) > 0 {
+				packet.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, MatchingRuleAssertionMatchingRule, extensibleMatchingRule, MatchingRuleAssertionMap[MatchingRuleAssertionMatchingRule]))
+			}
+
+			// Include the attribute, if specified
+			if len(attribute) > 0 {
+				packet.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, MatchingRuleAssertionType, attribute, MatchingRuleAssertionMap[MatchingRuleAssertionType]))
+			}
+
+			// Add the value (only required child)
+			encodedString, encodeErr := escapedStringToEncodedBytes(condition)
+			if encodeErr != nil {
+				return packet, newPos, encodeErr
+			}
+			packet.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, MatchingRuleAssertionMatchValue, encodedString, MatchingRuleAssertionMap[MatchingRuleAssertionMatchValue]))
+
+			// Defaults to false, so only include in the sequence if true
+			if extensibleDNAttributes {
+				packet.AppendChild(ber.NewBoolean(ber.ClassContext, ber.TypePrimitive, MatchingRuleAssertionDNAttributes, extensibleDNAttributes, MatchingRuleAssertionMap[MatchingRuleAssertionDNAttributes]))
+			}
+
+		case packet.Tag == FilterEqualityMatch && condition == "*":
+			packet = ber.NewString(ber.ClassContext, ber.TypePrimitive, FilterPresent, attribute, FilterMap[FilterPresent])
+		case packet.Tag == FilterEqualityMatch && strings.Contains(condition, "*"):
+			packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute, "Attribute"))
+			packet.Tag = FilterSubstrings
+			packet.Description = FilterMap[uint64(packet.Tag)]
+			seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Substrings")
+			parts := strings.Split(condition, "*")
+			for i, part := range parts {
+				if part == "" {
+					continue
+				}
+				var tag ber.Tag
+				switch i {
+				case 0:
+					tag = FilterSubstringsInitial
+				case len(parts) - 1:
+					tag = FilterSubstringsFinal
+				default:
+					tag = FilterSubstringsAny
+				}
+				encodedString, encodeErr := escapedStringToEncodedBytes(part)
+				if encodeErr != nil {
+					return packet, newPos, encodeErr
+				}
+				seq.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, tag, encodedString, FilterSubstringsMap[uint64(tag)]))
+			}
+			packet.AppendChild(seq)
+		default:
+			encodedString, encodeErr := escapedStringToEncodedBytes(condition)
+			if encodeErr != nil {
+				return packet, newPos, encodeErr
+			}
+			packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute, "Attribute"))
+			packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, encodedString, "Condition"))
+		}
+
+		newPos += currentWidth
+		return packet, newPos, err
+	}
+}
+
+// Convert from "ABC\xx\xx\xx" form to literal bytes for transport
+func escapedStringToEncodedBytes(escapedString string) (string, error) {
+	var buffer bytes.Buffer
+	i := 0
+	for i < len(escapedString) {
+		currentRune, currentWidth := utf8.DecodeRuneInString(escapedString[i:])
+		if currentRune == utf8.RuneError {
+			return "", NewError(ErrorFilterCompile, fmt.Errorf("ldap: error reading rune at position %d", i))
+		}
+
+		// Check for escaped hex characters and convert them to their literal value for transport.
+		if currentRune == '\\' {
+			// http://tools.ietf.org/search/rfc4515
+			// \ (%x5C) is not a valid character unless it is followed by two HEX characters due to not
+			// being a member of UTF1SUBSET.
+			if i+2 > len(escapedString) {
+				return "", NewError(ErrorFilterCompile, errors.New("ldap: missing characters for escape in filter"))
+			}
+			escByte, decodeErr := hexpac.DecodeString(escapedString[i+1 : i+3])
+			if decodeErr != nil {
+				return "", NewError(ErrorFilterCompile, errors.New("ldap: invalid characters for escape in filter"))
+			}
+			buffer.WriteByte(escByte[0])
+			i += 2 // +1 from end of loop, so 3 total for \xx.
+		} else {
+			buffer.WriteRune(currentRune)
+		}
+
+		i += currentWidth
+	}
+	return buffer.String(), nil
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/filter_test.go b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/filter_test.go
new file mode 100644
index 0000000..2b019ac
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/filter_test.go
@@ -0,0 +1,254 @@
+package ldap_test
+
+import (
+	"strings"
+	"testing"
+
+	"gopkg.in/asn1-ber.v1"
+	"gopkg.in/ldap.v2"
+)
+
+type compileTest struct {
+	filterStr string
+
+	expectedFilter string
+	expectedType   int
+	expectedErr    string
+}
+
+var testFilters = []compileTest{
+	compileTest{
+		filterStr:      "(&(sn=Miller)(givenName=Bob))",
+		expectedFilter: "(&(sn=Miller)(givenName=Bob))",
+		expectedType:   ldap.FilterAnd,
+	},
+	compileTest{
+		filterStr:      "(|(sn=Miller)(givenName=Bob))",
+		expectedFilter: "(|(sn=Miller)(givenName=Bob))",
+		expectedType:   ldap.FilterOr,
+	},
+	compileTest{
+		filterStr:      "(!(sn=Miller))",
+		expectedFilter: "(!(sn=Miller))",
+		expectedType:   ldap.FilterNot,
+	},
+	compileTest{
+		filterStr:      "(sn=Miller)",
+		expectedFilter: "(sn=Miller)",
+		expectedType:   ldap.FilterEqualityMatch,
+	},
+	compileTest{
+		filterStr:      "(sn=Mill*)",
+		expectedFilter: "(sn=Mill*)",
+		expectedType:   ldap.FilterSubstrings,
+	},
+	compileTest{
+		filterStr:      "(sn=*Mill)",
+		expectedFilter: "(sn=*Mill)",
+		expectedType:   ldap.FilterSubstrings,
+	},
+	compileTest{
+		filterStr:      "(sn=*Mill*)",
+		expectedFilter: "(sn=*Mill*)",
+		expectedType:   ldap.FilterSubstrings,
+	},
+	compileTest{
+		filterStr:      "(sn=*i*le*)",
+		expectedFilter: "(sn=*i*le*)",
+		expectedType:   ldap.FilterSubstrings,
+	},
+	compileTest{
+		filterStr:      "(sn=Mi*l*r)",
+		expectedFilter: "(sn=Mi*l*r)",
+		expectedType:   ldap.FilterSubstrings,
+	},
+	// substring filters escape properly
+	compileTest{
+		filterStr:      `(sn=Mi*함*r)`,
+		expectedFilter: `(sn=Mi*\ed\95\a8*r)`,
+		expectedType:   ldap.FilterSubstrings,
+	},
+	// already escaped substring filters don't get double-escaped
+	compileTest{
+		filterStr:      `(sn=Mi*\ed\95\a8*r)`,
+		expectedFilter: `(sn=Mi*\ed\95\a8*r)`,
+		expectedType:   ldap.FilterSubstrings,
+	},
+	compileTest{
+		filterStr:      "(sn=Mi*le*)",
+		expectedFilter: "(sn=Mi*le*)",
+		expectedType:   ldap.FilterSubstrings,
+	},
+	compileTest{
+		filterStr:      "(sn=*i*ler)",
+		expectedFilter: "(sn=*i*ler)",
+		expectedType:   ldap.FilterSubstrings,
+	},
+	compileTest{
+		filterStr:      "(sn>=Miller)",
+		expectedFilter: "(sn>=Miller)",
+		expectedType:   ldap.FilterGreaterOrEqual,
+	},
+	compileTest{
+		filterStr:      "(sn<=Miller)",
+		expectedFilter: "(sn<=Miller)",
+		expectedType:   ldap.FilterLessOrEqual,
+	},
+	compileTest{
+		filterStr:      "(sn=*)",
+		expectedFilter: "(sn=*)",
+		expectedType:   ldap.FilterPresent,
+	},
+	compileTest{
+		filterStr:      "(sn~=Miller)",
+		expectedFilter: "(sn~=Miller)",
+		expectedType:   ldap.FilterApproxMatch,
+	},
+	compileTest{
+		filterStr:      `(objectGUID='\fc\fe\a3\ab\f9\90N\aaGm\d5I~\d12)`,
+		expectedFilter: `(objectGUID='\fc\fe\a3\ab\f9\90N\aaGm\d5I~\d12)`,
+		expectedType:   ldap.FilterEqualityMatch,
+	},
+	compileTest{
+		filterStr:      `(objectGUID=абвгдеёжзийклмнопрстуфхцчшщъыьэюя)`,
+		expectedFilter: `(objectGUID=\d0\b0\d0\b1\d0\b2\d0\b3\d0\b4\d0\b5\d1\91\d0\b6\d0\b7\d0\b8\d0\b9\d0\ba\d0\bb\d0\bc\d0\bd\d0\be\d0\bf\d1\80\d1\81\d1\82\d1\83\d1\84\d1\85\d1\86\d1\87\d1\88\d1\89\d1\8a\d1\8b\d1\8c\d1\8d\d1\8e\d1\8f)`,
+		expectedType:   ldap.FilterEqualityMatch,
+	},
+	compileTest{
+		filterStr:      `(objectGUID=함수목록)`,
+		expectedFilter: `(objectGUID=\ed\95\a8\ec\88\98\eb\aa\a9\eb\a1\9d)`,
+		expectedType:   ldap.FilterEqualityMatch,
+	},
+	compileTest{
+		filterStr:      `(objectGUID=`,
+		expectedFilter: ``,
+		expectedType:   0,
+		expectedErr:    "unexpected end of filter",
+	},
+	compileTest{
+		filterStr:      `(objectGUID=함수목록`,
+		expectedFilter: ``,
+		expectedType:   0,
+		expectedErr:    "unexpected end of filter",
+	},
+	compileTest{
+		filterStr:      `((cn=)`,
+		expectedFilter: ``,
+		expectedType:   0,
+		expectedErr:    "unexpected end of filter",
+	},
+	compileTest{
+		filterStr:      `(&(objectclass=inetorgperson)(cn=中文))`,
+		expectedFilter: `(&(objectclass=inetorgperson)(cn=\e4\b8\ad\e6\96\87))`,
+		expectedType:   0,
+	},
+	// attr extension
+	compileTest{
+		filterStr:      `(memberOf:=foo)`,
+		expectedFilter: `(memberOf:=foo)`,
+		expectedType:   ldap.FilterExtensibleMatch,
+	},
+	// attr+named matching rule extension
+	compileTest{
+		filterStr:      `(memberOf:test:=foo)`,
+		expectedFilter: `(memberOf:test:=foo)`,
+		expectedType:   ldap.FilterExtensibleMatch,
+	},
+	// attr+oid matching rule extension
+	compileTest{
+		filterStr:      `(cn:1.2.3.4.5:=Fred Flintstone)`,
+		expectedFilter: `(cn:1.2.3.4.5:=Fred Flintstone)`,
+		expectedType:   ldap.FilterExtensibleMatch,
+	},
+	// attr+dn+oid matching rule extension
+	compileTest{
+		filterStr:      `(sn:dn:2.4.6.8.10:=Barney Rubble)`,
+		expectedFilter: `(sn:dn:2.4.6.8.10:=Barney Rubble)`,
+		expectedType:   ldap.FilterExtensibleMatch,
+	},
+	// attr+dn extension
+	compileTest{
+		filterStr:      `(o:dn:=Ace Industry)`,
+		expectedFilter: `(o:dn:=Ace Industry)`,
+		expectedType:   ldap.FilterExtensibleMatch,
+	},
+	// dn extension
+	compileTest{
+		filterStr:      `(:dn:2.4.6.8.10:=Dino)`,
+		expectedFilter: `(:dn:2.4.6.8.10:=Dino)`,
+		expectedType:   ldap.FilterExtensibleMatch,
+	},
+	compileTest{
+		filterStr:      `(memberOf:1.2.840.113556.1.4.1941:=CN=User1,OU=blah,DC=mydomain,DC=net)`,
+		expectedFilter: `(memberOf:1.2.840.113556.1.4.1941:=CN=User1,OU=blah,DC=mydomain,DC=net)`,
+		expectedType:   ldap.FilterExtensibleMatch,
+	},
+
+	// compileTest{ filterStr: "()", filterType: FilterExtensibleMatch },
+}
+
+var testInvalidFilters = []string{
+	`(objectGUID=\zz)`,
+	`(objectGUID=\a)`,
+}
+
+func TestFilter(t *testing.T) {
+	// Test Compiler and Decompiler
+	for _, i := range testFilters {
+		filter, err := ldap.CompileFilter(i.filterStr)
+		if err != nil {
+			if i.expectedErr == "" || !strings.Contains(err.Error(), i.expectedErr) {
+				t.Errorf("Problem compiling '%s' - '%v' (expected error to contain '%v')", i.filterStr, err, i.expectedErr)
+			}
+		} else if filter.Tag != ber.Tag(i.expectedType) {
+			t.Errorf("%q Expected %q got %q", i.filterStr, ldap.FilterMap[uint64(i.expectedType)], ldap.FilterMap[uint64(filter.Tag)])
+		} else {
+			o, err := ldap.DecompileFilter(filter)
+			if err != nil {
+				t.Errorf("Problem compiling %s - %s", i.filterStr, err.Error())
+			} else if i.expectedFilter != o {
+				t.Errorf("%q expected, got %q", i.expectedFilter, o)
+			}
+		}
+	}
+}
+
+func TestInvalidFilter(t *testing.T) {
+	for _, filterStr := range testInvalidFilters {
+		if _, err := ldap.CompileFilter(filterStr); err == nil {
+			t.Errorf("Problem compiling %s - expected err", filterStr)
+		}
+	}
+}
+
+func BenchmarkFilterCompile(b *testing.B) {
+	b.StopTimer()
+	filters := make([]string, len(testFilters))
+
+	// Test Compiler and Decompiler
+	for idx, i := range testFilters {
+		filters[idx] = i.filterStr
+	}
+
+	maxIdx := len(filters)
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		ldap.CompileFilter(filters[i%maxIdx])
+	}
+}
+
+func BenchmarkFilterDecompile(b *testing.B) {
+	b.StopTimer()
+	filters := make([]*ber.Packet, len(testFilters))
+
+	// Test Compiler and Decompiler
+	for idx, i := range testFilters {
+		filters[idx], _ = ldap.CompileFilter(i.filterStr)
+	}
+
+	maxIdx := len(filters)
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		ldap.DecompileFilter(filters[i%maxIdx])
+	}
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/ldap.go b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/ldap.go
new file mode 100644
index 0000000..4969247
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/ldap.go
@@ -0,0 +1,320 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ldap
+
+import (
+	"errors"
+	"io/ioutil"
+	"os"
+
+	"gopkg.in/asn1-ber.v1"
+)
+
+// LDAP Application Codes
+const (
+	ApplicationBindRequest           = 0
+	ApplicationBindResponse          = 1
+	ApplicationUnbindRequest         = 2
+	ApplicationSearchRequest         = 3
+	ApplicationSearchResultEntry     = 4
+	ApplicationSearchResultDone      = 5
+	ApplicationModifyRequest         = 6
+	ApplicationModifyResponse        = 7
+	ApplicationAddRequest            = 8
+	ApplicationAddResponse           = 9
+	ApplicationDelRequest            = 10
+	ApplicationDelResponse           = 11
+	ApplicationModifyDNRequest       = 12
+	ApplicationModifyDNResponse      = 13
+	ApplicationCompareRequest        = 14
+	ApplicationCompareResponse       = 15
+	ApplicationAbandonRequest        = 16
+	ApplicationSearchResultReference = 19
+	ApplicationExtendedRequest       = 23
+	ApplicationExtendedResponse      = 24
+)
+
+// ApplicationMap contains human readable descriptions of LDAP Application Codes
+var ApplicationMap = map[uint8]string{
+	ApplicationBindRequest:           "Bind Request",
+	ApplicationBindResponse:          "Bind Response",
+	ApplicationUnbindRequest:         "Unbind Request",
+	ApplicationSearchRequest:         "Search Request",
+	ApplicationSearchResultEntry:     "Search Result Entry",
+	ApplicationSearchResultDone:      "Search Result Done",
+	ApplicationModifyRequest:         "Modify Request",
+	ApplicationModifyResponse:        "Modify Response",
+	ApplicationAddRequest:            "Add Request",
+	ApplicationAddResponse:           "Add Response",
+	ApplicationDelRequest:            "Del Request",
+	ApplicationDelResponse:           "Del Response",
+	ApplicationModifyDNRequest:       "Modify DN Request",
+	ApplicationModifyDNResponse:      "Modify DN Response",
+	ApplicationCompareRequest:        "Compare Request",
+	ApplicationCompareResponse:       "Compare Response",
+	ApplicationAbandonRequest:        "Abandon Request",
+	ApplicationSearchResultReference: "Search Result Reference",
+	ApplicationExtendedRequest:       "Extended Request",
+	ApplicationExtendedResponse:      "Extended Response",
+}
+
+// Ldap Behera Password Policy Draft 10 (https://tools.ietf.org/html/draft-behera-ldap-password-policy-10)
+const (
+	BeheraPasswordExpired             = 0
+	BeheraAccountLocked               = 1
+	BeheraChangeAfterReset            = 2
+	BeheraPasswordModNotAllowed       = 3
+	BeheraMustSupplyOldPassword       = 4
+	BeheraInsufficientPasswordQuality = 5
+	BeheraPasswordTooShort            = 6
+	BeheraPasswordTooYoung            = 7
+	BeheraPasswordInHistory           = 8
+)
+
+// BeheraPasswordPolicyErrorMap contains human readable descriptions of Behera Password Policy error codes
+var BeheraPasswordPolicyErrorMap = map[int8]string{
+	BeheraPasswordExpired:             "Password expired",
+	BeheraAccountLocked:               "Account locked",
+	BeheraChangeAfterReset:            "Password must be changed",
+	BeheraPasswordModNotAllowed:       "Policy prevents password modification",
+	BeheraMustSupplyOldPassword:       "Policy requires old password in order to change password",
+	BeheraInsufficientPasswordQuality: "Password fails quality checks",
+	BeheraPasswordTooShort:            "Password is too short for policy",
+	BeheraPasswordTooYoung:            "Password has been changed too recently",
+	BeheraPasswordInHistory:           "New password is in list of old passwords",
+}
+
+// Adds descriptions to an LDAP Response packet for debugging
+func addLDAPDescriptions(packet *ber.Packet) (err error) {
+	defer func() {
+		if r := recover(); r != nil {
+			err = NewError(ErrorDebugging, errors.New("ldap: cannot process packet to add descriptions"))
+		}
+	}()
+	packet.Description = "LDAP Response"
+	packet.Children[0].Description = "Message ID"
+
+	application := uint8(packet.Children[1].Tag)
+	packet.Children[1].Description = ApplicationMap[application]
+
+	switch application {
+	case ApplicationBindRequest:
+		addRequestDescriptions(packet)
+	case ApplicationBindResponse:
+		addDefaultLDAPResponseDescriptions(packet)
+	case ApplicationUnbindRequest:
+		addRequestDescriptions(packet)
+	case ApplicationSearchRequest:
+		addRequestDescriptions(packet)
+	case ApplicationSearchResultEntry:
+		packet.Children[1].Children[0].Description = "Object Name"
+		packet.Children[1].Children[1].Description = "Attributes"
+		for _, child := range packet.Children[1].Children[1].Children {
+			child.Description = "Attribute"
+			child.Children[0].Description = "Attribute Name"
+			child.Children[1].Description = "Attribute Values"
+			for _, grandchild := range child.Children[1].Children {
+				grandchild.Description = "Attribute Value"
+			}
+		}
+		if len(packet.Children) == 3 {
+			addControlDescriptions(packet.Children[2])
+		}
+	case ApplicationSearchResultDone:
+		addDefaultLDAPResponseDescriptions(packet)
+	case ApplicationModifyRequest:
+		addRequestDescriptions(packet)
+	case ApplicationModifyResponse:
+	case ApplicationAddRequest:
+		addRequestDescriptions(packet)
+	case ApplicationAddResponse:
+	case ApplicationDelRequest:
+		addRequestDescriptions(packet)
+	case ApplicationDelResponse:
+	case ApplicationModifyDNRequest:
+		addRequestDescriptions(packet)
+	case ApplicationModifyDNResponse:
+	case ApplicationCompareRequest:
+		addRequestDescriptions(packet)
+	case ApplicationCompareResponse:
+	case ApplicationAbandonRequest:
+		addRequestDescriptions(packet)
+	case ApplicationSearchResultReference:
+	case ApplicationExtendedRequest:
+		addRequestDescriptions(packet)
+	case ApplicationExtendedResponse:
+	}
+
+	return nil
+}
+
+func addControlDescriptions(packet *ber.Packet) {
+	packet.Description = "Controls"
+	for _, child := range packet.Children {
+		var value *ber.Packet
+		controlType := ""
+		child.Description = "Control"
+		switch len(child.Children) {
+		case 0:
+			// at least one child is required for control type
+			continue
+
+		case 1:
+			// just type, no criticality or value
+			controlType = child.Children[0].Value.(string)
+			child.Children[0].Description = "Control Type (" + ControlTypeMap[controlType] + ")"
+
+		case 2:
+			controlType = child.Children[0].Value.(string)
+			child.Children[0].Description = "Control Type (" + ControlTypeMap[controlType] + ")"
+			// Children[1] could be criticality or value (both are optional)
+			// duck-type on whether this is a boolean
+			if _, ok := child.Children[1].Value.(bool); ok {
+				child.Children[1].Description = "Criticality"
+			} else {
+				child.Children[1].Description = "Control Value"
+				value = child.Children[1]
+			}
+
+		case 3:
+			// criticality and value present
+			controlType = child.Children[0].Value.(string)
+			child.Children[0].Description = "Control Type (" + ControlTypeMap[controlType] + ")"
+			child.Children[1].Description = "Criticality"
+			child.Children[2].Description = "Control Value"
+			value = child.Children[2]
+
+		default:
+			// more than 3 children is invalid
+			continue
+		}
+		if value == nil {
+			continue
+		}
+		switch controlType {
+		case ControlTypePaging:
+			value.Description += " (Paging)"
+			if value.Value != nil {
+				valueChildren := ber.DecodePacket(value.Data.Bytes())
+				value.Data.Truncate(0)
+				value.Value = nil
+				valueChildren.Children[1].Value = valueChildren.Children[1].Data.Bytes()
+				value.AppendChild(valueChildren)
+			}
+			value.Children[0].Description = "Real Search Control Value"
+			value.Children[0].Children[0].Description = "Paging Size"
+			value.Children[0].Children[1].Description = "Cookie"
+
+		case ControlTypeBeheraPasswordPolicy:
+			value.Description += " (Password Policy - Behera Draft)"
+			if value.Value != nil {
+				valueChildren := ber.DecodePacket(value.Data.Bytes())
+				value.Data.Truncate(0)
+				value.Value = nil
+				value.AppendChild(valueChildren)
+			}
+			sequence := value.Children[0]
+			for _, child := range sequence.Children {
+				if child.Tag == 0 {
+					//Warning
+					warningPacket := child.Children[0]
+					packet := ber.DecodePacket(warningPacket.Data.Bytes())
+					val, ok := packet.Value.(int64)
+					if ok {
+						if warningPacket.Tag == 0 {
+							//timeBeforeExpiration
+							value.Description += " (TimeBeforeExpiration)"
+							warningPacket.Value = val
+						} else if warningPacket.Tag == 1 {
+							//graceAuthNsRemaining
+							value.Description += " (GraceAuthNsRemaining)"
+							warningPacket.Value = val
+						}
+					}
+				} else if child.Tag == 1 {
+					// Error
+					packet := ber.DecodePacket(child.Data.Bytes())
+					val, ok := packet.Value.(int8)
+					if !ok {
+						val = -1
+					}
+					child.Description = "Error"
+					child.Value = val
+				}
+			}
+		}
+	}
+}
+
+func addRequestDescriptions(packet *ber.Packet) {
+	packet.Description = "LDAP Request"
+	packet.Children[0].Description = "Message ID"
+	packet.Children[1].Description = ApplicationMap[uint8(packet.Children[1].Tag)]
+	if len(packet.Children) == 3 {
+		addControlDescriptions(packet.Children[2])
+	}
+}
+
+func addDefaultLDAPResponseDescriptions(packet *ber.Packet) {
+	resultCode, _ := getLDAPResultCode(packet)
+	packet.Children[1].Children[0].Description = "Result Code (" + LDAPResultCodeMap[resultCode] + ")"
+	packet.Children[1].Children[1].Description = "Matched DN"
+	packet.Children[1].Children[2].Description = "Error Message"
+	if len(packet.Children[1].Children) > 3 {
+		packet.Children[1].Children[3].Description = "Referral"
+	}
+	if len(packet.Children) == 3 {
+		addControlDescriptions(packet.Children[2])
+	}
+}
+
+// DebugBinaryFile reads and prints packets from the given filename
+func DebugBinaryFile(fileName string) error {
+	file, err := ioutil.ReadFile(fileName)
+	if err != nil {
+		return NewError(ErrorDebugging, err)
+	}
+	ber.PrintBytes(os.Stdout, file, "")
+	packet := ber.DecodePacket(file)
+	addLDAPDescriptions(packet)
+	ber.PrintPacket(packet)
+
+	return nil
+}
+
+var hex = "0123456789abcdef"
+
+func mustEscape(c byte) bool {
+	return c > 0x7f || c == '(' || c == ')' || c == '\\' || c == '*' || c == 0
+}
+
+// EscapeFilter escapes from the provided LDAP filter string the special
+// characters in the set `()*\` and those out of the range 0 < c < 0x80,
+// as defined in RFC4515.
+func EscapeFilter(filter string) string {
+	escape := 0
+	for i := 0; i < len(filter); i++ {
+		if mustEscape(filter[i]) {
+			escape++
+		}
+	}
+	if escape == 0 {
+		return filter
+	}
+	buf := make([]byte, len(filter)+escape*2)
+	for i, j := 0, 0; i < len(filter); i++ {
+		c := filter[i]
+		if mustEscape(c) {
+			buf[j+0] = '\\'
+			buf[j+1] = hex[c>>4]
+			buf[j+2] = hex[c&0xf]
+			j += 3
+		} else {
+			buf[j] = c
+			j++
+		}
+	}
+	return string(buf)
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/ldap_test.go b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/ldap_test.go
new file mode 100644
index 0000000..9f43051
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/ldap_test.go
@@ -0,0 +1,275 @@
+package ldap_test
+
+import (
+	"crypto/tls"
+	"fmt"
+	"testing"
+
+	"gopkg.in/ldap.v2"
+)
+
+var ldapServer = "ldap.itd.umich.edu"
+var ldapPort = uint16(389)
+var ldapTLSPort = uint16(636)
+var baseDN = "dc=umich,dc=edu"
+var filter = []string{
+	"(cn=cis-fac)",
+	"(&(owner=*)(cn=cis-fac))",
+	"(&(objectclass=rfc822mailgroup)(cn=*Computer*))",
+	"(&(objectclass=rfc822mailgroup)(cn=*Mathematics*))"}
+var attributes = []string{
+	"cn",
+	"description"}
+
+func TestDial(t *testing.T) {
+	fmt.Printf("TestDial: starting...\n")
+	l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort))
+	if err != nil {
+		t.Errorf(err.Error())
+		return
+	}
+	defer l.Close()
+	fmt.Printf("TestDial: finished...\n")
+}
+
+func TestDialTLS(t *testing.T) {
+	fmt.Printf("TestDialTLS: starting...\n")
+	l, err := ldap.DialTLS("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapTLSPort), &tls.Config{InsecureSkipVerify: true})
+	if err != nil {
+		t.Errorf(err.Error())
+		return
+	}
+	defer l.Close()
+	fmt.Printf("TestDialTLS: finished...\n")
+}
+
+func TestStartTLS(t *testing.T) {
+	fmt.Printf("TestStartTLS: starting...\n")
+	l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort))
+	if err != nil {
+		t.Errorf(err.Error())
+		return
+	}
+	err = l.StartTLS(&tls.Config{InsecureSkipVerify: true})
+	if err != nil {
+		t.Errorf(err.Error())
+		return
+	}
+	fmt.Printf("TestStartTLS: finished...\n")
+}
+
+func TestSearch(t *testing.T) {
+	fmt.Printf("TestSearch: starting...\n")
+	l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort))
+	if err != nil {
+		t.Errorf(err.Error())
+		return
+	}
+	defer l.Close()
+
+	searchRequest := ldap.NewSearchRequest(
+		baseDN,
+		ldap.ScopeWholeSubtree, ldap.DerefAlways, 0, 0, false,
+		filter[0],
+		attributes,
+		nil)
+
+	sr, err := l.Search(searchRequest)
+	if err != nil {
+		t.Errorf(err.Error())
+		return
+	}
+
+	fmt.Printf("TestSearch: %s -> num of entries = %d\n", searchRequest.Filter, len(sr.Entries))
+}
+
+func TestSearchStartTLS(t *testing.T) {
+	fmt.Printf("TestSearchStartTLS: starting...\n")
+	l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort))
+	if err != nil {
+		t.Errorf(err.Error())
+		return
+	}
+	defer l.Close()
+
+	searchRequest := ldap.NewSearchRequest(
+		baseDN,
+		ldap.ScopeWholeSubtree, ldap.DerefAlways, 0, 0, false,
+		filter[0],
+		attributes,
+		nil)
+
+	sr, err := l.Search(searchRequest)
+	if err != nil {
+		t.Errorf(err.Error())
+		return
+	}
+
+	fmt.Printf("TestSearchStartTLS: %s -> num of entries = %d\n", searchRequest.Filter, len(sr.Entries))
+
+	fmt.Printf("TestSearchStartTLS: upgrading with startTLS\n")
+	err = l.StartTLS(&tls.Config{InsecureSkipVerify: true})
+	if err != nil {
+		t.Errorf(err.Error())
+		return
+	}
+
+	sr, err = l.Search(searchRequest)
+	if err != nil {
+		t.Errorf(err.Error())
+		return
+	}
+
+	fmt.Printf("TestSearchStartTLS: %s -> num of entries = %d\n", searchRequest.Filter, len(sr.Entries))
+}
+
+func TestSearchWithPaging(t *testing.T) {
+	fmt.Printf("TestSearchWithPaging: starting...\n")
+	l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort))
+	if err != nil {
+		t.Errorf(err.Error())
+		return
+	}
+	defer l.Close()
+
+	err = l.Bind("", "")
+	if err != nil {
+		t.Errorf(err.Error())
+		return
+	}
+
+	searchRequest := ldap.NewSearchRequest(
+		baseDN,
+		ldap.ScopeWholeSubtree, ldap.DerefAlways, 0, 0, false,
+		filter[2],
+		attributes,
+		nil)
+	sr, err := l.SearchWithPaging(searchRequest, 5)
+	if err != nil {
+		t.Errorf(err.Error())
+		return
+	}
+
+	fmt.Printf("TestSearchWithPaging: %s -> num of entries = %d\n", searchRequest.Filter, len(sr.Entries))
+
+	searchRequest = ldap.NewSearchRequest(
+		baseDN,
+		ldap.ScopeWholeSubtree, ldap.DerefAlways, 0, 0, false,
+		filter[2],
+		attributes,
+		[]ldap.Control{ldap.NewControlPaging(5)})
+	sr, err = l.SearchWithPaging(searchRequest, 5)
+	if err != nil {
+		t.Errorf(err.Error())
+		return
+	}
+
+	fmt.Printf("TestSearchWithPaging: %s -> num of entries = %d\n", searchRequest.Filter, len(sr.Entries))
+
+	searchRequest = ldap.NewSearchRequest(
+		baseDN,
+		ldap.ScopeWholeSubtree, ldap.DerefAlways, 0, 0, false,
+		filter[2],
+		attributes,
+		[]ldap.Control{ldap.NewControlPaging(500)})
+	sr, err = l.SearchWithPaging(searchRequest, 5)
+	if err == nil {
+		t.Errorf("expected an error when paging size in control in search request doesn't match size given in call, got none")
+		return
+	}
+}
+
+func searchGoroutine(t *testing.T, l *ldap.Conn, results chan *ldap.SearchResult, i int) {
+	searchRequest := ldap.NewSearchRequest(
+		baseDN,
+		ldap.ScopeWholeSubtree, ldap.DerefAlways, 0, 0, false,
+		filter[i],
+		attributes,
+		nil)
+	sr, err := l.Search(searchRequest)
+	if err != nil {
+		t.Errorf(err.Error())
+		results <- nil
+		return
+	}
+	results <- sr
+}
+
+func testMultiGoroutineSearch(t *testing.T, TLS bool, startTLS bool) {
+	fmt.Printf("TestMultiGoroutineSearch: starting...\n")
+	var l *ldap.Conn
+	var err error
+	if TLS {
+		l, err = ldap.DialTLS("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapTLSPort), &tls.Config{InsecureSkipVerify: true})
+		if err != nil {
+			t.Errorf(err.Error())
+			return
+		}
+		defer l.Close()
+	} else {
+		l, err = ldap.Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort))
+		if err != nil {
+			t.Errorf(err.Error())
+			return
+		}
+		if startTLS {
+			fmt.Printf("TestMultiGoroutineSearch: using StartTLS...\n")
+			err := l.StartTLS(&tls.Config{InsecureSkipVerify: true})
+			if err != nil {
+				t.Errorf(err.Error())
+				return
+			}
+
+		}
+	}
+
+	results := make([]chan *ldap.SearchResult, len(filter))
+	for i := range filter {
+		results[i] = make(chan *ldap.SearchResult)
+		go searchGoroutine(t, l, results[i], i)
+	}
+	for i := range filter {
+		sr := <-results[i]
+		if sr == nil {
+			t.Errorf("Did not receive results from goroutine for %q", filter[i])
+		} else {
+			fmt.Printf("TestMultiGoroutineSearch(%d): %s -> num of entries = %d\n", i, filter[i], len(sr.Entries))
+		}
+	}
+}
+
+func TestMultiGoroutineSearch(t *testing.T) {
+	testMultiGoroutineSearch(t, false, false)
+	testMultiGoroutineSearch(t, true, true)
+	testMultiGoroutineSearch(t, false, true)
+}
+
+func TestEscapeFilter(t *testing.T) {
+	if got, want := ldap.EscapeFilter("a\x00b(c)d*e\\f"), `a\00b\28c\29d\2ae\5cf`; got != want {
+		t.Errorf("Got %s, expected %s", want, got)
+	}
+	if got, want := ldap.EscapeFilter("Lučić"), `Lu\c4\8di\c4\87`; got != want {
+		t.Errorf("Got %s, expected %s", want, got)
+	}
+}
+
+func TestCompare(t *testing.T) {
+	fmt.Printf("TestCompare: starting...\n")
+	l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort))
+	if err != nil {
+		t.Fatal(err.Error())
+	}
+	defer l.Close()
+
+	dn := "cn=math mich,ou=User Groups,ou=Groups,dc=umich,dc=edu"
+	attribute := "cn"
+	value := "math mich"
+
+	sr, err := l.Compare(dn, attribute, value)
+	if err != nil {
+		t.Errorf(err.Error())
+		return
+	}
+
+	fmt.Printf("TestCompare: -> %v\n", sr)
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/modify.go b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/modify.go
new file mode 100644
index 0000000..e4ab6ce
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/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
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/passwdmodify.go b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/passwdmodify.go
new file mode 100644
index 0000000..7d8246f
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/passwdmodify.go
@@ -0,0 +1,148 @@
+// This file contains the password modify extended operation as specified in rfc 3062
+//
+// https://tools.ietf.org/html/rfc3062
+//
+
+package ldap
+
+import (
+	"errors"
+	"fmt"
+
+	"gopkg.in/asn1-ber.v1"
+)
+
+const (
+	passwordModifyOID = "1.3.6.1.4.1.4203.1.11.1"
+)
+
+// PasswordModifyRequest implements the Password Modify Extended Operation as defined in https://www.ietf.org/rfc/rfc3062.txt
+type PasswordModifyRequest struct {
+	// UserIdentity is an optional string representation of the user associated with the request.
+	// This string may or may not be an LDAPDN [RFC2253].
+	// If no UserIdentity field is present, the request acts up upon the password of the user currently associated with the LDAP session
+	UserIdentity string
+	// OldPassword, if present, contains the user's current password
+	OldPassword string
+	// NewPassword, if present, contains the desired password for this user
+	NewPassword string
+}
+
+// PasswordModifyResult holds the server response to a PasswordModifyRequest
+type PasswordModifyResult struct {
+	// GeneratedPassword holds a password generated by the server, if present
+	GeneratedPassword string
+}
+
+func (r *PasswordModifyRequest) encode() (*ber.Packet, error) {
+	request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationExtendedRequest, nil, "Password Modify Extended Operation")
+	request.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, passwordModifyOID, "Extended Request Name: Password Modify OID"))
+	extendedRequestValue := ber.Encode(ber.ClassContext, ber.TypePrimitive, 1, nil, "Extended Request Value: Password Modify Request")
+	passwordModifyRequestValue := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Password Modify Request")
+	if r.UserIdentity != "" {
+		passwordModifyRequestValue.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, r.UserIdentity, "User Identity"))
+	}
+	if r.OldPassword != "" {
+		passwordModifyRequestValue.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 1, r.OldPassword, "Old Password"))
+	}
+	if r.NewPassword != "" {
+		passwordModifyRequestValue.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 2, r.NewPassword, "New Password"))
+	}
+
+	extendedRequestValue.AppendChild(passwordModifyRequestValue)
+	request.AppendChild(extendedRequestValue)
+
+	return request, nil
+}
+
+// NewPasswordModifyRequest creates a new PasswordModifyRequest
+//
+// According to the RFC 3602:
+// userIdentity is a string representing the user associated with the request.
+// This string may or may not be an LDAPDN (RFC 2253).
+// If userIdentity is empty then the operation will act on the user associated
+// with the session.
+//
+// oldPassword is the current user's password, it can be empty or it can be
+// needed depending on the session user access rights (usually an administrator
+// can change a user's password without knowing the current one) and the
+// password policy (see pwdSafeModify password policy's attribute)
+//
+// newPassword is the desired user's password. If empty the server can return
+// an error or generate a new password that will be available in the
+// PasswordModifyResult.GeneratedPassword
+//
+func NewPasswordModifyRequest(userIdentity string, oldPassword string, newPassword string) *PasswordModifyRequest {
+	return &PasswordModifyRequest{
+		UserIdentity: userIdentity,
+		OldPassword:  oldPassword,
+		NewPassword:  newPassword,
+	}
+}
+
+// PasswordModify performs the modification request
+func (l *Conn) PasswordModify(passwordModifyRequest *PasswordModifyRequest) (*PasswordModifyResult, error) {
+	packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
+	packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID"))
+
+	encodedPasswordModifyRequest, err := passwordModifyRequest.encode()
+	if err != nil {
+		return nil, err
+	}
+	packet.AppendChild(encodedPasswordModifyRequest)
+
+	l.Debug.PrintPacket(packet)
+
+	msgCtx, err := l.sendMessage(packet)
+	if err != nil {
+		return nil, err
+	}
+	defer l.finishMessage(msgCtx)
+
+	result := &PasswordModifyResult{}
+
+	l.Debug.Printf("%d: waiting for response", msgCtx.id)
+	packetResponse, ok := <-msgCtx.responses
+	if !ok {
+		return nil, NewError(ErrorNetwork, errors.New("ldap: response channel closed"))
+	}
+	packet, err = packetResponse.ReadPacket()
+	l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
+	if err != nil {
+		return nil, err
+	}
+
+	if packet == nil {
+		return nil, NewError(ErrorNetwork, errors.New("ldap: could not retrieve message"))
+	}
+
+	if l.Debug {
+		if err := addLDAPDescriptions(packet); err != nil {
+			return nil, err
+		}
+		ber.PrintPacket(packet)
+	}
+
+	if packet.Children[1].Tag == ApplicationExtendedResponse {
+		resultCode, resultDescription := getLDAPResultCode(packet)
+		if resultCode != 0 {
+			return nil, NewError(resultCode, errors.New(resultDescription))
+		}
+	} else {
+		return nil, NewError(ErrorUnexpectedResponse, fmt.Errorf("Unexpected Response: %d", packet.Children[1].Tag))
+	}
+
+	extendedResponse := packet.Children[1]
+	for _, child := range extendedResponse.Children {
+		if child.Tag == 11 {
+			passwordModifyResponseValue := ber.DecodePacket(child.Data.Bytes())
+			if len(passwordModifyResponseValue.Children) == 1 {
+				if passwordModifyResponseValue.Children[0].Tag == 0 {
+					result.GeneratedPassword = ber.DecodeString(passwordModifyResponseValue.Children[0].Data.Bytes())
+				}
+			}
+		}
+	}
+
+	return result, nil
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/search.go b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/search.go
new file mode 100644
index 0000000..2a99894
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/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
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/search_test.go b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/search_test.go
new file mode 100644
index 0000000..5f77b22
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/gopkg.in/ldap.v2/search_test.go
@@ -0,0 +1,31 @@
+package ldap
+
+import (
+	"reflect"
+	"testing"
+)
+
+// TestNewEntry tests that repeated calls to NewEntry return the same value with the same input
+func TestNewEntry(t *testing.T) {
+	dn := "testDN"
+	attributes := map[string][]string{
+		"alpha":   {"value"},
+		"beta":    {"value"},
+		"gamma":   {"value"},
+		"delta":   {"value"},
+		"epsilon": {"value"},
+	}
+	executedEntry := NewEntry(dn, attributes)
+
+	iteration := 0
+	for {
+		if iteration == 100 {
+			break
+		}
+		testEntry := NewEntry(dn, attributes)
+		if !reflect.DeepEqual(executedEntry, testEntry) {
+			t.Fatalf("subsequent calls to NewEntry did not yield the same result:\n\texpected:\n\t%s\n\tgot:\n\t%s\n", executedEntry, testEntry)
+		}
+		iteration = iteration + 1
+	}
+}

-- 
To stop receiving notification emails like this one, please contact
rob@apache.org.