Merge pull request #151 from rasky/ldap
Implementation of the LDAP credential backend
This commit is contained in:
commit
4591bb6427
|
@ -109,6 +109,14 @@
|
|||
"Comment": "v2.0.1-6-g44cb478",
|
||||
"Rev": "44cb4788b2ec3c3d158dd3d1b50aba7d66f4b59a"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/vanackere/asn1-ber",
|
||||
"Rev": "295c7b21db5d9525ad959e3382610f3aff029663"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/vanackere/ldap",
|
||||
"Rev": "e29b797d1abde6567ccb4ab56236e033cabf845a"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/vaughan0/go-ini",
|
||||
"Rev": "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1"
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
language: go
|
||||
go:
|
||||
- 1.2
|
||||
- 1.3
|
||||
- tip
|
||||
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 build -v ./...
|
||||
script:
|
||||
- go test -v -cover ./...
|
|
@ -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.
|
|
@ -0,0 +1,18 @@
|
|||
[![GoDoc](https://godoc.org/github.com/vanackere/asn1-ber?status.svg)](https://godoc.org/github.com/vanackere/asn1-ber) [![Build Status](https://travis-ci.org/vanackere/asn1-ber.svg)](https://travis-ci.org/vanackere/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
|
|
@ -0,0 +1,528 @@
|
|||
package ber
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type Packet struct {
|
||||
ClassType Class
|
||||
TagType Type
|
||||
Tag Tag
|
||||
Value interface{}
|
||||
ByteValue []byte
|
||||
Data *bytes.Buffer
|
||||
Children []*Packet
|
||||
Description string
|
||||
}
|
||||
|
||||
type Tag uint8
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func resizeBuffer(in []byte, new_size int) (out []byte) {
|
||||
out = make([]byte, new_size)
|
||||
|
||||
copy(out, in)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func ReadPacket(reader io.Reader) (*Packet, error) {
|
||||
var header [2]byte
|
||||
buf := header[:]
|
||||
_, err := io.ReadFull(reader, buf)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
idx := 2
|
||||
var datalen int
|
||||
l := buf[1]
|
||||
|
||||
if l&0x80 == 0 {
|
||||
// The length is encoded in the bottom 7 bits.
|
||||
datalen = int(l & 0x7f)
|
||||
if Debug {
|
||||
fmt.Printf("Read: datalen = %d len(buf) = %d\n ", l, len(buf))
|
||||
|
||||
for _, b := range buf {
|
||||
fmt.Printf("%02X ", b)
|
||||
}
|
||||
|
||||
fmt.Printf("\n")
|
||||
}
|
||||
} else {
|
||||
// Bottom 7 bits give the number of length bytes to follow.
|
||||
numBytes := int(l & 0x7f)
|
||||
if numBytes == 0 {
|
||||
return nil, fmt.Errorf("invalid length found")
|
||||
}
|
||||
idx += numBytes
|
||||
buf = resizeBuffer(buf, 2+numBytes)
|
||||
_, err := io.ReadFull(reader, buf[2:])
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
datalen = 0
|
||||
for i := 0; i < numBytes; i++ {
|
||||
b := buf[2+i]
|
||||
datalen <<= 8
|
||||
datalen |= int(b)
|
||||
}
|
||||
|
||||
if Debug {
|
||||
fmt.Printf("Read: datalen = %d numbytes=%d len(buf) = %d\n ", datalen, numBytes, len(buf))
|
||||
|
||||
for _, b := range buf {
|
||||
fmt.Printf("%02X ", b)
|
||||
}
|
||||
|
||||
fmt.Printf("\n")
|
||||
}
|
||||
}
|
||||
|
||||
buf = resizeBuffer(buf, idx+datalen)
|
||||
_, err = io.ReadFull(reader, buf[idx:])
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if Debug {
|
||||
fmt.Printf("Read: len( buf ) = %d idx=%d datalen=%d idx+datalen=%d\n ", len(buf), idx, datalen, idx+datalen)
|
||||
|
||||
for _, b := range buf {
|
||||
fmt.Printf("%02X ", b)
|
||||
}
|
||||
}
|
||||
|
||||
p, _ := decodePacket(buf)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func DecodePacket(data []byte) *Packet {
|
||||
p, _ := decodePacket(data)
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func decodePacket(data []byte) (*Packet, []byte) {
|
||||
if Debug {
|
||||
fmt.Printf("decodePacket: enter %d\n", len(data))
|
||||
}
|
||||
|
||||
p := new(Packet)
|
||||
|
||||
p.ClassType = Class(data[0]) & ClassBitmask
|
||||
p.TagType = Type(data[0]) & TypeBitmask
|
||||
p.Tag = Tag(data[0]) & TagBitmask
|
||||
|
||||
var datalen int
|
||||
l := data[1]
|
||||
datapos := 2
|
||||
if l&0x80 == 0 {
|
||||
// The length is encoded in the bottom 7 bits.
|
||||
datalen = int(l & 0x7f)
|
||||
} else {
|
||||
// Bottom 7 bits give the number of length bytes to follow.
|
||||
numBytes := int(l & 0x7f)
|
||||
if numBytes == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
datapos += numBytes
|
||||
datalen = 0
|
||||
for i := 0; i < numBytes; i++ {
|
||||
b := data[2+i]
|
||||
datalen <<= 8
|
||||
datalen |= int(b)
|
||||
}
|
||||
}
|
||||
|
||||
p.Data = new(bytes.Buffer)
|
||||
|
||||
p.Children = make([]*Packet, 0, 2)
|
||||
|
||||
p.Value = nil
|
||||
|
||||
value_data := data[datapos : datapos+datalen]
|
||||
|
||||
if p.TagType == TypeConstructed {
|
||||
for len(value_data) != 0 {
|
||||
var child *Packet
|
||||
|
||||
child, value_data = decodePacket(value_data)
|
||||
p.AppendChild(child)
|
||||
}
|
||||
} else if p.ClassType == ClassUniversal {
|
||||
p.Data.Write(data[datapos : datapos+datalen])
|
||||
p.ByteValue = value_data
|
||||
|
||||
switch p.Tag {
|
||||
case TagEOC:
|
||||
case TagBoolean:
|
||||
val, _ := parseInt64(value_data)
|
||||
|
||||
p.Value = val != 0
|
||||
case TagInteger:
|
||||
p.Value, _ = parseInt64(value_data)
|
||||
case TagBitString:
|
||||
case TagOctetString:
|
||||
// the actual string encoding is not known here
|
||||
// (e.g. for LDAP value_data is already an UTF8-encoded
|
||||
// string). Return the data without further processing
|
||||
p.Value = DecodeString(value_data)
|
||||
case TagNULL:
|
||||
case TagObjectIdentifier:
|
||||
case TagObjectDescriptor:
|
||||
case TagExternal:
|
||||
case TagRealFloat:
|
||||
case TagEnumerated:
|
||||
p.Value, _ = parseInt64(value_data)
|
||||
case TagEmbeddedPDV:
|
||||
case TagUTF8String:
|
||||
case TagRelativeOID:
|
||||
case TagSequence:
|
||||
case TagSet:
|
||||
case TagNumericString:
|
||||
case TagPrintableString:
|
||||
p.Value = DecodeString(value_data)
|
||||
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(data[datapos : datapos+datalen])
|
||||
}
|
||||
|
||||
return p, data[datapos+datalen:]
|
||||
}
|
||||
|
||||
func (p *Packet) Bytes() []byte {
|
||||
var out bytes.Buffer
|
||||
|
||||
out.Write([]byte{byte(p.ClassType) | byte(p.TagType) | byte(p.Tag)})
|
||||
packet_length := encodeInteger(int64(p.Data.Len()))
|
||||
|
||||
if p.Data.Len() > 127 || len(packet_length) > 1 {
|
||||
out.Write([]byte{byte(len(packet_length) | 128)})
|
||||
out.Write(packet_length)
|
||||
} else {
|
||||
out.Write(packet_length)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
package ber
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"io"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEncodeDecodeInteger(t *testing.T) {
|
||||
for _, v := range []int64{0, 10, 128, 1024, -1, -100, -128, -1024} {
|
||||
enc := encodeInteger(v)
|
||||
dec, err := parseInt64(enc)
|
||||
if err != nil {
|
||||
t.Fatalf("Error decoding %d : %s", v, err)
|
||||
}
|
||||
if v != dec {
|
||||
t.Error("TestEncodeDecodeInteger failed for %d (got %d)", v, dec)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoolean(t *testing.T) {
|
||||
var value bool = true
|
||||
|
||||
packet := NewBoolean(ClassUniversal, TypePrimitive, TagBoolean, value, "first Packet, True")
|
||||
|
||||
newBoolean, ok := packet.Value.(bool)
|
||||
if !ok || newBoolean != value {
|
||||
t.Error("error during creating packet")
|
||||
}
|
||||
|
||||
encodedPacket := packet.Bytes()
|
||||
|
||||
newPacket := DecodePacket(encodedPacket)
|
||||
|
||||
newBoolean, ok = newPacket.Value.(bool)
|
||||
if !ok || newBoolean != value {
|
||||
t.Error("error during decoding packet")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestInteger(t *testing.T) {
|
||||
var value int64 = 10
|
||||
|
||||
packet := NewInteger(ClassUniversal, TypePrimitive, TagInteger, value, "Integer, 10")
|
||||
|
||||
{
|
||||
newInteger, ok := packet.Value.(int64)
|
||||
if !ok || newInteger != value {
|
||||
t.Error("error creating packet")
|
||||
}
|
||||
}
|
||||
|
||||
encodedPacket := packet.Bytes()
|
||||
|
||||
newPacket := DecodePacket(encodedPacket)
|
||||
|
||||
{
|
||||
newInteger, ok := newPacket.Value.(int64)
|
||||
if !ok || int64(newInteger) != value {
|
||||
t.Error("error decoding packet")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestString(t *testing.T) {
|
||||
var value string = "Hic sunt dracones"
|
||||
|
||||
packet := NewString(ClassUniversal, TypePrimitive, TagOctetString, value, "String")
|
||||
|
||||
newValue, ok := packet.Value.(string)
|
||||
if !ok || newValue != value {
|
||||
t.Error("error during creating packet")
|
||||
}
|
||||
|
||||
encodedPacket := packet.Bytes()
|
||||
|
||||
newPacket := DecodePacket(encodedPacket)
|
||||
|
||||
newValue, ok = newPacket.Value.(string)
|
||||
if !ok || newValue != value {
|
||||
t.Error("error during decoding packet")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestSequenceAndAppendChild(t *testing.T) {
|
||||
|
||||
p1 := NewString(ClassUniversal, TypePrimitive, TagOctetString, "HIC SVNT LEONES", "String")
|
||||
p2 := NewString(ClassUniversal, TypePrimitive, TagOctetString, "HIC SVNT DRACONES", "String")
|
||||
p3 := NewString(ClassUniversal, TypePrimitive, TagOctetString, "Terra Incognita", "String")
|
||||
|
||||
sequence := NewSequence("a sequence")
|
||||
sequence.AppendChild(p1)
|
||||
sequence.AppendChild(p2)
|
||||
sequence.AppendChild(p3)
|
||||
|
||||
if len(sequence.Children) != 3 {
|
||||
t.Error("wrong length for children array should be three =>", len(sequence.Children))
|
||||
}
|
||||
|
||||
encodedSequence := sequence.Bytes()
|
||||
|
||||
decodedSequence := DecodePacket(encodedSequence)
|
||||
if len(decodedSequence.Children) != 3 {
|
||||
t.Error("wrong length for children array should be three =>", len(decodedSequence.Children))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestReadPacket(t *testing.T) {
|
||||
packet := NewString(ClassUniversal, TypePrimitive, TagOctetString, "Ad impossibilia nemo tenetur", "string")
|
||||
var buffer io.ReadWriter
|
||||
buffer = new(bytes.Buffer)
|
||||
|
||||
buffer.Write(packet.Bytes())
|
||||
|
||||
newPacket, err := ReadPacket(buffer)
|
||||
if err != nil {
|
||||
t.Error("error during ReadPacket", err)
|
||||
}
|
||||
newPacket.ByteValue = nil
|
||||
if !bytes.Equal(newPacket.ByteValue, packet.ByteValue) {
|
||||
t.Error("packets should be the same")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBinaryInteger(t *testing.T) {
|
||||
// data src : http://luca.ntop.org/Teaching/Appunti/asn1.html 5.7
|
||||
var data = []struct {
|
||||
v int64
|
||||
e []byte
|
||||
}{
|
||||
{v: 0, e: []byte{0x02, 0x01, 0x00}},
|
||||
{v: 127, e: []byte{0x02, 0x01, 0x7F}},
|
||||
{v: 128, e: []byte{0x02, 0x02, 0x00, 0x80}},
|
||||
{v: 256, e: []byte{0x02, 0x02, 0x01, 0x00}},
|
||||
{v: -128, e: []byte{0x02, 0x01, 0x80}},
|
||||
{v: -129, e: []byte{0x02, 0x02, 0xFF, 0x7F}},
|
||||
}
|
||||
|
||||
for _, d := range data {
|
||||
if b := NewInteger(ClassUniversal, TypePrimitive, TagInteger, int64(d.v), "").Bytes(); !bytes.Equal(d.e, b) {
|
||||
t.Errorf("Wrong binary generated for %d : got % X, expected % X", d.v, b, d.e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBinaryOctetString(t *testing.T) {
|
||||
// data src : http://luca.ntop.org/Teaching/Appunti/asn1.html 5.10
|
||||
|
||||
if !bytes.Equal([]byte{0x04, 0x08, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, NewString(ClassUniversal, TypePrimitive, TagOctetString, "\x01\x23\x45\x67\x89\xab\xcd\xef", "").Bytes()) {
|
||||
t.Error("wrong binary generated")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
examples/modify
|
||||
examples/search
|
||||
examples/searchSSL
|
||||
examples/searchTLS
|
|
@ -0,0 +1,12 @@
|
|||
language: go
|
||||
go:
|
||||
- 1.2
|
||||
- 1.3
|
||||
- tip
|
||||
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 build -v ./...
|
||||
script:
|
||||
- go test -v -cover ./...
|
|
@ -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.
|
|
@ -0,0 +1,37 @@
|
|||
[![GoDoc](https://godoc.org/github.com/vanackere/ldap?status.svg)](https://godoc.org/github.com/vanackere/ldap) [![Build Status](https://travis-ci.org/vanackere/ldap.svg)](https://travis-ci.org/vanackere/ldap)
|
||||
|
||||
Basic LDAP v3 functionality for the GO programming language.
|
||||
------------------------------------------------------------
|
||||
|
||||
* Required library:
|
||||
- github.com/vanackere/asn1-ber
|
||||
|
||||
* Working:
|
||||
- Connecting to LDAP server
|
||||
- Binding to LDAP server
|
||||
- Searching for entries
|
||||
- Compiling string filters to LDAP filters
|
||||
- Paging Search Results
|
||||
- Modify Requests / Responses
|
||||
|
||||
* Examples:
|
||||
- search
|
||||
- modify
|
||||
|
||||
* Tests Implemented:
|
||||
- Filter Compile / Decompile
|
||||
|
||||
* TODO:
|
||||
- Add Requests / Responses
|
||||
- Delete Requests / Responses
|
||||
- Modify DN Requests / Responses
|
||||
- Compare Requests / Responses
|
||||
- Implement Tests / Benchmarks
|
||||
|
||||
|
||||
This feature is disabled at the moment, because in some cases the "Search Request Done" packet will be handled before the last "Search Request Entry":
|
||||
- Mulitple internal goroutines to handle network traffic
|
||||
Makes library goroutine safe
|
||||
Can perform multiple search requests at the same time and return
|
||||
the results to the proper goroutine. All requests are blocking
|
||||
requests, so the goroutine does not need special handling
|
|
@ -0,0 +1,55 @@
|
|||
// 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"
|
||||
|
||||
"github.com/vanackere/asn1-ber"
|
||||
)
|
||||
|
||||
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, int64(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
|
||||
}
|
|
@ -0,0 +1,281 @@
|
|||
// 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"
|
||||
"log"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/vanackere/asn1-ber"
|
||||
)
|
||||
|
||||
const (
|
||||
MessageQuit = 0
|
||||
MessageRequest = 1
|
||||
MessageResponse = 2
|
||||
MessageFinish = 3
|
||||
)
|
||||
|
||||
type messagePacket struct {
|
||||
Op int
|
||||
MessageID uint64
|
||||
Packet *ber.Packet
|
||||
Channel chan *ber.Packet
|
||||
}
|
||||
|
||||
// Conn represents an LDAP Connection
|
||||
type Conn struct {
|
||||
conn net.Conn
|
||||
isTLS bool
|
||||
Debug debugging
|
||||
chanConfirm chan bool
|
||||
chanResults map[uint64]chan *ber.Packet
|
||||
chanMessage chan *messagePacket
|
||||
chanMessageID chan uint64
|
||||
wgSender sync.WaitGroup
|
||||
chanDone chan struct{}
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// 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.Dial(network, addr)
|
||||
if err != nil {
|
||||
return nil, NewError(ErrorNetwork, err)
|
||||
}
|
||||
conn := NewConn(c)
|
||||
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) {
|
||||
c, err := tls.Dial(network, addr, config)
|
||||
if err != nil {
|
||||
return nil, NewError(ErrorNetwork, err)
|
||||
}
|
||||
conn := NewConn(c)
|
||||
conn.isTLS = true
|
||||
conn.start()
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// NewConn returns a new Conn using conn for network I/O.
|
||||
func NewConn(conn net.Conn) *Conn {
|
||||
return &Conn{
|
||||
conn: conn,
|
||||
chanConfirm: make(chan bool),
|
||||
chanMessageID: make(chan uint64),
|
||||
chanMessage: make(chan *messagePacket, 10),
|
||||
chanResults: map[uint64]chan *ber.Packet{},
|
||||
chanDone: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Conn) start() {
|
||||
go l.reader()
|
||||
go l.processMessages()
|
||||
}
|
||||
|
||||
// Close closes the connection.
|
||||
func (l *Conn) Close() {
|
||||
l.once.Do(func() {
|
||||
close(l.chanDone)
|
||||
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.chanDone
|
||||
}
|
||||
|
||||
// Returns the next available messageID
|
||||
func (l *Conn) nextMessageID() uint64 {
|
||||
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, int64(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)
|
||||
|
||||
_, err := l.conn.Write(packet.Bytes())
|
||||
if err != nil {
|
||||
return NewError(ErrorNetwork, err)
|
||||
}
|
||||
|
||||
packet, err = ber.ReadPacket(l.conn)
|
||||
if err != nil {
|
||||
return NewError(ErrorNetwork, err)
|
||||
}
|
||||
|
||||
if l.Debug {
|
||||
if err := addLDAPDescriptions(packet); err != nil {
|
||||
return err
|
||||
}
|
||||
ber.PrintPacket(packet)
|
||||
}
|
||||
|
||||
if packet.Children[1].Children[0].Value.(int64) == 0 {
|
||||
conn := tls.Client(l.conn, config)
|
||||
l.isTLS = true
|
||||
l.conn = conn
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Conn) closing() bool {
|
||||
select {
|
||||
case <-l.chanDone:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Conn) sendMessage(packet *ber.Packet) (chan *ber.Packet, error) {
|
||||
if l.closing() {
|
||||
return nil, NewError(ErrorNetwork, errors.New("ldap: connection closed"))
|
||||
}
|
||||
out := make(chan *ber.Packet)
|
||||
message := &messagePacket{
|
||||
Op: MessageRequest,
|
||||
MessageID: uint64(packet.Children[0].Value.(int64)),
|
||||
Packet: packet,
|
||||
Channel: out,
|
||||
}
|
||||
l.sendProcessMessage(message)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (l *Conn) finishMessage(messageID uint64) {
|
||||
if l.closing() {
|
||||
return
|
||||
}
|
||||
message := &messagePacket{
|
||||
Op: MessageFinish,
|
||||
MessageID: messageID,
|
||||
}
|
||||
l.sendProcessMessage(message)
|
||||
}
|
||||
|
||||
func (l *Conn) sendProcessMessage(message *messagePacket) bool {
|
||||
l.wgSender.Add(1)
|
||||
defer l.wgSender.Done()
|
||||
|
||||
if l.closing() {
|
||||
return false
|
||||
}
|
||||
l.chanMessage <- message
|
||||
return true
|
||||
}
|
||||
|
||||
func (l *Conn) processMessages() {
|
||||
defer func() {
|
||||
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 uint64 = 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() {
|
||||
defer func() {
|
||||
l.Close()
|
||||
}()
|
||||
|
||||
for {
|
||||
packet, err := ber.ReadPacket(l.conn)
|
||||
if err != nil {
|
||||
l.Debug.Printf("reader: %s", err.Error())
|
||||
return
|
||||
}
|
||||
addLDAPDescriptions(packet)
|
||||
message := &messagePacket{
|
||||
Op: MessageResponse,
|
||||
MessageID: uint64(packet.Children[0].Value.(int64)),
|
||||
Packet: packet,
|
||||
}
|
||||
if !l.sendProcessMessage(message) {
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
// 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"
|
||||
|
||||
"github.com/vanackere/asn1-ber"
|
||||
)
|
||||
|
||||
const (
|
||||
ControlTypePaging = "1.2.840.113556.1.4.319"
|
||||
)
|
||||
|
||||
var ControlTypeMap = map[string]string{
|
||||
ControlTypePaging: "Paging",
|
||||
}
|
||||
|
||||
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, 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, int64(c.PagingSize), "Paging Size"))
|
||||
cookie := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Cookie")
|
||||
cookie.Value = c.Cookie
|
||||
cookie.Data.Write(c.Cookie)
|
||||
seq.AppendChild(cookie)
|
||||
p2.AppendChild(seq)
|
||||
|
||||
packet.AppendChild(p2)
|
||||
return packet
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
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 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
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package ldap
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/vanackere/asn1-ber"
|
||||
)
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
63
Godeps/_workspace/src/github.com/vanackere/ldap/examples/enterprise.ldif
generated
vendored
Normal file
63
Godeps/_workspace/src/github.com/vanackere/ldap/examples/enterprise.ldif
generated
vendored
Normal file
|
@ -0,0 +1,63 @@
|
|||
dn: dc=enterprise,dc=org
|
||||
objectClass: dcObject
|
||||
objectClass: organization
|
||||
o: acme
|
||||
|
||||
dn: cn=admin,dc=enterprise,dc=org
|
||||
objectClass: person
|
||||
cn: admin
|
||||
sn: admin
|
||||
description: "LDAP Admin"
|
||||
|
||||
dn: ou=crew,dc=enterprise,dc=org
|
||||
ou: crew
|
||||
objectClass: organizationalUnit
|
||||
|
||||
|
||||
dn: cn=kirkj,ou=crew,dc=enterprise,dc=org
|
||||
cn: kirkj
|
||||
sn: Kirk
|
||||
gn: James Tiberius
|
||||
mail: james.kirk@enterprise.org
|
||||
objectClass: inetOrgPerson
|
||||
|
||||
dn: cn=spock,ou=crew,dc=enterprise,dc=org
|
||||
cn: spock
|
||||
sn: Spock
|
||||
mail: spock@enterprise.org
|
||||
objectClass: inetOrgPerson
|
||||
|
||||
dn: cn=mccoyl,ou=crew,dc=enterprise,dc=org
|
||||
cn: mccoyl
|
||||
sn: McCoy
|
||||
gn: Leonard
|
||||
mail: leonard.mccoy@enterprise.org
|
||||
objectClass: inetOrgPerson
|
||||
|
||||
dn: cn=scottm,ou=crew,dc=enterprise,dc=org
|
||||
cn: scottm
|
||||
sn: Scott
|
||||
gn: Montgomery
|
||||
mail: Montgomery.scott@enterprise.org
|
||||
objectClass: inetOrgPerson
|
||||
|
||||
dn: cn=uhuran,ou=crew,dc=enterprise,dc=org
|
||||
cn: uhuran
|
||||
sn: Uhura
|
||||
gn: Nyota
|
||||
mail: nyota.uhura@enterprise.org
|
||||
objectClass: inetOrgPerson
|
||||
|
||||
dn: cn=suluh,ou=crew,dc=enterprise,dc=org
|
||||
cn: suluh
|
||||
sn: Sulu
|
||||
gn: Hikaru
|
||||
mail: hikaru.sulu@enterprise.org
|
||||
objectClass: inetOrgPerson
|
||||
|
||||
dn: cn=chekovp,ou=crew,dc=enterprise,dc=org
|
||||
cn: chekovp
|
||||
sn: Chekov
|
||||
gn: pavel
|
||||
mail: pavel.chekov@enterprise.org
|
||||
objectClass: inetOrgPerson
|
91
Godeps/_workspace/src/github.com/vanackere/ldap/examples/modify.go
generated
vendored
Normal file
91
Godeps/_workspace/src/github.com/vanackere/ldap/examples/modify.go
generated
vendored
Normal file
|
@ -0,0 +1,91 @@
|
|||
// +build ignore
|
||||
|
||||
// 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.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/vanackere/ldap"
|
||||
)
|
||||
|
||||
var (
|
||||
LdapServer string = "localhost"
|
||||
LdapPort uint16 = 389
|
||||
BaseDN string = "dc=enterprise,dc=org"
|
||||
BindDN string = "cn=admin,dc=enterprise,dc=org"
|
||||
BindPW string = "enterprise"
|
||||
Filter string = "(cn=kirkj)"
|
||||
)
|
||||
|
||||
func search(l *ldap.Conn, filter string, attributes []string) (*ldap.Entry, *ldap.Error) {
|
||||
search := ldap.NewSearchRequest(
|
||||
BaseDN,
|
||||
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
|
||||
filter,
|
||||
attributes,
|
||||
nil)
|
||||
|
||||
sr, err := l.Search(search)
|
||||
if err != nil {
|
||||
log.Fatalf("ERROR: %s\n", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Printf("Search: %s -> num of entries = %d\n", search.Filter, len(sr.Entries))
|
||||
if len(sr.Entries) == 0 {
|
||||
return nil, ldap.NewError(ldap.ErrorDebugging, errors.New(fmt.Sprintf("no entries found for: %s", filter)))
|
||||
}
|
||||
return sr.Entries[0], nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", LdapServer, LdapPort))
|
||||
if err != nil {
|
||||
log.Fatalf("ERROR: %s\n", err.Error())
|
||||
}
|
||||
defer l.Close()
|
||||
// l.Debug = true
|
||||
|
||||
l.Bind(BindDN, BindPW)
|
||||
|
||||
log.Printf("The Search for Kirk ... %s\n", Filter)
|
||||
entry, err := search(l, Filter, []string{})
|
||||
if err != nil {
|
||||
log.Fatal("could not get entry")
|
||||
}
|
||||
entry.PrettyPrint(0)
|
||||
|
||||
log.Printf("modify the mail address and add a description ... \n")
|
||||
modify := ldap.NewModifyRequest(entry.DN)
|
||||
modify.Add("description", []string{"Captain of the USS Enterprise"})
|
||||
modify.Replace("mail", []string{"captain@enterprise.org"})
|
||||
if err := l.Modify(modify); err != nil {
|
||||
log.Fatalf("ERROR: %s\n", err.Error())
|
||||
}
|
||||
|
||||
entry, err = search(l, Filter, []string{})
|
||||
if err != nil {
|
||||
log.Fatal("could not get entry")
|
||||
}
|
||||
entry.PrettyPrint(0)
|
||||
|
||||
log.Printf("reset the entry ... \n")
|
||||
modify = ldap.NewModifyRequest(entry.DN)
|
||||
modify.Delete("description", []string{})
|
||||
modify.Replace("mail", []string{"james.kirk@enterprise.org"})
|
||||
if err := l.Modify(modify); err != nil {
|
||||
log.Fatalf("ERROR: %s\n", err.Error())
|
||||
}
|
||||
|
||||
entry, err = search(l, Filter, []string{})
|
||||
if err != nil {
|
||||
log.Fatal("could not get entry")
|
||||
}
|
||||
entry.PrettyPrint(0)
|
||||
}
|
54
Godeps/_workspace/src/github.com/vanackere/ldap/examples/search.go
generated
vendored
Normal file
54
Godeps/_workspace/src/github.com/vanackere/ldap/examples/search.go
generated
vendored
Normal file
|
@ -0,0 +1,54 @@
|
|||
// +build ignore
|
||||
|
||||
// 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.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/vanackere/ldap"
|
||||
)
|
||||
|
||||
var (
|
||||
ldapServer string = "adserver"
|
||||
ldapPort uint16 = 3268
|
||||
baseDN string = "dc=*,dc=*"
|
||||
filter string = "(&(objectClass=user)(sAMAccountName=*)(memberOf=CN=*,OU=*,DC=*,DC=*))"
|
||||
Attributes []string = []string{"memberof"}
|
||||
user string = "*"
|
||||
passwd string = "*"
|
||||
)
|
||||
|
||||
func main() {
|
||||
l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort))
|
||||
if err != nil {
|
||||
log.Fatalf("ERROR: %s\n", err.Error())
|
||||
}
|
||||
defer l.Close()
|
||||
// l.Debug = true
|
||||
|
||||
err = l.Bind(user, passwd)
|
||||
if err != nil {
|
||||
log.Printf("ERROR: Cannot bind: %s\n", err.Error())
|
||||
return
|
||||
}
|
||||
search := ldap.NewSearchRequest(
|
||||
baseDN,
|
||||
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
|
||||
filter,
|
||||
Attributes,
|
||||
nil)
|
||||
|
||||
sr, err := l.Search(search)
|
||||
if err != nil {
|
||||
log.Fatalf("ERROR: %s\n", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Search: %s -> num of entries = %d\n", search.Filter, len(sr.Entries))
|
||||
sr.PrettyPrint(0)
|
||||
}
|
47
Godeps/_workspace/src/github.com/vanackere/ldap/examples/searchSSL.go
generated
vendored
Normal file
47
Godeps/_workspace/src/github.com/vanackere/ldap/examples/searchSSL.go
generated
vendored
Normal file
|
@ -0,0 +1,47 @@
|
|||
// +build ignore
|
||||
|
||||
// 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.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/vanackere/ldap"
|
||||
)
|
||||
|
||||
var (
|
||||
LdapServer string = "localhost"
|
||||
LdapPort uint16 = 636
|
||||
BaseDN string = "dc=enterprise,dc=org"
|
||||
Filter string = "(cn=kirkj)"
|
||||
Attributes []string = []string{"mail"}
|
||||
)
|
||||
|
||||
func main() {
|
||||
l, err := ldap.DialSSL("tcp", fmt.Sprintf("%s:%d", LdapServer, LdapPort), nil)
|
||||
if err != nil {
|
||||
log.Fatalf("ERROR: %s\n", err.String())
|
||||
}
|
||||
defer l.Close()
|
||||
// l.Debug = true
|
||||
|
||||
search := ldap.NewSearchRequest(
|
||||
BaseDN,
|
||||
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
|
||||
Filter,
|
||||
Attributes,
|
||||
nil)
|
||||
|
||||
sr, err := l.Search(search)
|
||||
if err != nil {
|
||||
log.Fatalf("ERROR: %s\n", err.String())
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Search: %s -> num of entries = %d\n", search.Filter, len(sr.Entries))
|
||||
sr.PrettyPrint(0)
|
||||
}
|
47
Godeps/_workspace/src/github.com/vanackere/ldap/examples/searchTLS.go
generated
vendored
Normal file
47
Godeps/_workspace/src/github.com/vanackere/ldap/examples/searchTLS.go
generated
vendored
Normal file
|
@ -0,0 +1,47 @@
|
|||
// +build ignore
|
||||
|
||||
// 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.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/vanackere/ldap"
|
||||
)
|
||||
|
||||
var (
|
||||
LdapServer string = "localhost"
|
||||
LdapPort uint16 = 389
|
||||
BaseDN string = "dc=enterprise,dc=org"
|
||||
Filter string = "(cn=kirkj)"
|
||||
Attributes []string = []string{"mail"}
|
||||
)
|
||||
|
||||
func main() {
|
||||
l, err := ldap.DialTLS("tcp", fmt.Sprintf("%s:%d", LdapServer, LdapPort), nil)
|
||||
if err != nil {
|
||||
log.Fatalf("ERROR: %s\n", err.Error())
|
||||
}
|
||||
defer l.Close()
|
||||
// l.Debug = true
|
||||
|
||||
search := ldap.NewSearchRequest(
|
||||
BaseDN,
|
||||
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
|
||||
Filter,
|
||||
Attributes,
|
||||
nil)
|
||||
|
||||
sr, err := l.Search(search)
|
||||
if err != nil {
|
||||
log.Fatalf("ERROR: %s\n", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Search: %s -> num of entries = %d\n", search.Filter, len(sr.Entries))
|
||||
sr.PrettyPrint(0)
|
||||
}
|
67
Godeps/_workspace/src/github.com/vanackere/ldap/examples/slapd.conf
generated
vendored
Normal file
67
Godeps/_workspace/src/github.com/vanackere/ldap/examples/slapd.conf
generated
vendored
Normal file
|
@ -0,0 +1,67 @@
|
|||
#
|
||||
# See slapd.conf(5) for details on configuration options.
|
||||
# This file should NOT be world readable.
|
||||
#
|
||||
include /private/etc/openldap/schema/core.schema
|
||||
include /private/etc/openldap/schema/cosine.schema
|
||||
include /private/etc/openldap/schema/inetorgperson.schema
|
||||
|
||||
# Define global ACLs to disable default read access.
|
||||
|
||||
# Do not enable referrals until AFTER you have a working directory
|
||||
# service AND an understanding of referrals.
|
||||
#referral ldap://root.openldap.org
|
||||
|
||||
pidfile /private/var/db/openldap/run/slapd.pid
|
||||
argsfile /private/var/db/openldap/run/slapd.args
|
||||
|
||||
# Load dynamic backend modules:
|
||||
# modulepath /usr/libexec/openldap
|
||||
# moduleload back_bdb.la
|
||||
# moduleload back_hdb.la
|
||||
# moduleload back_ldap.la
|
||||
|
||||
# Sample security restrictions
|
||||
# Require integrity protection (prevent hijacking)
|
||||
# Require 112-bit (3DES or better) encryption for updates
|
||||
# Require 63-bit encryption for simple bind
|
||||
# security ssf=1 update_ssf=112 simple_bind=64
|
||||
|
||||
# Sample access control policy:
|
||||
# Root DSE: allow anyone to read it
|
||||
# Subschema (sub)entry DSE: allow anyone to read it
|
||||
# Other DSEs:
|
||||
# Allow self write access
|
||||
# Allow authenticated users read access
|
||||
# Allow anonymous users to authenticate
|
||||
# Directives needed to implement policy:
|
||||
# access to dn.base="" by * read
|
||||
# access to dn.base="cn=Subschema" by * read
|
||||
# access to *
|
||||
# by self write
|
||||
# by users read
|
||||
# by anonymous auth
|
||||
#
|
||||
# if no access controls are present, the default policy
|
||||
# allows anyone and everyone to read anything but restricts
|
||||
# updates to rootdn. (e.g., "access to * by * read")
|
||||
#
|
||||
# rootdn can always read and write EVERYTHING!
|
||||
|
||||
#######################################################################
|
||||
# BDB database definitions
|
||||
#######################################################################
|
||||
|
||||
database bdb
|
||||
suffix "dc=enterprise,dc=org"
|
||||
rootdn "cn=admin,dc=enterprise,dc=org"
|
||||
# Cleartext passwords, especially for the rootdn, should
|
||||
# be avoid. See slappasswd(8) and slapd.conf(5) for details.
|
||||
# Use of strong authentication encouraged.
|
||||
rootpw {SSHA}laO00HsgszhK1O0Z5qR0/i/US69Osfeu
|
||||
# The database directory MUST exist prior to running slapd AND
|
||||
# should only be accessible by the slapd and slap tools.
|
||||
# Mode 700 recommended.
|
||||
directory /private/var/db/openldap/openldap-data
|
||||
# Indices to maintain
|
||||
index objectClass eq
|
|
@ -0,0 +1,247 @@
|
|||
// 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"
|
||||
"fmt"
|
||||
|
||||
"github.com/vanackere/asn1-ber"
|
||||
)
|
||||
|
||||
const (
|
||||
FilterAnd ber.Tag = 0
|
||||
FilterOr ber.Tag = 1
|
||||
FilterNot ber.Tag = 2
|
||||
FilterEqualityMatch ber.Tag = 3
|
||||
FilterSubstrings ber.Tag = 4
|
||||
FilterGreaterOrEqual ber.Tag = 5
|
||||
FilterLessOrEqual ber.Tag = 6
|
||||
FilterPresent ber.Tag = 7
|
||||
FilterApproxMatch ber.Tag = 8
|
||||
FilterExtensibleMatch ber.Tag = 9
|
||||
)
|
||||
|
||||
var filterMap = map[ber.Tag]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
|
||||
)
|
||||
|
||||
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 += "="
|
||||
switch packet.Children[1].Children[0].Tag {
|
||||
case FilterSubstringsInitial:
|
||||
ret += ber.DecodeString(packet.Children[1].Children[0].Data.Bytes()) + "*"
|
||||
case FilterSubstringsAny:
|
||||
ret += "*" + ber.DecodeString(packet.Children[1].Children[0].Data.Bytes()) + "*"
|
||||
case FilterSubstringsFinal:
|
||||
ret += "*" + ber.DecodeString(packet.Children[1].Children[0].Data.Bytes())
|
||||
}
|
||||
case FilterEqualityMatch:
|
||||
ret += ber.DecodeString(packet.Children[0].Data.Bytes())
|
||||
ret += "="
|
||||
ret += ber.DecodeString(packet.Children[1].Data.Bytes())
|
||||
case FilterGreaterOrEqual:
|
||||
ret += ber.DecodeString(packet.Children[0].Data.Bytes())
|
||||
ret += ">="
|
||||
ret += ber.DecodeString(packet.Children[1].Data.Bytes())
|
||||
case FilterLessOrEqual:
|
||||
ret += ber.DecodeString(packet.Children[0].Data.Bytes())
|
||||
ret += "<="
|
||||
ret += 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 += ber.DecodeString(packet.Children[1].Data.Bytes())
|
||||
}
|
||||
|
||||
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
|
||||
var err error
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = NewError(ErrorFilterCompile, errors.New("ldap: error compiling filter"))
|
||||
}
|
||||
}()
|
||||
|
||||
newPos := pos
|
||||
switch filter[pos] {
|
||||
case '(':
|
||||
packet, newPos, err = compileFilter(filter, pos+1)
|
||||
newPos++
|
||||
return packet, newPos, err
|
||||
case '&':
|
||||
packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterAnd, nil, filterMap[FilterAnd])
|
||||
newPos, err = compileFilterSet(filter, pos+1, packet)
|
||||
return packet, newPos, err
|
||||
case '|':
|
||||
packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterOr, nil, filterMap[FilterOr])
|
||||
newPos, err = compileFilterSet(filter, pos+1, 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+1)
|
||||
packet.AppendChild(child)
|
||||
return packet, newPos, err
|
||||
default:
|
||||
attribute := ""
|
||||
condition := ""
|
||||
for newPos < len(filter) && filter[newPos] != ')' {
|
||||
switch {
|
||||
case packet != nil:
|
||||
condition += fmt.Sprintf("%c", filter[newPos])
|
||||
case filter[newPos] == '=':
|
||||
packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterEqualityMatch, nil, filterMap[FilterEqualityMatch])
|
||||
case filter[newPos] == '>' && filter[newPos+1] == '=':
|
||||
packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterGreaterOrEqual, nil, filterMap[FilterGreaterOrEqual])
|
||||
newPos++
|
||||
case filter[newPos] == '<' && filter[newPos+1] == '=':
|
||||
packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterLessOrEqual, nil, filterMap[FilterLessOrEqual])
|
||||
newPos++
|
||||
case filter[newPos] == '~' && filter[newPos+1] == '=':
|
||||
packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterApproxMatch, nil, filterMap[FilterLessOrEqual])
|
||||
newPos++
|
||||
case packet == nil:
|
||||
attribute += fmt.Sprintf("%c", filter[newPos])
|
||||
}
|
||||
newPos++
|
||||
}
|
||||
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
|
||||
}
|
||||
// Handle FilterEqualityMatch as a separate case (is primitive, not constructed like the other filters)
|
||||
if packet.Tag == FilterEqualityMatch && condition == "*" {
|
||||
packet.TagType = ber.TypePrimitive
|
||||
packet.Tag = FilterPresent
|
||||
packet.Description = filterMap[packet.Tag]
|
||||
packet.Data.WriteString(attribute)
|
||||
return packet, newPos + 1, nil
|
||||
}
|
||||
packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute, "Attribute"))
|
||||
switch {
|
||||
case packet.Tag == FilterEqualityMatch && condition[0] == '*' && condition[len(condition)-1] == '*':
|
||||
// Any
|
||||
packet.Tag = FilterSubstrings
|
||||
packet.Description = filterMap[packet.Tag]
|
||||
seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Substrings")
|
||||
seq.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, FilterSubstringsAny, condition[1:len(condition)-1], "Any Substring"))
|
||||
packet.AppendChild(seq)
|
||||
case packet.Tag == FilterEqualityMatch && condition[0] == '*':
|
||||
// Final
|
||||
packet.Tag = FilterSubstrings
|
||||
packet.Description = filterMap[packet.Tag]
|
||||
seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Substrings")
|
||||
seq.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, FilterSubstringsFinal, condition[1:], "Final Substring"))
|
||||
packet.AppendChild(seq)
|
||||
case packet.Tag == FilterEqualityMatch && condition[len(condition)-1] == '*':
|
||||
// Initial
|
||||
packet.Tag = FilterSubstrings
|
||||
packet.Description = filterMap[packet.Tag]
|
||||
seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Substrings")
|
||||
seq.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, FilterSubstringsInitial, condition[:len(condition)-1], "Initial Substring"))
|
||||
packet.AppendChild(seq)
|
||||
default:
|
||||
packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, condition, "Condition"))
|
||||
}
|
||||
newPos++
|
||||
return packet, newPos, err
|
||||
}
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
package ldap
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/vanackere/asn1-ber"
|
||||
)
|
||||
|
||||
type compileTest struct {
|
||||
filterStr string
|
||||
filterType ber.Tag
|
||||
}
|
||||
|
||||
var testFilters = []compileTest{
|
||||
compileTest{filterStr: "(&(sn=Miller)(givenName=Bob))", filterType: FilterAnd},
|
||||
compileTest{filterStr: "(|(sn=Miller)(givenName=Bob))", filterType: FilterOr},
|
||||
compileTest{filterStr: "(!(sn=Miller))", filterType: FilterNot},
|
||||
compileTest{filterStr: "(sn=Miller)", filterType: FilterEqualityMatch},
|
||||
compileTest{filterStr: "(sn=Mill*)", filterType: FilterSubstrings},
|
||||
compileTest{filterStr: "(sn=*Mill)", filterType: FilterSubstrings},
|
||||
compileTest{filterStr: "(sn=*Mill*)", filterType: FilterSubstrings},
|
||||
compileTest{filterStr: "(sn>=Miller)", filterType: FilterGreaterOrEqual},
|
||||
compileTest{filterStr: "(sn<=Miller)", filterType: FilterLessOrEqual},
|
||||
compileTest{filterStr: "(sn=*)", filterType: FilterPresent},
|
||||
compileTest{filterStr: "(sn~=Miller)", filterType: FilterApproxMatch},
|
||||
// compileTest{ filterStr: "()", filterType: FilterExtensibleMatch },
|
||||
}
|
||||
|
||||
func TestFilter(t *testing.T) {
|
||||
// Test Compiler and Decompiler
|
||||
for _, i := range testFilters {
|
||||
filter, err := CompileFilter(i.filterStr)
|
||||
if err != nil {
|
||||
t.Errorf("Problem compiling %s - %s", i.filterStr, err.Error())
|
||||
} else if filter.Tag != i.filterType {
|
||||
t.Errorf("%q Expected %q got %q", i.filterStr, filterMap[i.filterType], filterMap[filter.Tag])
|
||||
} else {
|
||||
o, err := DecompileFilter(filter)
|
||||
if err != nil {
|
||||
t.Errorf("Problem compiling %s - %s", i.filterStr, err.Error())
|
||||
} else if i.filterStr != o {
|
||||
t.Errorf("%q expected, got %q", i.filterStr, o)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type binTestFilter struct {
|
||||
bin []byte
|
||||
str string
|
||||
}
|
||||
|
||||
var binTestFilters = []binTestFilter{
|
||||
{bin: []byte{0x87, 0x06, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72}, str: "(member=*)"},
|
||||
}
|
||||
|
||||
func TestFiltersDecode(t *testing.T) {
|
||||
for i, test := range binTestFilters {
|
||||
p := ber.DecodePacket(test.bin)
|
||||
if filter, err := DecompileFilter(p); err != nil {
|
||||
t.Errorf("binTestFilters[%d], DecompileFilter returned : %s", i, err)
|
||||
} else if filter != test.str {
|
||||
t.Errorf("binTestFilters[%d], %q expected, got %q", i, test.str, filter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFiltersEncode(t *testing.T) {
|
||||
for i, test := range binTestFilters {
|
||||
p, err := CompileFilter(test.str)
|
||||
if err != nil {
|
||||
t.Errorf("binTestFilters[%d], CompileFilter returned : %s", i, err)
|
||||
continue
|
||||
}
|
||||
b := p.Bytes()
|
||||
if !reflect.DeepEqual(b, test.bin) {
|
||||
t.Errorf("binTestFilters[%d], %q expected for CompileFilter(%q), got %q", i, test.bin, test.str, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFilterCompile(b *testing.B) {
|
||||
b.StopTimer()
|
||||
filters := make([]string, len(testFilters))
|
||||
|
||||
// Test Compiler and Decompiler
|
||||
for idx, i := range testFilters {
|
||||
filters[idx] = i.filterStr
|
||||
}
|
||||
|
||||
maxIdx := len(filters)
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
CompileFilter(filters[i%maxIdx])
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFilterDecompile(b *testing.B) {
|
||||
b.StopTimer()
|
||||
filters := make([]*ber.Packet, len(testFilters))
|
||||
|
||||
// Test Compiler and Decompiler
|
||||
for idx, i := range testFilters {
|
||||
filters[idx], _ = CompileFilter(i.filterStr)
|
||||
}
|
||||
|
||||
maxIdx := len(filters)
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
DecompileFilter(filters[i%maxIdx])
|
||||
}
|
||||
}
|
|
@ -0,0 +1,303 @@
|
|||
// 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"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/vanackere/asn1-ber"
|
||||
)
|
||||
|
||||
// LDAP Application Codes
|
||||
const (
|
||||
ApplicationBindRequest ber.Tag = 0
|
||||
ApplicationBindResponse ber.Tag = 1
|
||||
ApplicationUnbindRequest ber.Tag = 2
|
||||
ApplicationSearchRequest ber.Tag = 3
|
||||
ApplicationSearchResultEntry ber.Tag = 4
|
||||
ApplicationSearchResultDone ber.Tag = 5
|
||||
ApplicationModifyRequest ber.Tag = 6
|
||||
ApplicationModifyResponse ber.Tag = 7
|
||||
ApplicationAddRequest ber.Tag = 8
|
||||
ApplicationAddResponse ber.Tag = 9
|
||||
ApplicationDelRequest ber.Tag = 10
|
||||
ApplicationDelResponse ber.Tag = 11
|
||||
ApplicationModifyDNRequest ber.Tag = 12
|
||||
ApplicationModifyDNResponse ber.Tag = 13
|
||||
ApplicationCompareRequest ber.Tag = 14
|
||||
ApplicationCompareResponse ber.Tag = 15
|
||||
ApplicationAbandonRequest ber.Tag = 16
|
||||
ApplicationSearchResultReference ber.Tag = 19
|
||||
ApplicationExtendedRequest ber.Tag = 23
|
||||
ApplicationExtendedResponse ber.Tag = 24
|
||||
)
|
||||
|
||||
var ApplicationMap = map[ber.Tag]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 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
|
||||
)
|
||||
|
||||
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",
|
||||
}
|
||||
|
||||
// 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 := 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func addRequestDescriptions(packet *ber.Packet) {
|
||||
packet.Description = "LDAP Request"
|
||||
packet.Children[0].Description = "Message ID"
|
||||
packet.Children[1].Description = ApplicationMap[packet.Children[1].Tag]
|
||||
if len(packet.Children) == 3 {
|
||||
addControlDescriptions(packet.Children[2])
|
||||
}
|
||||
}
|
||||
|
||||
func addDefaultLDAPResponseDescriptions(packet *ber.Packet) {
|
||||
resultCode := packet.Children[1].Children[0].Value.(int64)
|
||||
packet.Children[1].Children[0].Description = "Result Code (" + LDAPResultCodeMap[uint8(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
|
||||
}
|
||||
|
||||
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 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 {
|
||||
return uint8(response.Children[0].Value.(int64)), response.Children[2].Value.(string)
|
||||
}
|
||||
}
|
||||
|
||||
return ErrorNetwork, "Invalid packet format"
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
package ldap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var ldapServer = "ldap.itd.umich.edu"
|
||||
var ldapPort = uint16(389)
|
||||
var baseDN = "dc=umich,dc=edu"
|
||||
var filter = []string{
|
||||
"(cn=cis-fac)",
|
||||
"(&(objectclass=rfc822mailgroup)(cn=*Computer*))",
|
||||
"(&(objectclass=rfc822mailgroup)(cn=*Mathematics*))"}
|
||||
var attributes = []string{
|
||||
"cn",
|
||||
"description"}
|
||||
|
||||
func TestConnect(t *testing.T) {
|
||||
fmt.Printf("TestConnect: starting...\n")
|
||||
l, err := Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort))
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
return
|
||||
}
|
||||
defer l.Close()
|
||||
fmt.Printf("TestConnect: finished...\n")
|
||||
}
|
||||
|
||||
func TestSearch(t *testing.T) {
|
||||
fmt.Printf("TestSearch: starting...\n")
|
||||
l, err := Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort))
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
return
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
searchRequest := NewSearchRequest(
|
||||
baseDN,
|
||||
ScopeWholeSubtree, DerefAlways, 0, 0, false,
|
||||
filter[0],
|
||||
attributes,
|
||||
nil)
|
||||
|
||||
sr, err := l.Search(searchRequest)
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("TestSearch: %s -> num of entries = %d\n", searchRequest.Filter, len(sr.Entries))
|
||||
}
|
||||
|
||||
func TestSearchWithPaging(t *testing.T) {
|
||||
fmt.Printf("TestSearchWithPaging: starting...\n")
|
||||
l, err := Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort))
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
return
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
err = l.Bind("", "")
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
searchRequest := NewSearchRequest(
|
||||
baseDN,
|
||||
ScopeWholeSubtree, DerefAlways, 0, 0, false,
|
||||
filter[1],
|
||||
attributes,
|
||||
nil)
|
||||
sr, err := l.SearchWithPaging(searchRequest, 5)
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("TestSearchWithPaging: %s -> num of entries = %d\n", searchRequest.Filter, len(sr.Entries))
|
||||
}
|
||||
|
||||
func testMultiGoroutineSearch(t *testing.T, l *Conn, results chan *SearchResult, i int) {
|
||||
searchRequest := NewSearchRequest(
|
||||
baseDN,
|
||||
ScopeWholeSubtree, DerefAlways, 0, 0, false,
|
||||
filter[i],
|
||||
attributes,
|
||||
nil)
|
||||
sr, err := l.Search(searchRequest)
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
results <- nil
|
||||
return
|
||||
}
|
||||
results <- sr
|
||||
}
|
||||
|
||||
func TestMultiGoroutineSearch(t *testing.T) {
|
||||
fmt.Printf("TestMultiGoroutineSearch: starting...\n")
|
||||
l, err := Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort))
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
return
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
results := make([]chan *SearchResult, len(filter))
|
||||
for i := range filter {
|
||||
results[i] = make(chan *SearchResult)
|
||||
go testMultiGoroutineSearch(t, l, results[i], i)
|
||||
}
|
||||
for i := range filter {
|
||||
sr := <-results[i]
|
||||
if sr == nil {
|
||||
t.Errorf("Did not receive results from goroutine for %q", filter[i])
|
||||
} else {
|
||||
fmt.Printf("TestMultiGoroutineSearch(%d): %s -> num of entries = %d\n", i, filter[i], len(sr.Entries))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 <attributedescription>
|
||||
// -- [RFC4512]
|
||||
//
|
||||
// AttributeValue ::= OCTET STRING
|
||||
//
|
||||
|
||||
package ldap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
|
||||
"github.com/vanackere/asn1-ber"
|
||||
)
|
||||
|
||||
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, int64(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, int64(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, int64(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, int64(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
|
||||
}
|
|
@ -0,0 +1,350 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
//
|
||||
// File contains Search functionality
|
||||
//
|
||||
// https://tools.ietf.org/html/rfc4511
|
||||
//
|
||||
// SearchRequest ::= [APPLICATION 3] SEQUENCE {
|
||||
// baseObject LDAPDN,
|
||||
// scope ENUMERATED {
|
||||
// baseObject (0),
|
||||
// singleLevel (1),
|
||||
// wholeSubtree (2),
|
||||
// ... },
|
||||
// derefAliases ENUMERATED {
|
||||
// neverDerefAliases (0),
|
||||
// derefInSearching (1),
|
||||
// derefFindingBaseObj (2),
|
||||
// derefAlways (3) },
|
||||
// sizeLimit INTEGER (0 .. maxInt),
|
||||
// timeLimit INTEGER (0 .. maxInt),
|
||||
// typesOnly BOOLEAN,
|
||||
// filter Filter,
|
||||
// attributes AttributeSelection }
|
||||
//
|
||||
// AttributeSelection ::= SEQUENCE OF selector LDAPString
|
||||
// -- The LDAPString is constrained to
|
||||
// -- <attributeSelector> in Section 4.5.1.8
|
||||
//
|
||||
// Filter ::= CHOICE {
|
||||
// and [0] SET SIZE (1..MAX) OF filter Filter,
|
||||
// or [1] SET SIZE (1..MAX) OF filter Filter,
|
||||
// not [2] Filter,
|
||||
// equalityMatch [3] AttributeValueAssertion,
|
||||
// substrings [4] SubstringFilter,
|
||||
// greaterOrEqual [5] AttributeValueAssertion,
|
||||
// lessOrEqual [6] AttributeValueAssertion,
|
||||
// present [7] AttributeDescription,
|
||||
// approxMatch [8] AttributeValueAssertion,
|
||||
// extensibleMatch [9] MatchingRuleAssertion,
|
||||
// ... }
|
||||
//
|
||||
// SubstringFilter ::= SEQUENCE {
|
||||
// type AttributeDescription,
|
||||
// substrings SEQUENCE SIZE (1..MAX) OF substring CHOICE {
|
||||
// initial [0] AssertionValue, -- can occur at most once
|
||||
// any [1] AssertionValue,
|
||||
// final [2] AssertionValue } -- can occur at most once
|
||||
// }
|
||||
//
|
||||
// MatchingRuleAssertion ::= SEQUENCE {
|
||||
// matchingRule [1] MatchingRuleId OPTIONAL,
|
||||
// type [2] AttributeDescription OPTIONAL,
|
||||
// matchValue [3] AssertionValue,
|
||||
// dnAttributes [4] BOOLEAN DEFAULT FALSE }
|
||||
//
|
||||
//
|
||||
|
||||
package ldap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/vanackere/asn1-ber"
|
||||
)
|
||||
|
||||
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",
|
||||
}
|
||||
|
||||
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) GetAttributeValue(attribute string) string {
|
||||
values := e.GetAttributeValues(attribute)
|
||||
if len(values) == 0 {
|
||||
return ""
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
type EntryAttribute struct {
|
||||
Name string
|
||||
Values []string
|
||||
}
|
||||
|
||||
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, int64(s.Scope), "Scope"))
|
||||
request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, int64(s.DerefAliases), "Deref Aliases"))
|
||||
request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(s.SizeLimit), "Size Limit"))
|
||||
request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(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, int64(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))
|
||||
}
|
||||
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
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
package ldap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/vault/logical"
|
||||
"github.com/hashicorp/vault/logical/framework"
|
||||
"github.com/vanackere/ldap"
|
||||
)
|
||||
|
||||
func Factory(map[string]string) (logical.Backend, error) {
|
||||
return Backend(), nil
|
||||
}
|
||||
|
||||
func Backend() *framework.Backend {
|
||||
var b backend
|
||||
b.Backend = &framework.Backend{
|
||||
Help: backendHelp,
|
||||
|
||||
PathsSpecial: &logical.Paths{
|
||||
Root: []string{
|
||||
"config",
|
||||
"groups/*",
|
||||
},
|
||||
|
||||
Unauthenticated: []string{
|
||||
"login/*",
|
||||
},
|
||||
},
|
||||
|
||||
Paths: append([]*framework.Path{
|
||||
pathLogin(&b),
|
||||
pathConfig(&b),
|
||||
pathGroups(&b),
|
||||
}),
|
||||
|
||||
AuthRenew: b.pathLoginRenew,
|
||||
}
|
||||
|
||||
return b.Backend
|
||||
}
|
||||
|
||||
type backend struct {
|
||||
*framework.Backend
|
||||
}
|
||||
|
||||
func (b *backend) Login(req *logical.Request, username string, password string) ([]string, *logical.Response, error) {
|
||||
|
||||
cfg, err := b.Config(req)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if cfg == nil {
|
||||
return nil, logical.ErrorResponse("ldap backend not configured"), nil
|
||||
}
|
||||
|
||||
c, err := cfg.DialLDAP()
|
||||
if err != nil {
|
||||
return nil, logical.ErrorResponse(err.Error()), nil
|
||||
}
|
||||
|
||||
// Try to authenticate to the server using the provided credentials
|
||||
binddn := fmt.Sprintf("%s=%s,%s", cfg.UserAttr, username, cfg.UserDN)
|
||||
if err = c.Bind(binddn, password); err != nil {
|
||||
return nil, logical.ErrorResponse(fmt.Sprintf("LDAP bind failed: %v", err)), nil
|
||||
}
|
||||
|
||||
// Enumerate all groups the user is member of. The search filter should
|
||||
// work with both openldap and MS AD standard schemas.
|
||||
sresult, err := c.Search(&ldap.SearchRequest{
|
||||
BaseDN: cfg.GroupDN,
|
||||
Scope: 2, // subtree
|
||||
Filter: fmt.Sprintf("(|(memberUid=%s)(member=%s)(uniqueMember=%s))", username, binddn, binddn),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, logical.ErrorResponse(fmt.Sprintf("LDAP search failed: %v", err)), nil
|
||||
}
|
||||
|
||||
var allgroups []string
|
||||
var policies []string
|
||||
for _, e := range sresult.Entries {
|
||||
// Expected syntax for group DN: cn=groupanem,ou=Group,dc=example,dc=com
|
||||
dn := strings.Split(e.DN, ",")
|
||||
gname := strings.SplitN(dn[0], "=", 2)[1]
|
||||
allgroups = append(allgroups, gname)
|
||||
group, err := b.Group(req.Storage, gname)
|
||||
if err == nil && group != nil {
|
||||
policies = append(policies, group.Policies...)
|
||||
}
|
||||
}
|
||||
|
||||
if len(policies) == 0 {
|
||||
return nil, logical.ErrorResponse("user is not member of any authorized group"), nil
|
||||
}
|
||||
|
||||
return policies, nil, nil
|
||||
}
|
||||
|
||||
const backendHelp = `
|
||||
The "ldap" credential provider allows authentication querying
|
||||
a LDAP server, checking username and password, and associating groups
|
||||
to set of policies.
|
||||
|
||||
Configuration of the server is done through the "config" and "groups"
|
||||
endpoints by a user with root access. Authentication is then done
|
||||
by suppying the two fields for "login".
|
||||
`
|
|
@ -0,0 +1,110 @@
|
|||
package ldap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/vault/logical"
|
||||
logicaltest "github.com/hashicorp/vault/logical/testing"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
func TestBackend_basic(t *testing.T) {
|
||||
b := Backend()
|
||||
|
||||
logicaltest.Test(t, logicaltest.TestCase{
|
||||
Backend: b,
|
||||
Steps: []logicaltest.TestStep{
|
||||
testAccStepConfigUrl(t),
|
||||
testAccStepGroup(t, "scientists", "foo"),
|
||||
testAccStepLogin(t, "tesla", "password"),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestBackend_groupCrud(t *testing.T) {
|
||||
b := Backend()
|
||||
|
||||
logicaltest.Test(t, logicaltest.TestCase{
|
||||
Backend: b,
|
||||
Steps: []logicaltest.TestStep{
|
||||
testAccStepGroup(t, "g1", "foo"),
|
||||
testAccStepReadGroup(t, "g1", "foo"),
|
||||
testAccStepDeleteGroup(t, "g1"),
|
||||
testAccStepReadGroup(t, "g1", ""),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccStepConfigUrl(t *testing.T) logicaltest.TestStep {
|
||||
return logicaltest.TestStep{
|
||||
Operation: logical.WriteOperation,
|
||||
Path: "config",
|
||||
Data: map[string]interface{}{
|
||||
// Online LDAP test server
|
||||
// http://www.forumsys.com/tutorials/integration-how-to/ldap/online-ldap-test-server/
|
||||
"url": "ldap://ldap.forumsys.com",
|
||||
"userattr": "uid",
|
||||
"userdn": "dc=example,dc=com",
|
||||
"groupdn": "dc=example,dc=com",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func testAccStepGroup(t *testing.T, group string, policies string) logicaltest.TestStep {
|
||||
return logicaltest.TestStep{
|
||||
Operation: logical.WriteOperation,
|
||||
Path: "groups/" + group,
|
||||
Data: map[string]interface{}{
|
||||
"policies": policies,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func testAccStepReadGroup(t *testing.T, group string, policies string) logicaltest.TestStep {
|
||||
return logicaltest.TestStep{
|
||||
Operation: logical.ReadOperation,
|
||||
Path: "groups/" + group,
|
||||
Check: func(resp *logical.Response) error {
|
||||
if resp == nil {
|
||||
if policies == "" {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("bad: %#v", resp)
|
||||
}
|
||||
|
||||
var d struct {
|
||||
Policies string `mapstructure:"policies"`
|
||||
}
|
||||
if err := mapstructure.Decode(resp.Data, &d); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if d.Policies != policies {
|
||||
return fmt.Errorf("bad: %#v", resp)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func testAccStepDeleteGroup(t *testing.T, group string) logicaltest.TestStep {
|
||||
return logicaltest.TestStep{
|
||||
Operation: logical.DeleteOperation,
|
||||
Path: "groups/" + group,
|
||||
}
|
||||
}
|
||||
|
||||
func testAccStepLogin(t *testing.T, user string, pass string) logicaltest.TestStep {
|
||||
return logicaltest.TestStep{
|
||||
Operation: logical.WriteOperation,
|
||||
Path: "login/" + user,
|
||||
Data: map[string]interface{}{
|
||||
"password": pass,
|
||||
},
|
||||
Unauthenticated: true,
|
||||
|
||||
Check: logicaltest.TestCheckAuth([]string{"foo"}),
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package ldap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/vault/api"
|
||||
pwd "github.com/hashicorp/vault/helper/password"
|
||||
)
|
||||
|
||||
type CLIHandler struct{}
|
||||
|
||||
func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (string, error) {
|
||||
mount, ok := m["mount"]
|
||||
if !ok {
|
||||
mount = "ldap"
|
||||
}
|
||||
|
||||
username, ok := m["username"]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("'username' var must be set")
|
||||
}
|
||||
password, ok := m["password"]
|
||||
if !ok {
|
||||
fmt.Printf("Password (will be hidden): ")
|
||||
var err error
|
||||
password, err = pwd.Read(os.Stdin)
|
||||
fmt.Println()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("auth/%s/login/%s", mount, username)
|
||||
secret, err := c.Logical().Write(path, map[string]interface{}{
|
||||
"password": password,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if secret == nil {
|
||||
return "", fmt.Errorf("empty response from credential provider")
|
||||
}
|
||||
|
||||
return secret.Auth.ClientToken, nil
|
||||
}
|
||||
|
||||
func (h *CLIHandler) Help() string {
|
||||
help := `
|
||||
The LDAP credential provider allows you to authenticate with LDAP.
|
||||
To use it, first configure it through the "config" endpoint, and then
|
||||
login by specifying username and password. If password is not provided
|
||||
on the command line, it will be read from stdin.
|
||||
|
||||
Example: vault auth -method=ldap username=john
|
||||
|
||||
`
|
||||
|
||||
return strings.TrimSpace(help)
|
||||
}
|
|
@ -0,0 +1,180 @@
|
|||
package ldap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/vault/logical"
|
||||
"github.com/hashicorp/vault/logical/framework"
|
||||
"github.com/vanackere/ldap"
|
||||
)
|
||||
|
||||
func pathConfig(b *backend) *framework.Path {
|
||||
return &framework.Path{
|
||||
Pattern: `config`,
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"url": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Description: "ldap URL to connect to (default: ldap://127.0.0.1)",
|
||||
},
|
||||
"userdn": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Description: "LDAP domain to use for users (eg: ou=People,dc=example,dc=org)",
|
||||
},
|
||||
"groupdn": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Description: "LDAP domain to use for groups (eg: ou=Groups,dc=example,dc=org)",
|
||||
},
|
||||
"userattr": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Description: "Attribute used for users (default: cn)",
|
||||
},
|
||||
},
|
||||
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.ReadOperation: b.pathConfigRead,
|
||||
logical.WriteOperation: b.pathConfigWrite,
|
||||
},
|
||||
|
||||
HelpSynopsis: pathConfigHelpSyn,
|
||||
HelpDescription: pathConfigHelpDesc,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *backend) Config(req *logical.Request) (*ConfigEntry, error) {
|
||||
entry, err := req.Storage.Get("config")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if entry == nil {
|
||||
return nil, nil
|
||||
}
|
||||
var result ConfigEntry
|
||||
result.SetDefaults()
|
||||
if err := entry.DecodeJSON(&result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (b *backend) pathConfigRead(
|
||||
req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
|
||||
cfg, err := b.Config(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cfg == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return &logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
"url": cfg.Url,
|
||||
"userdn": cfg.UserDN,
|
||||
"groupdn": cfg.GroupDN,
|
||||
"userattr": cfg.UserAttr,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *backend) pathConfigWrite(
|
||||
req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
|
||||
cfg := &ConfigEntry{}
|
||||
url := d.Get("url").(string)
|
||||
if url != "" {
|
||||
cfg.Url = strings.ToLower(url)
|
||||
}
|
||||
userattr := d.Get("userattr").(string)
|
||||
if userattr != "" {
|
||||
cfg.UserAttr = strings.ToLower(userattr)
|
||||
}
|
||||
userdn := d.Get("userdn").(string)
|
||||
if userdn != "" {
|
||||
cfg.UserDN = userdn
|
||||
}
|
||||
groupdn := d.Get("groupdn").(string)
|
||||
if groupdn != "" {
|
||||
cfg.GroupDN = groupdn
|
||||
}
|
||||
|
||||
// Try to connect to the LDAP server, to validate the URL configuration
|
||||
// We can also check the URL at this stage, as anything else would probably
|
||||
// require authentication.
|
||||
conn, cerr := cfg.DialLDAP()
|
||||
if cerr != nil {
|
||||
return logical.ErrorResponse(cerr.Error()), nil
|
||||
}
|
||||
conn.Close()
|
||||
|
||||
entry, err := logical.StorageEntryJSON("config", cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := req.Storage.Put(entry); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type ConfigEntry struct {
|
||||
Url string
|
||||
UserDN string
|
||||
GroupDN string
|
||||
UserAttr string
|
||||
}
|
||||
|
||||
func (c *ConfigEntry) DialLDAP() (*ldap.Conn, error) {
|
||||
|
||||
u, err := url.Parse(c.Url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
host, port, err := net.SplitHostPort(u.Host)
|
||||
if err != nil {
|
||||
host = u.Host
|
||||
}
|
||||
|
||||
var conn *ldap.Conn
|
||||
switch u.Scheme {
|
||||
case "ldap":
|
||||
if port == "" {
|
||||
port = "389"
|
||||
}
|
||||
conn, err = ldap.Dial("tcp", host+":"+port)
|
||||
case "ldaps":
|
||||
if port == "" {
|
||||
port = "636"
|
||||
}
|
||||
conn, err = ldap.DialTLS("tcp", host+":"+port, nil)
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid LDAP scheme")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot connect to LDAP: %v", err)
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (c *ConfigEntry) SetDefaults() {
|
||||
c.Url = "ldap://127.0.0.1"
|
||||
c.UserAttr = "cn"
|
||||
}
|
||||
|
||||
const pathConfigHelpSyn = `
|
||||
Configure the LDAP server to connect to.
|
||||
`
|
||||
|
||||
const pathConfigHelpDesc = `
|
||||
This endpoint allows you to configure the LDAP server to connect to, and give
|
||||
basic information of the schema of that server.
|
||||
|
||||
The LDAP URL can use either the "ldap://" or "ldaps://" schema. In the former
|
||||
case, an unencrypted connection will be done, with default port 389; in the latter
|
||||
case, a SSL connection will be done, with default port 636.
|
||||
`
|
|
@ -0,0 +1,118 @@
|
|||
package ldap
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/vault/logical"
|
||||
"github.com/hashicorp/vault/logical/framework"
|
||||
)
|
||||
|
||||
func pathGroups(b *backend) *framework.Path {
|
||||
return &framework.Path{
|
||||
Pattern: `groups/(?P<name>\w+)`,
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"name": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Description: "Name of the LDAP group.",
|
||||
},
|
||||
|
||||
"policies": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Description: "Comma-separated list of policies associated to the group.",
|
||||
},
|
||||
},
|
||||
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.DeleteOperation: b.pathGroupDelete,
|
||||
logical.ReadOperation: b.pathGroupRead,
|
||||
logical.WriteOperation: b.pathGroupWrite,
|
||||
},
|
||||
|
||||
HelpSynopsis: pathGroupHelpSyn,
|
||||
HelpDescription: pathGroupHelpDesc,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *backend) Group(s logical.Storage, n string) (*GroupEntry, error) {
|
||||
entry, err := s.Get("group/" + n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if entry == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var result GroupEntry
|
||||
if err := entry.DecodeJSON(&result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (b *backend) pathGroupDelete(
|
||||
req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
err := req.Storage.Delete("group/" + d.Get("name").(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *backend) pathGroupRead(
|
||||
req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
group, err := b.Group(req.Storage, d.Get("name").(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if group == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return &logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
"policies": strings.Join(group.Policies, ","),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *backend) pathGroupWrite(
|
||||
req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
name := d.Get("name").(string)
|
||||
policies := strings.Split(d.Get("policies").(string), ",")
|
||||
for i, p := range policies {
|
||||
policies[i] = strings.TrimSpace(p)
|
||||
}
|
||||
|
||||
// Store it
|
||||
entry, err := logical.StorageEntryJSON("group/"+name, &GroupEntry{
|
||||
Policies: policies,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := req.Storage.Put(entry); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type GroupEntry struct {
|
||||
Policies []string
|
||||
}
|
||||
|
||||
const pathGroupHelpSyn = `
|
||||
Manage users allowed to authenticate.
|
||||
`
|
||||
|
||||
const pathGroupHelpDesc = `
|
||||
This endpoint allows you to create, read, update, and delete configuration
|
||||
for LDAP groups that are allowed to authenticate, and associate policies to
|
||||
them.
|
||||
|
||||
Deleting a group will not revoke auth for prior authenticated users in that
|
||||
group. To do this, do a revoke on "login/<username>" for
|
||||
the usernames you want revoked.
|
||||
`
|
|
@ -0,0 +1,89 @@
|
|||
package ldap
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/vault/logical"
|
||||
"github.com/hashicorp/vault/logical/framework"
|
||||
)
|
||||
|
||||
func pathLogin(b *backend) *framework.Path {
|
||||
return &framework.Path{
|
||||
Pattern: `login/(?P<username>\w+)`,
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"username": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Description: "DN (distinguished name) to be used for login.",
|
||||
},
|
||||
|
||||
"password": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Description: "Password for this user.",
|
||||
},
|
||||
},
|
||||
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.WriteOperation: b.pathLogin,
|
||||
},
|
||||
|
||||
HelpSynopsis: pathLoginSyn,
|
||||
HelpDescription: pathLoginDesc,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *backend) pathLogin(
|
||||
req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
username := d.Get("username").(string)
|
||||
password := d.Get("password").(string)
|
||||
|
||||
policies, resp, err := b.Login(req, username, password)
|
||||
if len(policies) == 0 {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
sort.Strings(policies)
|
||||
|
||||
return &logical.Response{
|
||||
Auth: &logical.Auth{
|
||||
Policies: policies,
|
||||
Metadata: map[string]string{
|
||||
"username": username,
|
||||
"policies": strings.Join(policies, ","),
|
||||
},
|
||||
InternalData: map[string]interface{}{
|
||||
"password": password,
|
||||
},
|
||||
DisplayName: username,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *backend) pathLoginRenew(
|
||||
req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
|
||||
username := req.Auth.Metadata["username"]
|
||||
password := req.Auth.InternalData["password"].(string)
|
||||
prevpolicies := req.Auth.Metadata["policies"]
|
||||
|
||||
policies, resp, err := b.Login(req, username, password)
|
||||
if len(policies) == 0 {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
sort.Strings(policies)
|
||||
if strings.Join(policies, ",") != prevpolicies {
|
||||
return logical.ErrorResponse("policies have changed, revoking login"), nil
|
||||
}
|
||||
|
||||
return framework.LeaseExtend(1*time.Hour, 0)(req, d)
|
||||
}
|
||||
|
||||
const pathLoginSyn = `
|
||||
Log in with a username and password.
|
||||
`
|
||||
|
||||
const pathLoginDesc = `
|
||||
This endpoint authenticates using a username and password.
|
||||
`
|
|
@ -9,6 +9,7 @@ import (
|
|||
credAppId "github.com/hashicorp/vault/builtin/credential/app-id"
|
||||
credCert "github.com/hashicorp/vault/builtin/credential/cert"
|
||||
credGitHub "github.com/hashicorp/vault/builtin/credential/github"
|
||||
credLdap "github.com/hashicorp/vault/builtin/credential/ldap"
|
||||
credUserpass "github.com/hashicorp/vault/builtin/credential/userpass"
|
||||
|
||||
"github.com/hashicorp/vault/builtin/logical/aws"
|
||||
|
@ -58,6 +59,7 @@ func Commands(metaPtr *command.Meta) map[string]cli.CommandFactory {
|
|||
"app-id": credAppId.Factory,
|
||||
"github": credGitHub.Factory,
|
||||
"userpass": credUserpass.Factory,
|
||||
"ldap": credLdap.Factory,
|
||||
},
|
||||
LogicalBackends: map[string]logical.Factory{
|
||||
"aws": aws.Factory,
|
||||
|
@ -81,6 +83,7 @@ func Commands(metaPtr *command.Meta) map[string]cli.CommandFactory {
|
|||
Handlers: map[string]command.AuthHandler{
|
||||
"github": &credGitHub.CLIHandler{},
|
||||
"userpass": &credUserpass.CLIHandler{},
|
||||
"ldap": &credLdap.CLIHandler{},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue