Explorar el Código

Remove ldap dep

Unknwon hace 10 años
padre
commit
59a7c7c5a5

+ 3 - 1
.gopmfile

@@ -2,6 +2,8 @@
 path = github.com/gogits/gogs
 
 [deps]
+github.com/beego/memcache = 
+github.com/beego/redigo = 
 github.com/Unknwon/cae = 
 github.com/Unknwon/com = 
 github.com/Unknwon/goconfig = 
@@ -14,7 +16,6 @@ github.com/go-xorm/xorm =
 github.com/gogits/gfm = 
 github.com/gogits/git = 
 github.com/gogits/oauth2 = 
-github.com/juju2013/goldap = 
 github.com/lib/pq = 
 github.com/macaron-contrib/cache = 
 github.com/macaron-contrib/captcha = 
@@ -22,6 +23,7 @@ github.com/macaron-contrib/csrf =
 github.com/macaron-contrib/i18n = 
 github.com/macaron-contrib/session = 
 github.com/macaron-contrib/toolbox = 
+github.com/mattn/go-sqlite3 = 
 github.com/nfnt/resize = 
 github.com/saintfish/chardet = 
 

+ 27 - 0
modules/asn1-ber/LICENSE

@@ -0,0 +1,27 @@
+Copyright (c) 2012 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 11 - 0
modules/asn1-ber/Makefile

@@ -0,0 +1,11 @@
+# Copyright 2009 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.
+
+include $(GOROOT)/src/Make.inc
+
+TARG=github.com/mmitton/asn1-ber
+GOFILES=\
+	ber.go\
+
+include $(GOROOT)/src/Make.pkg

+ 14 - 0
modules/asn1-ber/README

@@ -0,0 +1,14 @@
+ASN1 BER Encoding / Decoding Library for the GO programming language.
+
+Required Librarys: 
+   None
+
+Working:
+   Very basic encoding / decoding needed for LDAP protocol
+
+Tests Implemented:
+   None
+
+TODO:
+   Fix all encoding / decoding to conform to ASN1 BER spec
+   Implement Tests / Benchmarks

+ 492 - 0
modules/asn1-ber/ber.go

@@ -0,0 +1,492 @@
+package ber
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"reflect"
+)
+
+type Packet struct {
+	ClassType   uint8
+	TagType     uint8
+	Tag         uint8
+	Value       interface{}
+	ByteValue   []byte
+	Data        *bytes.Buffer
+	Children    []*Packet
+	Description string
+}
+
+const (
+	TagEOC              = 0x00
+	TagBoolean          = 0x01
+	TagInteger          = 0x02
+	TagBitString        = 0x03
+	TagOctetString      = 0x04
+	TagNULL             = 0x05
+	TagObjectIdentifier = 0x06
+	TagObjectDescriptor = 0x07
+	TagExternal         = 0x08
+	TagRealFloat        = 0x09
+	TagEnumerated       = 0x0a
+	TagEmbeddedPDV      = 0x0b
+	TagUTF8String       = 0x0c
+	TagRelativeOID      = 0x0d
+	TagSequence         = 0x10
+	TagSet              = 0x11
+	TagNumericString    = 0x12
+	TagPrintableString  = 0x13
+	TagT61String        = 0x14
+	TagVideotexString   = 0x15
+	TagIA5String        = 0x16
+	TagUTCTime          = 0x17
+	TagGeneralizedTime  = 0x18
+	TagGraphicString    = 0x19
+	TagVisibleString    = 0x1a
+	TagGeneralString    = 0x1b
+	TagUniversalString  = 0x1c
+	TagCharacterString  = 0x1d
+	TagBMPString        = 0x1e
+	TagBitmask          = 0x1f // xxx11111b
+)
+
+var TagMap = map[uint8]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",
+}
+
+const (
+	ClassUniversal   = 0   // 00xxxxxxb
+	ClassApplication = 64  // 01xxxxxxb
+	ClassContext     = 128 // 10xxxxxxb
+	ClassPrivate     = 192 // 11xxxxxxb
+	ClassBitmask     = 192 // 11xxxxxxb
+)
+
+var ClassMap = map[uint8]string{
+	ClassUniversal:   "Universal",
+	ClassApplication: "Application",
+	ClassContext:     "Context",
+	ClassPrivate:     "Private",
+}
+
+const (
+	TypePrimitive   = 0  // xx0xxxxxb
+	TypeConstructed = 32 // xx1xxxxxb
+	TypeBitmask     = 32 // xx1xxxxxb
+)
+
+var TypeMap = map[uint8]string{
+	TypePrimitive:   "Primative",
+	TypeConstructed: "Constructed",
+}
+
+var Debug bool = false
+
+func PrintBytes(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++ {
+		fmt.Print(indent + data_lines[i] + "\n")
+		fmt.Print(indent + num_lines[i] + "\n\n")
+	}
+}
+
+func PrintPacket(p *Packet) {
+	printPacket(p, 0, false)
+}
+
+func printPacket(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.Printf("%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(p.Bytes(), indent_str)
+	}
+
+	for _, child := range p.Children {
+		printPacket(child, indent+1, printBytes)
+	}
+}
+
+func resizeBuffer(in []byte, new_size uint64) (out []byte) {
+	out = make([]byte, new_size)
+
+	copy(out, in)
+
+	return
+}
+
+func readBytes(reader io.Reader, buf []byte) error {
+	idx := 0
+	buflen := len(buf)
+
+	for idx < buflen {
+		n, err := reader.Read(buf[idx:])
+		if err != nil {
+			return err
+		}
+		idx += n
+	}
+
+	return nil
+}
+
+func ReadPacket(reader io.Reader) (*Packet, error) {
+	buf := make([]byte, 2)
+
+	err := readBytes(reader, buf)
+
+	if err != nil {
+		return nil, err
+	}
+
+	idx := uint64(2)
+	datalen := uint64(buf[1])
+
+	if Debug {
+		fmt.Printf("Read: datalen = %d len(buf) = %d ", datalen, len(buf))
+
+		for _, b := range buf {
+			fmt.Printf("%02X ", b)
+		}
+
+		fmt.Printf("\n")
+	}
+
+	if datalen&128 != 0 {
+		a := datalen - 128
+
+		idx += a
+		buf = resizeBuffer(buf, 2+a)
+
+		err := readBytes(reader, buf[2:])
+
+		if err != nil {
+			return nil, err
+		}
+
+		datalen = DecodeInteger(buf[2 : 2+a])
+
+		if Debug {
+			fmt.Printf("Read: a = %d  idx = %d  datalen = %d  len(buf) = %d", a, idx, datalen, len(buf))
+
+			for _, b := range buf {
+				fmt.Printf("%02X ", b)
+			}
+
+			fmt.Printf("\n")
+		}
+	}
+
+	buf = resizeBuffer(buf, idx+datalen)
+	err = readBytes(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) (ret string) {
+	for _, c := range data {
+		ret += fmt.Sprintf("%c", c)
+	}
+
+	return
+}
+
+func DecodeInteger(data []byte) (ret uint64) {
+	for _, i := range data {
+		ret = ret * 256
+		ret = ret + uint64(i)
+	}
+
+	return
+}
+
+func EncodeInteger(val uint64) []byte {
+	var out bytes.Buffer
+
+	found := false
+
+	shift := uint(56)
+
+	mask := uint64(0xFF00000000000000)
+
+	for mask > 0 {
+		if !found && (val&mask != 0) {
+			found = true
+		}
+
+		if found || (shift == 0) {
+			out.Write([]byte{byte((val & mask) >> shift)})
+		}
+
+		shift -= 8
+		mask = mask >> 8
+	}
+
+	return out.Bytes()
+}
+
+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 = data[0] & ClassBitmask
+	p.TagType = data[0] & TypeBitmask
+	p.Tag = data[0] & TagBitmask
+
+	datalen := DecodeInteger(data[1:2])
+	datapos := uint64(2)
+
+	if datalen&128 != 0 {
+		datalen -= 128
+		datapos += datalen
+		datalen = DecodeInteger(data[2 : 2+datalen])
+	}
+
+	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 := DecodeInteger(value_data)
+
+			p.Value = val != 0
+		case TagInteger:
+			p.Value = DecodeInteger(value_data)
+		case TagBitString:
+		case TagOctetString:
+			p.Value = DecodeString(value_data)
+		case TagNULL:
+		case TagObjectIdentifier:
+		case TagObjectDescriptor:
+		case TagExternal:
+		case TagRealFloat:
+		case TagEnumerated:
+			p.Value = DecodeInteger(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) DataLength() uint64 {
+	return uint64(p.Data.Len())
+}
+
+func (p *Packet) Bytes() []byte {
+	var out bytes.Buffer
+
+	out.Write([]byte{p.ClassType | p.TagType | p.Tag})
+	packet_length := EncodeInteger(p.DataLength())
+
+	if p.DataLength() > 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())
+
+	if len(p.Children) == cap(p.Children) {
+		newChildren := make([]*Packet, cap(p.Children)*2)
+
+		copy(newChildren, p.Children)
+		p.Children = newChildren[0:len(p.Children)]
+	}
+
+	p.Children = p.Children[0 : len(p.Children)+1]
+	p.Children[len(p.Children)-1] = child
+}
+
+func Encode(ClassType, TagType, Tag uint8, 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, TypePrimitive, TagSequence, nil, Description)
+}
+
+func NewBoolean(ClassType, TagType, Tag uint8, Value bool, Description string) *Packet {
+	intValue := 0
+
+	if Value {
+		intValue = 1
+	}
+
+	p := Encode(ClassType, TagType, Tag, nil, Description)
+
+	p.Value = Value
+	p.Data.Write(EncodeInteger(uint64(intValue)))
+
+	return p
+}
+
+func NewInteger(ClassType, TagType, Tag uint8, Value uint64, Description string) *Packet {
+	p := Encode(ClassType, TagType, Tag, nil, Description)
+
+	p.Value = Value
+	p.Data.Write(EncodeInteger(Value))
+
+	return p
+}
+
+func NewString(ClassType, TagType, Tag uint8, Value, Description string) *Packet {
+	p := Encode(ClassType, TagType, Tag, nil, Description)
+
+	p.Value = Value
+	p.Data.Write([]byte(Value))
+
+	return p
+}

+ 6 - 6
modules/auth/ldap/ldap.go

@@ -9,8 +9,8 @@ package ldap
 import (
 	"fmt"
 
+	"github.com/gogits/gogs/modules/ldap"
 	"github.com/gogits/gogs/modules/log"
-	goldap "github.com/juju2013/goldap"
 )
 
 // Basic LDAP authentication service
@@ -68,9 +68,9 @@ func (ls Ldapsource) SearchEntry(name, passwd string) (string, bool) {
 		return "", false
 	}
 
-	search := goldap.NewSearchRequest(
+	search := ldap.NewSearchRequest(
 		ls.BaseDN,
-		goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 0, 0, false,
+		ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
 		fmt.Sprintf(ls.Filter, name),
 		[]string{ls.Attributes},
 		nil)
@@ -87,10 +87,10 @@ func (ls Ldapsource) SearchEntry(name, passwd string) (string, bool) {
 	return "", true
 }
 
-func ldapDial(ls Ldapsource) (*goldap.Conn, error) {
+func ldapDial(ls Ldapsource) (*ldap.Conn, error) {
 	if ls.UseSSL {
-		return goldap.DialTLS("tcp", fmt.Sprintf("%s:%d", ls.Host, ls.Port), nil)
+		return ldap.DialTLS("tcp", fmt.Sprintf("%s:%d", ls.Host, ls.Port), nil)
 	} else {
-		return goldap.Dial("tcp", fmt.Sprintf("%s:%d", ls.Host, ls.Port))
+		return ldap.Dial("tcp", fmt.Sprintf("%s:%d", ls.Host, ls.Port))
 	}
 }

+ 27 - 0
modules/ldap/LICENSE

@@ -0,0 +1,27 @@
+Copyright (c) 2012 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 33 - 0
modules/ldap/README

@@ -0,0 +1,33 @@
+Basic LDAP v3 functionality for the GO programming language.
+
+Required Librarys: 
+   github.com/johnweldon/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

+ 55 - 0
modules/ldap/bind.go

@@ -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/gogits/gogs/modules/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, 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
+}

+ 275 - 0
modules/ldap/conn.go

@@ -0,0 +1,275 @@
+// 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/gogits/gogs/modules/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
+	isClosing     bool
+	Debug         debugging
+	chanConfirm   chan bool
+	chanResults   map[uint64]chan *ber.Packet
+	chanMessage   chan *messagePacket
+	chanMessageID chan uint64
+	wgSender      sync.WaitGroup
+	wgClose       sync.WaitGroup
+	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{},
+	}
+}
+
+func (l *Conn) start() {
+	go l.reader()
+	go l.processMessages()
+	l.wgClose.Add(1)
+}
+
+// Close closes the connection.
+func (l *Conn) Close() {
+	l.once.Do(func() {
+		l.isClosing = true
+		l.wgSender.Wait()
+
+		l.Debug.Printf("Sending quit message and waiting for confirmation")
+		l.chanMessage <- &messagePacket{Op: MessageQuit}
+		<-l.chanConfirm
+		close(l.chanMessage)
+
+		l.Debug.Printf("Closing network connection")
+		if err := l.conn.Close(); err != nil {
+			log.Print(err)
+		}
+
+		l.conn = nil
+		l.wgClose.Done()
+	})
+	l.wgClose.Wait()
+}
+
+// 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, 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.(uint64) == 0 {
+		conn := tls.Client(l.conn, config)
+		l.isTLS = true
+		l.conn = conn
+	}
+
+	return nil
+}
+
+func (l *Conn) sendMessage(packet *ber.Packet) (chan *ber.Packet, error) {
+	if l.isClosing {
+		return nil, NewError(ErrorNetwork, errors.New("ldap: connection closed"))
+	}
+	out := make(chan *ber.Packet)
+	message := &messagePacket{
+		Op:        MessageRequest,
+		MessageID: packet.Children[0].Value.(uint64),
+		Packet:    packet,
+		Channel:   out,
+	}
+	l.sendProcessMessage(message)
+	return out, nil
+}
+
+func (l *Conn) finishMessage(messageID uint64) {
+	if l.isClosing {
+		return
+	}
+	message := &messagePacket{
+		Op:        MessageFinish,
+		MessageID: messageID,
+	}
+	l.sendProcessMessage(message)
+}
+
+func (l *Conn) sendProcessMessage(message *messagePacket) bool {
+	if l.isClosing {
+		return false
+	}
+	l.wgSender.Add(1)
+	l.chanMessage <- message
+	l.wgSender.Done()
+	return true
+}
+
+func (l *Conn) processMessages() {
+	defer func() {
+		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: packet.Children[0].Value.(uint64),
+			Packet:    packet,
+		}
+		if !l.sendProcessMessage(message) {
+			return
+		}
+
+	}
+}

+ 157 - 0
modules/ldap/control.go

@@ -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/gogits/gogs/modules/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, uint64(c.PagingSize), "Paging Size"))
+	cookie := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Cookie")
+	cookie.Value = c.Cookie
+	cookie.Data.Write(c.Cookie)
+	seq.AppendChild(cookie)
+	p2.AppendChild(seq)
+
+	packet.AppendChild(p2)
+	return packet
+}
+
+func (c *ControlPaging) String() string {
+	return fmt.Sprintf(
+		"Control Type: %s (%q)  Criticality: %t  PagingSize: %d  Cookie: %q",
+		ControlTypeMap[ControlTypePaging],
+		ControlTypePaging,
+		false,
+		c.PagingSize,
+		c.Cookie)
+}
+
+func (c *ControlPaging) SetCookie(cookie []byte) {
+	c.Cookie = cookie
+}
+
+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.(uint64))
+		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
+}

+ 24 - 0
modules/ldap/debug.go

@@ -0,0 +1,24 @@
+package ldap
+
+import (
+	"log"
+
+	"github.com/gogits/gogs/modules/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 - 0
modules/ldap/examples/enterprise.ldif

@@ -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

+ 89 - 0
modules/ldap/examples/modify.go

@@ -0,0 +1,89 @@
+// 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/juju2013/goldap"
+)
+
+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)
+}

+ 52 - 0
modules/ldap/examples/search.go

@@ -0,0 +1,52 @@
+// 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/juju2013/goldap"
+)
+
+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)
+}

+ 45 - 0
modules/ldap/examples/searchSSL.go

@@ -0,0 +1,45 @@
+// 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/juju2013/goldap"
+)
+
+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)
+}

+ 45 - 0
modules/ldap/examples/searchTLS.go

@@ -0,0 +1,45 @@
+// 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/juju2013/goldap"
+)
+
+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 - 0
modules/ldap/examples/slapd.conf

@@ -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

+ 248 - 0
modules/ldap/filter.go

@@ -0,0 +1,248 @@
+// 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/gogits/gogs/modules/asn1-ber"
+)
+
+const (
+	FilterAnd             = 0
+	FilterOr              = 1
+	FilterNot             = 2
+	FilterEqualityMatch   = 3
+	FilterSubstrings      = 4
+	FilterGreaterOrEqual  = 5
+	FilterLessOrEqual     = 6
+	FilterPresent         = 7
+	FilterApproxMatch     = 8
+	FilterExtensibleMatch = 9
+)
+
+var FilterMap = map[uint64]string{
+	FilterAnd:             "And",
+	FilterOr:              "Or",
+	FilterNot:             "Not",
+	FilterEqualityMatch:   "Equality Match",
+	FilterSubstrings:      "Substrings",
+	FilterGreaterOrEqual:  "Greater Or Equal",
+	FilterLessOrEqual:     "Less Or Equal",
+	FilterPresent:         "Present",
+	FilterApproxMatch:     "Approx Match",
+	FilterExtensibleMatch: "Extensible Match",
+}
+
+const (
+	FilterSubstringsInitial = 0
+	FilterSubstringsAny     = 1
+	FilterSubstringsFinal   = 2
+)
+
+var FilterSubstringsMap = map[uint64]string{
+	FilterSubstringsInitial: "Substrings Initial",
+	FilterSubstringsAny:     "Substrings Any",
+	FilterSubstringsFinal:   "Substrings Final",
+}
+
+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.Children[0].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
+		}
+		packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute, "Attribute"))
+		switch {
+		case packet.Tag == FilterEqualityMatch && condition == "*":
+			packet.Tag = FilterPresent
+			packet.Description = FilterMap[uint64(packet.Tag)]
+		case packet.Tag == FilterEqualityMatch && condition[0] == '*' && condition[len(condition)-1] == '*':
+			// Any
+			packet.Tag = FilterSubstrings
+			packet.Description = FilterMap[uint64(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[uint64(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[uint64(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
+	}
+}

+ 78 - 0
modules/ldap/filter_test.go

@@ -0,0 +1,78 @@
+package ldap
+
+import (
+	"testing"
+
+	"github.com/johnweldon/asn1-ber"
+)
+
+type compileTest struct {
+	filterStr  string
+	filterType int
+}
+
+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 != uint8(i.filterType) {
+			t.Errorf("%q Expected %q got %q", i.filterStr, FilterMap[uint64(i.filterType)], FilterMap[uint64(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)
+			}
+		}
+	}
+}
+
+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])
+	}
+}

+ 302 - 0
modules/ldap/ldap.go

@@ -0,0 +1,302 @@
+// 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"
+
+	"github.com/gogits/gogs/modules/asn1-ber"
+)
+
+// LDAP Application Codes
+const (
+	ApplicationBindRequest           = 0
+	ApplicationBindResponse          = 1
+	ApplicationUnbindRequest         = 2
+	ApplicationSearchRequest         = 3
+	ApplicationSearchResultEntry     = 4
+	ApplicationSearchResultDone      = 5
+	ApplicationModifyRequest         = 6
+	ApplicationModifyResponse        = 7
+	ApplicationAddRequest            = 8
+	ApplicationAddResponse           = 9
+	ApplicationDelRequest            = 10
+	ApplicationDelResponse           = 11
+	ApplicationModifyDNRequest       = 12
+	ApplicationModifyDNResponse      = 13
+	ApplicationCompareRequest        = 14
+	ApplicationCompareResponse       = 15
+	ApplicationAbandonRequest        = 16
+	ApplicationSearchResultReference = 19
+	ApplicationExtendedRequest       = 23
+	ApplicationExtendedResponse      = 24
+)
+
+var ApplicationMap = map[uint8]string{
+	ApplicationBindRequest:           "Bind Request",
+	ApplicationBindResponse:          "Bind Response",
+	ApplicationUnbindRequest:         "Unbind Request",
+	ApplicationSearchRequest:         "Search Request",
+	ApplicationSearchResultEntry:     "Search Result Entry",
+	ApplicationSearchResultDone:      "Search Result Done",
+	ApplicationModifyRequest:         "Modify Request",
+	ApplicationModifyResponse:        "Modify Response",
+	ApplicationAddRequest:            "Add Request",
+	ApplicationAddResponse:           "Add Response",
+	ApplicationDelRequest:            "Del Request",
+	ApplicationDelResponse:           "Del Response",
+	ApplicationModifyDNRequest:       "Modify DN Request",
+	ApplicationModifyDNResponse:      "Modify DN Response",
+	ApplicationCompareRequest:        "Compare Request",
+	ApplicationCompareResponse:       "Compare Response",
+	ApplicationAbandonRequest:        "Abandon Request",
+	ApplicationSearchResultReference: "Search Result Reference",
+	ApplicationExtendedRequest:       "Extended Request",
+	ApplicationExtendedResponse:      "Extended Response",
+}
+
+// LDAP 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.(uint64)
+	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(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.(uint64)), response.Children[2].Value.(string)
+		}
+	}
+
+	return ErrorNetwork, "Invalid packet format"
+}

+ 123 - 0
modules/ldap/ldap_test.go

@@ -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))
+		}
+	}
+}

+ 156 - 0
modules/ldap/modify.go

@@ -0,0 +1,156 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+//
+// File contains Modify functionality
+//
+// https://tools.ietf.org/html/rfc4511
+//
+// ModifyRequest ::= [APPLICATION 6] SEQUENCE {
+//      object          LDAPDN,
+//      changes         SEQUENCE OF change SEQUENCE {
+//           operation       ENUMERATED {
+//                add     (0),
+//                delete  (1),
+//                replace (2),
+//                ...  },
+//           modification    PartialAttribute } }
+//
+// PartialAttribute ::= SEQUENCE {
+//      type       AttributeDescription,
+//      vals       SET OF value AttributeValue }
+//
+// AttributeDescription ::= LDAPString
+//                         -- Constrained to <attributedescription>
+//                         -- [RFC4512]
+//
+// AttributeValue ::= OCTET STRING
+//
+
+package ldap
+
+import (
+	"errors"
+	"log"
+
+	"github.com/gogits/gogs/modules/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, uint64(AddAttribute), "Operation"))
+		change.AppendChild(attribute.encode())
+		changes.AppendChild(change)
+	}
+	for _, attribute := range m.deleteAttributes {
+		change := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Change")
+		change.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(DeleteAttribute), "Operation"))
+		change.AppendChild(attribute.encode())
+		changes.AppendChild(change)
+	}
+	for _, attribute := range m.replaceAttributes {
+		change := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Change")
+		change.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(ReplaceAttribute), "Operation"))
+		change.AppendChild(attribute.encode())
+		changes.AppendChild(change)
+	}
+	request.AppendChild(changes)
+	return request
+}
+
+func NewModifyRequest(
+	dn string,
+) *ModifyRequest {
+	return &ModifyRequest{
+		dn: dn,
+	}
+}
+
+func (l *Conn) Modify(modifyRequest *ModifyRequest) error {
+	messageID := l.nextMessageID()
+	packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
+	packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, messageID, "MessageID"))
+	packet.AppendChild(modifyRequest.encode())
+
+	l.Debug.PrintPacket(packet)
+
+	channel, err := l.sendMessage(packet)
+	if err != nil {
+		return err
+	}
+	if channel == nil {
+		return NewError(ErrorNetwork, errors.New("ldap: could not send message"))
+	}
+	defer l.finishMessage(messageID)
+
+	l.Debug.Printf("%d: waiting for response", messageID)
+	packet = <-channel
+	l.Debug.Printf("%d: got response %p", messageID, packet)
+	if packet == nil {
+		return NewError(ErrorNetwork, errors.New("ldap: could not retrieve message"))
+	}
+
+	if l.Debug {
+		if err := addLDAPDescriptions(packet); err != nil {
+			return err
+		}
+		ber.PrintPacket(packet)
+	}
+
+	if packet.Children[1].Tag == ApplicationModifyResponse {
+		resultCode, resultDescription := getLDAPResultCode(packet)
+		if resultCode != 0 {
+			return NewError(resultCode, errors.New(resultDescription))
+		}
+	} else {
+		log.Printf("Unexpected Response: %d", packet.Children[1].Tag)
+	}
+
+	l.Debug.Printf("%d: returning", messageID)
+	return nil
+}

+ 350 - 0
modules/ldap/search.go

@@ -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/gogits/gogs/modules/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, uint64(s.Scope), "Scope"))
+	request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(s.DerefAliases), "Deref Aliases"))
+	request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, uint64(s.SizeLimit), "Size Limit"))
+	request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, uint64(s.TimeLimit), "Time Limit"))
+	request.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, s.TypesOnly, "Types Only"))
+	// compile and encode filter
+	filterPacket, err := CompileFilter(s.Filter)
+	if err != nil {
+		return nil, err
+	}
+	request.AppendChild(filterPacket)
+	// encode attributes
+	attributesPacket := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Attributes")
+	for _, attribute := range s.Attributes {
+		attributesPacket.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute, "Attribute"))
+	}
+	request.AppendChild(attributesPacket)
+	return request, nil
+}
+
+func NewSearchRequest(
+	BaseDN string,
+	Scope, DerefAliases, SizeLimit, TimeLimit int,
+	TypesOnly bool,
+	Filter string,
+	Attributes []string,
+	Controls []Control,
+) *SearchRequest {
+	return &SearchRequest{
+		BaseDN:       BaseDN,
+		Scope:        Scope,
+		DerefAliases: DerefAliases,
+		SizeLimit:    SizeLimit,
+		TimeLimit:    TimeLimit,
+		TypesOnly:    TypesOnly,
+		Filter:       Filter,
+		Attributes:   Attributes,
+		Controls:     Controls,
+	}
+}
+
+func (l *Conn) SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32) (*SearchResult, error) {
+	if searchRequest.Controls == nil {
+		searchRequest.Controls = make([]Control, 0)
+	}
+
+	pagingControl := NewControlPaging(pagingSize)
+	searchRequest.Controls = append(searchRequest.Controls, pagingControl)
+	searchResult := new(SearchResult)
+	for {
+		result, err := l.Search(searchRequest)
+		l.Debug.Printf("Looking for Paging Control...")
+		if err != nil {
+			return searchResult, err
+		}
+		if result == nil {
+			return searchResult, NewError(ErrorNetwork, errors.New("ldap: packet not received"))
+		}
+
+		for _, entry := range result.Entries {
+			searchResult.Entries = append(searchResult.Entries, entry)
+		}
+		for _, referral := range result.Referrals {
+			searchResult.Referrals = append(searchResult.Referrals, referral)
+		}
+		for _, control := range result.Controls {
+			searchResult.Controls = append(searchResult.Controls, control)
+		}
+
+		l.Debug.Printf("Looking for Paging Control...")
+		pagingResult := FindControl(result.Controls, ControlTypePaging)
+		if pagingResult == nil {
+			pagingControl = nil
+			l.Debug.Printf("Could not find paging control.  Breaking...")
+			break
+		}
+
+		cookie := pagingResult.(*ControlPaging).Cookie
+		if len(cookie) == 0 {
+			pagingControl = nil
+			l.Debug.Printf("Could not find cookie.  Breaking...")
+			break
+		}
+		pagingControl.SetCookie(cookie)
+	}
+
+	if pagingControl != nil {
+		l.Debug.Printf("Abandoning Paging...")
+		pagingControl.PagingSize = 0
+		l.Search(searchRequest)
+	}
+
+	return searchResult, nil
+}
+
+func (l *Conn) Search(searchRequest *SearchRequest) (*SearchResult, error) {
+	messageID := l.nextMessageID()
+	packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
+	packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, messageID, "MessageID"))
+	// encode search request
+	encodedSearchRequest, err := searchRequest.encode()
+	if err != nil {
+		return nil, err
+	}
+	packet.AppendChild(encodedSearchRequest)
+	// encode search controls
+	if searchRequest.Controls != nil {
+		packet.AppendChild(encodeControls(searchRequest.Controls))
+	}
+
+	l.Debug.PrintPacket(packet)
+
+	channel, err := l.sendMessage(packet)
+	if err != nil {
+		return nil, err
+	}
+	if channel == nil {
+		return nil, NewError(ErrorNetwork, errors.New("ldap: could not send message"))
+	}
+	defer l.finishMessage(messageID)
+
+	result := &SearchResult{
+		Entries:   make([]*Entry, 0),
+		Referrals: make([]string, 0),
+		Controls:  make([]Control, 0)}
+
+	foundSearchResultDone := false
+	for !foundSearchResultDone {
+		l.Debug.Printf("%d: waiting for response", messageID)
+		packet = <-channel
+		l.Debug.Printf("%d: got response %p", messageID, packet)
+		if packet == nil {
+			return nil, NewError(ErrorNetwork, errors.New("ldap: could not retrieve message"))
+		}
+
+		if l.Debug {
+			if err := addLDAPDescriptions(packet); err != nil {
+				return nil, err
+			}
+			ber.PrintPacket(packet)
+		}
+
+		switch packet.Children[1].Tag {
+		case 4:
+			entry := new(Entry)
+			entry.DN = packet.Children[1].Children[0].Value.(string)
+			for _, child := range packet.Children[1].Children[1].Children {
+				attr := new(EntryAttribute)
+				attr.Name = child.Children[0].Value.(string)
+				for _, value := range child.Children[1].Children {
+					attr.Values = append(attr.Values, value.Value.(string))
+				}
+				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
+}