From bb53e5bb8124918e520b12b0b7721aa5b8716c85 Mon Sep 17 00:00:00 2001 From: Frode Nordahl Date: Mon, 8 Feb 2016 19:09:35 +0100 Subject: [PATCH 1/2] Godeps: add ldap and asn1-ber library --- Godeps/Godeps.json | 10 + .../src/gopkg.in/asn1-ber.v1/.travis.yml | 15 + .../src/gopkg.in/asn1-ber.v1/LICENSE | 27 + .../src/gopkg.in/asn1-ber.v1/README.md | 24 + .../src/gopkg.in/asn1-ber.v1/ber.go | 504 ++++++++++++++++++ .../src/gopkg.in/asn1-ber.v1/content_int.go | 25 + .../src/gopkg.in/asn1-ber.v1/header.go | 29 + .../src/gopkg.in/asn1-ber.v1/identifier.go | 103 ++++ .../src/gopkg.in/asn1-ber.v1/length.go | 71 +++ .../src/gopkg.in/asn1-ber.v1/tests/tc1.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc10.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc11.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc12.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc13.ber | Bin 0 -> 11 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc14.ber | Bin 0 -> 7 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc15.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc16.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc17.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc18.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc19.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc2.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc20.ber | Bin 0 -> 11 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc21.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc22.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc23.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc24.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc25.ber | Bin 0 -> 5 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc26.ber | Bin 0 -> 5 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc27.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc28.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc29.ber | Bin 0 -> 3 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc3.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc30.ber | Bin 0 -> 5 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc31.ber | Bin 0 -> 4 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc32.ber | Bin 0 -> 2 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc33.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc34.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc35.ber | Bin 0 -> 16 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc36.ber | Bin 0 -> 20 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc37.ber | Bin 0 -> 14 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc38.ber | Bin 0 -> 16 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc39.ber | Bin 0 -> 2 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc4.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc40.ber | Bin 0 -> 2 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc41.ber | Bin 0 -> 16 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc42.ber | Bin 0 -> 14 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc43.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc44.ber | Bin 0 -> 2 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc45.ber | Bin 0 -> 2 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc46.ber | Bin 0 -> 11 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc47.ber | Bin 0 -> 16 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc48.ber | Bin 0 -> 16 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc5.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc6.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc7.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc8.ber | Bin 0 -> 5 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc9.ber | 1 + .../src/gopkg.in/asn1-ber.v1/util.go | 24 + .../src/gopkg.in/ldap.v2/.gitignore | 0 .../src/gopkg.in/ldap.v2/.travis.yml | 15 + .../_workspace/src/gopkg.in/ldap.v2/LICENSE | 27 + .../_workspace/src/gopkg.in/ldap.v2/README.md | 55 ++ Godeps/_workspace/src/gopkg.in/ldap.v2/add.go | 104 ++++ .../_workspace/src/gopkg.in/ldap.v2/bind.go | 135 +++++ .../_workspace/src/gopkg.in/ldap.v2/client.go | 23 + .../src/gopkg.in/ldap.v2/compare.go | 85 +++ .../_workspace/src/gopkg.in/ldap.v2/conn.go | 369 +++++++++++++ .../src/gopkg.in/ldap.v2/control.go | 332 ++++++++++++ .../_workspace/src/gopkg.in/ldap.v2/debug.go | 24 + Godeps/_workspace/src/gopkg.in/ldap.v2/del.go | 79 +++ Godeps/_workspace/src/gopkg.in/ldap.v2/dn.go | 155 ++++++ Godeps/_workspace/src/gopkg.in/ldap.v2/doc.go | 4 + .../_workspace/src/gopkg.in/ldap.v2/error.go | 137 +++++ .../_workspace/src/gopkg.in/ldap.v2/filter.go | 456 ++++++++++++++++ .../_workspace/src/gopkg.in/ldap.v2/ldap.go | 286 ++++++++++ .../_workspace/src/gopkg.in/ldap.v2/modify.go | 156 ++++++ .../src/gopkg.in/ldap.v2/passwdmodify.go | 137 +++++ .../_workspace/src/gopkg.in/ldap.v2/search.go | 403 ++++++++++++++ 78 files changed, 3839 insertions(+) create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/.travis.yml create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/LICENSE create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/README.md create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/ber.go create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/content_int.go create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/header.go create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/identifier.go create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/length.go create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc1.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc10.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc11.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc12.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc13.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc14.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc15.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc16.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc17.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc18.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc19.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc2.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc20.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc21.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc22.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc23.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc24.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc25.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc26.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc27.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc28.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc29.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc3.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc30.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc31.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc32.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc33.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc34.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc35.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc36.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc37.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc38.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc39.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc4.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc40.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc41.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc42.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc43.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc44.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc45.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc46.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc47.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc48.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc5.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc6.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc7.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc8.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc9.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/util.go create mode 100644 Godeps/_workspace/src/gopkg.in/ldap.v2/.gitignore create mode 100644 Godeps/_workspace/src/gopkg.in/ldap.v2/.travis.yml create mode 100644 Godeps/_workspace/src/gopkg.in/ldap.v2/LICENSE create mode 100644 Godeps/_workspace/src/gopkg.in/ldap.v2/README.md create mode 100644 Godeps/_workspace/src/gopkg.in/ldap.v2/add.go create mode 100644 Godeps/_workspace/src/gopkg.in/ldap.v2/bind.go create mode 100644 Godeps/_workspace/src/gopkg.in/ldap.v2/client.go create mode 100644 Godeps/_workspace/src/gopkg.in/ldap.v2/compare.go create mode 100644 Godeps/_workspace/src/gopkg.in/ldap.v2/conn.go create mode 100644 Godeps/_workspace/src/gopkg.in/ldap.v2/control.go create mode 100644 Godeps/_workspace/src/gopkg.in/ldap.v2/debug.go create mode 100644 Godeps/_workspace/src/gopkg.in/ldap.v2/del.go create mode 100644 Godeps/_workspace/src/gopkg.in/ldap.v2/dn.go create mode 100644 Godeps/_workspace/src/gopkg.in/ldap.v2/doc.go create mode 100644 Godeps/_workspace/src/gopkg.in/ldap.v2/error.go create mode 100644 Godeps/_workspace/src/gopkg.in/ldap.v2/filter.go create mode 100644 Godeps/_workspace/src/gopkg.in/ldap.v2/ldap.go create mode 100644 Godeps/_workspace/src/gopkg.in/ldap.v2/modify.go create mode 100644 Godeps/_workspace/src/gopkg.in/ldap.v2/passwdmodify.go create mode 100644 Godeps/_workspace/src/gopkg.in/ldap.v2/search.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 6f702bdd..1920be2e 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -135,6 +135,11 @@ "ImportPath": "gopkg.in/alexcesaro/quotedprintable.v3", "Rev": "2caba252f4dc53eaf6b553000885530023f54623" }, + { + "ImportPath": "gopkg.in/asn1-ber.v1", + "Comment": "v1.1", + "Rev": "4e86f4367175e39f69d9358a5f17b4dda270378d" + }, { "ImportPath": "gopkg.in/gomail.v2", "Comment": "2.0.0-2-gb1e5552", @@ -144,6 +149,11 @@ "ImportPath": "gopkg.in/gorp.v1", "Comment": "v1.7.1", "Rev": "c87af80f3cc5036b55b83d77171e156791085e2e" + }, + { + "ImportPath": "gopkg.in/ldap.v2", + "Comment": "v2.2", + "Rev": "e9a325d64989e2844be629682cb085d2c58eef8d" } ] } diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/.travis.yml b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/.travis.yml new file mode 100644 index 00000000..44aa48b8 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/.travis.yml @@ -0,0 +1,15 @@ +language: go +go: + - 1.2 + - 1.3 + - 1.4 + - 1.5 + - tip +go_import_path: gopkg.in/asn-ber.v1 +install: + - go list -f '{{range .Imports}}{{.}} {{end}}' ./... | xargs go get -v + - go list -f '{{range .TestImports}}{{.}} {{end}}' ./... | xargs go get -v + - go get code.google.com/p/go.tools/cmd/cover || go get golang.org/x/tools/cmd/cover + - go build -v ./... +script: + - go test -v -cover ./... diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/LICENSE b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/LICENSE new file mode 100644 index 00000000..74487567 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/README.md b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/README.md new file mode 100644 index 00000000..e3a9560d --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/README.md @@ -0,0 +1,24 @@ +[![GoDoc](https://godoc.org/gopkg.in/asn1-ber.v1?status.svg)](https://godoc.org/gopkg.in/asn1-ber.v1) [![Build Status](https://travis-ci.org/go-asn1-ber/asn1-ber.svg)](https://travis-ci.org/go-asn1-ber/asn1-ber) + + +ASN1 BER Encoding / Decoding Library for the GO programming language. +--------------------------------------------------------------------- + +Required libraries: + None + +Working: + Very basic encoding / decoding needed for LDAP protocol + +Tests Implemented: + A few + +TODO: + Fix all encoding / decoding to conform to ASN1 BER spec + Implement Tests / Benchmarks + +--- + +The Go gopher was designed by Renee French. (http://reneefrench.blogspot.com/) +The design is licensed under the Creative Commons 3.0 Attributions license. +Read this article for more details: http://blog.golang.org/gopher diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/ber.go b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/ber.go new file mode 100644 index 00000000..25cc921b --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/ber.go @@ -0,0 +1,504 @@ +package ber + +import ( + "bytes" + "errors" + "fmt" + "io" + "os" + "reflect" +) + +type Packet struct { + Identifier + Value interface{} + ByteValue []byte + Data *bytes.Buffer + Children []*Packet + Description string +} + +type Identifier struct { + ClassType Class + TagType Type + Tag Tag +} + +type Tag uint64 + +const ( + TagEOC Tag = 0x00 + TagBoolean Tag = 0x01 + TagInteger Tag = 0x02 + TagBitString Tag = 0x03 + TagOctetString Tag = 0x04 + TagNULL Tag = 0x05 + TagObjectIdentifier Tag = 0x06 + TagObjectDescriptor Tag = 0x07 + TagExternal Tag = 0x08 + TagRealFloat Tag = 0x09 + TagEnumerated Tag = 0x0a + TagEmbeddedPDV Tag = 0x0b + TagUTF8String Tag = 0x0c + TagRelativeOID Tag = 0x0d + TagSequence Tag = 0x10 + TagSet Tag = 0x11 + TagNumericString Tag = 0x12 + TagPrintableString Tag = 0x13 + TagT61String Tag = 0x14 + TagVideotexString Tag = 0x15 + TagIA5String Tag = 0x16 + TagUTCTime Tag = 0x17 + TagGeneralizedTime Tag = 0x18 + TagGraphicString Tag = 0x19 + TagVisibleString Tag = 0x1a + TagGeneralString Tag = 0x1b + TagUniversalString Tag = 0x1c + TagCharacterString Tag = 0x1d + TagBMPString Tag = 0x1e + TagBitmask Tag = 0x1f // xxx11111b + + // HighTag indicates the start of a high-tag byte sequence + HighTag Tag = 0x1f // xxx11111b + // HighTagContinueBitmask indicates the high-tag byte sequence should continue + HighTagContinueBitmask Tag = 0x80 // 10000000b + // HighTagValueBitmask obtains the tag value from a high-tag byte sequence byte + HighTagValueBitmask Tag = 0x7f // 01111111b +) + +const ( + // LengthLongFormBitmask is the mask to apply to the length byte to see if a long-form byte sequence is used + LengthLongFormBitmask = 0x80 + // LengthValueBitmask is the mask to apply to the length byte to get the number of bytes in the long-form byte sequence + LengthValueBitmask = 0x7f + + // LengthIndefinite is returned from readLength to indicate an indefinite length + LengthIndefinite = -1 +) + +var tagMap = map[Tag]string{ + TagEOC: "EOC (End-of-Content)", + TagBoolean: "Boolean", + TagInteger: "Integer", + TagBitString: "Bit String", + TagOctetString: "Octet String", + TagNULL: "NULL", + TagObjectIdentifier: "Object Identifier", + TagObjectDescriptor: "Object Descriptor", + TagExternal: "External", + TagRealFloat: "Real (float)", + TagEnumerated: "Enumerated", + TagEmbeddedPDV: "Embedded PDV", + TagUTF8String: "UTF8 String", + TagRelativeOID: "Relative-OID", + TagSequence: "Sequence and Sequence of", + TagSet: "Set and Set OF", + TagNumericString: "Numeric String", + TagPrintableString: "Printable String", + TagT61String: "T61 String", + TagVideotexString: "Videotex String", + TagIA5String: "IA5 String", + TagUTCTime: "UTC Time", + TagGeneralizedTime: "Generalized Time", + TagGraphicString: "Graphic String", + TagVisibleString: "Visible String", + TagGeneralString: "General String", + TagUniversalString: "Universal String", + TagCharacterString: "Character String", + TagBMPString: "BMP String", +} + +type Class uint8 + +const ( + ClassUniversal Class = 0 // 00xxxxxxb + ClassApplication Class = 64 // 01xxxxxxb + ClassContext Class = 128 // 10xxxxxxb + ClassPrivate Class = 192 // 11xxxxxxb + ClassBitmask Class = 192 // 11xxxxxxb +) + +var ClassMap = map[Class]string{ + ClassUniversal: "Universal", + ClassApplication: "Application", + ClassContext: "Context", + ClassPrivate: "Private", +} + +type Type uint8 + +const ( + TypePrimitive Type = 0 // xx0xxxxxb + TypeConstructed Type = 32 // xx1xxxxxb + TypeBitmask Type = 32 // xx1xxxxxb +) + +var TypeMap = map[Type]string{ + TypePrimitive: "Primitive", + TypeConstructed: "Constructed", +} + +var Debug bool = false + +func PrintBytes(out io.Writer, buf []byte, indent string) { + data_lines := make([]string, (len(buf)/30)+1) + num_lines := make([]string, (len(buf)/30)+1) + + for i, b := range buf { + data_lines[i/30] += fmt.Sprintf("%02x ", b) + num_lines[i/30] += fmt.Sprintf("%02d ", (i+1)%100) + } + + for i := 0; i < len(data_lines); i++ { + out.Write([]byte(indent + data_lines[i] + "\n")) + out.Write([]byte(indent + num_lines[i] + "\n\n")) + } +} + +func PrintPacket(p *Packet) { + printPacket(os.Stdout, p, 0, false) +} + +func printPacket(out io.Writer, p *Packet, indent int, printBytes bool) { + indent_str := "" + + for len(indent_str) != indent { + indent_str += " " + } + + class_str := ClassMap[p.ClassType] + + tagtype_str := TypeMap[p.TagType] + + tag_str := fmt.Sprintf("0x%02X", p.Tag) + + if p.ClassType == ClassUniversal { + tag_str = tagMap[p.Tag] + } + + value := fmt.Sprint(p.Value) + description := "" + + if p.Description != "" { + description = p.Description + ": " + } + + fmt.Fprintf(out, "%s%s(%s, %s, %s) Len=%d %q\n", indent_str, description, class_str, tagtype_str, tag_str, p.Data.Len(), value) + + if printBytes { + PrintBytes(out, p.Bytes(), indent_str) + } + + for _, child := range p.Children { + printPacket(out, child, indent+1, printBytes) + } +} + +// ReadPacket reads a single Packet from the reader +func ReadPacket(reader io.Reader) (*Packet, error) { + p, _, err := readPacket(reader) + if err != nil { + return nil, err + } + return p, nil +} + +func DecodeString(data []byte) string { + return string(data) +} + +func parseInt64(bytes []byte) (ret int64, err error) { + if len(bytes) > 8 { + // We'll overflow an int64 in this case. + err = fmt.Errorf("integer too large") + return + } + for bytesRead := 0; bytesRead < len(bytes); bytesRead++ { + ret <<= 8 + ret |= int64(bytes[bytesRead]) + } + + // Shift up and down in order to sign extend the result. + ret <<= 64 - uint8(len(bytes))*8 + ret >>= 64 - uint8(len(bytes))*8 + return +} + +func encodeInteger(i int64) []byte { + n := int64Length(i) + out := make([]byte, n) + + var j int + for ; n > 0; n-- { + out[j] = (byte(i >> uint((n-1)*8))) + j++ + } + + return out +} + +func int64Length(i int64) (numBytes int) { + numBytes = 1 + + for i > 127 { + numBytes++ + i >>= 8 + } + + for i < -128 { + numBytes++ + i >>= 8 + } + + return +} + +// DecodePacket decodes the given bytes into a single Packet +// If a decode error is encountered, nil is returned. +func DecodePacket(data []byte) *Packet { + p, _, _ := readPacket(bytes.NewBuffer(data)) + + return p +} + +// DecodePacketErr decodes the given bytes into a single Packet +// If a decode error is encountered, nil is returned +func DecodePacketErr(data []byte) (*Packet, error) { + p, _, err := readPacket(bytes.NewBuffer(data)) + if err != nil { + return nil, err + } + return p, nil +} + +// readPacket reads a single Packet from the reader, returning the number of bytes read +func readPacket(reader io.Reader) (*Packet, int, error) { + identifier, length, read, err := readHeader(reader) + if err != nil { + return nil, read, err + } + + p := &Packet{ + Identifier: identifier, + } + + p.Data = new(bytes.Buffer) + p.Children = make([]*Packet, 0, 2) + p.Value = nil + + if p.TagType == TypeConstructed { + // TODO: if universal, ensure tag type is allowed to be constructed + + // Track how much content we've read + contentRead := 0 + for { + if length != LengthIndefinite { + // End if we've read what we've been told to + if contentRead == length { + break + } + // Detect if a packet boundary didn't fall on the expected length + if contentRead > length { + return nil, read, fmt.Errorf("expected to read %d bytes, read %d", length, contentRead) + } + } + + // Read the next packet + child, r, err := readPacket(reader) + if err != nil { + return nil, read, err + } + contentRead += r + read += r + + // Test is this is the EOC marker for our packet + if isEOCPacket(child) { + if length == LengthIndefinite { + break + } + return nil, read, errors.New("eoc child not allowed with definite length") + } + + // Append and continue + p.AppendChild(child) + } + return p, read, nil + } + + if length == LengthIndefinite { + return nil, read, errors.New("indefinite length used with primitive type") + } + + // Read definite-length content + content := make([]byte, length, length) + if length > 0 { + _, err := io.ReadFull(reader, content) + if err != nil { + if err == io.EOF { + return nil, read, io.ErrUnexpectedEOF + } + return nil, read, err + } + read += length + } + + if p.ClassType == ClassUniversal { + p.Data.Write(content) + p.ByteValue = content + + switch p.Tag { + case TagEOC: + case TagBoolean: + val, _ := parseInt64(content) + + p.Value = val != 0 + case TagInteger: + p.Value, _ = parseInt64(content) + case TagBitString: + case TagOctetString: + // the actual string encoding is not known here + // (e.g. for LDAP content is already an UTF8-encoded + // string). Return the data without further processing + p.Value = DecodeString(content) + case TagNULL: + case TagObjectIdentifier: + case TagObjectDescriptor: + case TagExternal: + case TagRealFloat: + case TagEnumerated: + p.Value, _ = parseInt64(content) + case TagEmbeddedPDV: + case TagUTF8String: + p.Value = DecodeString(content) + case TagRelativeOID: + case TagSequence: + case TagSet: + case TagNumericString: + case TagPrintableString: + p.Value = DecodeString(content) + case TagT61String: + case TagVideotexString: + case TagIA5String: + case TagUTCTime: + case TagGeneralizedTime: + case TagGraphicString: + case TagVisibleString: + case TagGeneralString: + case TagUniversalString: + case TagCharacterString: + case TagBMPString: + } + } else { + p.Data.Write(content) + } + + return p, read, nil +} + +func (p *Packet) Bytes() []byte { + var out bytes.Buffer + + out.Write(encodeIdentifier(p.Identifier)) + out.Write(encodeLength(p.Data.Len())) + out.Write(p.Data.Bytes()) + + return out.Bytes() +} + +func (p *Packet) AppendChild(child *Packet) { + p.Data.Write(child.Bytes()) + p.Children = append(p.Children, child) +} + +func Encode(ClassType Class, TagType Type, Tag Tag, Value interface{}, Description string) *Packet { + p := new(Packet) + + p.ClassType = ClassType + p.TagType = TagType + p.Tag = Tag + p.Data = new(bytes.Buffer) + + p.Children = make([]*Packet, 0, 2) + + p.Value = Value + p.Description = Description + + if Value != nil { + v := reflect.ValueOf(Value) + + if ClassType == ClassUniversal { + switch Tag { + case TagOctetString: + sv, ok := v.Interface().(string) + + if ok { + p.Data.Write([]byte(sv)) + } + } + } + } + + return p +} + +func NewSequence(Description string) *Packet { + return Encode(ClassUniversal, TypeConstructed, TagSequence, nil, Description) +} + +func NewBoolean(ClassType Class, TagType Type, Tag Tag, Value bool, Description string) *Packet { + intValue := int64(0) + + if Value { + intValue = 1 + } + + p := Encode(ClassType, TagType, Tag, nil, Description) + + p.Value = Value + p.Data.Write(encodeInteger(intValue)) + + return p +} + +func NewInteger(ClassType Class, TagType Type, Tag Tag, Value interface{}, Description string) *Packet { + p := Encode(ClassType, TagType, Tag, nil, Description) + + p.Value = Value + switch v := Value.(type) { + case int: + p.Data.Write(encodeInteger(int64(v))) + case uint: + p.Data.Write(encodeInteger(int64(v))) + case int64: + p.Data.Write(encodeInteger(v)) + case uint64: + // TODO : check range or add encodeUInt... + p.Data.Write(encodeInteger(int64(v))) + case int32: + p.Data.Write(encodeInteger(int64(v))) + case uint32: + p.Data.Write(encodeInteger(int64(v))) + case int16: + p.Data.Write(encodeInteger(int64(v))) + case uint16: + p.Data.Write(encodeInteger(int64(v))) + case int8: + p.Data.Write(encodeInteger(int64(v))) + case uint8: + p.Data.Write(encodeInteger(int64(v))) + default: + // TODO : add support for big.Int ? + panic(fmt.Sprintf("Invalid type %T, expected {u|}int{64|32|16|8}", v)) + } + + return p +} + +func NewString(ClassType Class, TagType Type, Tag Tag, Value, Description string) *Packet { + p := Encode(ClassType, TagType, Tag, nil, Description) + + p.Value = Value + p.Data.Write([]byte(Value)) + + return p +} diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/content_int.go b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/content_int.go new file mode 100644 index 00000000..1858b74b --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/content_int.go @@ -0,0 +1,25 @@ +package ber + +func encodeUnsignedInteger(i uint64) []byte { + n := uint64Length(i) + out := make([]byte, n) + + var j int + for ; n > 0; n-- { + out[j] = (byte(i >> uint((n-1)*8))) + j++ + } + + return out +} + +func uint64Length(i uint64) (numBytes int) { + numBytes = 1 + + for i > 255 { + numBytes++ + i >>= 8 + } + + return +} diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/header.go b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/header.go new file mode 100644 index 00000000..123744e9 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/header.go @@ -0,0 +1,29 @@ +package ber + +import ( + "errors" + "io" +) + +func readHeader(reader io.Reader) (identifier Identifier, length int, read int, err error) { + if i, c, err := readIdentifier(reader); err != nil { + return Identifier{}, 0, read, err + } else { + identifier = i + read += c + } + + if l, c, err := readLength(reader); err != nil { + return Identifier{}, 0, read, err + } else { + length = l + read += c + } + + // Validate length type with identifier (x.600, 8.1.3.2.a) + if length == LengthIndefinite && identifier.TagType == TypePrimitive { + return Identifier{}, 0, read, errors.New("indefinite length used with primitive type") + } + + return identifier, length, read, nil +} diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/identifier.go b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/identifier.go new file mode 100644 index 00000000..f7672a84 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/identifier.go @@ -0,0 +1,103 @@ +package ber + +import ( + "errors" + "fmt" + "io" + "math" +) + +func readIdentifier(reader io.Reader) (Identifier, int, error) { + identifier := Identifier{} + read := 0 + + // identifier byte + b, err := readByte(reader) + if err != nil { + if Debug { + fmt.Printf("error reading identifier byte: %v\n", err) + } + return Identifier{}, read, err + } + read++ + + identifier.ClassType = Class(b) & ClassBitmask + identifier.TagType = Type(b) & TypeBitmask + + if tag := Tag(b) & TagBitmask; tag != HighTag { + // short-form tag + identifier.Tag = tag + return identifier, read, nil + } + + // high-tag-number tag + tagBytes := 0 + for { + b, err := readByte(reader) + if err != nil { + if Debug { + fmt.Printf("error reading high-tag-number tag byte %d: %v\n", tagBytes, err) + } + return Identifier{}, read, err + } + tagBytes++ + read++ + + // Lowest 7 bits get appended to the tag value (x.690, 8.1.2.4.2.b) + identifier.Tag <<= 7 + identifier.Tag |= Tag(b) & HighTagValueBitmask + + // First byte may not be all zeros (x.690, 8.1.2.4.2.c) + if tagBytes == 1 && identifier.Tag == 0 { + return Identifier{}, read, errors.New("invalid first high-tag-number tag byte") + } + // Overflow of int64 + // TODO: support big int tags? + if tagBytes > 9 { + return Identifier{}, read, errors.New("high-tag-number tag overflow") + } + + // Top bit of 0 means this is the last byte in the high-tag-number tag (x.690, 8.1.2.4.2.a) + if Tag(b)&HighTagContinueBitmask == 0 { + break + } + } + + return identifier, read, nil +} + +func encodeIdentifier(identifier Identifier) []byte { + b := []byte{0x0} + b[0] |= byte(identifier.ClassType) + b[0] |= byte(identifier.TagType) + + if identifier.Tag < HighTag { + // Short-form + b[0] |= byte(identifier.Tag) + } else { + // high-tag-number + b[0] |= byte(HighTag) + + tag := identifier.Tag + + highBit := uint(63) + for { + if tag&(1<= 0; i-- { + offset := uint(i) * 7 + mask := Tag(0x7f) << offset + tagByte := (tag & mask) >> offset + if i != 0 { + tagByte |= 0x80 + } + b = append(b, byte(tagByte)) + } + } + return b +} diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/length.go b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/length.go new file mode 100644 index 00000000..8e2ae4dd --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/length.go @@ -0,0 +1,71 @@ +package ber + +import ( + "errors" + "fmt" + "io" +) + +func readLength(reader io.Reader) (length int, read int, err error) { + // length byte + b, err := readByte(reader) + if err != nil { + if Debug { + fmt.Printf("error reading length byte: %v\n", err) + } + return 0, 0, err + } + read++ + + switch { + case b == 0xFF: + // Invalid 0xFF (x.600, 8.1.3.5.c) + return 0, read, errors.New("invalid length byte 0xff") + + case b == LengthLongFormBitmask: + // Indefinite form, we have to decode packets until we encounter an EOC packet (x.600, 8.1.3.6) + length = LengthIndefinite + + case b&LengthLongFormBitmask == 0: + // Short definite form, extract the length from the bottom 7 bits (x.600, 8.1.3.4) + length = int(b) & LengthValueBitmask + + case b&LengthLongFormBitmask != 0: + // Long definite form, extract the number of length bytes to follow from the bottom 7 bits (x.600, 8.1.3.5.b) + lengthBytes := int(b) & LengthValueBitmask + // Protect against overflow + // TODO: support big int length? + if lengthBytes > 8 { + return 0, read, errors.New("long-form length overflow") + } + for i := 0; i < lengthBytes; i++ { + b, err = readByte(reader) + if err != nil { + if Debug { + fmt.Printf("error reading long-form length byte %d: %v\n", i, err) + } + return 0, read, err + } + read++ + + // x.600, 8.1.3.5 + length <<= 8 + length |= int(b) + } + + default: + return 0, read, errors.New("invalid length byte") + } + + return length, read, nil +} + +func encodeLength(length int) []byte { + length_bytes := encodeUnsignedInteger(uint64(length)) + if length > 127 || len(length_bytes) > 1 { + longFormBytes := []byte{(LengthLongFormBitmask | byte(len(length_bytes)))} + longFormBytes = append(longFormBytes, length_bytes...) + length_bytes = longFormBytes + } + return length_bytes +} diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc1.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc1.ber new file mode 100644 index 00000000..5c6ba1c6 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc1.ber @@ -0,0 +1 @@ +@ \ No newline at end of file diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc10.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc10.ber new file mode 100644 index 00000000..f733125d --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc10.ber @@ -0,0 +1 @@ +  \ No newline at end of file diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc11.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc11.ber new file mode 100644 index 00000000..cc4a609c --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc11.ber @@ -0,0 +1 @@ +  015625 \ No newline at end of file diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc12.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc12.ber new file mode 100644 index 00000000..dbb538d6 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc12.ber @@ -0,0 +1 @@ + I \ No newline at end of file diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc13.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc13.ber new file mode 100644 index 0000000000000000000000000000000000000000..f4f438e0d34bb543eff8eda5bcfd193fdcdb161b GIT binary patch literal 11 Scmd;VW?*1%X8HgB|8D>i)ddp( literal 0 HcmV?d00001 diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc14.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc14.ber new file mode 100644 index 0000000000000000000000000000000000000000..b6f2fd3a408a9ac19e2a101b186823137eaffc2b GIT binary patch literal 7 Ocmd;VW?*1%W&r>McL5s! literal 0 HcmV?d00001 diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc15.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc15.ber new file mode 100644 index 00000000..3d6da676 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc15.ber @@ -0,0 +1 @@ +  \ No newline at end of file diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc16.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc16.ber new file mode 100644 index 00000000..68634f5f --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc16.ber @@ -0,0 +1 @@ +  \ No newline at end of file diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc17.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc17.ber new file mode 100644 index 00000000..adb9e332 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc17.ber @@ -0,0 +1 @@ +   \ No newline at end of file diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc18.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc18.ber new file mode 100644 index 00000000..fb6843f7 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc18.ber @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc19.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc19.ber new file mode 100644 index 00000000..03afaa5d --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc19.ber @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc2.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc2.ber new file mode 100644 index 00000000..7e785773 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc2.ber @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc20.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc20.ber new file mode 100644 index 0000000000000000000000000000000000000000..a976464b9cddae507e4872a490b43911133221b9 GIT binary patch literal 11 NcmZSLY+zu7000FS0FwX! literal 0 HcmV?d00001 diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc21.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc21.ber new file mode 100644 index 00000000..d6c2f9aa --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc21.ber @@ -0,0 +1 @@ +Q \ No newline at end of file diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc22.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc22.ber new file mode 100644 index 00000000..d1d70afa --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc22.ber @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc23.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc23.ber new file mode 100644 index 00000000..0e8d18f6 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc23.ber @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc24.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc24.ber new file mode 100644 index 00000000..10565aef --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc24.ber @@ -0,0 +1 @@ +`HO Jc/ \ No newline at end of file diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc25.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc25.ber new file mode 100644 index 0000000000000000000000000000000000000000..1e11405246241124952cd1ed9a06a83d444b78e7 GIT binary patch literal 5 McmZQ%W?*0d000&M1poj5 literal 0 HcmV?d00001 diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc26.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc26.ber new file mode 100644 index 0000000000000000000000000000000000000000..d28653b3b35c4c1ccb1487ae81e1a01c303e387f GIT binary patch literal 5 McmZQ%W?*0h000*N1^@s6 literal 0 HcmV?d00001 diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc27.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc27.ber new file mode 100644 index 00000000..c8c78114 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc27.ber @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc28.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc28.ber new file mode 100644 index 00000000..415fe23e --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc28.ber @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc29.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc29.ber new file mode 100644 index 0000000000000000000000000000000000000000..4076f448719ff73e9ccc8964b54d4dd9cd67fdcc GIT binary patch literal 3 KcmZQ%WB>pF2mk{B literal 0 HcmV?d00001 diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc3.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc3.ber new file mode 100644 index 00000000..c05c900b --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc3.ber @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc30.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc30.ber new file mode 100644 index 0000000000000000000000000000000000000000..72bcf80f448376abee4e31ccd6a7f4b7d41d7472 GIT binary patch literal 5 McmZQ&W?*0d001fg2><{9 literal 0 HcmV?d00001 diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc31.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc31.ber new file mode 100644 index 0000000000000000000000000000000000000000..1fcc4f2549a0f6b0bb0188e81c90a90083988b94 GIT binary patch literal 4 LcmZQ&W?%pS03iSg literal 0 HcmV?d00001 diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc32.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc32.ber new file mode 100644 index 0000000000000000000000000000000000000000..19b3e940af55e37df01b2a2b4598ef47944da756 GIT binary patch literal 2 JcmZQ&0000C00sa6 literal 0 HcmV?d00001 diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc33.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc33.ber new file mode 100644 index 00000000..6ea70c4d --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc33.ber @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc34.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc34.ber new file mode 100644 index 00000000..61337095 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc34.ber @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc35.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc35.ber new file mode 100644 index 0000000000000000000000000000000000000000..d27eb301aaafb83fc9de0473679cbe04016dc0e5 GIT binary patch literal 16 XcmY#xU}0w9vSwjriPx05z`y_i6Dk66 literal 0 HcmV?d00001 diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc36.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc36.ber new file mode 100644 index 0000000000000000000000000000000000000000..e5baaeacdff4665514c48e47adae6295721434e0 GIT binary patch literal 20 bcmY#xP;OvmVqj!uVq{`qU}j?BXJ7yT7*GLi literal 0 HcmV?d00001 diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc37.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc37.ber new file mode 100644 index 0000000000000000000000000000000000000000..d0b1cfbe16013042a5ed3a7d43982b1aeb8ebc74 GIT binary patch literal 14 QcmY%9VP;}rgkTna00S!kQ~&?~ literal 0 HcmV?d00001 diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc38.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc38.ber new file mode 100644 index 0000000000000000000000000000000000000000..090bce74b8020fc2dfa1b73c30927b934be6baa8 GIT binary patch literal 16 XcmY#xU}k3EvSwywiPx05z`y_i6B7b& literal 0 HcmV?d00001 diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc39.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc39.ber new file mode 100644 index 0000000000000000000000000000000000000000..d9d01199b5d4cf65413f777019090a6cab29ff66 GIT binary patch literal 2 JcmY#p0000;03-ka literal 0 HcmV?d00001 diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc4.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc4.ber new file mode 100644 index 00000000..2b888baa --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc4.ber @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc40.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc40.ber new file mode 100644 index 0000000000000000000000000000000000000000..15294a501aa6e73201b85ff460b2fcf0adb11e48 GIT binary patch literal 2 JcmZQ(0000800aO4 literal 0 HcmV?d00001 diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc41.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc41.ber new file mode 100644 index 0000000000000000000000000000000000000000..276836b6543423c0c85770f4b0929bc70aa0a18a GIT binary patch literal 16 XcmY#sU}k3EvSwywiPx05z`y_i6C(m} literal 0 HcmV?d00001 diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc42.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc42.ber new file mode 100644 index 0000000000000000000000000000000000000000..21cbfd10f612289cdafe08654c27e2fe5dee8331 GIT binary patch literal 14 VcmY#sU}0upVP%Qel)1pb000jr0xbXl literal 0 HcmV?d00001 diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc43.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc43.ber new file mode 100644 index 00000000..98dbd741 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc43.ber @@ -0,0 +1 @@ +$ \ No newline at end of file diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc44.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc44.ber new file mode 100644 index 0000000000000000000000000000000000000000..d825e1ad776558a390c09389f5b2ce26cd573be3 GIT binary patch literal 2 JcmZQ!0000A00jU5 literal 0 HcmV?d00001 diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc45.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc45.ber new file mode 100644 index 0000000000000000000000000000000000000000..7b861b02c9b7e4870e84dbd035b541a5f5a5c1d4 GIT binary patch literal 2 JcmY#k0000=03`qb literal 0 HcmV?d00001 diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc46.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc46.ber new file mode 100644 index 0000000000000000000000000000000000000000..e78deee34bb95de961e604524969e51d7c793631 GIT binary patch literal 11 ScmZQ>VBxZk*Oa-yzyJUZBmzMI literal 0 HcmV?d00001 diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc47.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc47.ber new file mode 100644 index 0000000000000000000000000000000000000000..190bb86f669d744054844d3a593ed059fb9eddce GIT binary patch literal 16 UcmY%9V`gGtWMBZ1%uFo&00XrERsaA1 literal 0 HcmV?d00001 diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc48.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc48.ber new file mode 100644 index 0000000000000000000000000000000000000000..f7f111ae605c27470c344365925b3821fc177d3e GIT binary patch literal 16 ScmY#xU}j=qgkXMt1_l5M%K*~= literal 0 HcmV?d00001 diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc5.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc5.ber new file mode 100644 index 00000000..45e0a009 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc5.ber @@ -0,0 +1 @@ +@ \ No newline at end of file diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc6.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc6.ber new file mode 100644 index 00000000..cee1aaf0 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc6.ber @@ -0,0 +1 @@ + +0.E-5 \ No newline at end of file diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc7.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc7.ber new file mode 100644 index 00000000..d5ae6857 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc7.ber @@ -0,0 +1 @@ + -0.E-5 \ No newline at end of file diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc8.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc8.ber new file mode 100644 index 0000000000000000000000000000000000000000..cb32a09cb749b0c39f1d4576a6ad384cf844f900 GIT binary patch literal 5 Mcmd;Nc4S}x00992P5=M^ literal 0 HcmV?d00001 diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc9.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc9.ber new file mode 100644 index 00000000..50b43a51 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc9.ber @@ -0,0 +1 @@ +  \ No newline at end of file diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/util.go b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/util.go new file mode 100644 index 00000000..3e56b66c --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/util.go @@ -0,0 +1,24 @@ +package ber + +import "io" + +func readByte(reader io.Reader) (byte, error) { + bytes := make([]byte, 1, 1) + _, err := io.ReadFull(reader, bytes) + if err != nil { + if err == io.EOF { + return 0, io.ErrUnexpectedEOF + } + return 0, err + } + return bytes[0], nil +} + +func isEOCPacket(p *Packet) bool { + return p != nil && + p.Tag == TagEOC && + p.ClassType == ClassUniversal && + p.TagType == TypePrimitive && + len(p.ByteValue) == 0 && + len(p.Children) == 0 +} diff --git a/Godeps/_workspace/src/gopkg.in/ldap.v2/.gitignore b/Godeps/_workspace/src/gopkg.in/ldap.v2/.gitignore new file mode 100644 index 00000000..e69de29b diff --git a/Godeps/_workspace/src/gopkg.in/ldap.v2/.travis.yml b/Godeps/_workspace/src/gopkg.in/ldap.v2/.travis.yml new file mode 100644 index 00000000..a7a38951 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/ldap.v2/.travis.yml @@ -0,0 +1,15 @@ +language: go +go: + - 1.2 + - 1.3 + - 1.4 + - 1.5 + - 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 build -v ./... +script: + - go test -v -cover ./... diff --git a/Godeps/_workspace/src/gopkg.in/ldap.v2/LICENSE b/Godeps/_workspace/src/gopkg.in/ldap.v2/LICENSE new file mode 100644 index 00000000..74487567 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/ldap.v2/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Godeps/_workspace/src/gopkg.in/ldap.v2/README.md b/Godeps/_workspace/src/gopkg.in/ldap.v2/README.md new file mode 100644 index 00000000..68121c3e --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/ldap.v2/README.md @@ -0,0 +1,55 @@ +[![GoDoc](https://godoc.org/gopkg.in/ldap.v1?status.svg)](https://godoc.org/gopkg.in/ldap.v1) +[![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 + +## Working: + + - Connecting to LDAP server + - Binding to LDAP server + - Searching for entries + - Compiling string filters to LDAP filters + - Paging Search Results + - Modify Requests / Responses + - Add Requests / Responses + - Delete Requests / Responses + - Better Unicode support + +## Examples: + + - search + - modify + +## Tests Implemented: + + - Filter Compile / Decompile + +## TODO: + + - [x] Add Requests / Responses + - [x] Delete Requests / Responses + - [x] Modify DN Requests / Responses + - [ ] Compare Requests / Responses + - [ ] Implement Tests / Benchmarks + + + +--- +The Go gopher was designed by Renee French. (http://reneefrench.blogspot.com/) +The design is licensed under the Creative Commons 3.0 Attributions license. +Read this article for more details: http://blog.golang.org/gopher diff --git a/Godeps/_workspace/src/gopkg.in/ldap.v2/add.go b/Godeps/_workspace/src/gopkg.in/ldap.v2/add.go new file mode 100644 index 00000000..643ce5ff --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/ldap.v2/add.go @@ -0,0 +1,104 @@ +// +// 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" +) + +type Attribute struct { + attrType string + attrVals []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.attrType, "Type")) + set := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSet, nil, "AttributeValue") + for _, value := range a.attrVals { + set.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, value, "Vals")) + } + seq.AppendChild(set) + return seq +} + +type AddRequest struct { + dn string + 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 +} + +func (a *AddRequest) Attribute(attrType string, attrVals []string) { + a.attributes = append(a.attributes, Attribute{attrType: attrType, attrVals: attrVals}) +} + +func NewAddRequest(dn string) *AddRequest { + return &AddRequest{ + dn: dn, + } + +} + +func (l *Conn) Add(addRequest *AddRequest) error { + messageID := l.nextMessageID() + packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request") + packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, messageID, "MessageID")) + packet.AppendChild(addRequest.encode()) + + l.Debug.PrintPacket(packet) + + channel, err := l.sendMessage(packet) + if err != nil { + return err + } + if channel == nil { + return NewError(ErrorNetwork, errors.New("ldap: could not send message")) + } + defer l.finishMessage(messageID) + + l.Debug.Printf("%d: waiting for response", messageID) + packet = <-channel + l.Debug.Printf("%d: got response %p", messageID, packet) + if packet == nil { + return NewError(ErrorNetwork, errors.New("ldap: could not retrieve message")) + } + + 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", messageID) + return nil +} diff --git a/Godeps/_workspace/src/gopkg.in/ldap.v2/bind.go b/Godeps/_workspace/src/gopkg.in/ldap.v2/bind.go new file mode 100644 index 00000000..4ad4b896 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/ldap.v2/bind.go @@ -0,0 +1,135 @@ +// 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" +) + +type SimpleBindRequest struct { + Username string + Password string + Controls []Control +} + +type SimpleBindResult struct { + Controls []Control +} + +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 +} + +func (l *Conn) SimpleBind(simpleBindRequest *SimpleBindRequest) (*SimpleBindResult, error) { + messageID := l.nextMessageID() + + packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request") + packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, messageID, "MessageID")) + encodedBindRequest := simpleBindRequest.encode() + packet.AppendChild(encodedBindRequest) + + if l.Debug { + ber.PrintPacket(packet) + } + + channel, err := l.sendMessage(packet) + if err != nil { + return nil, err + } + if channel == nil { + return nil, NewError(ErrorNetwork, errors.New("ldap: could not send message")) + } + defer l.finishMessage(messageID) + + packet = <-channel + if packet == nil { + return nil, NewError(ErrorNetwork, errors.New("ldap: could not retrieve response")) + } + + 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 +} + +func (l *Conn) Bind(username, password string) error { + messageID := l.nextMessageID() + + packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request") + packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, messageID, "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) + } + + channel, err := l.sendMessage(packet) + if err != nil { + return err + } + if channel == nil { + return NewError(ErrorNetwork, errors.New("ldap: could not send message")) + } + defer l.finishMessage(messageID) + + packet = <-channel + if packet == nil { + return NewError(ErrorNetwork, errors.New("ldap: could not retrieve response")) + } + + 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/Godeps/_workspace/src/gopkg.in/ldap.v2/client.go b/Godeps/_workspace/src/gopkg.in/ldap.v2/client.go new file mode 100644 index 00000000..d3401f9e --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/ldap.v2/client.go @@ -0,0 +1,23 @@ +package ldap + +import "crypto/tls" + +// Client knows how to interact with an LDAP server +type Client interface { + Start() + StartTLS(config *tls.Config) error + Close() + + 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/Godeps/_workspace/src/gopkg.in/ldap.v2/compare.go b/Godeps/_workspace/src/gopkg.in/ldap.v2/compare.go new file mode 100644 index 00000000..802e9cc9 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/ldap.v2/compare.go @@ -0,0 +1,85 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// File contains Compare functionality +// +// https://tools.ietf.org/html/rfc4511 +// +// CompareRequest ::= [APPLICATION 14] SEQUENCE { +// entry LDAPDN, +// ava AttributeValueAssertion } +// +// AttributeValueAssertion ::= SEQUENCE { +// attributeDesc AttributeDescription, +// assertionValue AssertionValue } +// +// AttributeDescription ::= LDAPString +// -- Constrained to +// -- [RFC4512] +// +// AttributeValue ::= OCTET STRING +// + +package ldap + +import ( + "errors" + "fmt" + + "gopkg.in/asn1-ber.v1" +) + +// Compare checks to see if the attribute of the dn matches value. Returns true if it does otherwise +// false with any error that occurs if any. +func (l *Conn) Compare(dn, attribute, value string) (bool, error) { + messageID := l.nextMessageID() + packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request") + packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, messageID, "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) + + channel, err := l.sendMessage(packet) + if err != nil { + return false, err + } + if channel == nil { + return false, NewError(ErrorNetwork, errors.New("ldap: could not send message")) + } + defer l.finishMessage(messageID) + + l.Debug.Printf("%d: waiting for response", messageID) + packet = <-channel + l.Debug.Printf("%d: got response %p", messageID, packet) + if packet == nil { + return false, NewError(ErrorNetwork, errors.New("ldap: could not retrieve message")) + } + + 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/Godeps/_workspace/src/gopkg.in/ldap.v2/conn.go b/Godeps/_workspace/src/gopkg.in/ldap.v2/conn.go new file mode 100644 index 00000000..2f16443f --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/ldap.v2/conn.go @@ -0,0 +1,369 @@ +// 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" + "time" + + "gopkg.in/asn1-ber.v1" +) + +const ( + MessageQuit = 0 + MessageRequest = 1 + MessageResponse = 2 + MessageFinish = 3 +) + +type messagePacket struct { + Op int + MessageID int64 + Packet *ber.Packet + Channel chan *ber.Packet +} + +type sendMessageFlags uint + +const ( + startTLS sendMessageFlags = 1 << iota +) + +// Conn represents an LDAP Connection +type Conn struct { + conn net.Conn + isTLS bool + isClosing bool + isStartingTLS bool + Debug debugging + chanConfirm chan bool + chanResults map[int64]chan *ber.Packet + chanMessage chan *messagePacket + chanMessageID chan int64 + wgSender sync.WaitGroup + wgClose sync.WaitGroup + once sync.Once + outstandingRequests uint + messageMutex sync.Mutex +} + +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 bool), + chanMessageID: make(chan int64), + chanMessage: make(chan *messagePacket, 10), + chanResults: map[int64]chan *ber.Packet{}, + isTLS: isTLS, + } +} + +func (l *Conn) Start() { + go l.reader() + go l.processMessages() + l.wgClose.Add(1) +} + +// Close closes the connection. +func (l *Conn) Close() { + l.once.Do(func() { + l.isClosing = true + l.wgSender.Wait() + + 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.Print(err) + } + + l.wgClose.Done() + }) + l.wgClose.Wait() +} + +// Returns the next available messageID +func (l *Conn) nextMessageID() int64 { + if l.chanMessageID != nil { + 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 { + messageID := l.nextMessageID() + + 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, messageID, "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) + + channel, err := l.sendMessageWithFlags(packet, startTLS) + if err != nil { + return err + } + if channel == nil { + return NewError(ErrorNetwork, errors.New("ldap: could not send message")) + } + + l.Debug.Printf("%d: waiting for response", messageID) + packet = <-channel + l.Debug.Printf("%d: got response %p", messageID, packet) + l.finishMessage(messageID) + + 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) (chan *ber.Packet, error) { + return l.sendMessageWithFlags(packet, 0) +} + +func (l *Conn) sendMessageWithFlags(packet *ber.Packet, flags sendMessageFlags) (chan *ber.Packet, 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")) + } else { + l.isStartingTLS = true + } + } + l.outstandingRequests++ + + l.messageMutex.Unlock() + + out := make(chan *ber.Packet) + message := &messagePacket{ + Op: MessageRequest, + MessageID: packet.Children[0].Value.(int64), + Packet: packet, + Channel: out, + } + l.sendProcessMessage(message) + return out, nil +} + +func (l *Conn) finishMessage(messageID int64) { + if l.isClosing { + return + } + + l.messageMutex.Lock() + l.outstandingRequests-- + if l.isStartingTLS { + l.isStartingTLS = false + } + l.messageMutex.Unlock() + + message := &messagePacket{ + Op: MessageFinish, + MessageID: messageID, + } + l.sendProcessMessage(message) +} + +func (l *Conn) sendProcessMessage(message *messagePacket) bool { + if l.isClosing { + return false + } + l.wgSender.Add(1) + l.chanMessage <- message + l.wgSender.Done() + return true +} + +func (l *Conn) processMessages() { + defer func() { + if err := recover(); err != nil { + log.Printf("ldap: recovered panic in processMessages: %v", err) + } + for messageID, channel := range l.chanResults { + l.Debug.Printf("Closing channel for MessageID %d", messageID) + close(channel) + delete(l.chanResults, messageID) + } + close(l.chanMessageID) + l.chanConfirm <- true + close(l.chanConfirm) + }() + + var messageID int64 = 1 + for { + select { + case l.chanMessageID <- messageID: + messageID++ + case messagePacket, ok := <-l.chanMessage: + if !ok { + l.Debug.Printf("Shutting down - message channel is closed") + return + } + switch messagePacket.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", messagePacket.MessageID) + l.chanResults[messagePacket.MessageID] = messagePacket.Channel + // go routine + buf := messagePacket.Packet.Bytes() + + _, err := l.conn.Write(buf) + if err != nil { + l.Debug.Printf("Error Sending Message: %s", err.Error()) + break + } + case MessageResponse: + l.Debug.Printf("Receiving message %d", messagePacket.MessageID) + if chanResult, ok := l.chanResults[messagePacket.MessageID]; ok { + chanResult <- messagePacket.Packet + } else { + log.Printf("Received unexpected message %d", messagePacket.MessageID) + ber.PrintPacket(messagePacket.Packet) + } + case MessageFinish: + // Remove from message list + l.Debug.Printf("Finished message %d", messagePacket.MessageID) + close(l.chanResults[messagePacket.MessageID]) + delete(l.chanResults, messagePacket.MessageID) + } + } + } +} + +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.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/Godeps/_workspace/src/gopkg.in/ldap.v2/control.go b/Godeps/_workspace/src/gopkg.in/ldap.v2/control.go new file mode 100644 index 00000000..4d829809 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/ldap.v2/control.go @@ -0,0 +1,332 @@ +// 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 = "1.2.840.113556.1.4.319" + ControlTypeBeheraPasswordPolicy = "1.3.6.1.4.1.42.2.27.8.5.1" + ControlTypeVChuPasswordMustChange = "2.16.840.1.113730.3.4.4" + ControlTypeVChuPasswordWarning = "2.16.840.1.113730.3.4.5" + ControlTypeManageDsaIT = "2.16.840.1.113730.3.4.2" +) + +var ControlTypeMap = map[string]string{ + ControlTypePaging: "Paging", + ControlTypeBeheraPasswordPolicy: "Password Policy - Behera Draft", + ControlTypeManageDsaIT: "Manage DSA IT", +} + +type Control interface { + GetControlType() string + Encode() *ber.Packet + String() string +} + +type ControlString struct { + ControlType string + Criticality bool + ControlValue string +} + +func (c *ControlString) GetControlType() string { + return c.ControlType +} + +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 +} + +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) +} + +type ControlPaging struct { + PagingSize uint32 + Cookie []byte +} + +func (c *ControlPaging) GetControlType() string { + return ControlTypePaging +} + +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, uint64(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 +} + +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) +} + +func (c *ControlPaging) SetCookie(cookie []byte) { + c.Cookie = cookie +} + +type ControlBeheraPasswordPolicy struct { + Expire int64 + Grace int64 + Error int8 + ErrorString string +} + +func (c *ControlBeheraPasswordPolicy) GetControlType() string { + return ControlTypeBeheraPasswordPolicy +} + +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 +} + +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) +} + +type ControlVChuPasswordMustChange struct { + MustChange bool +} + +func (c *ControlVChuPasswordMustChange) GetControlType() string { + return ControlTypeVChuPasswordMustChange +} + +func (c *ControlVChuPasswordMustChange) Encode() *ber.Packet { + return nil +} + +func (c *ControlVChuPasswordMustChange) String() string { + return fmt.Sprintf( + "Control Type: %s (%q) Criticality: %t MustChange: %b", + ControlTypeMap[ControlTypeVChuPasswordMustChange], + ControlTypeVChuPasswordMustChange, + false, + c.MustChange) +} + +type ControlVChuPasswordWarning struct { + Expire int64 +} + +func (c *ControlVChuPasswordWarning) GetControlType() string { + return ControlTypeVChuPasswordWarning +} + +func (c *ControlVChuPasswordWarning) Encode() *ber.Packet { + return nil +} + +func (c *ControlVChuPasswordWarning) String() string { + return fmt.Sprintf( + "Control Type: %s (%q) Criticality: %t Expire: %b", + ControlTypeMap[ControlTypeVChuPasswordWarning], + ControlTypeVChuPasswordWarning, + false, + c.Expire) +} + +type ControlManageDsaIT struct { + Criticality bool +} + +func (c *ControlManageDsaIT) GetControlType() string { + return ControlTypeManageDsaIT +} + +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 +} + +func (c *ControlManageDsaIT) String() string { + return fmt.Sprintf( + "Control Type: %s (%q) Criticality: %t", + ControlTypeMap[ControlTypeManageDsaIT], + ControlTypeManageDsaIT, + c.Criticality) +} + +func NewControlManageDsaIT(Criticality bool) *ControlManageDsaIT { + return &ControlManageDsaIT{Criticality: Criticality} +} + +func FindControl(controls []Control, controlType string) Control { + for _, c := range controls { + if c.GetControlType() == controlType { + return c + } + } + return nil +} + +func DecodeControl(packet *ber.Packet) Control { + ControlType := packet.Children[0].Value.(string) + Criticality := false + + packet.Children[0].Description = "Control Type (" + ControlTypeMap[ControlType] + ")" + value := packet.Children[1] + if len(packet.Children) == 3 { + value = packet.Children[2] + packet.Children[1].Description = "Criticality" + Criticality = packet.Children[1].Value.(bool) + } + + value.Description = "Control Value" + switch ControlType { + 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 + child := child.Children[0] + packet := ber.DecodePacket(child.Data.Bytes()) + val, ok := packet.Value.(int64) + if ok { + if child.Tag == 0 { + //timeBeforeExpiration + c.Expire = val + child.Value = c.Expire + } else if child.Tag == 1 { + //graceAuthNsRemaining + c.Grace = val + child.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 + } + c := new(ControlString) + c.ControlType = ControlType + c.Criticality = Criticality + c.ControlValue = value.Value.(string) + return c +} + +func NewControlString(controlType string, criticality bool, controlValue string) *ControlString { + return &ControlString{ + ControlType: controlType, + Criticality: criticality, + ControlValue: controlValue, + } +} + +func NewControlPaging(pagingSize uint32) *ControlPaging { + return &ControlPaging{PagingSize: pagingSize} +} + +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/Godeps/_workspace/src/gopkg.in/ldap.v2/debug.go b/Godeps/_workspace/src/gopkg.in/ldap.v2/debug.go new file mode 100644 index 00000000..b8a7ecbf --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/ldap.v2/debug.go @@ -0,0 +1,24 @@ +package ldap + +import ( + "log" + + "gopkg.in/asn1-ber.v1" +) + +// debbuging 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/Godeps/_workspace/src/gopkg.in/ldap.v2/del.go b/Godeps/_workspace/src/gopkg.in/ldap.v2/del.go new file mode 100644 index 00000000..2f0eae1c --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/ldap.v2/del.go @@ -0,0 +1,79 @@ +// +// https://tools.ietf.org/html/rfc4511 +// +// DelRequest ::= [APPLICATION 10] LDAPDN + +package ldap + +import ( + "errors" + "log" + + "gopkg.in/asn1-ber.v1" +) + +type DelRequest struct { + DN string + 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 +} + +func NewDelRequest(DN string, + Controls []Control) *DelRequest { + return &DelRequest{ + DN: DN, + Controls: Controls, + } +} + +func (l *Conn) Del(delRequest *DelRequest) error { + messageID := l.nextMessageID() + packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request") + packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, messageID, "MessageID")) + packet.AppendChild(delRequest.encode()) + if delRequest.Controls != nil { + packet.AppendChild(encodeControls(delRequest.Controls)) + } + + l.Debug.PrintPacket(packet) + + channel, err := l.sendMessage(packet) + if err != nil { + return err + } + if channel == nil { + return NewError(ErrorNetwork, errors.New("ldap: could not send message")) + } + defer l.finishMessage(messageID) + + l.Debug.Printf("%d: waiting for response", messageID) + packet = <-channel + l.Debug.Printf("%d: got response %p", messageID, packet) + if packet == nil { + return NewError(ErrorNetwork, errors.New("ldap: could not retrieve message")) + } + + 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", messageID) + return nil +} diff --git a/Godeps/_workspace/src/gopkg.in/ldap.v2/dn.go b/Godeps/_workspace/src/gopkg.in/ldap.v2/dn.go new file mode 100644 index 00000000..5d83c5e9 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/ldap.v2/dn.go @@ -0,0 +1,155 @@ +// 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 functionallity +// +// https://tools.ietf.org/html/rfc4514 +// +// distinguishedName = [ relativeDistinguishedName +// *( COMMA relativeDistinguishedName ) ] +// relativeDistinguishedName = attributeTypeAndValue +// *( PLUS attributeTypeAndValue ) +// attributeTypeAndValue = attributeType EQUALS attributeValue +// attributeType = descr / numericoid +// attributeValue = string / hexstring +// +// ; The following characters are to be escaped when they appear +// ; in the value to be encoded: ESC, one of , leading +// ; SHARP or SPACE, trailing SPACE, and NULL. +// string = [ ( leadchar / pair ) [ *( stringchar / pair ) +// ( trailchar / pair ) ] ] +// +// leadchar = LUTF1 / UTFMB +// LUTF1 = %x01-1F / %x21 / %x24-2A / %x2D-3A / +// %x3D / %x3F-5B / %x5D-7F +// +// trailchar = TUTF1 / UTFMB +// TUTF1 = %x01-1F / %x21 / %x23-2A / %x2D-3A / +// %x3D / %x3F-5B / %x5D-7F +// +// stringchar = SUTF1 / UTFMB +// SUTF1 = %x01-21 / %x23-2A / %x2D-3A / +// %x3D / %x3F-5B / %x5D-7F +// +// pair = ESC ( ESC / special / hexpair ) +// special = escaped / SPACE / SHARP / EQUALS +// escaped = DQUOTE / PLUS / COMMA / SEMI / LANGLE / RANGLE +// hexstring = SHARP 1*hexpair +// hexpair = HEX HEX +// +// where the productions , , , , +// , , , , , , , , +// , , and are defined in [RFC4512]. +// + +package ldap + +import ( + "bytes" + enchex "encoding/hex" + "errors" + "fmt" + "strings" + + ber "gopkg.in/asn1-ber.v1" +) + +type AttributeTypeAndValue struct { + Type string + Value string +} + +type RelativeDN struct { + Attributes []*AttributeTypeAndValue +} + +type DN struct { + RDNs []*RelativeDN +} + +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 + + for i := 0; i < len(str); i++ { + char := str[i] + if escaping { + 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, errors.New( + fmt.Sprintf("Failed to decode escaped character: %s", err)) + } else if n != 1 { + return nil, errors.New( + fmt.Sprintf("Expected 1 byte when un-escaping, got %d", n)) + } + buffer.WriteByte(dst[0]) + i++ + } else if char == '\\' { + escaping = true + } else if char == '=' { + attribute.Type = buffer.String() + buffer.Reset() + // 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:] + } + raw_ber, err := enchex.DecodeString(data) + if err != nil { + return nil, errors.New( + fmt.Sprintf("Failed to decode BER encoding: %s", err)) + } + packet := ber.DecodePacket(raw_ber) + buffer.WriteString(packet.Data.String()) + i += len(data) - 1 + } + } else if char == ',' || char == '+' { + // We're done with this RDN or value, push it + attribute.Value = buffer.String() + 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) + } + buffer.Reset() + } else { + 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 = buffer.String() + rdn.Attributes = append(rdn.Attributes, attribute) + dn.RDNs = append(dn.RDNs, rdn) + } + return dn, nil +} diff --git a/Godeps/_workspace/src/gopkg.in/ldap.v2/doc.go b/Godeps/_workspace/src/gopkg.in/ldap.v2/doc.go new file mode 100644 index 00000000..f20d39bc --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/ldap.v2/doc.go @@ -0,0 +1,4 @@ +/* +Package ldap provides basic LDAP v3 functionality. +*/ +package ldap diff --git a/Godeps/_workspace/src/gopkg.in/ldap.v2/error.go b/Godeps/_workspace/src/gopkg.in/ldap.v2/error.go new file mode 100644 index 00000000..2dbc30ac --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/ldap.v2/error.go @@ -0,0 +1,137 @@ +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 +) + +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", +} + +func getLDAPResultCode(packet *ber.Packet) (code uint8, description string) { + if len(packet.Children) >= 2 { + response := packet.Children[1] + 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" +} + +type Error struct { + Err error + ResultCode uint8 +} + +func (e *Error) Error() string { + return fmt.Sprintf("LDAP Result Code %d %q: %s", e.ResultCode, LDAPResultCodeMap[e.ResultCode], e.Err.Error()) +} + +func NewError(resultCode uint8, err error) error { + return &Error{ResultCode: resultCode, Err: err} +} + +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/Godeps/_workspace/src/gopkg.in/ldap.v2/filter.go b/Godeps/_workspace/src/gopkg.in/ldap.v2/filter.go new file mode 100644 index 00000000..63bcec1e --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/ldap.v2/filter.go @@ -0,0 +1,456 @@ +// 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" +) + +const ( + FilterAnd = 0 + FilterOr = 1 + FilterNot = 2 + FilterEqualityMatch = 3 + FilterSubstrings = 4 + FilterGreaterOrEqual = 5 + FilterLessOrEqual = 6 + FilterPresent = 7 + FilterApproxMatch = 8 + FilterExtensibleMatch = 9 +) + +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", +} + +const ( + FilterSubstringsInitial = 0 + FilterSubstringsAny = 1 + FilterSubstringsFinal = 2 +) + +var FilterSubstringsMap = map[uint64]string{ + FilterSubstringsInitial: "Substrings Initial", + FilterSubstringsAny: "Substrings Any", + FilterSubstringsFinal: "Substrings Final", +} + +const ( + MatchingRuleAssertionMatchingRule = 1 + MatchingRuleAssertionType = 2 + MatchingRuleAssertionMatchValue = 3 + MatchingRuleAssertionDNAttributes = 4 +) + +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", +} + +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 + } + if pos != len(filter) { + return nil, NewError(ErrorFilterCompile, errors.New("ldap: finished compiling filter with extra at end: "+fmt.Sprint(filter[pos:]))) + } + return packet, nil +} + +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: + READING_ATTR := 0 + READING_EXTENSIBLE_MATCHING_RULE := 1 + READING_CONDITION := 2 + + state := READING_ATTR + + 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 READING_ATTR: + 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 = READING_CONDITION + 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 = READING_EXTENSIBLE_MATCHING_RULE + newPos += 4 + + // Extensible rule, with attr only + case currentRune == ':' && strings.HasPrefix(remainingFilter, ":="): + packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterExtensibleMatch, nil, FilterMap[FilterExtensibleMatch]) + state = READING_CONDITION + newPos += 2 + + // Extensible rule, with no DN attribute matching + case currentRune == ':': + packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterExtensibleMatch, nil, FilterMap[FilterExtensibleMatch]) + state = READING_EXTENSIBLE_MATCHING_RULE + newPos += 1 + + // Equality condition + case currentRune == '=': + packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterEqualityMatch, nil, FilterMap[FilterEqualityMatch]) + state = READING_CONDITION + newPos += 1 + + // Greater-than or equal + case currentRune == '>' && strings.HasPrefix(remainingFilter, ">="): + packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterGreaterOrEqual, nil, FilterMap[FilterGreaterOrEqual]) + state = READING_CONDITION + newPos += 2 + + // Less-than or equal + case currentRune == '<' && strings.HasPrefix(remainingFilter, "<="): + packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterLessOrEqual, nil, FilterMap[FilterLessOrEqual]) + state = READING_CONDITION + newPos += 2 + + // Approx + case currentRune == '~' && strings.HasPrefix(remainingFilter, "~="): + packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterApproxMatch, nil, FilterMap[FilterApproxMatch]) + state = READING_CONDITION + newPos += 2 + + // Still reading the attribute name + default: + attribute += fmt.Sprintf("%c", currentRune) + newPos += currentWidth + } + + case READING_EXTENSIBLE_MATCHING_RULE: + switch { + + // Matching rule OID is done + case currentRune == ':' && strings.HasPrefix(remainingFilter, ":="): + state = READING_CONDITION + newPos += 2 + + // Still reading the matching rule oid + default: + extensibleMatchingRule += fmt.Sprintf("%c", currentRune) + newPos += currentWidth + } + + case READING_CONDITION: + // 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, err := escapedStringToEncodedBytes(condition) + if err != nil { + return packet, newPos, err + } + 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, err := escapedStringToEncodedBytes(part) + if err != nil { + return packet, newPos, err + } + seq.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, tag, encodedString, FilterSubstringsMap[uint64(tag)])) + } + packet.AppendChild(seq) + default: + encodedString, err := escapedStringToEncodedBytes(condition) + if err != nil { + return packet, newPos, err + } + 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")) + } + if escByte, decodeErr := hexpac.DecodeString(escapedString[i+1 : i+3]); decodeErr != nil { + return "", NewError(ErrorFilterCompile, errors.New("ldap: invalid characters for escape in filter")) + } else { + 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/Godeps/_workspace/src/gopkg.in/ldap.v2/ldap.go b/Godeps/_workspace/src/gopkg.in/ldap.v2/ldap.go new file mode 100644 index 00000000..1620aaea --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/ldap.v2/ldap.go @@ -0,0 +1,286 @@ +// 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" + + ber "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 +) + +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 +) + +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 { + child.Description = "Control" + child.Children[0].Description = "Control Type (" + ControlTypeMap[child.Children[0].Value.(string)] + ")" + value := child.Children[1] + if len(child.Children) == 3 { + child.Children[1].Description = "Criticality" + value = child.Children[2] + } + value.Description = "Control Value" + + switch child.Children[0].Value.(string) { + 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 + child := child.Children[0] + packet := ber.DecodePacket(child.Data.Bytes()) + val, ok := packet.Value.(int64) + if ok { + if child.Tag == 0 { + //timeBeforeExpiration + value.Description += " (TimeBeforeExpiration)" + child.Value = val + } else if child.Tag == 1 { + //graceAuthNsRemaining + value.Description += " (GraceAuthNsRemaining)" + child.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]) + } +} + +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/Godeps/_workspace/src/gopkg.in/ldap.v2/modify.go b/Godeps/_workspace/src/gopkg.in/ldap.v2/modify.go new file mode 100644 index 00000000..4372a19d --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/ldap.v2/modify.go @@ -0,0 +1,156 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// File contains Modify functionality +// +// https://tools.ietf.org/html/rfc4511 +// +// ModifyRequest ::= [APPLICATION 6] SEQUENCE { +// object LDAPDN, +// changes SEQUENCE OF change SEQUENCE { +// operation ENUMERATED { +// add (0), +// delete (1), +// replace (2), +// ... }, +// modification PartialAttribute } } +// +// PartialAttribute ::= SEQUENCE { +// type AttributeDescription, +// vals SET OF value AttributeValue } +// +// AttributeDescription ::= LDAPString +// -- Constrained to +// -- [RFC4512] +// +// AttributeValue ::= OCTET STRING +// + +package ldap + +import ( + "errors" + "log" + + "gopkg.in/asn1-ber.v1" +) + +const ( + AddAttribute = 0 + DeleteAttribute = 1 + ReplaceAttribute = 2 +) + +type PartialAttribute struct { + attrType string + attrVals []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.attrType, "Type")) + set := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSet, nil, "AttributeValue") + for _, value := range p.attrVals { + set.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, value, "Vals")) + } + seq.AppendChild(set) + return seq +} + +type ModifyRequest struct { + dn string + addAttributes []PartialAttribute + deleteAttributes []PartialAttribute + replaceAttributes []PartialAttribute +} + +func (m *ModifyRequest) Add(attrType string, attrVals []string) { + m.addAttributes = append(m.addAttributes, PartialAttribute{attrType: attrType, attrVals: attrVals}) +} + +func (m *ModifyRequest) Delete(attrType string, attrVals []string) { + m.deleteAttributes = append(m.deleteAttributes, PartialAttribute{attrType: attrType, attrVals: attrVals}) +} + +func (m *ModifyRequest) Replace(attrType string, attrVals []string) { + m.replaceAttributes = append(m.replaceAttributes, PartialAttribute{attrType: attrType, attrVals: 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 +} + +func NewModifyRequest( + dn string, +) *ModifyRequest { + return &ModifyRequest{ + dn: dn, + } +} + +func (l *Conn) Modify(modifyRequest *ModifyRequest) error { + messageID := l.nextMessageID() + packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request") + packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, messageID, "MessageID")) + packet.AppendChild(modifyRequest.encode()) + + l.Debug.PrintPacket(packet) + + channel, err := l.sendMessage(packet) + if err != nil { + return err + } + if channel == nil { + return NewError(ErrorNetwork, errors.New("ldap: could not send message")) + } + defer l.finishMessage(messageID) + + l.Debug.Printf("%d: waiting for response", messageID) + packet = <-channel + l.Debug.Printf("%d: got response %p", messageID, packet) + if packet == nil { + return NewError(ErrorNetwork, errors.New("ldap: could not retrieve message")) + } + + 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", messageID) + return nil +} diff --git a/Godeps/_workspace/src/gopkg.in/ldap.v2/passwdmodify.go b/Godeps/_workspace/src/gopkg.in/ldap.v2/passwdmodify.go new file mode 100644 index 00000000..508b11ed --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/ldap.v2/passwdmodify.go @@ -0,0 +1,137 @@ +// 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" +) + +type PasswordModifyRequest struct { + UserIdentity string + OldPassword string + NewPassword string +} + +type PasswordModifyResult struct { + 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 +} + +// Create 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, + } +} + +func (l *Conn) PasswordModify(passwordModifyRequest *PasswordModifyRequest) (*PasswordModifyResult, error) { + messageID := l.nextMessageID() + + packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request") + packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, messageID, "MessageID")) + + encodedPasswordModifyRequest, err := passwordModifyRequest.encode() + if err != nil { + return nil, err + } + packet.AppendChild(encodedPasswordModifyRequest) + + l.Debug.PrintPacket(packet) + + channel, err := l.sendMessage(packet) + if err != nil { + return nil, err + } + if channel == nil { + return nil, NewError(ErrorNetwork, errors.New("ldap: could not send message")) + } + defer l.finishMessage(messageID) + + result := &PasswordModifyResult{} + + l.Debug.Printf("%d: waiting for response", messageID) + packet = <-channel + l.Debug.Printf("%d: got response %p", messageID, packet) + + 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 { + passwordModifyReponseValue := ber.DecodePacket(child.Data.Bytes()) + if len(passwordModifyReponseValue.Children) == 1 { + if passwordModifyReponseValue.Children[0].Tag == 0 { + result.GeneratedPassword = ber.DecodeString(passwordModifyReponseValue.Children[0].Data.Bytes()) + } + } + } + } + + return result, nil +} diff --git a/Godeps/_workspace/src/gopkg.in/ldap.v2/search.go b/Godeps/_workspace/src/gopkg.in/ldap.v2/search.go new file mode 100644 index 00000000..f63c9fb0 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/ldap.v2/search.go @@ -0,0 +1,403 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// File contains Search functionality +// +// https://tools.ietf.org/html/rfc4511 +// +// SearchRequest ::= [APPLICATION 3] SEQUENCE { +// baseObject LDAPDN, +// scope ENUMERATED { +// baseObject (0), +// singleLevel (1), +// wholeSubtree (2), +// ... }, +// derefAliases ENUMERATED { +// neverDerefAliases (0), +// derefInSearching (1), +// derefFindingBaseObj (2), +// derefAlways (3) }, +// sizeLimit INTEGER (0 .. maxInt), +// timeLimit INTEGER (0 .. maxInt), +// typesOnly BOOLEAN, +// filter Filter, +// attributes AttributeSelection } +// +// AttributeSelection ::= SEQUENCE OF selector LDAPString +// -- The LDAPString is constrained to +// -- in Section 4.5.1.8 +// +// Filter ::= CHOICE { +// and [0] SET SIZE (1..MAX) OF filter Filter, +// or [1] SET SIZE (1..MAX) OF filter Filter, +// not [2] Filter, +// equalityMatch [3] AttributeValueAssertion, +// substrings [4] SubstringFilter, +// greaterOrEqual [5] AttributeValueAssertion, +// lessOrEqual [6] AttributeValueAssertion, +// present [7] AttributeDescription, +// approxMatch [8] AttributeValueAssertion, +// extensibleMatch [9] MatchingRuleAssertion, +// ... } +// +// SubstringFilter ::= SEQUENCE { +// type AttributeDescription, +// substrings SEQUENCE SIZE (1..MAX) OF substring CHOICE { +// initial [0] AssertionValue, -- can occur at most once +// any [1] AssertionValue, +// final [2] AssertionValue } -- can occur at most once +// } +// +// MatchingRuleAssertion ::= SEQUENCE { +// matchingRule [1] MatchingRuleId OPTIONAL, +// type [2] AttributeDescription OPTIONAL, +// matchValue [3] AssertionValue, +// dnAttributes [4] BOOLEAN DEFAULT FALSE } +// +// + +package ldap + +import ( + "errors" + "fmt" + "sort" + "strings" + + "gopkg.in/asn1-ber.v1" +) + +const ( + ScopeBaseObject = 0 + ScopeSingleLevel = 1 + ScopeWholeSubtree = 2 +) + +var ScopeMap = map[int]string{ + ScopeBaseObject: "Base Object", + ScopeSingleLevel: "Single Level", + ScopeWholeSubtree: "Whole Subtree", +} + +const ( + NeverDerefAliases = 0 + DerefInSearching = 1 + DerefFindingBaseObj = 2 + DerefAlways = 3 +) + +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, + } +} + +type Entry struct { + DN string + Attributes []*EntryAttribute +} + +func (e *Entry) GetAttributeValues(attribute string) []string { + for _, attr := range e.Attributes { + if attr.Name == attribute { + return attr.Values + } + } + return []string{} +} + +func (e *Entry) GetRawAttributeValues(attribute string) [][]byte { + for _, attr := range e.Attributes { + if attr.Name == attribute { + return attr.ByteValues + } + } + return [][]byte{} +} + +func (e *Entry) GetAttributeValue(attribute string) string { + values := e.GetAttributeValues(attribute) + if len(values) == 0 { + return "" + } + return values[0] +} + +func (e *Entry) GetRawAttributeValue(attribute string) []byte { + values := e.GetRawAttributeValues(attribute) + if len(values) == 0 { + return []byte{} + } + return values[0] +} + +func (e *Entry) Print() { + fmt.Printf("DN: %s\n", e.DN) + for _, attr := range e.Attributes { + attr.Print() + } +} + +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, + } +} + +type EntryAttribute struct { + Name string + Values []string + ByteValues [][]byte +} + +func (e *EntryAttribute) Print() { + fmt.Printf("%s: %s\n", e.Name, e.Values) +} + +func (e *EntryAttribute) PrettyPrint(indent int) { + fmt.Printf("%s%s: %s\n", strings.Repeat(" ", indent), e.Name, e.Values) +} + +type SearchResult struct { + Entries []*Entry + Referrals []string + Controls []Control +} + +func (s *SearchResult) Print() { + for _, entry := range s.Entries { + entry.Print() + } +} + +func (s *SearchResult) PrettyPrint(indent int) { + for _, entry := range s.Entries { + entry.PrettyPrint(indent) + } +} + +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 +} + +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, + } +} + +func (l *Conn) SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32) (*SearchResult, error) { + if searchRequest.Controls == nil { + searchRequest.Controls = make([]Control, 0) + } + + pagingControl := NewControlPaging(pagingSize) + searchRequest.Controls = append(searchRequest.Controls, pagingControl) + 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 +} + +func (l *Conn) Search(searchRequest *SearchRequest) (*SearchResult, error) { + messageID := l.nextMessageID() + packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request") + packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, messageID, "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) + + channel, err := l.sendMessage(packet) + if err != nil { + return nil, err + } + if channel == nil { + return nil, NewError(ErrorNetwork, errors.New("ldap: could not send message")) + } + defer l.finishMessage(messageID) + + 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", messageID) + packet = <-channel + l.Debug.Printf("%d: got response %p", messageID, packet) + 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) + } + + 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", messageID) + return result, nil +} From 4d970d5fc4f62fe3ca8ba275b1c04c3307f09252 Mon Sep 17 00:00:00 2001 From: Frode Nordahl Date: Tue, 10 Nov 2015 20:38:40 +0100 Subject: [PATCH 2/2] connector: add LDAP connector Authentication is performed by binding to the configured LDAP server using the user supplied credentials. Successfull bind equals authenticated user. Optionally the connector can be configured to search before authentication. The entryDN found will be used to bind to the LDAP server. This feature must be enabled to get supplementary information from the directory (ID, Name, Email). This feature can also be used to limit access to the service. Example use case: Allow your users to log in with e-mail address instead of the identification string in your DNs (typically username). To make re-use of HTTP form handling code from the Local connector possible: - Implemented IdentityProvider interface - Moved the re-used functions to login_local.go Fixes #119 --- .travis.yml | 8 + Documentation/connectors-configuration.md | 77 +++++ connector/connector_ldap.go | 339 ++++++++++++++++++++++ connector/connector_ldap_test.go | 94 ++++++ connector/connector_local.go | 87 ------ connector/interface.go | 4 + connector/login_local.go | 97 +++++++ functional/ldap_test.go | 207 +++++++++++++ static/fixtures/connectors.json.sample | 19 ++ static/html/ldap-login.html | 30 ++ 10 files changed, 875 insertions(+), 87 deletions(-) create mode 100644 connector/connector_ldap.go create mode 100644 connector/connector_ldap_test.go create mode 100644 connector/login_local.go create mode 100644 functional/ldap_test.go create mode 100644 static/html/ldap-login.html diff --git a/.travis.yml b/.travis.yml index b916805a..76c716d7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,15 +10,23 @@ go: env: - DEX_TEST_DSN="postgres://postgres@127.0.0.1:15432/postgres?sslmode=disable" ISOLATED=true + DEX_TEST_LDAP_URI="ldap://tlstest.local:1389/????bindname=cn%3Dadmin%2Cdc%3Dexample%2Cdc%3Dorg,X-BINDPW=admin" install: - go get golang.org/x/tools/cmd/cover - go get golang.org/x/tools/cmd/vet - docker pull quay.io/coreos/postgres + - docker pull osixia/openldap script: - docker run -d -p 127.0.0.1:15432:5432 quay.io/coreos/postgres + - LDAPCONTAINER=`docker run -e LDAP_TLS_PROTOCOL_MIN=3.0 -e LDAP_TLS_CIPHER_SUITE=NORMAL -d -p 127.0.0.1:1389:389 -p 127.0.0.1:1636:636 -h tlstest.local osixia/openldap` - ./test + - docker cp ${LDAPCONTAINER}:container/service/:cfssl/assets/default-ca/default-ca.pem /tmp/openldap-ca.pem + - docker cp ${LDAPCONTAINER}:container/service/slapd/assets/certs/ldap.key /tmp/ldap.key + - chmod 644 /tmp/ldap.key + - docker cp ${LDAPCONTAINER}:container/service/slapd/assets/certs/ldap.crt /tmp/ldap.crt + - sudo sh -c 'echo "127.0.0.1 tlstest.local" >> /etc/hosts' - ./test-functional deploy: diff --git a/Documentation/connectors-configuration.md b/Documentation/connectors-configuration.md index 72f96950..3637f0a6 100644 --- a/Documentation/connectors-configuration.md +++ b/Documentation/connectors-configuration.md @@ -134,6 +134,83 @@ Here's an example of a `bitbucket` connector; the clientID and clientSecret shou } ``` +### `ldap` connector + +The `ldap` connector allows email/password based authentication hosted by dex, backed by a LDAP directory. + +Authentication is performed by binding to the configured LDAP server using the user supplied credentials. Successfull bind equals authenticated user. + +Optionally the connector can be configured to search before authentication. The entryDN found will be used to bind to the LDAP server. + +This feature must be enabled to get supplementary information from the directory (ID, Name, Email). This feature can also be used to limit access to the service. + +Example use case: Allow your users to log in with e-mail address as username instead of the identification string in your DNs (typically username). + +___NOTE:___ Users must register with dex at first login. For this to work you have to run dex-worker with --enable-registration. + +In addition to `id` and `type`, the `ldap` connector takes the following additional fields: +* serverHost: a `string`. The hostname for the LDAP Server. + +* serverPort: a `unsigned 16-bit integer`. The port for the LDAP Server. + +* timeout: `duration in milliseconds`. The timeout for connecting to and reading from LDAP Server in Milliseconds. Default: `60000` (60 Seconds) + +* useTLS: a `boolean`. Whether the LDAP Connector should issue a StartTLS after successfully connecting to the LDAP Server. + +* useSSL: a `boolean`. Whether the LDAP Connector should expect the connection to be encrypted, typically used with ldaps port (636/tcp). + +* certFile: a `string`. Optional Certificate to present to LDAP server. + +* keyFile: a `string`. Key associated with Certificate specified in `certFile`. + +* caFile: a `string`. Filename for PEM-file containing the set of root certificate authorities that the LDAP client use when verifying the server certificates. Default: use the host's root CA set. + +* skipCertVerification: a `boolean`. Skip server certificate chain verification. + +* baseDN: a `string`. Base DN from which Bind DN is built and searches are based. + +* nameAttribute: a `string`. Attribute to map to Name. Default: `cn` + +* emailAttribute: a `string`. Attribute to map to Email. Default: `mail` + +* searchBeforeAuth: a `boolean`. Perform search for entryDN to be used for bind. + +* searchFilter: a `string`. Filter to apply to search. Variable substititions: `%u` User supplied username/e-mail address. `%b` BaseDN. + +* searchScope: a `string`. Scope of the search. `base|one|sub`. Default: `one` + +* searchBindDN: a `string`. DN to bind as for search operations. + +* searchBindPw: a `string`. Password for bind for search operations. + +* bindTemplate: a `string`. Template to build bindDN from user supplied credentials. Variable subtitutions: `%u` User supplied username/e-mail address. `%b` BaseDN. Default: `uid=%u,%b` ___NOTE:___ This is not used when searchBeforeAuth is enabled. + +* trustedEmailProvider: a `boolean`. If true dex will trust the email address claims from this provider and not require that users verify their emails. + +Here's an example of a `ldap` connector; + +``` + { + "type": "ldap", + "id": "ldap", + "serverHost": "127.0.0.1", + "serverPort": 389, + "useTLS": true, + "useSSL": false, + "skipCertVerification": false, + "baseDN": "ou=People,dc=example,dc=com", + "nameAttribute": "cn", + "emailAttribute": "mail", + "searchBeforeAuth": true, + "searchFilter": "(mail=%u)", + "searchScope": "one", + "searchBindDN": "searchuser", + "searchBindPw": "supersecret", + "bindTemplate": "uid=%u,%b", + "trustedEmailProvider": true + } +``` + ## Setting the Configuration To set a connectors configuration in dex, put it in some temporary file, then use the dexctl command to upload it to dex: diff --git a/connector/connector_ldap.go b/connector/connector_ldap.go new file mode 100644 index 00000000..2b7747c2 --- /dev/null +++ b/connector/connector_ldap.go @@ -0,0 +1,339 @@ +package connector + +import ( + "crypto/tls" + "crypto/x509" + + "fmt" + + "html/template" + "io/ioutil" + "net/http" + "net/url" + "path" + "strings" + "time" + + "github.com/coreos/dex/pkg/log" + "github.com/coreos/go-oidc/oidc" + + "gopkg.in/ldap.v2" +) + +const ( + LDAPConnectorType = "ldap" + LDAPLoginPageTemplateName = "ldap-login.html" +) + +func init() { + RegisterConnectorConfigType(LDAPConnectorType, func() ConnectorConfig { return &LDAPConnectorConfig{} }) +} + +type LDAPConnectorConfig struct { + ID string `json:"id"` + ServerHost string `json:"serverHost"` + ServerPort uint16 `json:"serverPort"` + Timeout time.Duration `json:"timeout"` + UseTLS bool `json:"useTLS"` + UseSSL bool `json:"useSSL"` + CertFile string `json:"certFile"` + KeyFile string `json:"keyFile"` + CaFile string `json:"caFile"` + SkipCertVerification bool `json:"skipCertVerification"` + BaseDN string `json:"baseDN"` + NameAttribute string `json:"nameAttribute"` + EmailAttribute string `json:"emailAttribute"` + SearchBeforeAuth bool `json:"searchBeforeAuth"` + SearchFilter string `json:"searchFilter"` + SearchScope string `json:"searchScope"` + SearchBindDN string `json:"searchBindDN"` + SearchBindPw string `json:"searchBindPw"` + BindTemplate string `json:"bindTemplate"` + TrustedEmailProvider bool `json:"trustedEmailProvider"` +} + +func (cfg *LDAPConnectorConfig) ConnectorID() string { + return cfg.ID +} + +func (cfg *LDAPConnectorConfig) ConnectorType() string { + return LDAPConnectorType +} + +type LDAPConnector struct { + id string + idp *LDAPIdentityProvider + namespace url.URL + trustedEmailProvider bool + loginFunc oidc.LoginFunc + loginTpl *template.Template +} + +func (cfg *LDAPConnectorConfig) Connector(ns url.URL, lf oidc.LoginFunc, tpls *template.Template) (Connector, error) { + ns.Path = path.Join(ns.Path, httpPathCallback) + tpl := tpls.Lookup(LDAPLoginPageTemplateName) + if tpl == nil { + return nil, fmt.Errorf("unable to find necessary HTML template") + } + + if cfg.UseTLS && cfg.UseSSL { + return nil, fmt.Errorf("Invalid configuration. useTLS and useSSL are mutual exclusive.") + } + + if len(cfg.CertFile) > 0 && len(cfg.KeyFile) == 0 { + return nil, fmt.Errorf("Invalid configuration. Both certFile and keyFile must be specified.") + } + + var nameAttribute, emailAttribute, bindTemplate string + if len(cfg.NameAttribute) > 0 { + nameAttribute = cfg.NameAttribute + } else { + nameAttribute = "cn" + } + + if len(cfg.EmailAttribute) > 0 { + emailAttribute = cfg.EmailAttribute + } else { + emailAttribute = "mail" + } + + if len(cfg.BindTemplate) > 0 { + if cfg.SearchBeforeAuth { + log.Warningf("bindTemplate not used when searchBeforeAuth specified.") + } + bindTemplate = cfg.BindTemplate + } else { + bindTemplate = "uid=%u,%b" + } + + var searchScope int + if len(cfg.SearchScope) > 0 { + switch { + case strings.EqualFold(cfg.SearchScope, "BASE"): + searchScope = ldap.ScopeBaseObject + case strings.EqualFold(cfg.SearchScope, "ONE"): + searchScope = ldap.ScopeSingleLevel + case strings.EqualFold(cfg.SearchScope, "SUB"): + searchScope = ldap.ScopeWholeSubtree + default: + return nil, fmt.Errorf("Invalid value for searchScope: '%v'. Must be one of 'base', 'one' or 'sub'.", cfg.SearchScope) + } + } else { + searchScope = ldap.ScopeSingleLevel + } + + if cfg.Timeout != 0 { + ldap.DefaultTimeout = cfg.Timeout * time.Millisecond + } + + tlsConfig := &tls.Config{ + ServerName: cfg.ServerHost, + InsecureSkipVerify: cfg.SkipCertVerification, + } + + if (cfg.UseTLS || cfg.UseSSL) && len(cfg.CaFile) > 0 { + buf, err := ioutil.ReadFile(cfg.CaFile) + if err != nil { + return nil, err + } + + rootCertPool := x509.NewCertPool() + ok := rootCertPool.AppendCertsFromPEM(buf) + if ok { + tlsConfig.RootCAs = rootCertPool + } else { + return nil, fmt.Errorf("%v: Unable to parse certificate data.", cfg.CaFile) + } + } + + if (cfg.UseTLS || cfg.UseSSL) && len(cfg.CertFile) > 0 && len(cfg.KeyFile) > 0 { + cert, err := tls.LoadX509KeyPair(cfg.CertFile, cfg.KeyFile) + if err != nil { + return nil, err + } + tlsConfig.Certificates = []tls.Certificate{cert} + } + + idp := &LDAPIdentityProvider{ + serverHost: cfg.ServerHost, + serverPort: cfg.ServerPort, + useTLS: cfg.UseTLS, + useSSL: cfg.UseSSL, + baseDN: cfg.BaseDN, + nameAttribute: nameAttribute, + emailAttribute: emailAttribute, + searchBeforeAuth: cfg.SearchBeforeAuth, + searchFilter: cfg.SearchFilter, + searchScope: searchScope, + searchBindDN: cfg.SearchBindDN, + searchBindPw: cfg.SearchBindPw, + bindTemplate: bindTemplate, + tlsConfig: tlsConfig, + } + + idpc := &LDAPConnector{ + id: cfg.ID, + idp: idp, + namespace: ns, + trustedEmailProvider: cfg.TrustedEmailProvider, + loginFunc: lf, + loginTpl: tpl, + } + + return idpc, nil +} + +func (c *LDAPConnector) ID() string { + return c.id +} + +func (c *LDAPConnector) Healthy() error { + ldapConn, err := c.idp.LDAPConnect() + if err == nil { + ldapConn.Close() + } + return err +} + +func (c *LDAPConnector) LoginURL(sessionKey, prompt string) (string, error) { + q := url.Values{} + q.Set("session_key", sessionKey) + q.Set("prompt", prompt) + enc := q.Encode() + + return path.Join(c.namespace.Path, "login") + "?" + enc, nil +} + +func (c *LDAPConnector) Register(mux *http.ServeMux, errorURL url.URL) { + route := path.Join(c.namespace.Path, "login") + mux.Handle(route, handleLoginFunc(c.loginFunc, c.loginTpl, c.idp, route, errorURL)) +} + +func (c *LDAPConnector) Sync() chan struct{} { + return make(chan struct{}) +} + +func (c *LDAPConnector) TrustedEmailProvider() bool { + return c.trustedEmailProvider +} + +type LDAPIdentityProvider struct { + serverHost string + serverPort uint16 + useTLS bool + useSSL bool + baseDN string + nameAttribute string + emailAttribute string + searchBeforeAuth bool + searchFilter string + searchScope int + searchBindDN string + searchBindPw string + bindTemplate string + tlsConfig *tls.Config +} + +func (m *LDAPIdentityProvider) LDAPConnect() (*ldap.Conn, error) { + var err error + var ldapConn *ldap.Conn + + log.Debugf("LDAPConnect()") + if m.useSSL { + ldapConn, err = ldap.DialTLS("tcp", fmt.Sprintf("%s:%d", m.serverHost, m.serverPort), m.tlsConfig) + if err != nil { + return nil, err + } + } else { + ldapConn, err = ldap.Dial("tcp", fmt.Sprintf("%s:%d", m.serverHost, m.serverPort)) + if err != nil { + return nil, err + } + if m.useTLS { + err = ldapConn.StartTLS(m.tlsConfig) + if err != nil { + return nil, err + } + } + } + + return ldapConn, err +} + +func (m *LDAPIdentityProvider) ParseString(template, username string) string { + result := template + result = strings.Replace(result, "%u", username, -1) + result = strings.Replace(result, "%b", m.baseDN, -1) + + return result +} + +func (m *LDAPIdentityProvider) Identity(username, password string) (*oidc.Identity, error) { + var err error + var bindDN, ldapUid, ldapName, ldapEmail string + var ldapConn *ldap.Conn + + ldapConn, err = m.LDAPConnect() + if err != nil { + return nil, err + } + defer ldapConn.Close() + + if m.searchBeforeAuth { + err = ldapConn.Bind(m.searchBindDN, m.searchBindPw) + if err != nil { + return nil, err + } + + filter := m.ParseString(m.searchFilter, username) + + attributes := []string{ + "entryDN", + m.nameAttribute, + m.emailAttribute, + } + + s := ldap.NewSearchRequest(m.baseDN, m.searchScope, ldap.NeverDerefAliases, 0, 0, false, filter, attributes, nil) + + sr, err := ldapConn.Search(s) + if err != nil { + return nil, err + } + if len(sr.Entries) == 0 { + err = fmt.Errorf("Search returned no match. filter='%v' base='%v'", filter, m.baseDN) + return nil, err + } + + bindDN = sr.Entries[0].GetAttributeValue("entryDN") + ldapName = sr.Entries[0].GetAttributeValue(m.nameAttribute) + ldapEmail = sr.Entries[0].GetAttributeValue(m.emailAttribute) + + // drop to anonymous bind, prepare for bind as user + err = ldapConn.Bind("", "") + if err != nil { + // unsupported or disallowed, reconnect + log.Warningf("Re-connecting to LDAP Server after failure to bind anonymously: %v", err) + ldapConn.Close() + ldapConn, err = m.LDAPConnect() + if err != nil { + return nil, err + } + } + } else { + bindDN = m.ParseString(m.bindTemplate, username) + } + + // authenticate user + err = ldapConn.Bind(bindDN, password) + if err != nil { + return nil, err + } + + ldapUid = bindDN + + return &oidc.Identity{ + ID: ldapUid, + Name: ldapName, + Email: ldapEmail, + }, nil +} diff --git a/connector/connector_ldap_test.go b/connector/connector_ldap_test.go new file mode 100644 index 00000000..f5e2c9e8 --- /dev/null +++ b/connector/connector_ldap_test.go @@ -0,0 +1,94 @@ +package connector + +import ( + "html/template" + "net/url" + "testing" + + "github.com/coreos/go-oidc/oidc" +) + +var ( + ns url.URL + lf oidc.LoginFunc + templates *template.Template +) + +func init() { + templates = template.New(LDAPLoginPageTemplateName) +} + +func TestLDAPConnectorConfigValidTLS(t *testing.T) { + cc := LDAPConnectorConfig{ + ID: "ldap", + UseTLS: true, + UseSSL: false, + } + + _, err := cc.Connector(ns, lf, templates) + if err != nil { + t.Fatal(err) + } +} + +func TestLDAPConnectorConfigInvalidSSLandTLS(t *testing.T) { + cc := LDAPConnectorConfig{ + ID: "ldap", + UseTLS: true, + UseSSL: true, + } + + _, err := cc.Connector(ns, lf, templates) + if err == nil { + t.Fatal("Expected LDAPConnector initialization to fail when both TLS and SSL enabled.") + } +} + +func TestLDAPConnectorConfigValidSearchScope(t *testing.T) { + cc := LDAPConnectorConfig{ + ID: "ldap", + SearchScope: "one", + } + + _, err := cc.Connector(ns, lf, templates) + if err != nil { + t.Fatal(err) + } +} + +func TestLDAPConnectorConfigInvalidSearchScope(t *testing.T) { + cc := LDAPConnectorConfig{ + ID: "ldap", + SearchScope: "three", + } + + _, err := cc.Connector(ns, lf, templates) + if err == nil { + t.Fatal("Expected LDAPConnector initialization to fail when invalid value provided for SearchScope.") + } +} + +func TestLDAPConnectorConfigInvalidCertFileNoKeyFile(t *testing.T) { + cc := LDAPConnectorConfig{ + ID: "ldap", + CertFile: "/tmp/ldap.crt", + } + + _, err := cc.Connector(ns, lf, templates) + if err == nil { + t.Fatal("Expected LDAPConnector initialization to fail when CertFile specified without KeyFile.") + } +} + +func TestLDAPConnectorConfigValidCertFileAndKeyFile(t *testing.T) { + cc := LDAPConnectorConfig{ + ID: "ldap", + CertFile: "/tmp/ldap.crt", + KeyFile: "/tmp/ldap.key", + } + + _, err := cc.Connector(ns, lf, templates) + if err != nil { + t.Fatal(err) + } +} diff --git a/connector/connector_local.go b/connector/connector_local.go index 63a949ce..00fd0402 100644 --- a/connector/connector_local.go +++ b/connector/connector_local.go @@ -7,10 +7,7 @@ import ( "net/url" "path" - phttp "github.com/coreos/dex/pkg/http" - "github.com/coreos/dex/pkg/log" "github.com/coreos/dex/user" - "github.com/coreos/go-oidc/oauth2" "github.com/coreos/go-oidc/oidc" ) @@ -102,90 +99,6 @@ func (c *LocalConnector) TrustedEmailProvider() bool { return false } -func redirectPostError(w http.ResponseWriter, errorURL url.URL, q url.Values) { - redirectURL := phttp.MergeQuery(errorURL, q) - w.Header().Set("Location", redirectURL.String()) - w.WriteHeader(http.StatusSeeOther) -} - -func handleLoginFunc(lf oidc.LoginFunc, tpl *template.Template, idp *LocalIdentityProvider, localErrorPath string, errorURL url.URL) http.HandlerFunc { - handleGET := func(w http.ResponseWriter, r *http.Request, errMsg string) { - q := r.URL.Query() - sessionKey := q.Get("session_key") - - p := &Page{PostURL: r.URL.String(), Name: "Local", SessionKey: sessionKey} - if errMsg != "" { - p.Error = true - p.Message = errMsg - } - - if err := tpl.Execute(w, p); err != nil { - phttp.WriteError(w, http.StatusInternalServerError, err.Error()) - } - } - - handlePOST := func(w http.ResponseWriter, r *http.Request) { - if err := r.ParseForm(); err != nil { - msg := fmt.Sprintf("unable to parse form from body: %v", err) - phttp.WriteError(w, http.StatusBadRequest, msg) - return - } - - userid := r.PostForm.Get("userid") - if userid == "" { - handleGET(w, r, "missing email address") - return - } - - password := r.PostForm.Get("password") - if password == "" { - handleGET(w, r, "missing password") - return - } - - ident, err := idp.Identity(userid, password) - log.Errorf("IDENTITY: err: %v", err) - - if ident == nil || err != nil { - handleGET(w, r, "invalid login") - return - } - - q := r.URL.Query() - sessionKey := r.FormValue("session_key") - if sessionKey == "" { - q.Set("error", oauth2.ErrorInvalidRequest) - q.Set("error_description", "missing session_key") - redirectPostError(w, errorURL, q) - return - } - - redirectURL, err := lf(*ident, sessionKey) - if err != nil { - log.Errorf("Unable to log in %#v: %v", *ident, err) - q.Set("error", oauth2.ErrorAccessDenied) - q.Set("error_description", "login failed") - redirectPostError(w, errorURL, q) - return - } - - w.Header().Set("Location", redirectURL) - w.WriteHeader(http.StatusFound) - } - - return func(w http.ResponseWriter, r *http.Request) { - switch r.Method { - case "POST": - handlePOST(w, r) - case "GET": - handleGET(w, r, "") - default: - w.Header().Set("Allow", "GET, POST") - phttp.WriteError(w, http.StatusMethodNotAllowed, "GET and POST only acceptable methods") - } - } -} - type LocalIdentityProvider struct { PasswordInfoRepo user.PasswordInfoRepo UserRepo user.UserRepo diff --git a/connector/interface.go b/connector/interface.go index b7ca0139..e0348142 100644 --- a/connector/interface.go +++ b/connector/interface.go @@ -64,3 +64,7 @@ type ConnectorConfigRepo interface { All() ([]ConnectorConfig, error) GetConnectorByID(repo.Transaction, string) (ConnectorConfig, error) } + +type IdentityProvider interface { + Identity(email, password string) (*oidc.Identity, error) +} diff --git a/connector/login_local.go b/connector/login_local.go new file mode 100644 index 00000000..ddb2423a --- /dev/null +++ b/connector/login_local.go @@ -0,0 +1,97 @@ +package connector + +import ( + "fmt" + "html/template" + "net/http" + "net/url" + + phttp "github.com/coreos/dex/pkg/http" + "github.com/coreos/dex/pkg/log" + "github.com/coreos/go-oidc/oauth2" + "github.com/coreos/go-oidc/oidc" +) + +func redirectPostError(w http.ResponseWriter, errorURL url.URL, q url.Values) { + redirectURL := phttp.MergeQuery(errorURL, q) + w.Header().Set("Location", redirectURL.String()) + w.WriteHeader(http.StatusSeeOther) +} + +func handleLoginFunc(lf oidc.LoginFunc, tpl *template.Template, idp IdentityProvider, localErrorPath string, errorURL url.URL) http.HandlerFunc { + handleGET := func(w http.ResponseWriter, r *http.Request, errMsg string) { + q := r.URL.Query() + sessionKey := q.Get("session_key") + + p := &Page{PostURL: r.URL.String(), Name: "Local", SessionKey: sessionKey} + if errMsg != "" { + p.Error = true + p.Message = errMsg + } + + if err := tpl.Execute(w, p); err != nil { + phttp.WriteError(w, http.StatusInternalServerError, err.Error()) + } + } + + handlePOST := func(w http.ResponseWriter, r *http.Request) { + if err := r.ParseForm(); err != nil { + msg := fmt.Sprintf("unable to parse form from body: %v", err) + phttp.WriteError(w, http.StatusBadRequest, msg) + return + } + + userid := r.PostForm.Get("userid") + if userid == "" { + handleGET(w, r, "missing email address") + return + } + + password := r.PostForm.Get("password") + if password == "" { + handleGET(w, r, "missing password") + return + } + + ident, err := idp.Identity(userid, password) + log.Errorf("IDENTITY: err: %v", err) + + if ident == nil || err != nil { + handleGET(w, r, "invalid login") + return + } + + q := r.URL.Query() + sessionKey := r.FormValue("session_key") + if sessionKey == "" { + q.Set("error", oauth2.ErrorInvalidRequest) + q.Set("error_description", "missing session_key") + redirectPostError(w, errorURL, q) + return + } + + redirectURL, err := lf(*ident, sessionKey) + if err != nil { + log.Errorf("Unable to log in %#v: %v", *ident, err) + q.Set("error", oauth2.ErrorAccessDenied) + q.Set("error_description", "login failed") + redirectPostError(w, errorURL, q) + return + } + + w.Header().Set("Location", redirectURL) + w.WriteHeader(http.StatusFound) + } + + return func(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case "POST": + handlePOST(w, r) + case "GET": + handleGET(w, r, "") + default: + w.Header().Set("Allow", "GET, POST") + phttp.WriteError(w, http.StatusMethodNotAllowed, "GET and POST only acceptable methods") + } + } +} diff --git a/functional/ldap_test.go b/functional/ldap_test.go new file mode 100644 index 00000000..e4a46987 --- /dev/null +++ b/functional/ldap_test.go @@ -0,0 +1,207 @@ +package functional + +import ( + "fmt" + "html/template" + "net/url" + "os" + "strconv" + "strings" + "testing" + + "github.com/coreos/dex/connector" + "github.com/coreos/dex/repo" + "github.com/coreos/go-oidc/oidc" + "gopkg.in/ldap.v2" +) + +var ( + ldapHost string + ldapPort uint16 + ldapBindDN string + ldapBindPw string +) + +func init() { + ldapuri := os.Getenv("DEX_TEST_LDAP_URI") + if ldapuri == "" { + fmt.Println("Unable to proceed with empty env var " + + "DEX_TEST_LDAP_URI") + os.Exit(1) + } + u, err := url.Parse(ldapuri) + if err != nil { + fmt.Println("Unable to parse DEX_TEST_LDAP_URI") + os.Exit(1) + } + if strings.Index(u.RawQuery, "?") < 0 { + fmt.Println("Unable to parse DEX_TEST_LDAP_URI") + os.Exit(1) + } + extentions := make(map[string]string) + kvs := strings.Split(strings.TrimLeft(u.RawQuery, "?"), ",") + for i := range kvs { + fmt.Println(kvs[i]) + kv := strings.Split(kvs[i], "=") + if len(kv) < 2 { + fmt.Println("Unable to parse DEX_TEST_LDAP_URI") + os.Exit(1) + } + extentions[kv[0]] = kv[1] + } + hostport := strings.Split(u.Host, ":") + port := 389 + if len(hostport) > 1 { + port, _ = strconv.Atoi(hostport[1]) + } + + ldapHost = hostport[0] + ldapPort = uint16(port) + + if len(extentions["bindname"]) > 0 { + ldapBindDN, err = url.QueryUnescape(extentions["bindname"]) + if err != nil { + fmt.Println("Unable to parse DEX_TEST_LDAP_URI") + os.Exit(1) + } + } + if len(extentions["X-BINDPW"]) > 0 { + ldapBindPw = extentions["X-BINDPW"] + } +} + +func TestLDAPConnect(t *testing.T) { + fmt.Println("ldapHost: ", ldapHost) + fmt.Println("ldapPort: ", ldapPort) + fmt.Println("ldapBindDN: ", ldapBindDN) + fmt.Println("ldapBindPw: ", ldapBindPw) + l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", ldapHost, ldapPort)) + if err != nil { + t.Fatal(err) + } + err = l.Bind(ldapBindDN, ldapBindPw) + if err != nil { + t.Fatal(err) + } + l.Close() +} + +func TestConnectorLDAPConnectFail(t *testing.T) { + var tx repo.Transaction + var lf oidc.LoginFunc + var ns url.URL + + templates := template.New(connector.LDAPLoginPageTemplateName) + + ccr := connector.NewConnectorConfigRepoFromConfigs( + []connector.ConnectorConfig{&connector.LDAPConnectorConfig{ + ID: "ldap", + ServerHost: ldapHost, + ServerPort: ldapPort + 1, + }}, + ) + cc, err := ccr.GetConnectorByID(tx, "ldap") + if err != nil { + t.Fatal(err) + } + c, err := cc.Connector(ns, lf, templates) + if err != nil { + t.Fatal(err) + } + err = c.Healthy() + if err == nil { + t.Fatal(fmt.Errorf("LDAPConnector.Healty() supposed to fail, but succeeded!")) + } +} + +func TestConnectorLDAPConnectSuccess(t *testing.T) { + var tx repo.Transaction + var lf oidc.LoginFunc + var ns url.URL + + templates := template.New(connector.LDAPLoginPageTemplateName) + + ccr := connector.NewConnectorConfigRepoFromConfigs( + []connector.ConnectorConfig{&connector.LDAPConnectorConfig{ + ID: "ldap", + ServerHost: ldapHost, + ServerPort: ldapPort, + }}, + ) + cc, err := ccr.GetConnectorByID(tx, "ldap") + if err != nil { + t.Fatal(err) + } + c, err := cc.Connector(ns, lf, templates) + if err != nil { + t.Fatal(err) + } + err = c.Healthy() + if err != nil { + t.Fatal(err) + } +} + +func TestConnectorLDAPcaFilecertFileConnectTLS(t *testing.T) { + var tx repo.Transaction + var lf oidc.LoginFunc + var ns url.URL + + templates := template.New(connector.LDAPLoginPageTemplateName) + + ccr := connector.NewConnectorConfigRepoFromConfigs( + []connector.ConnectorConfig{&connector.LDAPConnectorConfig{ + ID: "ldap", + ServerHost: ldapHost, + ServerPort: ldapPort, + UseTLS: true, + CertFile: "/tmp/ldap.crt", + KeyFile: "/tmp/ldap.key", + CaFile: "/tmp/openldap-ca.pem", + }}, + ) + cc, err := ccr.GetConnectorByID(tx, "ldap") + if err != nil { + t.Fatal(err) + } + c, err := cc.Connector(ns, lf, templates) + if err != nil { + t.Fatal(err) + } + err = c.Healthy() + if err != nil { + t.Fatal(err) + } +} + +func TestConnectorLDAPcaFilecertFileConnectSSL(t *testing.T) { + var tx repo.Transaction + var lf oidc.LoginFunc + var ns url.URL + + templates := template.New(connector.LDAPLoginPageTemplateName) + + ccr := connector.NewConnectorConfigRepoFromConfigs( + []connector.ConnectorConfig{&connector.LDAPConnectorConfig{ + ID: "ldap", + ServerHost: ldapHost, + ServerPort: ldapPort + 247, // 636 + UseSSL: true, + CertFile: "/tmp/ldap.crt", + KeyFile: "/tmp/ldap.key", + CaFile: "/tmp/openldap-ca.pem", + }}, + ) + cc, err := ccr.GetConnectorByID(tx, "ldap") + if err != nil { + t.Fatal(err) + } + c, err := cc.Connector(ns, lf, templates) + if err != nil { + t.Fatal(err) + } + err = c.Healthy() + if err != nil { + t.Fatal(err) + } +} diff --git a/static/fixtures/connectors.json.sample b/static/fixtures/connectors.json.sample index a3cbe39f..681ed3e9 100644 --- a/static/fixtures/connectors.json.sample +++ b/static/fixtures/connectors.json.sample @@ -31,5 +31,24 @@ "id": "bitbucket", "clientID": "${CLIENT_ID}", "clientSecret": "${CLIENT_SECRET}" + }, + { + "type": "ldap", + "id": "ldap", + "serverHost": "127.0.0.1", + "serverPort": 389, + "useTLS": true, + "useSSL": false, + "caFile": "/etc/ssl/certs/example_com_root.crt", + "skipCertVerification": false, + "baseDN": "ou=People,dc=example,dc=com", + "nameAttribute": "cn", + "emailAttribute": "mail", + "searchBeforeAuth": true, + "searchFilter": "(mail=%u)", + "searchScope": "one", + "searchBindDN": "searchuser", + "searchBindPw": "supersecret", + "bindTemplate": "uid=%u,%b" } ] diff --git a/static/html/ldap-login.html b/static/html/ldap-login.html new file mode 100644 index 00000000..4ea4cd65 --- /dev/null +++ b/static/html/ldap-login.html @@ -0,0 +1,30 @@ +{{ template "header.html" }} + +
+

Log in to Your Account

+
+
+ LDAP +
+ +
+ +
+
+
+ + Forgot? Reset Password +
+ +
+ + {{ if .Error }} +
{{ .Message }}
+ {{ end }} + + + +
+
+ +{{ template "footer.html" }}