Vendor goldap, fix ASN.1 BER integer and length encoding

- Add tests for goldap to prevent regressions
 - Disable reconnection for our functional tests
This commit is contained in:
Quentin 2021-07-07 01:49:33 +02:00
parent aa912b5ceb
commit 563fc272a3
Signed by: quentin
GPG key ID: A98E9B769E4FF428
98 changed files with 12902 additions and 27 deletions

View file

@ -1,13 +1,25 @@
---
pipeline:
build:
image: golang:stretch
commands:
- go get -d -v
- go build -v
kind: pipeline
name: bottin
steps:
- name: build
image: golang:stretch
commands:
- go get -d -v
- go build -v
- cd test
- go test -i -c -o test
- name: test_bottin
image: consul:latest
environment:
BOTTIN_DEFAULT_ADMIN_PW: priZ4Cg0x5NkSyiIN/MpvWw4ZEy8f8s1
commands:
- ash test/runner.sh
---
kind: signature
hmac: 8f49fdf0e4abb0790827eed7cac8eedd5e11705d1fa01954a84929933eb7b254
hmac: ff246a04c3df8a2f39c8b446dea920622d61950e6caaac886931bdb05d0706ed
...

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
bottin
bottin.static
config.json
test/test

2
go.mod
View file

@ -5,6 +5,6 @@ go 1.13
require (
github.com/google/uuid v1.1.1
github.com/hashicorp/consul/api v1.3.0
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3
github.com/sirupsen/logrus v1.4.2
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 // indirect
)

12
go.sum
View file

@ -44,8 +44,6 @@ github.com/hashicorp/serf v0.8.2 h1:YZ7UKsJv+hKjqGVUUbtE3HNj79Eln2oQ75tniF6iPt0=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3 h1:wIONC+HMNRqmWBjuMxhatuSzHaljStc4gjDeKycxy0A=
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3/go.mod h1:37YR9jabpiIxsb8X9VCIx8qFOjTDIIrIHHODa8C4gz0=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA=
@ -77,14 +75,20 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3 h1:KYQXGkl6vs02hK7pK4eIbw0NpNPedieTSTEiJ//bwGs=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM=
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc h1:a3CU5tJYVj92DY2LaA1kUkrsqD5/3mLDhx2NcNqyW+0=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

25
goldap/abandon_request.go Normal file
View file

@ -0,0 +1,25 @@
package message
import "fmt"
//
// AbandonRequest ::= [APPLICATION 16] MessageID
func readAbandonRequest(bytes *Bytes) (ret AbandonRequest, err error) {
var mes MessageID
mes, err = readTaggedMessageID(bytes, classApplication, TagAbandonRequest)
if err != nil {
err = LdapError{fmt.Sprintf("readAbandonRequest:\n%s", err.Error())}
return
}
ret = AbandonRequest(mes)
return
}
func (abandon AbandonRequest) size() int {
return MessageID(abandon).sizeTagged(TagAbandonRequest)
}
func (abandon AbandonRequest) write(bytes *Bytes) int {
return MessageID(abandon).writeTagged(bytes, classApplication, TagAbandonRequest)
}

53
goldap/add_request.go Normal file
View file

@ -0,0 +1,53 @@
package message
import "fmt"
//
// AddRequest ::= [APPLICATION 8] SEQUENCE {
// entry LDAPDN,
// attributes AttributeList }
func (add *AddRequest) Entry() LDAPDN {
return add.entry
}
func (add *AddRequest) Attributes() AttributeList {
return add.attributes
}
func readAddRequest(bytes *Bytes) (ret AddRequest, err error) {
err = bytes.ReadSubBytes(classApplication, TagAddRequest, ret.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readAddRequest:\n%s", err.Error())}
return
}
return
}
func (add *AddRequest) readComponents(bytes *Bytes) (err error) {
add.entry, err = readLDAPDN(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
add.attributes, err = readAttributeList(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
return
}
func (add AddRequest) size() (size int) {
size += add.entry.size()
size += add.attributes.size()
size += sizeTagAndLength(TagAddRequest, size)
return
}
func (add AddRequest) write(bytes *Bytes) (size int) {
size += add.attributes.write(bytes)
size += add.entry.write(bytes)
size += bytes.WriteTagAndLength(classApplication, isCompound, TagAddRequest, size)
return
}

28
goldap/add_response.go Normal file
View file

@ -0,0 +1,28 @@
package message
import "fmt"
//
// AddResponse ::= [APPLICATION 9] LDAPResult
func (l *AddResponse) SetResultCode(code int) {
l.resultCode = ENUMERATED(code)
}
func readAddResponse(bytes *Bytes) (ret AddResponse, err error) {
var res LDAPResult
res, err = readTaggedLDAPResult(bytes, classApplication, TagAddResponse)
if err != nil {
err = LdapError{fmt.Sprintf("readAddResponse:\n%s", err.Error())}
return
}
ret = AddResponse(res)
return
}
func (a AddResponse) size() int {
return LDAPResult(a).sizeTagged(TagAddResponse)
}
func (a AddResponse) write(bytes *Bytes) int {
return LDAPResult(a).writeTagged(bytes, classApplication, TagAddResponse)
}

761
goldap/asn1.go Normal file
View file

@ -0,0 +1,761 @@
package message
// Below code is largely inspired from the standard golang library encoding/asn
// If put BEGIN / END tags in the comments to give the original library name
import (
// "errors"
"fmt"
"math/big"
// "strconv"
// "time"
)
//
// BEGIN: encoding/asn1/common.go
//
// 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.
const (
tagBoolean = 1
tagInteger = 2
// tagBitString = 3
tagOctetString = 4
// tagOID = 6
tagEnum = 10
// tagUTF8String = 12
tagSequence = 16
tagSet = 17
// tagPrintableString = 19
// tagT61String = 20
// tagIA5String = 22
// tagUTCTime = 23
// tagGeneralizedTime = 24
tagGeneralString = 27
)
var tagNames = map[int]string{
tagBoolean: "BOOLEAN",
tagInteger: "INTEGER",
tagOctetString: "OCTET STRING",
tagEnum: "ENUM",
tagSequence: "SEQUENCE",
tagSet: "SET",
}
const (
classUniversal = 0
classApplication = 1
classContextSpecific = 2
// classPrivate = 3
)
var classNames = map[int]string{
classUniversal: "UNIVERSAL",
classApplication: "APPLICATION",
classContextSpecific: "CONTEXT SPECIFIC",
}
const (
isCompound = true
isNotCompound = false
)
var compoundNames = map[bool]string{
isCompound: "COMPOUND",
isNotCompound: "NOT COMPOUND",
}
type TagAndLength struct {
Class, Tag, Length int
IsCompound bool
}
//
// END: encoding/asn1/common.go
//
func (t *TagAndLength) Expect(class int, tag int, isCompound bool) (err error) {
err = t.ExpectClass(class)
if err != nil {
return LdapError{fmt.Sprintf("Expect: %s.", err)}
}
err = t.ExpectTag(tag)
if err != nil {
return LdapError{fmt.Sprintf("Expect: %s.", err)}
}
err = t.ExpectCompound(isCompound)
if err != nil {
return LdapError{fmt.Sprintf("Expect: %s.", err)}
}
return
}
func (t *TagAndLength) ExpectClass(class int) (err error) {
if class != t.Class {
err = SyntaxError{fmt.Sprintf("ExpectClass: wrong tag class: got %d (%s), expected %d (%s)", t.Class, classNames[t.Class], class, classNames[class])}
}
return
}
func (t *TagAndLength) ExpectTag(tag int) (err error) {
if tag != t.Tag {
err = SyntaxError{fmt.Sprintf("ExpectTag: wrong tag value: got %d (%s), expected %d (%s)", t.Tag, tagNames[t.Tag], tag, tagNames[tag])}
}
return
}
func (t *TagAndLength) ExpectCompound(isCompound bool) (err error) {
if isCompound != t.IsCompound {
err = SyntaxError{fmt.Sprintf("ExpectCompound: wrong tag compound: got %t (%s), expected %t (%s)", t.IsCompound, compoundNames[t.IsCompound], isCompound, compoundNames[isCompound])}
}
return
}
func ParseTagAndLength(bytes []byte, initOffset int) (ret TagAndLength, offset int, err error) {
ret, offset, err = parseTagAndLength(bytes, initOffset)
return
}
//
// BEGIN encoding/asn1/asn1.go
//
// 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.
// Package asn1 implements parsing of DER-encoded ASN.1 data structures,
// as defined in ITU-T Rec X.690.
//
// See also ``A Layman's Guide to a Subset of ASN.1, BER, and DER,''
// http://luca.ntop.org/Teaching/Appunti/asn1.html.
// package asn1
// ASN.1 is a syntax for specifying abstract objects and BER, DER, PER, XER etc
// are different encoding formats for those objects. Here, we'll be dealing
// with DER, the Distinguished Encoding Rules. DER is used in X.509 because
// it's fast to parse and, unlike BER, has a unique encoding for every object.
// When calculating hashes over objects, it's important that the resulting
// bytes be the same at both ends and DER removes this margin of error.
//
// ASN.1 is very complex and this package doesn't attempt to implement
// everything by any means.
//import (
// "fmt"
// "math/big"
// "reflect"
// "strconv"
// "time"
//)
// A StructuralError suggests that the ASN.1 data is valid, but the Go type
// which is receiving it doesn't match.
type StructuralError struct {
Msg string
}
func (e StructuralError) Error() string { return "asn1: structure error: " + e.Msg }
// A SyntaxError suggests that the ASN.1 data is invalid.
type SyntaxError struct {
Msg string
}
func (e SyntaxError) Error() string { return "asn1: syntax error: " + e.Msg }
// We start by dealing with each of the primitive types in turn.
// BOOLEAN
func parseBool(bytes []byte) (ret bool, err error) {
if len(bytes) > 1 {
err = SyntaxError{"invalid boolean: should be encoded on one byte only"}
return
} else if len(bytes) == 0 {
err = SyntaxError{"invalid boolean: no data to read"}
}
// DER demands that "If the encoding represents the boolean value TRUE,
// its single contents octet shall have all eight bits set to one."
// Thus only 0 and 255 are valid encoded values.
switch bytes[0] {
case 0:
ret = false
case 0xff:
ret = true
default:
err = SyntaxError{"invalid boolean: should be 0x00 of 0xFF"}
}
return
}
func sizeBool(b bool) int {
return 1
}
func writeBool(bytes *Bytes, b bool) int {
if b == false {
return bytes.writeBytes([]byte{0x00})
} else {
return bytes.writeBytes([]byte{0xff})
}
}
// INTEGER
// parseInt64 treats the given bytes as a big-endian, signed integer and
// returns the result.
func parseInt64(bytes []byte) (ret int64, err error) {
if len(bytes) > 8 {
// We'll overflow an int64 in this case.
err = StructuralError{"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 sizeInt64(i int64) (size int) {
n := 1
for i > 127 {
n++
i >>= 8
}
for i < -128 {
n++
i >>= 8
}
return n
}
func writeInt64(bytes *Bytes, i int64) (size int) {
n := sizeInt64(i)
buf := [8]byte{}
for j := 0; j < n; j++ {
b := i >> uint((n-1-j)*8)
buf[j] = byte(b)
}
bytes.writeBytes(buf[:n])
return n
}
// parseInt treats the given bytes as a big-endian, signed integer and returns
// the result.
func parseInt32(bytes []byte) (int32, error) {
ret64, err := parseInt64(bytes)
if err != nil {
return 0, err
}
if ret64 != int64(int32(ret64)) {
return 0, StructuralError{"integer too large"}
}
return int32(ret64), nil
}
func sizeInt32(i int32) int {
return sizeInt64(int64(i))
}
func writeInt32(bytes *Bytes, i int32) int {
return writeInt64(bytes, int64(i))
}
var bigOne = big.NewInt(1)
// // parseBigInt treats the given bytes as a big-endian, signed integer and returns
// // the result.
// func parseBigInt(bytes []byte) *big.Int {
// ret := new(big.Int)
// if len(bytes) > 0 && bytes[0]&0x80 == 0x80 {
// // This is a negative number.
// notBytes := make([]byte, len(bytes))
// for i := range notBytes {
// notBytes[i] = ^bytes[i]
// }
// ret.SetBytes(notBytes)
// ret.Add(ret, bigOne)
// ret.Neg(ret)
// return ret
// }
// ret.SetBytes(bytes)
// return ret
// }
// // BIT STRING
// // BitString is the structure to use when you want an ASN.1 BIT STRING type. A
// // bit string is padded up to the nearest byte in memory and the number of
// // valid bits is recorded. Padding bits will be zero.
// type BitString struct {
// Bytes []byte // bits packed into bytes.
// BitLength int // length in bits.
// }
// // At returns the bit at the given index. If the index is out of range it
// // returns false.
// func (b BitString) At(i int) int {
// if i < 0 || i >= b.BitLength {
// return 0
// }
// x := i / 8
// y := 7 - uint(i%8)
// return int(b.Bytes[x]>>y) & 1
// }
// // RightAlign returns a slice where the padding bits are at the beginning. The
// // slice may share memory with the BitString.
// func (b BitString) RightAlign() []byte {
// shift := uint(8 - (b.BitLength % 8))
// if shift == 8 || len(b.Bytes) == 0 {
// return b.Bytes
// }
// a := make([]byte, len(b.Bytes))
// a[0] = b.Bytes[0] >> shift
// for i := 1; i < len(b.Bytes); i++ {
// a[i] = b.Bytes[i-1] << (8 - shift)
// a[i] |= b.Bytes[i] >> shift
// }
// return a
// }
// // parseBitString parses an ASN.1 bit string from the given byte slice and returns it.
// func parseBitString(bytes []byte) (ret BitString, err error) {
// if len(bytes) == 0 {
// err = SyntaxError{"zero length BIT STRING"}
// return
// }
// paddingBits := int(bytes[0])
// if paddingBits > 7 ||
// len(bytes) == 1 && paddingBits > 0 ||
// bytes[len(bytes)-1]&((1<<bytes[0])-1) != 0 {
// err = SyntaxError{"invalid padding bits in BIT STRING"}
// return
// }
// ret.BitLength = (len(bytes)-1)*8 - paddingBits
// ret.Bytes = bytes[1:]
// return
// }
// OBJECT IDENTIFIER
// An ObjectIdentifier represents an ASN.1 OBJECT IDENTIFIER.
// type ObjectIdentifier []int
// // Equal reports whether oi and other represent the same identifier.
// func (oi ObjectIdentifier) Equal(other ObjectIdentifier) bool {
// if len(oi) != len(other) {
// return false
// }
// for i := 0; i < len(oi); i++ {
// if oi[i] != other[i] {
// return false
// }
// }
// return true
// }
// func (oi ObjectIdentifier) String() string {
// var s string
// for i, v := range oi {
// if i > 0 {
// s += "."
// }
// s += strconv.Itoa(v)
// }
// return s
// }
// // parseObjectIdentifier parses an OBJECT IDENTIFIER from the given bytes and
// // returns it. An object identifier is a sequence of variable length integers
// // that are assigned in a hierarchy.
// func parseObjectIdentifier(bytes []byte) (s []int, err error) {
// if len(bytes) == 0 {
// err = SyntaxError{"zero length OBJECT IDENTIFIER"}
// return
// }
// // In the worst case, we get two elements from the first byte (which is
// // encoded differently) and then every varint is a single byte long.
// s = make([]int, len(bytes)+1)
// // The first varint is 40*value1 + value2:
// // According to this packing, value1 can take the values 0, 1 and 2 only.
// // When value1 = 0 or value1 = 1, then value2 is <= 39. When value1 = 2,
// // then there are no restrictions on value2.
// v, offset, err := parseBase128Int(bytes, 0)
// if err != nil {
// return
// }
// if v < 80 {
// s[0] = v / 40
// s[1] = v % 40
// } else {
// s[0] = 2
// s[1] = v - 80
// }
// i := 2
// for ; offset < len(bytes); i++ {
// v, offset, err = parseBase128Int(bytes, offset)
// if err != nil {
// return
// }
// s[i] = v
// }
// s = s[0:i]
// return
// }
// ENUMERATED
// An Enumerated is represented as a plain int.
type Enumerated int
// FLAG
// A Flag accepts any data and is set to true if present.
type Flag bool
// parseBase128Int parses a base-128 encoded int from the given offset in the
// given byte slice. It returns the value and the new offset.
func parseBase128Int(bytes []byte, initOffset int) (ret, offset int, err error) {
offset = initOffset
for shifted := 0; offset < len(bytes); shifted++ {
if shifted > 4 {
err = StructuralError{"base 128 integer too large"}
return
}
ret <<= 7
b := bytes[offset]
ret |= int(b & 0x7f)
offset++
if b&0x80 == 0 {
return
}
}
err = SyntaxError{"truncated base 128 integer"}
return
}
func sizeBase128Int(value int) (size int) {
for i := value; i > 0; i >>= 7 {
size++
}
return
}
// Write start as the end of the slice and goes back
// We assume we have enough size
func writeBase128Int(bytes *Bytes, value int) (size int) {
for ; value > 0 || size == 0; value >>= 7 { // Write at least one byte even if the value is 0
// Get the 7 lowest bits
b := byte(value) & 0x7f
if value < 128 {
b |= 0x80
}
bytes.writeBytes([]byte{b})
size++
}
return
}
// // UTCTime
// func parseUTCTime(bytes []byte) (ret time.Time, err error) {
// s := string(bytes)
// ret, err = time.Parse("0601021504Z0700", s)
// if err != nil {
// ret, err = time.Parse("060102150405Z0700", s)
// }
// if err == nil && ret.Year() >= 2050 {
// // UTCTime only encodes times prior to 2050. See https://tools.ietf.org/html/rfc5280#section-4.1.2.5.1
// ret = ret.AddDate(-100, 0, 0)
// }
// return
// }
// // parseGeneralizedTime parses the GeneralizedTime from the given byte slice
// // and returns the resulting time.
// func parseGeneralizedTime(bytes []byte) (ret time.Time, err error) {
// return time.Parse("20060102150405Z0700", string(bytes))
// }
// // PrintableString
// // parsePrintableString parses a ASN.1 PrintableString from the given byte
// // array and returns it.
// func parsePrintableString(bytes []byte) (ret string, err error) {
// for _, b := range bytes {
// if !isPrintable(b) {
// err = SyntaxError{"PrintableString contains invalid character"}
// return
// }
// }
// ret = string(bytes)
// return
// }
// // isPrintable returns true iff the given b is in the ASN.1 PrintableString set.
// func isPrintable(b byte) bool {
// return 'a' <= b && b <= 'z' ||
// 'A' <= b && b <= 'Z' ||
// '0' <= b && b <= '9' ||
// '\'' <= b && b <= ')' ||
// '+' <= b && b <= '/' ||
// b == ' ' ||
// b == ':' ||
// b == '=' ||
// b == '?' ||
// // This is technically not allowed in a PrintableString.
// // However, x509 certificates with wildcard strings don't
// // always use the correct string type so we permit it.
// b == '*'
// }
// // IA5String
// // parseIA5String parses a ASN.1 IA5String (ASCII string) from the given
// // byte slice and returns it.
// func parseIA5String(bytes []byte) (ret string, err error) {
// for _, b := range bytes {
// if b >= 0x80 {
// err = SyntaxError{"IA5String contains invalid character"}
// return
// }
// }
// ret = string(bytes)
// return
// }
// // T61String
// // parseT61String parses a ASN.1 T61String (8-bit clean string) from the given
// // byte slice and returns it.
// func parseT61String(bytes []byte) (ret string, err error) {
// return string(bytes), nil
// }
// UTF8String
// parseUTF8String parses a ASN.1 UTF8String (raw UTF-8) from the given byte
// array and returns it.
// func parseUTF8String(bytes []byte) (ret string, err error) {
// return string(bytes), nil
// }
// func sizeUTF8String(s string) int {
// return len(s)
// }
// func writeUTF8String(bytes *Bytes, s string) int {
// return bytes.writeString(s)
// }
// Octet string
func parseOctetString(bytes []byte) (ret []byte, err error) {
return bytes, nil
}
func sizeOctetString(s []byte) int {
return len(s)
}
func writeOctetString(bytes *Bytes, s []byte) int {
return bytes.writeBytes(s)
}
// A RawValue represents an undecoded ASN.1 object.
type RawValue struct {
Class, Tag int
IsCompound bool
Bytes []byte
FullBytes []byte // includes the tag and length
}
// RawContent is used to signal that the undecoded, DER data needs to be
// preserved for a struct. To use it, the first field of the struct must have
// this type. It's an error for any of the other fields to have this type.
type RawContent []byte
// Tagging
// parseTagAndLength parses an ASN.1 tag and length pair from the given offset
// into a byte slice. It returns the parsed data and the new offset. SET and
// SET OF (tag 17) are mapped to SEQUENCE and SEQUENCE OF (tag 16) since we
// don't distinguish between ordered and unordered objects in this code.
func parseTagAndLength(bytes []byte, initOffset int) (ret TagAndLength, offset int, err error) {
offset = initOffset
b := bytes[offset]
offset++
ret.Class = int(b >> 6)
ret.IsCompound = b&0x20 == 0x20
ret.Tag = int(b & 0x1f)
// If the bottom five bits are set, then the tag number is actually base 128
// encoded afterwards
if ret.Tag == 0x1f {
ret.Tag, offset, err = parseBase128Int(bytes, offset)
if err != nil {
return
}
}
if offset >= len(bytes) {
err = SyntaxError{"truncated tag or length"}
return
}
b = bytes[offset]
offset++
if b&0x80 == 0 {
// The length is encoded in the bottom 7 bits.
ret.Length = int(b & 0x7f)
} else {
// Bottom 7 bits give the number of length bytes to follow.
numBytes := int(b & 0x7f)
if numBytes == 0 {
err = SyntaxError{"indefinite length found (not DER)"}
return
}
ret.Length = 0
for i := 0; i < numBytes; i++ {
if offset >= len(bytes) {
err = SyntaxError{"truncated tag or length"}
return
}
b = bytes[offset]
offset++
if ret.Length >= 1<<23 {
// We can't shift ret.length up without
// overflowing.
err = StructuralError{"length too large"}
return
}
ret.Length <<= 8
ret.Length |= int(b)
if ret.Length == 0 {
// DER requires that lengths be minimal.
err = StructuralError{"superfluous leading zeros in length"}
return
}
}
}
return
}
// func writeTagAndLength(out *forkableWriter, t tagAndLength) (err error) {
// b := uint8(t.class) << 6
// if t.isCompound {
// b |= 0x20
// }
// if t.tag >= 31 {
// b |= 0x1f
// err = out.WriteByte(b)
// if err != nil {
// return
// }
// err = marshalBase128Int(out, int64(t.tag))
// if err != nil {
// return
// }
// } else {
// b |= uint8(t.tag)
// err = out.WriteByte(b)
// if err != nil {
// return
// }
// }
// if t.length >= 128 {
// l := lengthLength(t.length)
// err = out.WriteByte(0x80 | byte(l))
// if err != nil {
// return
// }
// err = marshalLength(out, t.length)
// if err != nil {
// return
// }
// } else {
// err = out.WriteByte(byte(t.length))
// if err != nil {
// return
// }
// }
// return nil
// }
func sizeTagAndLength(tag int, length int) (size int) {
// Compute the size of the tag
size = 1
if tag >= 31 {
// Long-form identifier if the tag is greater than 30
// http://en.wikipedia.org/wiki/X.690#Identifier_tags_greater_than_30
size += sizeBase128Int(tag)
}
// Compute the size of the length using the definite form
// http://en.wikipedia.org/wiki/X.690#The_definite_form
size += 1
if length >= 128 {
size += 1
for length > 255 {
size++
length >>= 8
}
}
return
}
func writeTagAndLength(bytes *Bytes, t TagAndLength) (size int) {
// We are writing backward, so write the length bytes first
if t.Length < 0 {
panic("Can't have a negative length")
} else if t.Length >= 128 {
//lengthBytes := writeInt64(bytes, int64(t.Length))
lengthBytes := 0
val := t.Length
for val > 0 {
lengthBytes++
bytes.writeBytes([]byte{byte(val & 0xff)})
val >>= 8
}
bytes.writeBytes([]byte{byte(0x80 | byte(lengthBytes))})
size += lengthBytes + 1
} else if t.Length < 128 {
size += bytes.writeBytes([]byte{byte(t.Length)})
}
// Then write the tag
b := uint8(t.Class) << 6
if t.IsCompound {
b |= 0x20
}
if t.Tag >= 31 {
b |= 0x1f
size += writeBase128Int(bytes, t.Tag)
} else {
b |= uint8(t.Tag)
}
size += bytes.writeBytes([]byte{byte(b)})
return
}
//
// END encoding/asn1/asn1.go
//

54
goldap/asn1_test.go Normal file
View file

@ -0,0 +1,54 @@
package message
import (
"testing"
"bytes"
)
func TestSizeInt64(t *testing.T) {
s := sizeInt64(0)
if s != 1 {
t.Errorf("computed size is %d, expected 1", s)
}
s = sizeInt64(127)
if s != 1 {
t.Errorf("computed size is %d, expected 1", s)
}
s = sizeInt64(128)
if s != 2 {
t.Errorf("computed size is %d, expected 2", s)
}
s = sizeInt64(50000)
if s != 3 {
t.Errorf("computed size is %d, expected 3", s)
}
s = sizeInt64(-12345)
if s != 2 {
t.Errorf("computed size is %d, expected 2", s)
}
}
func TestWriteInt64(t *testing.T) {
vtests := []int64{0, 127, 128, 50000, -12345}
expsize := []int{1, 1, 2, 3, 2}
expresult := [][]byte{{0x00}, {0x7F}, {0x00, 0x80}, {0x00, 0xc3, 0x50}, {0xcf, 0xc7}}
for idx, v := range vtests {
fs := sizeInt64(v)
b := NewBytes(fs, make([]byte, fs))
t.Log("computing", v)
s := writeInt64(b, v)
if s != expsize[idx] {
t.Errorf("computed size is %d, expected %d", s, expsize[idx])
}
if !bytes.Equal(b.Bytes(), expresult[idx]) {
t.Errorf("wrong computed bytes, got %v, expected %v", b.Bytes(), expresult[idx])
}
a, e := parseInt64(b.Bytes())
t.Log("parse", a, e)
}
}

44
goldap/assertion_value.go Normal file
View file

@ -0,0 +1,44 @@
package message
import "fmt"
//
// AssertionValue ::= OCTET STRING
func readAssertionValue(bytes *Bytes) (assertionvalue AssertionValue, err error) {
var octetstring OCTETSTRING
octetstring, err = readOCTETSTRING(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readAssertionValue:\n%s", err.Error())}
return
}
assertionvalue = AssertionValue(octetstring)
return
}
func readTaggedAssertionValue(bytes *Bytes, class int, tag int) (assertionvalue AssertionValue, err error) {
var octetstring OCTETSTRING
octetstring, err = readTaggedOCTETSTRING(bytes, class, tag)
if err != nil {
err = LdapError{fmt.Sprintf("readTaggedAssertionValue:\n%s", err.Error())}
return
}
assertionvalue = AssertionValue(octetstring)
return
}
func (assertion AssertionValue) size() int {
return OCTETSTRING(assertion).size()
}
func (assertion AssertionValue) sizeTagged(tag int) int {
return OCTETSTRING(assertion).sizeTagged(tag)
}
func (assertion AssertionValue) write(bytes *Bytes) int {
return OCTETSTRING(assertion).write(bytes)
}
func (assertion AssertionValue) writeTagged(bytes *Bytes, class int, tag int) int {
return OCTETSTRING(assertion).writeTagged(bytes, class, tag)
}

40
goldap/attribute.go Normal file
View file

@ -0,0 +1,40 @@
package message
import "fmt"
//
// Attribute ::= PartialAttribute(WITH COMPONENTS {
// ...,
// vals (SIZE(1..MAX))})
func (attribute *Attribute) Type_() AttributeDescription {
return attribute.type_
}
func (attribute *Attribute) Vals() []AttributeValue {
return attribute.vals
}
func readAttribute(bytes *Bytes) (ret Attribute, err error) {
var par PartialAttribute
par, err = readPartialAttribute(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readAttribute:\n%s", err.Error())}
return
}
if len(par.vals) == 0 {
err = LdapError{"readAttribute: expecting at least one value"}
return
}
ret = Attribute(par)
return
}
func (attribute Attribute) size() (size int) {
return PartialAttribute(attribute).size()
}
func (attribute Attribute) write(bytes *Bytes) (size int) {
return PartialAttribute(attribute).write(bytes)
}

View file

@ -0,0 +1,50 @@
package message
import "fmt"
//
// AttributeDescription ::= LDAPString
// -- Constrained to <attributedescription>
// -- [RFC4512]
func (description AttributeDescription) Pointer() *AttributeDescription { return &description }
func readAttributeDescription(bytes *Bytes) (ret AttributeDescription, err error) {
var ldapstring LDAPString
ldapstring, err = readLDAPString(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readAttributeDescription:\n%s", err.Error())}
return
}
// @TODO: check RFC4512
ret = AttributeDescription(ldapstring)
return
}
func readTaggedAttributeDescription(bytes *Bytes, class int, tag int) (ret AttributeDescription, err error) {
var ldapstring LDAPString
ldapstring, err = readTaggedLDAPString(bytes, class, tag)
// @TODO: check RFC4512
if err != nil {
err = LdapError{fmt.Sprintf("readTaggedAttributeDescription:\n%s", err.Error())}
return
}
ret = AttributeDescription(ldapstring)
return
}
func (description AttributeDescription) size() int {
return LDAPString(description).size()
}
func (description AttributeDescription) sizeTagged(tag int) int {
return LDAPString(description).sizeTagged(tag)
}
func (description AttributeDescription) write(bytes *Bytes) int {
return LDAPString(description).write(bytes)
}
func (description AttributeDescription) writeTagged(bytes *Bytes, class int, tag int) int {
return LDAPString(description).writeTagged(bytes, class, tag)
}

43
goldap/attribute_list.go Normal file
View file

@ -0,0 +1,43 @@
package message
import "fmt"
//
// AttributeList ::= SEQUENCE OF attribute Attribute
func readAttributeList(bytes *Bytes) (ret AttributeList, err error) {
err = bytes.ReadSubBytes(classUniversal, tagSequence, ret.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readAttributeList:\n%s", err.Error())}
return
}
return
}
func (list *AttributeList) readComponents(bytes *Bytes) (err error) {
for bytes.HasMoreData() {
var attr Attribute
attr, err = readAttribute(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
*list = append(*list, attr)
}
return
}
func (list AttributeList) size() (size int) {
for _, att := range list {
size += att.size()
}
size += sizeTagAndLength(tagSequence, size)
return
}
func (list AttributeList) write(bytes *Bytes) (size int) {
for i := len(list) - 1; i >= 0; i-- {
size += list[i].write(bytes)
}
size += bytes.WriteTagAndLength(classUniversal, isCompound, tagSequence, size)
return
}

View file

@ -0,0 +1,46 @@
package message
import "fmt"
//
// AttributeSelection ::= SEQUENCE OF selector LDAPString
// -- The LDAPString is constrained to
// -- <attributeSelector> in Section 4.5.1.8
func readAttributeSelection(bytes *Bytes) (attributeSelection AttributeSelection, err error) {
err = bytes.ReadSubBytes(classUniversal, tagSequence, attributeSelection.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readAttributeSelection:\n%s", err.Error())}
return
}
return
}
func (selection *AttributeSelection) readComponents(bytes *Bytes) (err error) {
for bytes.HasMoreData() {
var ldapstring LDAPString
ldapstring, err = readLDAPString(bytes)
// @TOTO: check <attributeSelector> in Section 4.5.1.8
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
*selection = append(*selection, ldapstring)
}
return
}
func (selection AttributeSelection) write(bytes *Bytes) (size int) {
for i := len(selection) - 1; i >= 0; i-- {
size += selection[i].write(bytes)
}
size += bytes.WriteTagAndLength(classUniversal, isCompound, tagSequence, size)
return
}
func (selection AttributeSelection) size() (size int) {
for _, selector := range selection {
size += selector.size()
}
size += sizeTagAndLength(tagSequence, size)
return
}

24
goldap/attribute_value.go Normal file
View file

@ -0,0 +1,24 @@
package message
import "fmt"
//
// AttributeValue ::= OCTET STRING
func readAttributeValue(bytes *Bytes) (ret AttributeValue, err error) {
octetstring, err := readOCTETSTRING(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readAttributeValue:\n%s", err.Error())}
return
}
ret = AttributeValue(octetstring)
return
}
func (value AttributeValue) write(bytes *Bytes) int {
return OCTETSTRING(value).write(bytes)
}
func (value AttributeValue) size() int {
return OCTETSTRING(value).size()
}

View file

@ -0,0 +1,77 @@
package message
import "fmt"
//
// AttributeValueAssertion ::= SEQUENCE {
// attributeDesc AttributeDescription,
// assertionValue AssertionValue }
func (assertion *AttributeValueAssertion) AttributeDesc() AttributeDescription {
return assertion.attributeDesc
}
func (assertion *AttributeValueAssertion) AssertionValue() AssertionValue {
return assertion.assertionValue
}
func readAttributeValueAssertion(bytes *Bytes) (ret AttributeValueAssertion, err error) {
err = bytes.ReadSubBytes(classUniversal, tagSequence, ret.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readAttributeValueAssertion:\n%s", err.Error())}
return
}
return
}
func readTaggedAttributeValueAssertion(bytes *Bytes, class int, tag int) (ret AttributeValueAssertion, err error) {
err = bytes.ReadSubBytes(class, tag, ret.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readTaggedAttributeValueAssertion:\n%s", err.Error())}
return
}
return
}
func (assertion *AttributeValueAssertion) readComponents(bytes *Bytes) (err error) {
assertion.attributeDesc, err = readAttributeDescription(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
assertion.assertionValue, err = readAssertionValue(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
return
}
func (assertion AttributeValueAssertion) write(bytes *Bytes) (size int) {
size += assertion.assertionValue.write(bytes)
size += assertion.attributeDesc.write(bytes)
size += bytes.WriteTagAndLength(classUniversal, isCompound, tagSequence, size)
return
}
func (assertion AttributeValueAssertion) writeTagged(bytes *Bytes, class int, tag int) (size int) {
size += assertion.assertionValue.write(bytes)
size += assertion.attributeDesc.write(bytes)
size += bytes.WriteTagAndLength(class, isCompound, tag, size)
return
}
func (assertion AttributeValueAssertion) size() (size int) {
size += assertion.attributeDesc.size()
size += assertion.assertionValue.size()
size += sizeTagAndLength(tagSequence, size)
return
}
func (assertion AttributeValueAssertion) sizeTagged(tag int) (size int) {
size += assertion.attributeDesc.size()
size += assertion.assertionValue.size()
size += sizeTagAndLength(tag, size)
return
}

View file

@ -0,0 +1,37 @@
package message
import "fmt"
//
// AuthenticationChoice ::= CHOICE {
// simple [0] OCTET STRING,
// -- 1 and 2 reserved
// sasl [3] SaslCredentials,
// ... }
func readAuthenticationChoice(bytes *Bytes) (ret AuthenticationChoice, err error) {
tagAndLength, err := bytes.PreviewTagAndLength()
if err != nil {
err = LdapError{fmt.Sprintf("readAuthenticationChoice:\n%s", err.Error())}
return
}
err = tagAndLength.ExpectClass(classContextSpecific)
if err != nil {
err = LdapError{fmt.Sprintf("readAuthenticationChoice:\n%s", err.Error())}
return
}
switch tagAndLength.Tag {
case TagAuthenticationChoiceSimple:
ret, err = readTaggedOCTETSTRING(bytes, classContextSpecific, TagAuthenticationChoiceSimple)
case TagAuthenticationChoiceSaslCredentials:
ret, err = readSaslCredentials(bytes)
default:
err = LdapError{fmt.Sprintf("readAuthenticationChoice: invalid tag value %d for AuthenticationChoice", tagAndLength.Tag)}
return
}
if err != nil {
err = LdapError{fmt.Sprintf("readAuthenticationChoice:\n%s", err.Error())}
return
}
return
}

93
goldap/bind_request.go Normal file
View file

@ -0,0 +1,93 @@
package message
import "fmt"
// BindRequest ::= [APPLICATION 0] SEQUENCE {
// version INTEGER (1 .. 127),
// name LDAPDN,
// authentication AuthenticationChoice }
func (request *BindRequest) Name() LDAPDN {
return request.name
}
func (request *BindRequest) Authentication() AuthenticationChoice {
return request.authentication
}
func (request *BindRequest) AuthenticationSimple() OCTETSTRING {
return request.Authentication().(OCTETSTRING)
}
func (request *BindRequest) AuthenticationChoice() string {
switch request.Authentication().(type) {
case OCTETSTRING:
return "simple"
case SaslCredentials:
return "sasl"
}
return ""
}
func readBindRequest(bytes *Bytes) (bindrequest BindRequest, err error) {
err = bytes.ReadSubBytes(classApplication, TagBindRequest, bindrequest.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readBindRequest:\n%s", err.Error())}
return
}
return
}
func (request *BindRequest) readComponents(bytes *Bytes) (err error) {
request.version, err = readINTEGER(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
if !(request.version >= BindRequestVersionMin && request.version <= BindRequestVersionMax) {
err = LdapError{fmt.Sprintf("readComponents: invalid version %d, must be between %d and %d", request.version, BindRequestVersionMin, BindRequestVersionMax)}
return
}
request.name, err = readLDAPDN(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
request.authentication, err = readAuthenticationChoice(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
return
}
func (request BindRequest) write(bytes *Bytes) (size int) {
switch request.authentication.(type) {
case OCTETSTRING:
size += request.authentication.(OCTETSTRING).writeTagged(bytes, classContextSpecific, TagAuthenticationChoiceSimple)
case SaslCredentials:
size += request.authentication.(SaslCredentials).writeTagged(bytes, classContextSpecific, TagAuthenticationChoiceSaslCredentials)
default:
panic(fmt.Sprintf("Unknown authentication choice: %#v", request.authentication))
}
size += request.name.write(bytes)
size += request.version.write(bytes)
size += bytes.WriteTagAndLength(classApplication, isCompound, TagBindRequest, size)
return
}
func (request BindRequest) size() (size int) {
size += request.version.size()
size += request.name.size()
switch request.authentication.(type) {
case OCTETSTRING:
size += request.authentication.(OCTETSTRING).sizeTagged(TagAuthenticationChoiceSimple)
case SaslCredentials:
size += request.authentication.(SaslCredentials).sizeTagged(TagAuthenticationChoiceSaslCredentials)
default:
panic(fmt.Sprintf("Unknown authentication choice: %#v", request.authentication))
}
size += sizeTagAndLength(TagBindRequest, size)
return
}

56
goldap/bind_response.go Normal file
View file

@ -0,0 +1,56 @@
package message
import "fmt"
// BindResponse ::= [APPLICATION 1] SEQUENCE {
// COMPONENTS OF LDAPResult,
// serverSaslCreds [7] OCTET STRING OPTIONAL }
func readBindResponse(bytes *Bytes) (bindresponse BindResponse, err error) {
err = bytes.ReadSubBytes(classApplication, TagBindResponse, bindresponse.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readBindResponse:\n%s", err.Error())}
return
}
return
}
func (response *BindResponse) readComponents(bytes *Bytes) (err error) {
response.LDAPResult.readComponents(bytes)
if bytes.HasMoreData() {
var tag TagAndLength
tag, err = bytes.PreviewTagAndLength()
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
if tag.Tag == TagBindResponseServerSaslCreds {
var serverSaslCreds OCTETSTRING
serverSaslCreds, err = readTaggedOCTETSTRING(bytes, classContextSpecific, TagBindResponseServerSaslCreds)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
response.serverSaslCreds = serverSaslCreds.Pointer()
}
}
return
}
func (response BindResponse) write(bytes *Bytes) (size int) {
if response.serverSaslCreds != nil {
size += response.serverSaslCreds.writeTagged(bytes, classContextSpecific, TagBindResponseServerSaslCreds)
}
size += response.LDAPResult.writeComponents(bytes)
size += bytes.WriteTagAndLength(classApplication, isCompound, TagBindResponse, size)
return
}
func (response BindResponse) size() (size int) {
if response.serverSaslCreds != nil {
size += response.serverSaslCreds.sizeTagged(TagBindResponseServerSaslCreds)
}
size += response.LDAPResult.sizeComponents()
size += sizeTagAndLength(TagBindResponse, size)
return
}

62
goldap/boolean.go Normal file
View file

@ -0,0 +1,62 @@
package message
import "fmt"
func readBOOLEAN(bytes *Bytes) (ret BOOLEAN, err error) {
var value interface{}
value, err = bytes.ReadPrimitiveSubBytes(classUniversal, tagBoolean, tagBoolean)
if err != nil {
err = LdapError{fmt.Sprintf("readBOOLEAN:\n%s", err.Error())}
return
}
ret = BOOLEAN(value.(bool))
return
}
func (boolean BOOLEAN) write(bytes *Bytes) int {
return bytes.WritePrimitiveSubBytes(classUniversal, tagBoolean, boolean)
}
func (boolean BOOLEAN) writeTagged(bytes *Bytes, class int, tag int) int {
return bytes.WritePrimitiveSubBytes(class, tag, boolean)
}
func readTaggedBOOLEAN(bytes *Bytes, class int, tag int) (ret BOOLEAN, err error) {
var value interface{}
value, err = bytes.ReadPrimitiveSubBytes(class, tag, tagBoolean)
if err != nil {
err = LdapError{fmt.Sprintf("readTaggedBOOLEAN:\n%s", err.Error())}
return
}
ret = BOOLEAN(value.(bool))
return
}
func SizePrimitiveSubBytes(tag int, value interface{}) (size int) {
switch value.(type) {
case BOOLEAN:
size = sizeBool(bool(value.(BOOLEAN)))
case INTEGER:
size = sizeInt32(int32(value.(INTEGER)))
case ENUMERATED:
size = sizeInt32(int32(value.(ENUMERATED)))
case OCTETSTRING:
size = sizeOctetString([]byte(string(value.(OCTETSTRING))))
default:
panic(fmt.Sprintf("SizePrimitiveSubBytes: invalid value type %v", value))
}
size += sizeTagAndLength(tag, size)
return
}
func (boolean BOOLEAN) size() int {
return SizePrimitiveSubBytes(tagBoolean, boolean)
}
func (boolean BOOLEAN) sizeTagged(tag int) int {
return SizePrimitiveSubBytes(tag, boolean)
}
func (boolean BOOLEAN) Bool() bool {
return bool(boolean)
}

199
goldap/bytes.go Normal file
View file

@ -0,0 +1,199 @@
package message
import (
"fmt"
)
type Bytes struct {
offset int
bytes []byte
}
func (bytes *Bytes) getBytes() []byte {
return bytes.bytes
}
func NewBytes(offset int, bytes []byte) (ret *Bytes) {
return &Bytes{offset: offset, bytes: bytes}
}
func (bytes Bytes) Debug() {
fmt.Printf("Offset: %d, Bytes: %+v\n", bytes.offset, bytes.bytes)
}
// Return a string with the hex dump of the bytes around the current offset
// The current offset byte is put in brackets
// Example: 0x01, [0x02], 0x03
func (bytes *Bytes) DumpCurrentBytes() (ret string) {
var strings [3]string
for i := -1; i <= 1; i++ {
if bytes.offset+i >= 0 && bytes.offset+i < len(bytes.bytes) {
strings[i+1] = fmt.Sprintf("%#x", bytes.bytes[bytes.offset+i])
}
}
ret = fmt.Sprintf("%s, [%s], %s", strings[0], strings[1], strings[2])
return
}
func (bytes *Bytes) HasMoreData() bool {
return bytes.offset < len(bytes.bytes)
}
func (bytes *Bytes) ParseTagAndLength() (ret TagAndLength, err error) {
var offset int
ret, offset, err = ParseTagAndLength(bytes.bytes, bytes.offset)
if err != nil {
err = LdapError{fmt.Sprintf("ParseTagAndLength: %s", err.Error())}
return
} else {
bytes.offset = offset
}
return
}
func (bytes *Bytes) ReadSubBytes(class int, tag int, callback func(bytes *Bytes) error) (err error) {
// Check tag
tagAndLength, err := bytes.ParseTagAndLength()
if err != nil {
return LdapError{fmt.Sprintf("ReadSubBytes:\n%s", err.Error())}
}
err = tagAndLength.Expect(class, tag, isCompound)
if err != nil {
return LdapError{fmt.Sprintf("ReadSubBytes:\n%s", err.Error())}
}
start := bytes.offset
end := bytes.offset + tagAndLength.Length
// Check we got enough bytes to process
if end > len(bytes.bytes) {
return LdapError{fmt.Sprintf("ReadSubBytes: data truncated: expecting %d bytes at offset %d", tagAndLength.Length, bytes.offset)}
}
// Process sub-bytes
subBytes := Bytes{offset: 0, bytes: bytes.bytes[start:end]}
err = callback(&subBytes)
if err != nil {
bytes.offset += subBytes.offset
err = LdapError{fmt.Sprintf("ReadSubBytes:\n%s", err.Error())}
return
}
// Check we got no more bytes to process
if subBytes.HasMoreData() {
return LdapError{fmt.Sprintf("ReadSubBytes: data too long: %d more bytes to read at offset %d", end-bytes.offset, bytes.offset)}
}
// Move offset
bytes.offset = end
return
}
func SizeSubBytes(tag int, callback func() int) (size int) {
size = callback()
size += sizeTagAndLength(tag, size)
return
}
func (bytes *Bytes) WritePrimitiveSubBytes(class int, tag int, value interface{}) (size int) {
switch value.(type) {
case BOOLEAN:
size = writeBool(bytes, bool(value.(BOOLEAN)))
case INTEGER:
size = writeInt32(bytes, int32(value.(INTEGER)))
case ENUMERATED:
size = writeInt32(bytes, int32(value.(ENUMERATED)))
case OCTETSTRING:
size = writeOctetString(bytes, []byte(string(value.(OCTETSTRING))))
default:
panic(fmt.Sprintf("WritePrimitiveSubBytes: invalid value type %v", value))
}
size += bytes.WriteTagAndLength(class, isNotCompound, tag, size)
return
}
func (bytes *Bytes) WriteTagAndLength(class int, compound bool, tag int, length int) int {
return writeTagAndLength(bytes, TagAndLength{Class: class, IsCompound: compound, Tag: tag, Length: length})
}
func (bytes *Bytes) writeString(s string) (size int) {
size = len(s)
start := bytes.offset - size
if start < 0 {
panic("Not enough space for string")
}
copy(bytes.bytes[start:], s)
bytes.offset = start
return
}
func (bytes *Bytes) writeBytes(b []byte) (size int) {
size = len(b)
start := bytes.offset - size
if start < 0 {
panic("Not enough space for bytes")
}
copy(bytes.bytes[start:], b)
bytes.offset = start
return
}
//
// Parse tag, length and read the a primitive value
// Supported types are:
// - boolean
// - integer (parsed as int32)
// - enumerated (parsed as int32)
// - UTF8 string
// - Octet string
//
// Parameters:
// - class: the expected class value(classUniversal, classApplication, classContextSpecific)
// - tag: the expected tag value
// - typeTag: the real primitive type to parse (tagBoolean, tagInteger, tagEnym, tagUTF8String, tagOctetString)
//
func (bytes *Bytes) ReadPrimitiveSubBytes(class int, tag int, typeTag int) (value interface{}, err error) {
// Check tag
tagAndLength, err := bytes.ParseTagAndLength()
if err != nil {
err = LdapError{fmt.Sprintf("ReadPrimitiveSubBytes:\n%s", err.Error())}
return
}
err = tagAndLength.Expect(class, tag, isNotCompound)
if err != nil {
err = LdapError{fmt.Sprintf("ReadPrimitiveSubBytes:\n%s", err.Error())}
return
}
start := bytes.offset
end := bytes.offset + tagAndLength.Length
// Check we got enough bytes to process
if end > len(bytes.bytes) {
// err = LdapError{fmt.Sprintf("ReadPrimitiveSubBytes: data truncated: expecting %d bytes at offset %d but only %d bytes are remaining (start: %d, length: %d, end: %d, len(b): %d, bytes: %#+v)", tagAndLength.Length, *b.offset, len(b.bytes)-start, start, tagAndLength.Length, end, len(b.bytes), b.bytes)}
err = LdapError{fmt.Sprintf("ReadPrimitiveSubBytes: data truncated: expecting %d bytes at offset %d but only %d bytes are remaining", tagAndLength.Length, bytes.offset, len(bytes.bytes)-start)}
return
}
// Process sub-bytes
subBytes := bytes.bytes[start:end]
switch typeTag {
case tagBoolean:
value, err = parseBool(subBytes)
case tagInteger:
value, err = parseInt32(subBytes)
case tagEnum:
value, err = parseInt32(subBytes)
case tagOctetString:
value, err = parseOctetString(subBytes)
default:
err = LdapError{fmt.Sprintf("ReadPrimitiveSubBytes: invalid type tag value %d", typeTag)}
return
}
if err != nil {
err = LdapError{fmt.Sprintf("ReadPrimitiveSubBytes:\n%s", err.Error())}
return
}
// Move offset
bytes.offset = end
return
}
func (bytes *Bytes) Bytes() []byte {
return bytes.bytes
}

172
goldap/bytes_test.go Normal file
View file

@ -0,0 +1,172 @@
package message
import (
"reflect"
"testing"
)
func TestReadPrimitiveSubBytesTestData(t *testing.T) {
for i, test := range PrimitiveSubBytesTestData() {
value, err := test.bytes.ReadPrimitiveSubBytes(test.class, test.tag, test.typeTag)
if err != nil {
t.Errorf("#%d failed: %s", i+1, err)
} else if !reflect.DeepEqual(test.value, value) {
t.Errorf("#%d: Wrong value %#v, got %#v", i+1, test.value, value)
} else if test.offset != test.bytes.offset {
t.Errorf("#%d: Wrong Offset, value %#v, got %#v", i+1, test.offset, test.bytes.offset)
}
}
}
func TestSizePrimitiveSubBytesTestData(t *testing.T) {
for i, test := range PrimitiveSubBytesTestData() {
value, err := test.bytes.ReadPrimitiveSubBytes(test.class, test.tag, test.typeTag)
if err != nil {
t.Errorf("#%d failed: %s", i+1, err)
} else if !reflect.DeepEqual(test.value, value) {
t.Errorf("#%d: Wrong value %#v, got %#v", i+1, test.value, value)
} else if test.offset != test.bytes.offset {
t.Errorf("#%d: Wrong Offset, value %#v, got %#v", i+1, test.offset, test.bytes.offset)
}
}
}
func NewInt(value int) (ret *int) {
ret = &value
return
}
type PrimitiveSubBytesTestSingleData struct {
bytes Bytes // Input
class int // Expected class
tag int // Expected tag
typeTag int // Expected type
value interface{} // Expected output
offset int // Expected offset after processing
}
func PrimitiveSubBytesTestData() []PrimitiveSubBytesTestSingleData {
return []PrimitiveSubBytesTestSingleData{
// Test 1
{
bytes: Bytes{
offset: 0,
bytes: []byte{0x02, 0x01, 0x09},
},
class: classUniversal,
tag: tagInteger,
typeTag: tagInteger,
value: int32(0x09),
offset: 3,
},
// Test 2
{
bytes: Bytes{
offset: 0,
bytes: []byte{0x02, 0x02, 0x09, 0x87},
},
class: classUniversal,
tag: tagInteger,
typeTag: tagInteger,
value: int32(0x0987),
offset: 4,
},
// Test 3
{
bytes: Bytes{
offset: 0,
bytes: []byte{0x02, 0x03, 0x09, 0x87, 0x65},
},
class: classUniversal,
tag: tagInteger,
typeTag: tagInteger,
value: int32(0x098765),
offset: 5,
},
// Test 4
{
bytes: Bytes{
offset: 0,
bytes: []byte{0x02, 0x04, 0x09, 0x87, 0x65, 0x43},
},
class: classUniversal,
tag: tagInteger,
typeTag: tagInteger,
value: int32(0x09876543),
offset: 6,
},
// Test 5
{
bytes: Bytes{
offset: 2,
bytes: []byte{0x30, 0x03, 0x02, 0x01, 0x0f},
},
class: classUniversal,
tag: tagInteger,
typeTag: tagInteger,
value: int32(0x0f),
offset: 5,
},
// Test 6
{
bytes: Bytes{
offset: 2,
bytes: []byte{0x30, 0x16, 0x02, 0x01, 0x0f, 0x60, 0x11, 0x02, 0x01, 0x03, 0x04, 0x00, 0xa3, 0x0a, 0x04, 0x08, 0x43, 0x52, 0x41, 0x4d, 0x2d, 0x4d, 0x44, 0x35},
},
class: classUniversal,
tag: tagInteger,
typeTag: tagInteger,
value: int32(0x0f),
offset: 5,
},
// Test 7
{
bytes: Bytes{
offset: 2,
bytes: []byte{0x30, 0x19, 0x02, 0x04, 0x7f, 0xff, 0xff, 0xff, 0x60, 0x11, 0x02, 0x01, 0x03, 0x04, 0x00, 0xa3, 0x0a, 0x04, 0x08, 0x43, 0x52, 0x41, 0x4d, 0x2d, 0x4d, 0x44, 0x35},
},
class: classUniversal,
tag: tagInteger,
typeTag: tagInteger,
value: int32(0x07fffffff),
offset: 8,
},
// Test 8
{
bytes: Bytes{
offset: 0,
bytes: []byte{0x04, 0x08, 0x43, 0x52, 0x41, 0x4d, 0x2d, 0x4d, 0x44, 0x35},
},
class: classUniversal,
tag: tagOctetString,
typeTag: tagOctetString,
value: []byte("CRAM-MD5"),
offset: 10,
},
// Test 9
{
bytes: Bytes{
offset: 0,
bytes: []byte{0x04, 0x0d, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0xe4, 0xb8, 0x96, 0xe7, 0x95, 0x8c},
},
class: classUniversal,
tag: tagOctetString,
typeTag: tagOctetString,
value: []byte("Hello, 世界"),
offset: 15,
},
// Test 10
{
bytes: Bytes{
offset: 10,
bytes: []byte{0x30, 0x1d, 0x02, 0x01, 0x05, 0x60, 0x18, 0x02, 0x01, 0x03, 0x04, 0x07, 0x6d, 0x79, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x80, 0x0a, 0x6d, 0x79, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64},
},
class: classUniversal,
tag: tagOctetString,
typeTag: tagOctetString,
value: []byte("myLogin"),
offset: 19,
},
}
}

53
goldap/compare_request.go Normal file
View file

@ -0,0 +1,53 @@
package message
import "fmt"
//
// CompareRequest ::= [APPLICATION 14] SEQUENCE {
// entry LDAPDN,
// ava AttributeValueAssertion }
func (request *CompareRequest) Entry() LDAPDN {
return request.entry
}
func (request *CompareRequest) Ava() *AttributeValueAssertion {
return &request.ava
}
func readCompareRequest(bytes *Bytes) (ret CompareRequest, err error) {
err = bytes.ReadSubBytes(classApplication, TagCompareRequest, ret.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readCompareRequest:\n%s", err.Error())}
return
}
return
}
func (request *CompareRequest) readComponents(bytes *Bytes) (err error) {
request.entry, err = readLDAPDN(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
request.ava, err = readAttributeValueAssertion(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
return
}
func (request CompareRequest) write(bytes *Bytes) (size int) {
size += request.ava.write(bytes)
size += request.entry.write(bytes)
size += bytes.WriteTagAndLength(classApplication, isCompound, TagCompareRequest, size)
return
}
func (request CompareRequest) size() (size int) {
size += request.entry.size()
size += request.ava.size()
size += sizeTagAndLength(TagCompareRequest, size)
return
}

View file

@ -0,0 +1,29 @@
package message
import "fmt"
//
// CompareResponse ::= [APPLICATION 15] LDAPResult
func (response *CompareResponse) SetResultCode(code int) {
response.resultCode = ENUMERATED(code)
}
func readCompareResponse(bytes *Bytes) (ret CompareResponse, err error) {
var res LDAPResult
res, err = readTaggedLDAPResult(bytes, classApplication, TagCompareResponse)
if err != nil {
err = LdapError{fmt.Sprintf("readCompareResponse:\n%s", err.Error())}
return
}
ret = CompareResponse(res)
return
}
func (response CompareResponse) write(bytes *Bytes) int {
return LDAPResult(response).writeTagged(bytes, classApplication, TagCompareResponse)
}
func (response CompareResponse) size() int {
return LDAPResult(response).sizeTagged(TagCompareResponse)
}

94
goldap/control.go Normal file
View file

@ -0,0 +1,94 @@
package message
import (
"errors"
"fmt"
)
//
// Control ::= SEQUENCE {
// controlType LDAPOID,
// criticality BOOLEAN DEFAULT FALSE,
// controlValue OCTET STRING OPTIONAL }
func (control *Control) ControlType() LDAPOID {
return control.controlType
}
func (control *Control) Criticality() BOOLEAN {
return control.criticality
}
func (control *Control) ControlValue() *OCTETSTRING {
return control.controlValue
}
func readControl(bytes *Bytes) (control Control, err error) {
err = bytes.ReadSubBytes(classUniversal, tagSequence, control.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readControl:\n%s", err.Error())}
return
}
return
}
func (control *Control) readComponents(bytes *Bytes) (err error) {
control.controlType, err = readLDAPOID(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
if bytes.HasMoreData() {
var tag TagAndLength
tag, err = bytes.PreviewTagAndLength()
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
if tag.Tag == tagBoolean {
control.criticality, err = readBOOLEAN(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
if control.criticality == false {
err = errors.New(fmt.Sprintf("readComponents: criticality default value FALSE should not be specified"))
return
}
}
}
if bytes.HasMoreData() {
var octetstring OCTETSTRING
octetstring, err = readOCTETSTRING(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
control.controlValue = octetstring.Pointer()
}
return
}
func (control Control) write(bytes *Bytes) (size int) {
if control.controlValue != nil {
size += control.controlValue.write(bytes)
}
if control.criticality != BOOLEAN(false) {
size += control.criticality.write(bytes)
}
size += control.controlType.write(bytes)
size += bytes.WriteTagAndLength(classUniversal, isCompound, tagSequence, size)
return
}
func (control Control) size() (size int) {
if control.controlValue != nil {
size += control.controlValue.size()
}
if control.criticality != BOOLEAN(false) {
size += control.criticality.size()
}
size += control.controlType.size()
size += sizeTagAndLength(tagSequence, size)
return
}

44
goldap/controls.go Normal file
View file

@ -0,0 +1,44 @@
package message
import "fmt"
//
// Controls ::= SEQUENCE OF control Control
func readTaggedControls(bytes *Bytes, class int, tag int) (controls Controls, err error) {
err = bytes.ReadSubBytes(class, tag, controls.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readTaggedControls:\n%s", err.Error())}
return
}
return
}
func (controls *Controls) readComponents(bytes *Bytes) (err error) {
for bytes.HasMoreData() {
var control Control
control, err = readControl(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
*controls = append(*controls, control)
}
return
}
func (controls Controls) Pointer() *Controls { return &controls }
func (controls Controls) writeTagged(bytes *Bytes, class int, tag int) (size int) {
for i := len(controls) - 1; i >= 0; i-- {
size += controls[i].write(bytes)
}
size += bytes.WriteTagAndLength(class, isCompound, tag, size)
return
}
func (controls Controls) sizeTagged(tag int) (size int) {
for _, control := range controls {
size += control.size()
}
size += sizeTagAndLength(tag, size)
return
}

24
goldap/del_request.go Normal file
View file

@ -0,0 +1,24 @@
package message
import "fmt"
//
// DelRequest ::= [APPLICATION 10] LDAPDN
func readDelRequest(bytes *Bytes) (ret DelRequest, err error) {
var res LDAPDN
res, err = readTaggedLDAPDN(bytes, classApplication, TagDelRequest)
if err != nil {
err = LdapError{fmt.Sprintf("readDelRequest:\n%s", err.Error())}
return
}
ret = DelRequest(res)
return
}
func (del DelRequest) write(bytes *Bytes) int {
return LDAPDN(del).writeTagged(bytes, classApplication, TagDelRequest)
}
func (del DelRequest) size() int {
return LDAPDN(del).sizeTagged(TagDelRequest)
}

29
goldap/del_response.go Normal file
View file

@ -0,0 +1,29 @@
package message
import "fmt"
//
// DelResponse ::= [APPLICATION 11] LDAPResult
func (del *DelResponse) SetResultCode(code int) {
del.resultCode = ENUMERATED(code)
}
func readDelResponse(bytes *Bytes) (ret DelResponse, err error) {
var res LDAPResult
res, err = readTaggedLDAPResult(bytes, classApplication, TagDelResponse)
if err != nil {
err = LdapError{fmt.Sprintf("readDelResponse:\n%s", err.Error())}
return
}
ret = DelResponse(res)
return
}
func (del DelResponse) write(bytes *Bytes) int {
return LDAPResult(del).writeTagged(bytes, classApplication, TagDelResponse)
}
func (del DelResponse) size() int {
return LDAPResult(del).sizeTagged(TagDelResponse)
}

60
goldap/dn.go Normal file
View file

@ -0,0 +1,60 @@
package message
import "fmt"
//
// LDAPDN ::= LDAPString -- Constrained to <distinguishedName>
// -- [RFC4514]
func readLDAPDN(bytes *Bytes) (ret LDAPDN, err error) {
var str LDAPString
str, err = readLDAPString(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readLDAPDN:\n%s", err.Error())}
return
}
ret = LDAPDN(str)
return
}
func readTaggedLDAPDN(bytes *Bytes, class int, tag int) (ret LDAPDN, err error) {
var ldapstring LDAPString
ldapstring, err = readTaggedLDAPString(bytes, class, tag)
if err != nil {
err = LdapError{fmt.Sprintf("readTaggedLDAPDN:\n%s", err.Error())}
return
}
// @TODO: check RFC4514
ret = LDAPDN(ldapstring)
return
}
func (l LDAPDN) Pointer() *LDAPDN { return &l }
func readRelativeLDAPDN(bytes *Bytes) (ret RelativeLDAPDN, err error) {
var ldapstring LDAPString
ldapstring, err = readLDAPString(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readRelativeLDAPDN:\n%s", err.Error())}
return
}
// @TODO: check RFC4514
ret = RelativeLDAPDN(ldapstring)
return
}
func (l LDAPDN) write(bytes *Bytes) int {
return LDAPString(l).write(bytes)
}
func (l LDAPDN) writeTagged(bytes *Bytes, class int, tag int) int {
return LDAPString(l).writeTagged(bytes, class, tag)
}
func (l LDAPDN) size() int {
return LDAPString(l).size()
}
func (l LDAPDN) sizeTagged(tag int) int {
return LDAPString(l).sizeTagged(tag)
}

34
goldap/enumerated.go Normal file
View file

@ -0,0 +1,34 @@
package message
import "fmt"
func (enum ENUMERATED) Int() int {
return int(enum)
}
func readENUMERATED(bytes *Bytes, allowedValues map[ENUMERATED]string) (ret ENUMERATED, err error) {
var value interface{}
value, err = bytes.ReadPrimitiveSubBytes(classUniversal, tagEnum, tagEnum)
if err != nil {
err = LdapError{fmt.Sprintf("readENUMERATED:\n%s", err.Error())}
return
}
ret = ENUMERATED(value.(int32))
if _, ok := allowedValues[ret]; !ok {
err = LdapError{fmt.Sprintf("readENUMERATED: Invalid ENUMERATED VALUE %d", ret)}
return
}
return
}
func (enum ENUMERATED) write(bytes *Bytes) int {
return bytes.WritePrimitiveSubBytes(classUniversal, tagEnum, enum)
}
func (enum ENUMERATED) writeTagged(bytes *Bytes, class int, tag int) int {
return bytes.WritePrimitiveSubBytes(class, tag, enum)
}
func (enum ENUMERATED) size() int {
return SizePrimitiveSubBytes(tagEnum, enum)
}

9
goldap/error.go Normal file
View file

@ -0,0 +1,9 @@
package message
type LdapError struct {
Msg string
}
func (err LdapError) Error() string {
return err.Msg
}

View file

@ -0,0 +1,69 @@
package message
import "fmt"
//
// ExtendedRequest ::= [APPLICATION 23] SEQUENCE {
// requestName [0] LDAPOID,
// requestValue [1] OCTET STRING OPTIONAL }
func (extended *ExtendedRequest) RequestName() LDAPOID {
return extended.requestName
}
func (extended *ExtendedRequest) RequestValue() *OCTETSTRING {
return extended.requestValue
}
func readExtendedRequest(bytes *Bytes) (ret ExtendedRequest, err error) {
err = bytes.ReadSubBytes(classApplication, TagExtendedRequest, ret.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readExtendedRequest:\n%s", err.Error())}
return
}
return
}
func (extended *ExtendedRequest) readComponents(bytes *Bytes) (err error) {
extended.requestName, err = readTaggedLDAPOID(bytes, classContextSpecific, TagExtendedRequestName)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
if bytes.HasMoreData() {
var tag TagAndLength
tag, err = bytes.PreviewTagAndLength()
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
if tag.Tag == TagExtendedRequestValue {
var requestValue OCTETSTRING
requestValue, err = readTaggedOCTETSTRING(bytes, classContextSpecific, TagExtendedRequestValue)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
extended.requestValue = requestValue.Pointer()
}
}
return
}
func (extended ExtendedRequest) write(bytes *Bytes) (size int) {
if extended.requestValue != nil {
size += extended.requestValue.writeTagged(bytes, classContextSpecific, TagExtendedRequestValue)
}
size += extended.requestName.writeTagged(bytes, classContextSpecific, TagExtendedRequestName)
size += bytes.WriteTagAndLength(classApplication, isCompound, TagExtendedRequest, size)
return
}
func (extended ExtendedRequest) size() (size int) {
size += extended.requestName.sizeTagged(TagExtendedRequestName)
if extended.requestValue != nil {
size += extended.requestValue.sizeTagged(TagExtendedRequestValue)
}
size += sizeTagAndLength(TagExtendedRequest, size)
return
}

View file

@ -0,0 +1,85 @@
package message
import "fmt"
//
// ExtendedResponse ::= [APPLICATION 24] SEQUENCE {
// COMPONENTS OF LDAPResult,
// responseName [10] LDAPOID OPTIONAL,
// responseValue [11] OCTET STRING OPTIONAL }
func (extended *ExtendedResponse) SetResponseName(name LDAPOID) {
extended.responseName = &name
}
func readExtendedResponse(bytes *Bytes) (ret ExtendedResponse, err error) {
err = bytes.ReadSubBytes(classApplication, TagExtendedResponse, ret.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readExtendedResponse:\n%s", err.Error())}
return
}
return
}
func (extended *ExtendedResponse) readComponents(bytes *Bytes) (err error) {
extended.LDAPResult.readComponents(bytes)
if bytes.HasMoreData() {
var tag TagAndLength
tag, err = bytes.PreviewTagAndLength()
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
if tag.Tag == TagExtendedResponseName {
var oid LDAPOID
oid, err = readTaggedLDAPOID(bytes, classContextSpecific, TagExtendedResponseName)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
extended.responseName = oid.Pointer()
}
}
if bytes.HasMoreData() {
var tag TagAndLength
tag, err = bytes.PreviewTagAndLength()
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
if tag.Tag == TagExtendedResponseValue {
var responseValue OCTETSTRING
responseValue, err = readTaggedOCTETSTRING(bytes, classContextSpecific, TagExtendedResponseValue)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
extended.responseValue = responseValue.Pointer()
}
}
return
}
func (extended ExtendedResponse) write(bytes *Bytes) (size int) {
if extended.responseValue != nil {
size += extended.responseValue.writeTagged(bytes, classContextSpecific, TagExtendedResponseValue)
}
if extended.responseName != nil {
size += extended.responseName.writeTagged(bytes, classContextSpecific, TagExtendedResponseName)
}
size += extended.LDAPResult.writeComponents(bytes)
size += bytes.WriteTagAndLength(classApplication, isCompound, TagExtendedResponse, size)
return
}
func (extended ExtendedResponse) size() (size int) {
size += extended.LDAPResult.sizeComponents()
if extended.responseName != nil {
size += extended.responseName.sizeTagged(TagExtendedResponseName)
}
if extended.responseValue != nil {
size += extended.responseValue.sizeTagged(TagExtendedResponseValue)
}
size += sizeTagAndLength(TagExtendedResponse, size)
return
}

70
goldap/filter.go Normal file
View file

@ -0,0 +1,70 @@
package message
import "fmt"
//
// 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,
//
//
//
//Sermersheim Standards Track [Page 57]
//
//
//RFC 4511 LDAPv3 June 2006
//
//
// substrings [4] SubstringFilter,
// greaterOrEqual [5] AttributeValueAssertion,
// lessOrEqual [6] AttributeValueAssertion,
// present [7] AttributeDescription,
// approxMatch [8] AttributeValueAssertion,
// extensibleMatch [9] MatchingRuleAssertion,
// ... }
func readFilter(bytes *Bytes) (filter Filter, err error) {
var tagAndLength TagAndLength
tagAndLength, err = bytes.PreviewTagAndLength()
if err != nil {
err = LdapError{fmt.Sprintf("readFilter:\n%s", err.Error())}
return
}
err = tagAndLength.ExpectClass(classContextSpecific)
if err != nil {
err = LdapError{fmt.Sprintf("readFilter:\n%s", err.Error())}
return
}
switch tagAndLength.Tag {
case TagFilterAnd:
filter, err = readFilterAnd(bytes)
case TagFilterOr:
filter, err = readFilterOr(bytes)
case TagFilterNot:
filter, err = readFilterNot(bytes)
case TagFilterEqualityMatch:
filter, err = readFilterEqualityMatch(bytes)
case TagFilterSubstrings:
filter, err = readFilterSubstrings(bytes)
case TagFilterGreaterOrEqual:
filter, err = readFilterGreaterOrEqual(bytes)
case TagFilterLessOrEqual:
filter, err = readFilterLessOrEqual(bytes)
case TagFilterPresent:
filter, err = readFilterPresent(bytes)
case TagFilterApproxMatch:
filter, err = readFilterApproxMatch(bytes)
case TagFilterExtensibleMatch:
filter, err = readFilterExtensibleMatch(bytes)
default:
err = LdapError{fmt.Sprintf("readFilter: invalid tag value %d for filter", tagAndLength.Tag)}
return
}
if err != nil {
err = LdapError{fmt.Sprintf("readFilter:\n%s", err.Error())}
return
}
return
}

54
goldap/filter_and.go Normal file
View file

@ -0,0 +1,54 @@
package message
import "fmt"
// and [0] SET SIZE (1..MAX) OF filter Filter,
func (filterAnd FilterAnd) getFilterTag() int {
return TagFilterAnd
}
func (filterAnd FilterAnd) size() (size int) {
for _, filter := range filterAnd {
size += filter.size()
}
size += sizeTagAndLength(TagFilterAnd, size)
return
}
func (filterAnd *FilterAnd) readComponents(bytes *Bytes) (err error) {
count := 0
for bytes.HasMoreData() {
count++
var filter Filter
filter, err = readFilter(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents (filter %d):\n%s", count, err.Error())}
return
}
*filterAnd = append(*filterAnd, filter)
}
if len(*filterAnd) == 0 {
err = LdapError{"readComponents: expecting at least one Filter"}
return
}
return
}
func (filterAnd FilterAnd) write(bytes *Bytes) (size int) {
for i := len(filterAnd) - 1; i >= 0; i-- {
size += filterAnd[i].write(bytes)
}
size += bytes.WriteTagAndLength(classContextSpecific, isCompound, TagFilterAnd, size)
return
}
func readFilterAnd(bytes *Bytes) (filterand FilterAnd, err error) {
err = bytes.ReadSubBytes(classContextSpecific, TagFilterAnd, filterand.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readFilterAnd:\n%s", err.Error())}
return
}
return
}

View file

@ -0,0 +1,34 @@
package message
import "fmt"
// approxMatch [8] AttributeValueAssertion,
func readFilterApproxMatch(bytes *Bytes) (ret FilterApproxMatch, err error) {
var attributevalueassertion AttributeValueAssertion
attributevalueassertion, err = readTaggedAttributeValueAssertion(bytes, classContextSpecific, TagFilterApproxMatch)
if err != nil {
err = LdapError{fmt.Sprintf("readFilterApproxMatch:\n%s", err.Error())}
return
}
ret = FilterApproxMatch(attributevalueassertion)
return
}
// approxMatch [8] AttributeValueAssertion,
func (f FilterApproxMatch) write(bytes *Bytes) int {
return AttributeValueAssertion(f).writeTagged(bytes, classContextSpecific, TagFilterApproxMatch)
}
func (filterAnd FilterApproxMatch) getFilterTag() int {
return TagFilterApproxMatch
}
// approxMatch [8] AttributeValueAssertion,
func (f FilterApproxMatch) size() int {
return AttributeValueAssertion(f).sizeTagged(TagFilterApproxMatch)
}
func (a *FilterApproxMatch) AttributeDesc() AttributeDescription {
return a.attributeDesc
}
func (a *FilterApproxMatch) AssertionValue() AssertionValue {
return a.assertionValue
}

View file

@ -0,0 +1,34 @@
package message
import "fmt"
// equalityMatch [3] AttributeValueAssertion,
func readFilterEqualityMatch(bytes *Bytes) (ret FilterEqualityMatch, err error) {
var attributevalueassertion AttributeValueAssertion
attributevalueassertion, err = readTaggedAttributeValueAssertion(bytes, classContextSpecific, TagFilterEqualityMatch)
if err != nil {
err = LdapError{fmt.Sprintf("readFilterEqualityMatch:\n%s", err.Error())}
return
}
ret = FilterEqualityMatch(attributevalueassertion)
return
}
// equalityMatch [3] AttributeValueAssertion,
func (f FilterEqualityMatch) write(bytes *Bytes) int {
return AttributeValueAssertion(f).writeTagged(bytes, classContextSpecific, TagFilterEqualityMatch)
}
func (filter FilterEqualityMatch) getFilterTag() int {
return TagFilterEqualityMatch
}
// equalityMatch [3] AttributeValueAssertion,
func (f FilterEqualityMatch) size() int {
return AttributeValueAssertion(f).sizeTagged(TagFilterEqualityMatch)
}
func (a *FilterEqualityMatch) AttributeDesc() AttributeDescription {
return a.attributeDesc
}
func (a *FilterEqualityMatch) AssertionValue() AssertionValue {
return a.assertionValue
}

View file

@ -0,0 +1,28 @@
package message
import "fmt"
// extensibleMatch [9] MatchingRuleAssertion,
func readFilterExtensibleMatch(bytes *Bytes) (filterextensiblematch FilterExtensibleMatch, err error) {
var matchingruleassertion MatchingRuleAssertion
matchingruleassertion, err = readTaggedMatchingRuleAssertion(bytes, classContextSpecific, TagFilterExtensibleMatch)
if err != nil {
err = LdapError{fmt.Sprintf("readFilterExtensibleMatch:\n%s", err.Error())}
return
}
filterextensiblematch = FilterExtensibleMatch(matchingruleassertion)
return
}
// extensibleMatch [9] MatchingRuleAssertion,
func (f FilterExtensibleMatch) write(bytes *Bytes) int {
return MatchingRuleAssertion(f).writeTagged(bytes, classContextSpecific, TagFilterExtensibleMatch)
}
func (filterAnd FilterExtensibleMatch) getFilterTag() int {
return TagFilterExtensibleMatch
}
// extensibleMatch [9] MatchingRuleAssertion,
func (f FilterExtensibleMatch) size() int {
return MatchingRuleAssertion(f).sizeTagged(TagFilterExtensibleMatch)
}

View file

@ -0,0 +1,34 @@
package message
import "fmt"
// greaterOrEqual [5] AttributeValueAssertion,
func readFilterGreaterOrEqual(bytes *Bytes) (ret FilterGreaterOrEqual, err error) {
var attributevalueassertion AttributeValueAssertion
attributevalueassertion, err = readTaggedAttributeValueAssertion(bytes, classContextSpecific, TagFilterGreaterOrEqual)
if err != nil {
err = LdapError{fmt.Sprintf("readFilterGreaterOrEqual:\n%s", err.Error())}
return
}
ret = FilterGreaterOrEqual(attributevalueassertion)
return
}
// greaterOrEqual [5] AttributeValueAssertion,
func (filter FilterGreaterOrEqual) write(bytes *Bytes) int {
return AttributeValueAssertion(filter).writeTagged(bytes, classContextSpecific, TagFilterGreaterOrEqual)
}
func (filter FilterGreaterOrEqual) getFilterTag() int {
return TagFilterGreaterOrEqual
}
// greaterOrEqual [5] AttributeValueAssertion,
func (filter FilterGreaterOrEqual) size() int {
return AttributeValueAssertion(filter).sizeTagged(TagFilterGreaterOrEqual)
}
func (filter *FilterGreaterOrEqual) AttributeDesc() AttributeDescription {
return filter.attributeDesc
}
func (filter *FilterGreaterOrEqual) AssertionValue() AssertionValue {
return filter.assertionValue
}

View file

@ -0,0 +1,34 @@
package message
import "fmt"
// lessOrEqual [6] AttributeValueAssertion,
func readFilterLessOrEqual(bytes *Bytes) (ret FilterLessOrEqual, err error) {
var attributevalueassertion AttributeValueAssertion
attributevalueassertion, err = readTaggedAttributeValueAssertion(bytes, classContextSpecific, TagFilterLessOrEqual)
if err != nil {
err = LdapError{fmt.Sprintf("readFilterLessOrEqual:\n%s", err.Error())}
return
}
ret = FilterLessOrEqual(attributevalueassertion)
return
}
// lessOrEqual [6] AttributeValueAssertion,
func (f FilterLessOrEqual) write(bytes *Bytes) int {
return AttributeValueAssertion(f).writeTagged(bytes, classContextSpecific, TagFilterLessOrEqual)
}
func (filterAnd FilterLessOrEqual) getFilterTag() int {
return TagFilterLessOrEqual
}
// lessOrEqual [6] AttributeValueAssertion,
func (f FilterLessOrEqual) size() int {
return AttributeValueAssertion(f).sizeTagged(TagFilterLessOrEqual)
}
func (a *FilterLessOrEqual) AttributeDesc() AttributeDescription {
return a.attributeDesc
}
func (a *FilterLessOrEqual) AssertionValue() AssertionValue {
return a.assertionValue
}

40
goldap/filter_not.go Normal file
View file

@ -0,0 +1,40 @@
package message
import "fmt"
func (filterNot FilterNot) getFilterTag() int {
return TagFilterNot
}
// not [2] Filter,
func (filterNot FilterNot) size() (size int) {
size = filterNot.Filter.size()
size += sizeTagAndLength(tagSequence, size)
return
}
func (filterNot *FilterNot) readComponents(bytes *Bytes) (err error) {
filterNot.Filter, err = readFilter(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
return
}
// not [2] Filter,
func (filterNot FilterNot) write(bytes *Bytes) (size int) {
size = filterNot.Filter.write(bytes)
size += bytes.WriteTagAndLength(classContextSpecific, isCompound, TagFilterNot, size)
return
}
// not [2] Filter,
func readFilterNot(bytes *Bytes) (filternot FilterNot, err error) {
err = bytes.ReadSubBytes(classContextSpecific, TagFilterNot, filternot.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readFilterNot:\n%s", err.Error())}
return
}
return
}

52
goldap/filter_or.go Normal file
View file

@ -0,0 +1,52 @@
package message
import "fmt"
// or [1] SET SIZE (1..MAX) OF filter Filter,
func readFilterOr(bytes *Bytes) (filteror FilterOr, err error) {
err = bytes.ReadSubBytes(classContextSpecific, TagFilterOr, filteror.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readFilterOr:\n%s", err.Error())}
return
}
return
}
func (filteror *FilterOr) readComponents(bytes *Bytes) (err error) {
count := 0
for bytes.HasMoreData() {
count++
var filter Filter
filter, err = readFilter(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents (filter %d): %s", count, err.Error())}
return
}
*filteror = append(*filteror, filter)
}
if len(*filteror) == 0 {
err = LdapError{"readComponents: expecting at least one Filter"}
return
}
return
}
// or [1] SET SIZE (1..MAX) OF filter Filter,
func (f FilterOr) write(bytes *Bytes) (size int) {
for i := len(f) - 1; i >= 0; i-- {
size += f[i].write(bytes)
}
size += bytes.WriteTagAndLength(classContextSpecific, isCompound, TagFilterOr, size)
return
}
func (filter FilterOr) getFilterTag() int {
return TagFilterOr
}
// or [1] SET SIZE (1..MAX) OF filter Filter,
func (f FilterOr) size() (size int) {
for _, filter := range f {
size += filter.size()
}
size += sizeTagAndLength(TagFilterOr, size)
return
}

28
goldap/filter_present.go Normal file
View file

@ -0,0 +1,28 @@
package message
import "fmt"
// present [7] AttributeDescription,
func readFilterPresent(bytes *Bytes) (ret FilterPresent, err error) {
var attributedescription AttributeDescription
attributedescription, err = readTaggedAttributeDescription(bytes, classContextSpecific, TagFilterPresent)
if err != nil {
err = LdapError{fmt.Sprintf("readFilterPresent:\n%s", err.Error())}
return
}
ret = FilterPresent(attributedescription)
return
}
// present [7] AttributeDescription,
func (f FilterPresent) write(bytes *Bytes) int {
return AttributeDescription(f).writeTagged(bytes, classContextSpecific, TagFilterPresent)
}
func (filterAnd FilterPresent) getFilterTag() int {
return TagFilterPresent
}
// present [7] AttributeDescription,
func (f FilterPresent) size() int {
return AttributeDescription(f).sizeTagged(TagFilterPresent)
}

187
goldap/filter_substring.go Normal file
View file

@ -0,0 +1,187 @@
package message
import "fmt"
// substrings [4] SubstringFilter,
func readFilterSubstrings(bytes *Bytes) (filtersubstrings FilterSubstrings, err error) {
var substringfilter SubstringFilter
substringfilter, err = readTaggedSubstringFilter(bytes, classContextSpecific, TagFilterSubstrings)
if err != nil {
err = LdapError{fmt.Sprintf("readFilterSubstrings:\n%s", err.Error())}
return
}
filtersubstrings = FilterSubstrings(substringfilter)
return
}
//
// 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
// }
func readTaggedSubstringFilter(bytes *Bytes, class int, tag int) (substringfilter SubstringFilter, err error) {
err = bytes.ReadSubBytes(class, tag, substringfilter.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readTaggedSubstringFilter:\n%s", err.Error())}
return
}
return
}
func (substringfilter *SubstringFilter) readComponents(bytes *Bytes) (err error) {
substringfilter.type_, err = readAttributeDescription(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
err = substringfilter.readSubstrings(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
return
}
func (substringfilter *SubstringFilter) readSubstrings(bytes *Bytes) (err error) {
err = bytes.ReadSubBytes(classUniversal, tagSequence, substringfilter.readSubstringsComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readSubstrings:\n%s", err.Error())}
return
}
return
}
func (substringfilter *SubstringFilter) readSubstringsComponents(bytes *Bytes) (err error) {
var foundInitial = 0
var foundFinal = 0
var tagAndLength TagAndLength
for bytes.HasMoreData() {
tagAndLength, err = bytes.PreviewTagAndLength()
if err != nil {
err = LdapError{fmt.Sprintf("readSubstringsComponents:\n%s", err.Error())}
return
}
var assertionvalue AssertionValue
switch tagAndLength.Tag {
case TagSubstringInitial:
foundInitial++
if foundInitial > 1 {
err = LdapError{"readSubstringsComponents: initial can occur at most once"}
return
}
assertionvalue, err = readTaggedAssertionValue(bytes, classContextSpecific, TagSubstringInitial)
if err != nil {
err = LdapError{fmt.Sprintf("readSubstringsComponents:\n%s", err.Error())}
return
}
substringfilter.substrings = append(substringfilter.substrings, SubstringInitial(assertionvalue))
case TagSubstringAny:
assertionvalue, err = readTaggedAssertionValue(bytes, classContextSpecific, TagSubstringAny)
if err != nil {
err = LdapError{fmt.Sprintf("readSubstringsComponents:\n%s", err.Error())}
return
}
substringfilter.substrings = append(substringfilter.substrings, SubstringAny(assertionvalue))
case TagSubstringFinal:
foundFinal++
if foundFinal > 1 {
err = LdapError{"readSubstringsComponents: final can occur at most once"}
return
}
assertionvalue, err = readTaggedAssertionValue(bytes, classContextSpecific, TagSubstringFinal)
if err != nil {
err = LdapError{fmt.Sprintf("readSubstringsComponents:\n%s", err.Error())}
return
}
substringfilter.substrings = append(substringfilter.substrings, SubstringFinal(assertionvalue))
default:
err = LdapError{fmt.Sprintf("readSubstringsComponents: invalid tag %d", tagAndLength.Tag)}
return
}
}
if len(substringfilter.substrings) == 0 {
err = LdapError{"readSubstringsComponents: expecting at least one substring"}
return
}
return
}
// substrings [4] SubstringFilter,
func (f FilterSubstrings) write(bytes *Bytes) int {
return SubstringFilter(f).writeTagged(bytes, classContextSpecific, TagFilterSubstrings)
}
func (s SubstringFilter) writeTagged(bytes *Bytes, class int, tag int) (size int) {
for i := len(s.substrings) - 1; i >= 0; i-- {
substring := s.substrings[i]
switch substring.(type) {
case SubstringInitial:
size += AssertionValue(substring.(SubstringInitial)).writeTagged(bytes, classContextSpecific, TagSubstringInitial)
case SubstringAny:
size += AssertionValue(substring.(SubstringAny)).writeTagged(bytes, classContextSpecific, TagSubstringAny)
case SubstringFinal:
size += AssertionValue(substring.(SubstringFinal)).writeTagged(bytes, classContextSpecific, TagSubstringFinal)
default:
panic("Unknown type for SubstringFilter substring")
}
}
size += bytes.WriteTagAndLength(classUniversal, isCompound, tagSequence, size)
size += s.type_.write(bytes)
size += bytes.WriteTagAndLength(class, isCompound, tag, size)
return
}
//
// 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
// }
func (s SubstringFilter) write(bytes *Bytes) (size int) {
return s.writeTagged(bytes, classUniversal, tagSequence)
}
func (filter FilterSubstrings) getFilterTag() int {
return TagFilterSubstrings
}
// substrings [4] SubstringFilter,
func (f FilterSubstrings) size() int {
return SubstringFilter(f).sizeTagged(TagFilterSubstrings)
}
//
// 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
// }
func (s SubstringFilter) size() (size int) {
return s.sizeTagged(tagSequence)
}
func (s SubstringFilter) sizeTagged(tag int) (size int) {
for _, substring := range s.substrings {
switch substring.(type) {
case SubstringInitial:
size += AssertionValue(substring.(SubstringInitial)).sizeTagged(TagSubstringInitial)
case SubstringAny:
size += AssertionValue(substring.(SubstringAny)).sizeTagged(TagSubstringAny)
case SubstringFinal:
size += AssertionValue(substring.(SubstringFinal)).sizeTagged(TagSubstringFinal)
default:
panic("Unknown type for SubstringFilter substring")
}
}
size += sizeTagAndLength(tagSequence, size)
size += s.type_.size()
size += sizeTagAndLength(tag, size)
return
}
func (s *FilterSubstrings) Type_() AttributeDescription {
return s.type_
}
func (s *FilterSubstrings) Substrings() []Substring {
return s.substrings
}

53
goldap/integer.go Normal file
View file

@ -0,0 +1,53 @@
package message
import "fmt"
func readINTEGER(bytes *Bytes) (ret INTEGER, err error) {
var value interface{}
value, err = bytes.ReadPrimitiveSubBytes(classUniversal, tagInteger, tagInteger)
if err != nil {
err = LdapError{fmt.Sprintf("readINTEGER:\n%s", err.Error())}
return
}
ret = INTEGER(value.(int32))
return
}
func readTaggedINTEGER(bytes *Bytes, class int, tag int) (ret INTEGER, err error) {
var value interface{}
value, err = bytes.ReadPrimitiveSubBytes(class, tag, tagInteger)
if err != nil {
err = LdapError{fmt.Sprintf("readTaggedINTEGER:\n%s", err.Error())}
return
}
ret = INTEGER(value.(int32))
return
}
func readTaggedPositiveINTEGER(bytes *Bytes, class int, tag int) (ret INTEGER, err error) {
ret, err = readTaggedINTEGER(bytes, class, tag)
if err != nil {
err = LdapError{fmt.Sprintf("readTaggedPositiveINTEGER:\n%s", err.Error())}
return
}
if !(ret >= 0 && ret <= maxInt) {
err = LdapError{fmt.Sprintf("readTaggedPositiveINTEGER: Invalid INTEGER value %d ! Expected value between 0 and %d", ret, maxInt)}
}
return
}
func readPositiveINTEGER(bytes *Bytes) (ret INTEGER, err error) {
return readTaggedPositiveINTEGER(bytes, classUniversal, tagInteger)
}
func (i INTEGER) write(bytes *Bytes) int {
return bytes.WritePrimitiveSubBytes(classUniversal, tagInteger, i)
}
func (i INTEGER) writeTagged(bytes *Bytes, class int, tag int) int {
return bytes.WritePrimitiveSubBytes(class, tag, i)
}
func (i INTEGER) size() int {
return SizePrimitiveSubBytes(tagInteger, i)
}
func (i INTEGER) sizeTagged(tag int) int {
return SizePrimitiveSubBytes(tag, i)
}
func (l INTEGER) Int() int {
return int(l)
}

View file

@ -0,0 +1,89 @@
package message
import "fmt"
//
// IntermediateResponse ::= [APPLICATION 25] SEQUENCE {
// responseName [0] LDAPOID OPTIONAL,
// responseValue [1] OCTET STRING OPTIONAL }
func readIntermediateResponse(bytes *Bytes) (ret IntermediateResponse, err error) {
err = bytes.ReadSubBytes(classApplication, TagIntermediateResponse, ret.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readIntermediateResponse:\n%s", err.Error())}
return
}
return
}
func (bytes *Bytes) PreviewTagAndLength() (tagAndLength TagAndLength, err error) {
previousOffset := bytes.offset // Save offset
tagAndLength, err = bytes.ParseTagAndLength()
bytes.offset = previousOffset // Restore offset
return
}
func (res *IntermediateResponse) readComponents(bytes *Bytes) (err error) {
if bytes.HasMoreData() {
var tag TagAndLength
tag, err = bytes.PreviewTagAndLength()
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
if tag.Tag == TagIntermediateResponseName {
var oid LDAPOID
oid, err = readTaggedLDAPOID(bytes, classContextSpecific, TagIntermediateResponseName)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
res.responseName = oid.Pointer()
}
}
if bytes.HasMoreData() {
var tag TagAndLength
tag, err = bytes.PreviewTagAndLength()
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
if tag.Tag == TagIntermediateResponseValue {
var str OCTETSTRING
str, err = readTaggedOCTETSTRING(bytes, classContextSpecific, TagIntermediateResponseValue)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
res.responseValue = str.Pointer()
}
}
return
}
//
// IntermediateResponse ::= [APPLICATION 25] SEQUENCE {
// responseName [0] LDAPOID OPTIONAL,
// responseValue [1] OCTET STRING OPTIONAL }
func (i IntermediateResponse) write(bytes *Bytes) (size int) {
if i.responseValue != nil {
size += i.responseValue.writeTagged(bytes, classContextSpecific, TagIntermediateResponseValue)
}
if i.responseName != nil {
size += i.responseName.writeTagged(bytes, classContextSpecific, TagIntermediateResponseName)
}
size += bytes.WriteTagAndLength(classApplication, isCompound, TagIntermediateResponse, size)
return
}
//
// IntermediateResponse ::= [APPLICATION 25] SEQUENCE {
// responseName [0] LDAPOID OPTIONAL,
// responseValue [1] OCTET STRING OPTIONAL }
func (i IntermediateResponse) size() (size int) {
if i.responseName != nil {
size += i.responseName.sizeTagged(TagIntermediateResponseName)
}
if i.responseValue != nil {
size += i.responseValue.sizeTagged(TagIntermediateResponseValue)
}
size += sizeTagAndLength(TagIntermediateResponse, size)
return
}

View file

@ -0,0 +1,117 @@
package message
import "fmt"
//
// MatchingRuleAssertion ::= SEQUENCE {
// matchingRule [1] MatchingRuleId OPTIONAL,
// type [2] AttributeDescription OPTIONAL,
// matchValue [3] AssertionValue,
// dnAttributes [4] BOOLEAN DEFAULT FALSE }
func readTaggedMatchingRuleAssertion(bytes *Bytes, class int, tag int) (ret MatchingRuleAssertion, err error) {
err = bytes.ReadSubBytes(class, tag, ret.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readTaggedMatchingRuleAssertion:\n%s", err.Error())}
return
}
return
}
func (matchingruleassertion *MatchingRuleAssertion) readComponents(bytes *Bytes) (err error) {
err = matchingruleassertion.readMatchingRule(bytes)
if err != nil {
return LdapError{fmt.Sprintf("readComponents: %s", err.Error())}
}
err = matchingruleassertion.readType(bytes)
if err != nil {
return LdapError{fmt.Sprintf("readComponents: %s", err.Error())}
}
matchingruleassertion.matchValue, err = readTaggedAssertionValue(bytes, classContextSpecific, TagMatchingRuleAssertionMatchValue)
if err != nil {
return LdapError{fmt.Sprintf("readComponents: %s", err.Error())}
}
matchingruleassertion.dnAttributes, err = readTaggedBOOLEAN(bytes, classContextSpecific, TagMatchingRuleAssertionDnAttributes)
if err != nil {
return LdapError{fmt.Sprintf("readComponents: %s", err.Error())}
}
return
}
func (matchingruleassertion *MatchingRuleAssertion) readMatchingRule(bytes *Bytes) (err error) {
var tagAndLength TagAndLength
tagAndLength, err = bytes.PreviewTagAndLength()
if err != nil {
return LdapError{fmt.Sprintf("readMatchingRule: %s", err.Error())}
}
if tagAndLength.Tag == TagMatchingRuleAssertionMatchingRule {
var matchingRule MatchingRuleId
matchingRule, err = readTaggedMatchingRuleId(bytes, classContextSpecific, TagMatchingRuleAssertionMatchingRule)
if err != nil {
return LdapError{fmt.Sprintf("readMatchingRule: %s", err.Error())}
}
matchingruleassertion.matchingRule = matchingRule.Pointer()
}
return
}
func (matchingruleassertion *MatchingRuleAssertion) readType(bytes *Bytes) (err error) {
var tagAndLength TagAndLength
tagAndLength, err = bytes.PreviewTagAndLength()
if err != nil {
return LdapError{fmt.Sprintf("readType: %s", err.Error())}
}
if tagAndLength.Tag == TagMatchingRuleAssertionType {
var attributedescription AttributeDescription
attributedescription, err = readTaggedAttributeDescription(bytes, classContextSpecific, TagMatchingRuleAssertionType)
if err != nil {
return LdapError{fmt.Sprintf("readType: %s", err.Error())}
}
matchingruleassertion.type_ = &attributedescription
}
return
}
func (m MatchingRuleAssertion) writeTagged(bytes *Bytes, class int, tag int) (size int) {
if m.dnAttributes != BOOLEAN(false) {
size += m.dnAttributes.writeTagged(bytes, classContextSpecific, TagMatchingRuleAssertionDnAttributes)
}
size += m.matchValue.writeTagged(bytes, classContextSpecific, TagMatchingRuleAssertionMatchValue)
if m.type_ != nil {
size += m.type_.writeTagged(bytes, classContextSpecific, TagMatchingRuleAssertionType)
}
if m.matchingRule != nil {
size += m.matchingRule.writeTagged(bytes, classContextSpecific, TagMatchingRuleAssertionMatchingRule)
}
size += bytes.WriteTagAndLength(class, isCompound, tag, size)
return
}
//
// MatchingRuleAssertion ::= SEQUENCE {
// matchingRule [1] MatchingRuleId OPTIONAL,
// type [2] AttributeDescription OPTIONAL,
// matchValue [3] AssertionValue,
// dnAttributes [4] BOOLEAN DEFAULT FALSE }
func (m MatchingRuleAssertion) write(bytes *Bytes) (size int) {
return m.writeTagged(bytes, classUniversal, tagSequence)
}
//
// MatchingRuleAssertion ::= SEQUENCE {
// matchingRule [1] MatchingRuleId OPTIONAL,
// type [2] AttributeDescription OPTIONAL,
// matchValue [3] AssertionValue,
// dnAttributes [4] BOOLEAN DEFAULT FALSE }
func (m MatchingRuleAssertion) size() (size int) {
return m.sizeTagged(tagSequence)
}
func (m MatchingRuleAssertion) sizeTagged(tag int) (size int) {
if m.matchingRule != nil {
size += m.matchingRule.sizeTagged(TagMatchingRuleAssertionMatchingRule)
}
if m.type_ != nil {
size += m.type_.sizeTagged(TagMatchingRuleAssertionType)
}
size += m.matchValue.sizeTagged(TagMatchingRuleAssertionMatchValue)
if m.dnAttributes != BOOLEAN(false) {
size += m.dnAttributes.sizeTagged(TagMatchingRuleAssertionDnAttributes)
}
size += sizeTagAndLength(tag, size)
return
}

View file

@ -0,0 +1,29 @@
package message
import "fmt"
//
// MatchingRuleId ::= LDAPString
func readTaggedMatchingRuleId(bytes *Bytes, class int, tag int) (matchingruleid MatchingRuleId, err error) {
var ldapstring LDAPString
ldapstring, err = readTaggedLDAPString(bytes, class, tag)
if err != nil {
err = LdapError{fmt.Sprintf("readTaggedMatchingRuleId:\n%s", err.Error())}
return
}
matchingruleid = MatchingRuleId(ldapstring)
return
}
func (m MatchingRuleId) Pointer() *MatchingRuleId { return &m }
//
// MatchingRuleId ::= LDAPString
func (m MatchingRuleId) writeTagged(bytes *Bytes, class int, tag int) int {
return LDAPString(m).writeTagged(bytes, class, tag)
}
//
// MatchingRuleId ::= LDAPString
func (m MatchingRuleId) sizeTagged(tag int) int {
return LDAPString(m).sizeTagged(tag)
}

139
goldap/message.go Normal file
View file

@ -0,0 +1,139 @@
package message
import (
"fmt"
"reflect"
)
// This appendix is normative.
//
// Lightweight-Directory-Access-Protocol-V3 {1 3 6 1 1 18}
// -- Copyright (C) The Internet Society (2006). This version of
// -- this ASN.1 module is part of RFC 4511; see the RFC itself
// -- for full legal notices.
// DEFINITIONS
// IMPLICIT TAGS
// EXTENSIBILITY IMPLIED ::=
//
// BEGIN
//
// LDAPMessage ::= SEQUENCE {
// messageID MessageID,
// protocolOp CHOICE {
// bindRequest BindRequest,
// bindResponse BindResponse,
// unbindRequest UnbindRequest,
// searchRequest SearchRequest,
// searchResEntry SearchResultEntry,
// searchResDone SearchResultDone,
// searchResRef SearchResultReference,
// modifyRequest ModifyRequest,
// modifyResponse ModifyResponse,
// addRequest AddRequest,
// addResponse AddResponse,
// delRequest DelRequest,
// delResponse DelResponse,
// modDNRequest ModifyDNRequest,
// modDNResponse ModifyDNResponse,
// compareRequest CompareRequest,
// compareResponse CompareResponse,
// abandonRequest AbandonRequest,
// extendedReq ExtendedRequest,
// extendedResp ExtendedResponse,
// ...,
// intermediateResponse IntermediateResponse },
// controls [0] Controls OPTIONAL }
//
func NewLDAPMessage() *LDAPMessage { return &LDAPMessage{} }
func (message *LDAPMessage) readComponents(bytes *Bytes) (err error) {
message.messageID, err = readMessageID(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
message.protocolOp, err = readProtocolOp(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
if bytes.HasMoreData() {
var tag TagAndLength
tag, err = bytes.PreviewTagAndLength()
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
if tag.Tag == TagLDAPMessageControls {
var controls Controls
controls, err = readTaggedControls(bytes, classContextSpecific, TagLDAPMessageControls)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
message.controls = controls.Pointer()
}
}
return
}
func (m *LDAPMessage) Write() (bytes *Bytes, err error) {
defer func() {
if e := recover(); e != nil {
err = LdapError{fmt.Sprintf("Error in LDAPMessage.Write: %s", e)}
}
}()
// Compute the needed size
totalSize := m.size()
// Initialize the structure
bytes = &Bytes{
bytes: make([]byte, totalSize),
offset: totalSize,
}
// Go !
size := 0
if m.controls != nil {
size += m.controls.writeTagged(bytes, classContextSpecific, TagLDAPMessageControls)
}
size += m.protocolOp.write(bytes)
size += m.messageID.write(bytes)
size += bytes.WriteTagAndLength(classUniversal, isCompound, tagSequence, size)
// Check
if size != totalSize || bytes.offset != 0 {
err = LdapError{fmt.Sprintf("Something went wrong while writing the message ! Size is %d instead of %d, final offset is %d instead of 0", size, totalSize, bytes.offset)}
}
return
}
func (m *LDAPMessage) size() (size int) {
size += m.messageID.size()
size += m.protocolOp.size()
if m.controls != nil {
size += m.controls.sizeTagged(TagLDAPMessageControls)
}
size += sizeTagAndLength(tagSequence, size)
return
}
func (l *LDAPMessage) MessageID() MessageID {
return l.messageID
}
func (l *LDAPMessage) SetMessageID(ID int) {
l.messageID = MessageID(ID)
}
func (l *LDAPMessage) Controls() *Controls {
return l.controls
}
func (l *LDAPMessage) ProtocolOp() ProtocolOp {
return l.protocolOp
}
func (l *LDAPMessage) ProtocolOpName() string {
return reflect.TypeOf(l.ProtocolOp()).Name()
}
func (l *LDAPMessage) ProtocolOpType() int {
switch l.protocolOp.(type) {
case BindRequest:
return TagBindRequest
}
return 0
}

46
goldap/message_id.go Normal file
View file

@ -0,0 +1,46 @@
package message
import "fmt"
func readTaggedMessageID(bytes *Bytes, class int, tag int) (ret MessageID, err error) {
var integer INTEGER
integer, err = readTaggedPositiveINTEGER(bytes, class, tag)
if err != nil {
err = LdapError{fmt.Sprintf("readTaggedMessageID:\n%s", err.Error())}
return
}
return MessageID(integer), err
}
// MessageID ::= INTEGER (0 .. maxInt)
//
// maxInt INTEGER ::= 2147483647 -- (2^^31 - 1) --
//
func readMessageID(bytes *Bytes) (ret MessageID, err error) {
return readTaggedMessageID(bytes, classUniversal, tagInteger)
}
// MessageID ::= INTEGER (0 .. maxInt)
//
// maxInt INTEGER ::= 2147483647 -- (2^^31 - 1) --
//
func (m MessageID) write(bytes *Bytes) int {
return INTEGER(m).write(bytes)
}
func (m MessageID) writeTagged(bytes *Bytes, class int, tag int) int {
return INTEGER(m).writeTagged(bytes, class, tag)
}
// MessageID ::= INTEGER (0 .. maxInt)
//
// maxInt INTEGER ::= 2147483647 -- (2^^31 - 1) --
//
func (m MessageID) size() int {
return INTEGER(m).size()
}
func (m MessageID) sizeTagged(tag int) int {
return INTEGER(m).sizeTagged(tag)
}
func (l MessageID) Int() int {
return int(l)
}

76
goldap/message_test.go Normal file
View file

@ -0,0 +1,76 @@
package message
import (
"testing"
"fmt"
)
func toHex(b []byte) (r string) {
r = "[ "
for _, e := range b {
r += fmt.Sprintf("0x%x ", e)
}
return r + "]"
}
func TestMessageID(t *testing.T) {
m := NewLDAPMessageWithProtocolOp(UnbindRequest{})
m.SetMessageID(128)
buf, err := m.Write()
if err != nil {
t.Errorf("marshalling failed with %v", err)
}
t.Logf("%v", toHex(buf.Bytes()))
ret, err := ReadLDAPMessage(NewBytes(0, buf.Bytes()))
if err != nil {
t.Errorf("unmarshalling failed with %v", err)
}
if _, ok := ret.ProtocolOp().(UnbindRequest); !ok {
t.Errorf("should be an unbind request")
}
if ret.MessageID() != 128 {
t.Errorf("Expect message id 128, got %d", ret.MessageID())
}
t.Log("Done, marshal/unmarshall worked")
}
func TestSearchEntry(t *testing.T) {
m := NewLDAPMessageWithProtocolOp(SearchResultEntry{
objectName:"cn=êige€nbgtz,ou=users,dc=deuxfleurs,dc=fr",
attributes: PartialAttributeList{
PartialAttribute{
type_:"displayname",
vals:[]AttributeValue{"êiGe€NBgTZ"},
},
PartialAttribute{
type_:"objectclass",
vals:[]AttributeValue{"inetOrgPerson"},
},
PartialAttribute{
type_:"objectclass",
vals:[]AttributeValue{"organizationalPerson"},
},
PartialAttribute{
type_:"objectclass",
vals:[]AttributeValue{"person"},
},
PartialAttribute{
type_:"objectclass",
vals:[]AttributeValue{"top"},
},
PartialAttribute{
type_:"structuralobjectclass",
vals:[]AttributeValue{"inetOrgPerson"},
},
},
})
m.SetMessageID(24)
buf, err := m.Write()
if err != nil {
t.Errorf("marshalling failed with %v", err)
}
if buf.Bytes()[0] != 0x30 {
t.Logf("Malformed message: %v", toHex(buf.Bytes()))
}
}

View file

@ -0,0 +1,87 @@
package message
import "fmt"
//
// ModifyDNRequest ::= [APPLICATION 12] SEQUENCE {
// entry LDAPDN,
// newrdn RelativeLDAPDN,
// deleteoldrdn BOOLEAN,
// newSuperior [0] LDAPDN OPTIONAL }
func readModifyDNRequest(bytes *Bytes) (ret ModifyDNRequest, err error) {
err = bytes.ReadSubBytes(classApplication, TagModifyDNRequest, ret.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readModifyDNRequest:\n%s", err.Error())}
return
}
return
}
func (req *ModifyDNRequest) readComponents(bytes *Bytes) (err error) {
req.entry, err = readLDAPDN(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
req.newrdn, err = readRelativeLDAPDN(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
req.deleteoldrdn, err = readBOOLEAN(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
if bytes.HasMoreData() {
var tag TagAndLength
tag, err = bytes.PreviewTagAndLength()
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
if tag.Tag == TagModifyDNRequestNewSuperior {
var ldapdn LDAPDN
ldapdn, err = readTaggedLDAPDN(bytes, classContextSpecific, TagModifyDNRequestNewSuperior)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
req.newSuperior = ldapdn.Pointer()
}
}
return
}
//
// ModifyDNRequest ::= [APPLICATION 12] SEQUENCE {
// entry LDAPDN,
// newrdn RelativeLDAPDN,
// deleteoldrdn BOOLEAN,
// newSuperior [0] LDAPDN OPTIONAL }
func (m ModifyDNRequest) write(bytes *Bytes) (size int) {
if m.newSuperior != nil {
size += m.newSuperior.writeTagged(bytes, classContextSpecific, TagModifyDNRequestNewSuperior)
}
size += m.deleteoldrdn.write(bytes)
size += m.newrdn.write(bytes)
size += m.entry.write(bytes)
size += bytes.WriteTagAndLength(classApplication, isCompound, TagModifyDNRequest, size)
return
}
//
// ModifyDNRequest ::= [APPLICATION 12] SEQUENCE {
// entry LDAPDN,
// newrdn RelativeLDAPDN,
// deleteoldrdn BOOLEAN,
// newSuperior [0] LDAPDN OPTIONAL }
func (m ModifyDNRequest) size() (size int) {
size += m.entry.size()
size += m.newrdn.size()
size += m.deleteoldrdn.size()
if m.newSuperior != nil {
size += m.newSuperior.sizeTagged(TagModifyDNRequestNewSuperior)
}
size += sizeTagAndLength(TagModifyDNRequest, size)
return
}

View file

@ -0,0 +1,28 @@
package message
import "fmt"
//
// ModifyDNResponse ::= [APPLICATION 13] LDAPResult
func readModifyDNResponse(bytes *Bytes) (ret ModifyDNResponse, err error) {
var res LDAPResult
res, err = readTaggedLDAPResult(bytes, classApplication, TagModifyDNResponse)
if err != nil {
err = LdapError{fmt.Sprintf("readModifyDNResponse:\n%s", err.Error())}
return
}
ret = ModifyDNResponse(res)
return
}
//
// ModifyDNResponse ::= [APPLICATION 13] LDAPResult
func (m ModifyDNResponse) write(bytes *Bytes) int {
return LDAPResult(m).writeTagged(bytes, classApplication, TagModifyDNResponse)
}
//
// ModifyDNResponse ::= [APPLICATION 13] LDAPResult
func (m ModifyDNResponse) size() int {
return LDAPResult(m).sizeTagged(TagModifyDNResponse)
}

89
goldap/modify_request.go Normal file
View file

@ -0,0 +1,89 @@
package message
import "fmt"
//
// ModifyRequest ::= [APPLICATION 6] SEQUENCE {
// object LDAPDN,
// changes SEQUENCE OF change SEQUENCE {
// operation ENUMERATED {
// add (0),
// delete (1),
// replace (2),
// ... },
// modification PartialAttribute } }
func readModifyRequest(bytes *Bytes) (ret ModifyRequest, err error) {
err = bytes.ReadSubBytes(classApplication, TagModifyRequest, ret.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readModifyRequest:\n%s", err.Error())}
return
}
return
}
func (m *ModifyRequest) readComponents(bytes *Bytes) (err error) {
m.object, err = readLDAPDN(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
err = bytes.ReadSubBytes(classUniversal, tagSequence, m.readChanges)
return
}
func (m *ModifyRequest) readChanges(bytes *Bytes) (err error) {
for bytes.HasMoreData() {
var c ModifyRequestChange
c, err = readModifyRequestChange(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readChanges:\n%s", err.Error())}
return
}
m.changes = append(m.changes, c)
}
return
}
//
// ModifyRequest ::= [APPLICATION 6] SEQUENCE {
// object LDAPDN,
// changes SEQUENCE OF change SEQUENCE {
// operation ENUMERATED {
// add (0),
// delete (1),
// replace (2),
// ... },
// modification PartialAttribute } }
func (m ModifyRequest) write(bytes *Bytes) (size int) {
for i := len(m.changes) - 1; i >= 0; i-- {
size += m.changes[i].write(bytes)
}
size += bytes.WriteTagAndLength(classUniversal, isCompound, tagSequence, size)
size += m.object.write(bytes)
size += bytes.WriteTagAndLength(classApplication, isCompound, TagModifyRequest, size)
return
}
//
// ModifyRequest ::= [APPLICATION 6] SEQUENCE {
// object LDAPDN,
// changes SEQUENCE OF change SEQUENCE {
// operation ENUMERATED {
// add (0),
// delete (1),
// replace (2),
// ... },
// modification PartialAttribute } }
func (m ModifyRequest) size() (size int) {
for _, change := range m.changes {
size += change.size()
}
size += sizeTagAndLength(tagSequence, size)
size += m.object.size()
size += sizeTagAndLength(TagModifyRequest, size)
return
}
func (m *ModifyRequest) Object() LDAPDN {
return m.object
}
func (m *ModifyRequest) Changes() []ModifyRequestChange {
return m.changes
}

View file

@ -0,0 +1,43 @@
package message
import "fmt"
func readModifyRequestChange(bytes *Bytes) (ret ModifyRequestChange, err error) {
err = bytes.ReadSubBytes(classUniversal, tagSequence, ret.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readModifyRequestChange:\n%s", err.Error())}
return
}
return
}
func (m *ModifyRequestChange) readComponents(bytes *Bytes) (err error) {
m.operation, err = readENUMERATED(bytes, EnumeratedModifyRequestChangeOperation)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
m.modification, err = readPartialAttribute(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
return
}
func (m ModifyRequestChange) write(bytes *Bytes) (size int) {
size += m.modification.write(bytes)
size += m.operation.write(bytes)
size += bytes.WriteTagAndLength(classUniversal, isCompound, tagSequence, size)
return
}
func (m ModifyRequestChange) size() (size int) {
size += m.operation.size()
size += m.modification.size()
size += sizeTagAndLength(tagSequence, size)
return
}
func (m *ModifyRequestChange) Operation() ENUMERATED {
return m.operation
}
func (m *ModifyRequestChange) Modification() *PartialAttribute {
return &m.modification
}

36
goldap/modify_response.go Normal file
View file

@ -0,0 +1,36 @@
package message
import "fmt"
//
// ModifyResponse ::= [APPLICATION 7] LDAPResult
func readModifyResponse(bytes *Bytes) (ret ModifyResponse, err error) {
var res LDAPResult
res, err = readTaggedLDAPResult(bytes, classApplication, TagModifyResponse)
if err != nil {
err = LdapError{fmt.Sprintf("readModifyResponse:\n%s", err.Error())}
return
}
ret = ModifyResponse(res)
return
}
func (l LDAPResult) writeTagged(bytes *Bytes, class int, tag int) (size int) {
size += l.writeComponents(bytes)
size += bytes.WriteTagAndLength(class, isCompound, tag, size)
return
}
//
// ModifyResponse ::= [APPLICATION 7] LDAPResult
func (m ModifyResponse) write(bytes *Bytes) int {
return LDAPResult(m).writeTagged(bytes, classApplication, TagModifyResponse)
}
//
// ModifyResponse ::= [APPLICATION 7] LDAPResult
func (m ModifyResponse) size() int {
return LDAPResult(m).sizeTagged(TagModifyResponse)
}
func (l *ModifyResponse) SetResultCode(code int) {
l.resultCode = ENUMERATED(code)
}

44
goldap/octetstring.go Normal file
View file

@ -0,0 +1,44 @@
package message
import "fmt"
func readOCTETSTRING(bytes *Bytes) (ret OCTETSTRING, err error) {
var value interface{}
value, err = bytes.ReadPrimitiveSubBytes(classUniversal, tagOctetString, tagOctetString)
if err != nil {
err = LdapError{fmt.Sprintf("readOCTETSTRING:\n%s", err.Error())}
return
}
ret = OCTETSTRING(value.([]byte))
return
}
func readTaggedOCTETSTRING(bytes *Bytes, class int, tag int) (ret OCTETSTRING, err error) {
var value interface{}
value, err = bytes.ReadPrimitiveSubBytes(class, tag, tagOctetString)
if err != nil {
err = LdapError{fmt.Sprintf("readTaggedOCTETSTRING:\n%s", err.Error())}
return
}
ret = OCTETSTRING(value.([]byte))
return
}
func (o OCTETSTRING) Pointer() *OCTETSTRING { return &o }
func (o OCTETSTRING) write(bytes *Bytes) int {
return bytes.WritePrimitiveSubBytes(classUniversal, tagOctetString, o)
}
func (o OCTETSTRING) writeTagged(bytes *Bytes, class int, tag int) int {
return bytes.WritePrimitiveSubBytes(class, tag, o)
}
func (o OCTETSTRING) size() int {
return SizePrimitiveSubBytes(tagOctetString, o)
}
func (o OCTETSTRING) sizeTagged(tag int) int {
return SizePrimitiveSubBytes(tag, o)
}
func (l OCTETSTRING) String() string {
return string(l)
}
func (l OCTETSTRING) Bytes() []byte {
return []byte(l)
}

50
goldap/oid.go Normal file
View file

@ -0,0 +1,50 @@
package message
import "fmt"
//
//
// LDAPOID ::= OCTET STRING -- Constrained to <numericoid>
// -- [RFC4512]
func (l LDAPOID) String() string {
return string(l)
}
func (l LDAPOID) Bytes() []byte {
return []byte(l)
}
func (l LDAPOID) Pointer() *LDAPOID { return &l }
func readTaggedLDAPOID(bytes *Bytes, class int, tag int) (ret LDAPOID, err error) {
var octetstring OCTETSTRING
octetstring, err = readTaggedOCTETSTRING(bytes, class, tag)
if err != nil {
err = LdapError{fmt.Sprintf("readTaggedLDAPOID:\n%s", err.Error())}
return
}
// @TODO: check RFC4512 for <numericoid>
ret = LDAPOID(octetstring)
return
}
func readLDAPOID(bytes *Bytes) (ret LDAPOID, err error) {
return readTaggedLDAPOID(bytes, classUniversal, tagOctetString)
}
func (l LDAPOID) write(bytes *Bytes) int {
return OCTETSTRING(l).write(bytes)
}
func (l LDAPOID) writeTagged(bytes *Bytes, class int, tag int) int {
return OCTETSTRING(l).writeTagged(bytes, class, tag)
}
func (l LDAPOID) size() int {
return OCTETSTRING(l).size()
}
func (l LDAPOID) sizeTagged(tag int) int {
return OCTETSTRING(l).sizeTagged(tag)
}

View file

@ -0,0 +1,76 @@
package message
import "fmt"
//
// PartialAttribute ::= SEQUENCE {
// type AttributeDescription,
// vals SET OF value AttributeValue }
func readPartialAttribute(bytes *Bytes) (ret PartialAttribute, err error) {
ret = PartialAttribute{vals: make([]AttributeValue, 0, 10)}
err = bytes.ReadSubBytes(classUniversal, tagSequence, ret.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readPartialAttribute:\n%s", err.Error())}
return
}
return
}
func (partialattribute *PartialAttribute) readComponents(bytes *Bytes) (err error) {
partialattribute.type_, err = readAttributeDescription(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
err = bytes.ReadSubBytes(classUniversal, tagSet, partialattribute.readValsComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
return
}
func (partialattribute *PartialAttribute) readValsComponents(bytes *Bytes) (err error) {
for bytes.HasMoreData() {
var attributevalue AttributeValue
attributevalue, err = readAttributeValue(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readValsComponents:\n%s", err.Error())}
return
}
partialattribute.vals = append(partialattribute.vals, attributevalue)
}
return
}
//
// PartialAttribute ::= SEQUENCE {
// type AttributeDescription,
// vals SET OF value AttributeValue }
func (p PartialAttribute) write(bytes *Bytes) (size int) {
for i := len(p.vals) - 1; i >= 0; i-- {
size += p.vals[i].write(bytes)
}
size += bytes.WriteTagAndLength(classUniversal, isCompound, tagSet, size)
size += p.type_.write(bytes)
size += bytes.WriteTagAndLength(classUniversal, isCompound, tagSequence, size)
return
}
//
// PartialAttribute ::= SEQUENCE {
// type AttributeDescription,
// vals SET OF value AttributeValue }
func (p PartialAttribute) size() (size int) {
for _, value := range p.vals {
size += value.size()
}
size += sizeTagAndLength(tagSet, size)
size += p.type_.size()
size += sizeTagAndLength(tagSequence, size)
return
}
func (p *PartialAttribute) Type_() AttributeDescription {
return p.type_
}
func (p *PartialAttribute) Vals() []AttributeValue {
return p.vals
}

View file

@ -0,0 +1,53 @@
package message
import "fmt"
//
// PartialAttributeList ::= SEQUENCE OF
// partialAttribute PartialAttribute
func readPartialAttributeList(bytes *Bytes) (ret PartialAttributeList, err error) {
ret = PartialAttributeList(make([]PartialAttribute, 0, 10))
err = bytes.ReadSubBytes(classUniversal, tagSequence, ret.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readPartialAttributeList:\n%s", err.Error())}
return
}
return
}
func (partialattributelist *PartialAttributeList) readComponents(bytes *Bytes) (err error) {
for bytes.HasMoreData() {
var partialattribute PartialAttribute
partialattribute, err = readPartialAttribute(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
*partialattributelist = append(*partialattributelist, partialattribute)
}
return
}
//
// PartialAttributeList ::= SEQUENCE OF
// partialAttribute PartialAttribute
func (p PartialAttributeList) write(bytes *Bytes) (size int) {
for i := len(p) - 1; i >= 0; i-- {
size += p[i].write(bytes)
}
size += bytes.WriteTagAndLength(classUniversal, isCompound, tagSequence, size)
return
}
//
// PartialAttributeList ::= SEQUENCE OF
// partialAttribute PartialAttribute
func (p PartialAttributeList) size() (size int) {
for _, att := range p {
size += att.size()
}
size += sizeTagAndLength(tagSequence, size)
return
}
func (p *PartialAttributeList) add(a PartialAttribute) {
*p = append(*p, a)
}

63
goldap/protocol_op.go Normal file
View file

@ -0,0 +1,63 @@
package message
import "fmt"
func readProtocolOp(bytes *Bytes) (ret ProtocolOp, err error) {
tagAndLength, err := bytes.PreviewTagAndLength()
if err != nil {
err = LdapError{fmt.Sprintf("readProtocolOp:\n%s", err.Error())}
return
}
switch tagAndLength.Tag {
case TagBindRequest:
ret, err = readBindRequest(bytes)
case TagBindResponse:
ret, err = readBindResponse(bytes)
case TagUnbindRequest:
ret, err = readUnbindRequest(bytes)
case TagSearchRequest:
ret, err = readSearchRequest(bytes)
case TagSearchResultEntry:
ret, err = readSearchResultEntry(bytes)
case TagSearchResultDone:
ret, err = readSearchResultDone(bytes)
case TagSearchResultReference:
ret, err = readSearchResultReference(bytes)
case TagModifyRequest:
ret, err = readModifyRequest(bytes)
case TagModifyResponse:
ret, err = readModifyResponse(bytes)
case TagAddRequest:
ret, err = readAddRequest(bytes)
case TagAddResponse:
ret, err = readAddResponse(bytes)
case TagDelRequest:
ret, err = readDelRequest(bytes)
case TagDelResponse:
ret, err = readDelResponse(bytes)
case TagModifyDNRequest:
ret, err = readModifyDNRequest(bytes)
case TagModifyDNResponse:
ret, err = readModifyDNResponse(bytes)
case TagCompareRequest:
ret, err = readCompareRequest(bytes)
case TagCompareResponse:
ret, err = readCompareResponse(bytes)
case TagAbandonRequest:
ret, err = readAbandonRequest(bytes)
case TagExtendedRequest:
ret, err = readExtendedRequest(bytes)
case TagExtendedResponse:
ret, err = readExtendedResponse(bytes)
case TagIntermediateResponse:
ret, err = readIntermediateResponse(bytes)
default:
err = LdapError{fmt.Sprintf("readProtocolOp: invalid tag value %d for protocolOp", tagAndLength.Tag)}
return
}
if err != nil {
err = LdapError{fmt.Sprintf("readProtocolOp:\n%s", err.Error())}
return
}
return
}

18
goldap/read.go Normal file
View file

@ -0,0 +1,18 @@
package message
import (
"fmt"
)
func ReadLDAPMessage(bytes *Bytes) (message LDAPMessage, err error) {
err = bytes.ReadSubBytes(classUniversal, tagSequence, message.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("ReadLDAPMessage:\n%s", err.Error())}
return
}
return
}
//
// END
//

3146
goldap/read_error_test.go Normal file

File diff suppressed because one or more lines are too long

2936
goldap/read_test.go Normal file

File diff suppressed because one or more lines are too long

50
goldap/referral.go Normal file
View file

@ -0,0 +1,50 @@
package message
import "fmt"
//
// Referral ::= SEQUENCE SIZE (1..MAX) OF uri URI
func readTaggedReferral(bytes *Bytes, class int, tag int) (referral Referral, err error) {
err = bytes.ReadSubBytes(class, tag, referral.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readTaggedReferral:\n%s", err.Error())}
return
}
return
}
func (referral *Referral) readComponents(bytes *Bytes) (err error) {
for bytes.HasMoreData() {
var uri URI
uri, err = readURI(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
*referral = append(*referral, uri)
}
if len(*referral) == 0 {
return LdapError{"readComponents: expecting at least one URI"}
}
return
}
func (referral Referral) Pointer() *Referral { return &referral }
//
// Referral ::= SEQUENCE SIZE (1..MAX) OF uri URI
func (r Referral) writeTagged(bytes *Bytes, class int, tag int) (size int) {
for i := len(r) - 1; i >= 0; i-- {
size += r[i].write(bytes)
}
size += bytes.WriteTagAndLength(class, isCompound, tag, size)
return
}
//
// Referral ::= SEQUENCE SIZE (1..MAX) OF uri URI
func (r Referral) sizeTagged(tag int) (size int) {
for _, uri := range r {
size += uri.size()
}
size += sizeTagAndLength(tag, size)
return
}

View file

@ -0,0 +1,15 @@
package message
//
// RelativeLDAPDN ::= LDAPString -- Constrained to <name-component>
// -- [RFC4514]
func (r RelativeLDAPDN) write(bytes *Bytes) int {
return LDAPString(r).write(bytes)
}
//
// RelativeLDAPDN ::= LDAPString -- Constrained to <name-component>
// -- [RFC4514]
func (r RelativeLDAPDN) size() int {
return LDAPString(r).size()
}

282
goldap/result.go Normal file
View file

@ -0,0 +1,282 @@
package message
import "fmt"
//
// LDAPResult ::= SEQUENCE {
// resultCode ENUMERATED {
// success (0),
// operationsError (1),
// protocolError (2),
// timeLimitExceeded (3),
// sizeLimitExceeded (4),
// compareFalse (5),
// compareTrue (6),
// authMethodNotSupported (7),
// strongerAuthRequired (8),
// -- 9 reserved --
// referral (10),
// adminLimitExceeded (11),
// unavailableCriticalExtension (12),
// confidentialityRequired (13),
// saslBindInProgress (14),
//
//
//
//Sermersheim Standards Track [Page 55]
//
//
//RFC 4511 LDAPv3 June 2006
//
//
// noSuchAttribute (16),
// undefinedAttributeType (17),
// inappropriateMatching (18),
// constraintViolation (19),
// attributeOrValueExists (20),
// invalidAttributeSyntax (21),
// -- 22-31 unused --
// noSuchObject (32),
// aliasProblem (33),
// invalidDNSyntax (34),
// -- 35 reserved for undefined isLeaf --
// aliasDereferencingProblem (36),
// -- 37-47 unused --
// inappropriateAuthentication (48),
// invalidCredentials (49),
// insufficientAccessRights (50),
// busy (51),
// unavailable (52),
// unwillingToPerform (53),
// loopDetect (54),
// -- 55-63 unused --
// namingViolation (64),
// objectClassViolation (65),
// notAllowedOnNonLeaf (66),
// notAllowedOnRDN (67),
// entryAlreadyExists (68),
// objectClassModsProhibited (69),
// -- 70 reserved for CLDAP --
// affectsMultipleDSAs (71),
// -- 72-79 unused --
// other (80),
// ... },
// matchedDN LDAPDN,
// diagnosticMessage LDAPString,
// referral [3] Referral OPTIONAL }
func readTaggedLDAPResult(bytes *Bytes, class int, tag int) (ret LDAPResult, err error) {
err = bytes.ReadSubBytes(class, tag, ret.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readTaggedLDAPResult:\n%s", err.Error())}
}
return
}
func readLDAPResult(bytes *Bytes) (ldapresult LDAPResult, err error) {
return readTaggedLDAPResult(bytes, classUniversal, tagSequence)
}
func (ldapresult *LDAPResult) readComponents(bytes *Bytes) (err error) {
ldapresult.resultCode, err = readENUMERATED(bytes, EnumeratedLDAPResultCode)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
ldapresult.matchedDN, err = readLDAPDN(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
ldapresult.diagnosticMessage, err = readLDAPString(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
if bytes.HasMoreData() {
var tag TagAndLength
tag, err = bytes.PreviewTagAndLength()
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
if tag.Tag == TagLDAPResultReferral {
var referral Referral
referral, err = readTaggedReferral(bytes, classContextSpecific, TagLDAPResultReferral)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
ldapresult.referral = referral.Pointer()
}
}
return
}
//
// LDAPResult ::= SEQUENCE {
// resultCode ENUMERATED {
// success (0),
// operationsError (1),
// protocolError (2),
// timeLimitExceeded (3),
// sizeLimitExceeded (4),
// compareFalse (5),
// compareTrue (6),
// authMethodNotSupported (7),
// strongerAuthRequired (8),
// -- 9 reserved --
// referral (10),
// adminLimitExceeded (11),
// unavailableCriticalExtension (12),
// confidentialityRequired (13),
// saslBindInProgress (14),
//
//
//
//Sermersheim Standards Track [Page 55]
//
//
//RFC 4511 LDAPv3 June 2006
//
//
// noSuchAttribute (16),
// undefinedAttributeType (17),
// inappropriateMatching (18),
// constraintViolation (19),
// attributeOrValueExists (20),
// invalidAttributeSyntax (21),
// -- 22-31 unused --
// noSuchObject (32),
// aliasProblem (33),
// invalidDNSyntax (34),
// -- 35 reserved for undefined isLeaf --
// aliasDereferencingProblem (36),
// -- 37-47 unused --
// inappropriateAuthentication (48),
// invalidCredentials (49),
// insufficientAccessRights (50),
// busy (51),
// unavailable (52),
// unwillingToPerform (53),
// loopDetect (54),
// -- 55-63 unused --
// namingViolation (64),
// objectClassViolation (65),
// notAllowedOnNonLeaf (66),
// notAllowedOnRDN (67),
// entryAlreadyExists (68),
// objectClassModsProhibited (69),
// -- 70 reserved for CLDAP --
// affectsMultipleDSAs (71),
// -- 72-79 unused --
// other (80),
// ... },
// matchedDN LDAPDN,
// diagnosticMessage LDAPString,
// referral [3] Referral OPTIONAL }
func (l LDAPResult) write(bytes *Bytes) (size int) {
size += l.writeComponents(bytes)
size += bytes.WriteTagAndLength(classUniversal, isCompound, tagSequence, size)
return
}
func (l LDAPResult) writeComponents(bytes *Bytes) (size int) {
if l.referral != nil {
size += l.referral.writeTagged(bytes, classContextSpecific, TagLDAPResultReferral)
}
size += l.diagnosticMessage.write(bytes)
size += l.matchedDN.write(bytes)
size += l.resultCode.write(bytes)
return
}
//
// LDAPResult ::= SEQUENCE {
// resultCode ENUMERATED {
// success (0),
// operationsError (1),
// protocolError (2),
// timeLimitExceeded (3),
// sizeLimitExceeded (4),
// compareFalse (5),
// compareTrue (6),
// authMethodNotSupported (7),
// strongerAuthRequired (8),
// -- 9 reserved --
// referral (10),
// adminLimitExceeded (11),
// unavailableCriticalExtension (12),
// confidentialityRequired (13),
// saslBindInProgress (14),
//
//
//
//Sermersheim Standards Track [Page 55]
//
//
//RFC 4511 LDAPv3 June 2006
//
//
// noSuchAttribute (16),
// undefinedAttributeType (17),
// inappropriateMatching (18),
// constraintViolation (19),
// attributeOrValueExists (20),
// invalidAttributeSyntax (21),
// -- 22-31 unused --
// noSuchObject (32),
// aliasProblem (33),
// invalidDNSyntax (34),
// -- 35 reserved for undefined isLeaf --
// aliasDereferencingProblem (36),
// -- 37-47 unused --
// inappropriateAuthentication (48),
// invalidCredentials (49),
// insufficientAccessRights (50),
// busy (51),
// unavailable (52),
// unwillingToPerform (53),
// loopDetect (54),
// -- 55-63 unused --
// namingViolation (64),
// objectClassViolation (65),
// notAllowedOnNonLeaf (66),
// notAllowedOnRDN (67),
// entryAlreadyExists (68),
// objectClassModsProhibited (69),
// -- 70 reserved for CLDAP --
// affectsMultipleDSAs (71),
// -- 72-79 unused --
// other (80),
// ... },
// matchedDN LDAPDN,
// diagnosticMessage LDAPString,
// referral [3] Referral OPTIONAL }
func (l LDAPResult) size() (size int) {
size += l.sizeComponents()
size += sizeTagAndLength(tagSequence, size)
return
}
func (l LDAPResult) sizeTagged(tag int) (size int) {
size += l.sizeComponents()
size += sizeTagAndLength(tag, size)
return
}
func (l LDAPResult) sizeComponents() (size int) {
if l.referral != nil {
size += l.referral.sizeTagged(TagLDAPResultReferral)
}
size += l.diagnosticMessage.size()
size += l.matchedDN.size()
size += l.resultCode.size()
return
}
func (l *LDAPResult) SetResultCode(code int) {
l.resultCode = ENUMERATED(code)
}
func (l *LDAPResult) SeMatchedDN(code string) {
l.matchedDN = LDAPDN(code)
}
func (l *LDAPResult) SetDiagnosticMessage(code string) {
l.diagnosticMessage = LDAPString(code)
}
func (l *LDAPResult) SetReferral(r *Referral) {
l.referral = r
}

View file

@ -0,0 +1,63 @@
package message
import "fmt"
//
// SaslCredentials ::= SEQUENCE {
// mechanism LDAPString,
// credentials OCTET STRING OPTIONAL }
//
func readSaslCredentials(bytes *Bytes) (authentication SaslCredentials, err error) {
authentication = SaslCredentials{}
err = bytes.ReadSubBytes(classContextSpecific, TagAuthenticationChoiceSaslCredentials, authentication.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readSaslCredentials:\n%s", err.Error())}
return
}
return
}
func (authentication *SaslCredentials) readComponents(bytes *Bytes) (err error) {
authentication.mechanism, err = readLDAPString(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
if bytes.HasMoreData() {
var credentials OCTETSTRING
credentials, err = readOCTETSTRING(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
authentication.credentials = credentials.Pointer()
}
return
}
//
// SaslCredentials ::= SEQUENCE {
// mechanism LDAPString,
// credentials OCTET STRING OPTIONAL }
//
func (s SaslCredentials) writeTagged(bytes *Bytes, class int, tag int) (size int) {
if s.credentials != nil {
size += s.credentials.write(bytes)
}
size += s.mechanism.write(bytes)
size += bytes.WriteTagAndLength(class, isCompound, tag, size)
return
}
//
// SaslCredentials ::= SEQUENCE {
// mechanism LDAPString,
// credentials OCTET STRING OPTIONAL }
//
func (s SaslCredentials) sizeTagged(tag int) (size int) {
if s.credentials != nil {
size += s.credentials.size()
}
size += s.mechanism.size()
size += sizeTagAndLength(tag, size)
return
}

246
goldap/search_request.go Normal file
View file

@ -0,0 +1,246 @@
package message
import (
"errors"
"fmt"
)
//
// 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 }
func readSearchRequest(bytes *Bytes) (searchrequest SearchRequest, err error) {
err = bytes.ReadSubBytes(classApplication, TagSearchRequest, searchrequest.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readSearchRequest:\n%s", err.Error())}
return
}
return
}
func (searchrequest *SearchRequest) readComponents(bytes *Bytes) (err error) {
searchrequest.baseObject, err = readLDAPDN(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
searchrequest.scope, err = readENUMERATED(bytes, EnumeratedSearchRequestScope)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
searchrequest.derefAliases, err = readENUMERATED(bytes, EnumeratedSearchRequestDerefAliases)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
searchrequest.sizeLimit, err = readPositiveINTEGER(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
searchrequest.timeLimit, err = readPositiveINTEGER(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
searchrequest.typesOnly, err = readBOOLEAN(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
searchrequest.filter, err = readFilter(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
searchrequest.attributes, err = readAttributeSelection(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
return
}
//
// 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 }
func (s SearchRequest) write(bytes *Bytes) (size int) {
size += s.attributes.write(bytes)
size += s.filter.write(bytes)
size += s.typesOnly.write(bytes)
size += s.timeLimit.write(bytes)
size += s.sizeLimit.write(bytes)
size += s.derefAliases.write(bytes)
size += s.scope.write(bytes)
size += s.baseObject.write(bytes)
size += bytes.WriteTagAndLength(classApplication, isCompound, TagSearchRequest, size)
return
}
//
// 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 }
func (s SearchRequest) size() (size int) {
size += s.baseObject.size()
size += s.scope.size()
size += s.derefAliases.size()
size += s.sizeLimit.size()
size += s.timeLimit.size()
size += s.typesOnly.size()
size += s.filter.size()
size += s.attributes.size()
size += sizeTagAndLength(TagSearchRequest, size)
return
}
func (s *SearchRequest) BaseObject() LDAPDN {
return s.baseObject
}
func (s *SearchRequest) Scope() ENUMERATED {
return s.scope
}
func (s *SearchRequest) DerefAliases() ENUMERATED {
return s.derefAliases
}
func (s *SearchRequest) SizeLimit() INTEGER {
return s.sizeLimit
}
func (s *SearchRequest) TimeLimit() INTEGER {
return s.timeLimit
}
func (s *SearchRequest) TypesOnly() BOOLEAN {
return s.typesOnly
}
func (s *SearchRequest) Attributes() AttributeSelection {
return s.attributes
}
func (s *SearchRequest) Filter() Filter {
return s.filter
}
func (s *SearchRequest) FilterString() string {
str, _ := s.decompileFilter(s.Filter())
return str
}
func (s *SearchRequest) decompileFilter(packet Filter) (ret string, err error) {
defer func() {
if r := recover(); r != nil {
err = errors.New("error decompiling filter")
}
}()
ret = "("
err = nil
childStr := ""
switch f := packet.(type) {
case FilterAnd:
ret += "&"
for _, child := range f {
childStr, err = s.decompileFilter(child)
if err != nil {
return
}
ret += childStr
}
case FilterOr:
ret += "|"
for _, child := range f {
childStr, err = s.decompileFilter(child)
if err != nil {
return
}
ret += childStr
}
case FilterNot:
ret += "!"
childStr, err = s.decompileFilter(f.Filter)
if err != nil {
return
}
ret += childStr
case FilterSubstrings:
ret += string(f.Type_())
ret += "="
for _, fs := range f.Substrings() {
switch fsv := fs.(type) {
case SubstringInitial:
ret += string(fsv) + "*"
case SubstringAny:
ret += "*" + string(fsv) + "*"
case SubstringFinal:
ret += "*" + string(fsv)
}
}
case FilterEqualityMatch:
ret += string(f.AttributeDesc())
ret += "="
ret += string(f.AssertionValue())
case FilterGreaterOrEqual:
ret += string(f.AttributeDesc())
ret += ">="
ret += string(f.AssertionValue())
case FilterLessOrEqual:
ret += string(f.AttributeDesc())
ret += "<="
ret += string(f.AssertionValue())
case FilterPresent:
// if 0 == len(packet.Children) {
// ret += ber.DecodeString(packet.Data.Bytes())
// } else {
// ret += ber.DecodeString(packet.Children[0].Data.Bytes())
// }
ret += string(f)
ret += "=*"
case FilterApproxMatch:
ret += string(f.AttributeDesc())
ret += "~="
ret += string(f.AssertionValue())
}
ret += ")"
return
}

View file

@ -0,0 +1,31 @@
package message
import "fmt"
//
// SearchResultDone ::= [APPLICATION 5] LDAPResult
func readSearchResultDone(bytes *Bytes) (ret SearchResultDone, err error) {
var ldapresult LDAPResult
ldapresult, err = readTaggedLDAPResult(bytes, classApplication, TagSearchResultDone)
if err != nil {
err = LdapError{fmt.Sprintf("readSearchResultDone:\n%s", err.Error())}
return
}
ret = SearchResultDone(ldapresult)
return
}
//
// SearchResultDone ::= [APPLICATION 5] LDAPResult
func (s SearchResultDone) write(bytes *Bytes) int {
return LDAPResult(s).writeTagged(bytes, classApplication, TagSearchResultDone)
}
//
// SearchResultDone ::= [APPLICATION 5] LDAPResult
func (s SearchResultDone) size() int {
return LDAPResult(s).sizeTagged(TagSearchResultDone)
}
func (l *SearchResultDone) SetResultCode(code int) {
l.resultCode = ENUMERATED(code)
}

View file

@ -0,0 +1,58 @@
package message
import "fmt"
//
// SearchResultEntry ::= [APPLICATION 4] SEQUENCE {
// objectName LDAPDN,
// attributes PartialAttributeList }
func readSearchResultEntry(bytes *Bytes) (searchresultentry SearchResultEntry, err error) {
err = bytes.ReadSubBytes(classApplication, TagSearchResultEntry, searchresultentry.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readSearchResultEntry:\n%s", err.Error())}
return
}
return
}
func (searchresultentry *SearchResultEntry) readComponents(bytes *Bytes) (err error) {
searchresultentry.objectName, err = readLDAPDN(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
searchresultentry.attributes, err = readPartialAttributeList(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
return
}
//
// SearchResultEntry ::= [APPLICATION 4] SEQUENCE {
// objectName LDAPDN,
// attributes PartialAttributeList }
func (s SearchResultEntry) write(bytes *Bytes) (size int) {
size += s.attributes.write(bytes)
size += s.objectName.write(bytes)
size += bytes.WriteTagAndLength(classApplication, isCompound, TagSearchResultEntry, size)
return
}
//
// SearchResultEntry ::= [APPLICATION 4] SEQUENCE {
// objectName LDAPDN,
// attributes PartialAttributeList }
func (s SearchResultEntry) size() (size int) {
size += s.objectName.size()
size += s.attributes.size()
size += sizeTagAndLength(tagSequence, size)
return
}
func (s *SearchResultEntry) SetObjectName(on string) {
s.objectName = LDAPDN(on)
}
func (s *SearchResultEntry) AddAttribute(name AttributeDescription, values ...AttributeValue) {
var ea = PartialAttribute{type_: name, vals: values}
s.attributes.add(ea)
}

View file

@ -0,0 +1,53 @@
package message
import "fmt"
//
// SearchResultReference ::= [APPLICATION 19] SEQUENCE
// SIZE (1..MAX) OF uri URI
func readSearchResultReference(bytes *Bytes) (ret SearchResultReference, err error) {
err = bytes.ReadSubBytes(classApplication, TagSearchResultReference, ret.readComponents)
if err != nil {
err = LdapError{fmt.Sprintf("readSearchResultReference:\n%s", err.Error())}
return
}
return
}
func (s *SearchResultReference) readComponents(bytes *Bytes) (err error) {
for bytes.HasMoreData() {
var uri URI
uri, err = readURI(bytes)
if err != nil {
err = LdapError{fmt.Sprintf("readComponents:\n%s", err.Error())}
return
}
*s = append(*s, uri)
}
if len(*s) == 0 {
err = LdapError{"readComponents: expecting at least one URI"}
return
}
return
}
//
// SearchResultReference ::= [APPLICATION 19] SEQUENCE
// SIZE (1..MAX) OF uri URI
func (s SearchResultReference) write(bytes *Bytes) (size int) {
for i := len(s) - 1; i >= 0; i-- {
size += s[i].write(bytes)
}
size += bytes.WriteTagAndLength(classApplication, isCompound, TagSearchResultReference, size)
return
}
//
// SearchResultReference ::= [APPLICATION 19] SEQUENCE
// SIZE (1..MAX) OF uri URI
func (s SearchResultReference) size() (size int) {
for _, uri := range s {
size += uri.size()
}
size += sizeTagAndLength(tagSequence, size)
return
}

85
goldap/size_test.go Normal file
View file

@ -0,0 +1,85 @@
package message
import (
"testing"
)
func TestSizeLDAPMessage(t *testing.T) {
var testData = getLDAPMessageTestData()
for i, test := range testData {
message, err := ReadLDAPMessage(&test.bytes)
if err != nil {
t.Errorf("#%d error at offset %d (%s): %s", i, test.bytes.offset, test.bytes.DumpCurrentBytes(), err)
}
size := message.size()
expected := len(test.bytes.bytes)
if size != expected {
t.Errorf("#%d: wrong size, GOT: %d, EXPECTED: %d", i, size, expected)
}
}
}
type tagAndLengthTestData struct {
tag int
length int
expectedSize int
}
func getSizeTagAndLengthTestData() (ret []tagAndLengthTestData) {
return []tagAndLengthTestData{
// Length between 0 and 127 are encoded on one byte
{
tag: tagSequence,
length: 0,
expectedSize: 2,
},
{
tag: tagSequence,
length: 127,
expectedSize: 2,
},
// Length between 128 and 255 are encoded on two bytes
{
tag: tagSequence,
length: 128,
expectedSize: 3,
},
{
tag: tagSequence,
length: 255,
expectedSize: 3,
},
// Length between 256 (2^8) and 65535 (2^16-1) are encoded on three bytes
{
tag: tagSequence,
length: 256,
expectedSize: 4,
},
{
tag: tagSequence,
length: 65535,
expectedSize: 4,
},
// Length between 65536 (2^16) and 16777215 (2^24-1) are encoded on four bytes
{
tag: tagSequence,
length: 65536,
expectedSize: 5,
},
{
tag: tagSequence,
length: 16777215,
expectedSize: 5,
},
}
}
func TestSizeTagAndLength(t *testing.T) {
for i, test := range getSizeTagAndLengthTestData() {
size := sizeTagAndLength(test.tag, test.length)
if test.expectedSize != size {
t.Errorf("#%d: wrong size, GOT: %d, EXPECTED: %d", i, size, test.expectedSize)
}
}
}

38
goldap/string.go Normal file
View file

@ -0,0 +1,38 @@
package message
import "fmt"
func readTaggedLDAPString(bytes *Bytes, class int, tag int) (ldapstring LDAPString, err error) {
var octetstring OCTETSTRING
octetstring, err = readTaggedOCTETSTRING(bytes, class, tag)
if err != nil {
err = LdapError{fmt.Sprintf("readTaggedLDAPString:\n%s", err.Error())}
return
}
ldapstring = LDAPString(octetstring)
return
}
// LDAPString ::= OCTET STRING -- UTF-8 encoded,
// -- [ISO10646] characters
func readLDAPString(bytes *Bytes) (ldapstring LDAPString, err error) {
return readTaggedLDAPString(bytes, classUniversal, tagOctetString)
}
// LDAPString ::= OCTET STRING -- UTF-8 encoded,
// -- [ISO10646] characters
func (s LDAPString) write(bytes *Bytes) int {
return OCTETSTRING(s).write(bytes)
}
func (s LDAPString) writeTagged(bytes *Bytes, class int, tag int) int {
return OCTETSTRING(s).writeTagged(bytes, class, tag)
}
// LDAPString ::= OCTET STRING -- UTF-8 encoded,
// -- [ISO10646] characters
func (s LDAPString) size() int {
return OCTETSTRING(s).size()
}
func (s LDAPString) sizeTagged(tag int) int {
return OCTETSTRING(s).sizeTagged(tag)
}

739
goldap/struct.go Normal file
View file

@ -0,0 +1,739 @@
package message
type OCTETSTRING string
type INTEGER int32 // In this RFC the max INTEGER value is 2^31 - 1, so int32 is enough
type BOOLEAN bool
type ENUMERATED int32
// This appendix is normative.
//
// Lightweight-Directory-Access-Protocol-V3 {1 3 6 1 1 18}
// -- Copyright (C) The Internet Society (2006). This version of
// -- this ASN.1 module is part of RFC 4511; see the RFC itself
// -- for full legal notices.
// DEFINITIONS
// IMPLICIT TAGS
// EXTENSIBILITY IMPLIED ::=
//
// BEGIN
//
// LDAPMessage ::= SEQUENCE {
// messageID MessageID,
// protocolOp CHOICE {
// bindRequest BindRequest,
// bindResponse BindResponse,
// unbindRequest UnbindRequest,
// searchRequest SearchRequest,
// searchResEntry SearchResultEntry,
// searchResDone SearchResultDone,
// searchResRef SearchResultReference,
// modifyRequest ModifyRequest,
// modifyResponse ModifyResponse,
// addRequest AddRequest,
// addResponse AddResponse,
// delRequest DelRequest,
// delResponse DelResponse,
// modDNRequest ModifyDNRequest,
// modDNResponse ModifyDNResponse,
// compareRequest CompareRequest,
// compareResponse CompareResponse,
// abandonRequest AbandonRequest,
// extendedReq ExtendedRequest,
// extendedResp ExtendedResponse,
// ...,
// intermediateResponse IntermediateResponse },
// controls [0] Controls OPTIONAL }
//
type LDAPMessage struct {
messageID MessageID
protocolOp ProtocolOp
controls *Controls
}
const TagLDAPMessageControls = 0
type ProtocolOp interface {
size() int
write(*Bytes) int
}
// MessageID ::= INTEGER (0 .. maxInt)
//
type MessageID INTEGER
// maxInt INTEGER ::= 2147483647 -- (2^^31 - 1) --
const maxInt = INTEGER(2147483647)
//
// LDAPString ::= OCTET STRING -- UTF-8 encoded,
// -- [ISO10646] characters
type LDAPString OCTETSTRING
//
//
//
//
//Sermersheim Standards Track [Page 54]
//
//
//RFC 4511 LDAPv3 June 2006
//
//
// LDAPOID ::= OCTET STRING -- Constrained to <numericoid>
// -- [RFC4512]
type LDAPOID OCTETSTRING
//
// LDAPDN ::= LDAPString -- Constrained to <distinguishedName>
// -- [RFC4514]
type LDAPDN LDAPString
//
// RelativeLDAPDN ::= LDAPString -- Constrained to <name-component>
// -- [RFC4514]
type RelativeLDAPDN LDAPString
//
// AttributeDescription ::= LDAPString
// -- Constrained to <attributedescription>
// -- [RFC4512]
type AttributeDescription LDAPString
//
// AttributeValue ::= OCTET STRING
type AttributeValue OCTETSTRING
//
// AttributeValueAssertion ::= SEQUENCE {
// attributeDesc AttributeDescription,
// assertionValue AssertionValue }
type AttributeValueAssertion struct {
attributeDesc AttributeDescription
assertionValue AssertionValue
}
//
// AssertionValue ::= OCTET STRING
type AssertionValue OCTETSTRING
//
// PartialAttribute ::= SEQUENCE {
// type AttributeDescription,
// vals SET OF value AttributeValue }
type PartialAttribute struct {
type_ AttributeDescription
vals []AttributeValue
}
//
// Attribute ::= PartialAttribute(WITH COMPONENTS {
// ...,
// vals (SIZE(1..MAX))})
type Attribute PartialAttribute
//
// MatchingRuleId ::= LDAPString
type MatchingRuleId LDAPString
//
// LDAPResult ::= SEQUENCE {
// resultCode ENUMERATED {
// success (0),
// operationsError (1),
// protocolError (2),
// timeLimitExceeded (3),
// sizeLimitExceeded (4),
// compareFalse (5),
// compareTrue (6),
// authMethodNotSupported (7),
// strongerAuthRequired (8),
// -- 9 reserved --
// referral (10),
// adminLimitExceeded (11),
// unavailableCriticalExtension (12),
// confidentialityRequired (13),
// saslBindInProgress (14),
//
//
//
//Sermersheim Standards Track [Page 55]
//
//
//RFC 4511 LDAPv3 June 2006
//
//
// noSuchAttribute (16),
// undefinedAttributeType (17),
// inappropriateMatching (18),
// constraintViolation (19),
// attributeOrValueExists (20),
// invalidAttributeSyntax (21),
// -- 22-31 unused --
// noSuchObject (32),
// aliasProblem (33),
// invalidDNSyntax (34),
// -- 35 reserved for undefined isLeaf --
// aliasDereferencingProblem (36),
// -- 37-47 unused --
// inappropriateAuthentication (48),
// invalidCredentials (49),
// insufficientAccessRights (50),
// busy (51),
// unavailable (52),
// unwillingToPerform (53),
// loopDetect (54),
// -- 55-63 unused --
// namingViolation (64),
// objectClassViolation (65),
// notAllowedOnNonLeaf (66),
// notAllowedOnRDN (67),
// entryAlreadyExists (68),
// objectClassModsProhibited (69),
// -- 70 reserved for CLDAP --
// affectsMultipleDSAs (71),
// -- 72-79 unused --
// other (80),
// ... },
// matchedDN LDAPDN,
// diagnosticMessage LDAPString,
// referral [3] Referral OPTIONAL }
//
type LDAPResult struct {
resultCode ENUMERATED
matchedDN LDAPDN
diagnosticMessage LDAPString
referral *Referral
}
const TagLDAPResultReferral = 3
const ResultCodeSuccess = 0
const ResultCodeOperationsError = 1
const ResultCodeProtocolError = 2
const ResultCodeTimeLimitExceeded = 3
const ResultCodeSizeLimitExceeded = 4
const ResultCodeCompareFalse = 5
const ResultCodeCompareTrue = 6
const ResultCodeAuthMethodNotSupported = 7
const ResultCodeStrongerAuthRequired = 8
const ResultCodeReferral = 10
const ResultCodeAdminLimitExceeded = 11
const ResultCodeUnavailableCriticalExtension = 12
const ResultCodeConfidentialityRequired = 13
const ResultCodeSaslBindInProgress = 14
const ResultCodeNoSuchAttribute = 16
const ResultCodeUndefinedAttributeType = 17
const ResultCodeInappropriateMatching = 18
const ResultCodeConstraintViolation = 19
const ResultCodeAttributeOrValueExists = 20
const ResultCodeInvalidAttributeSyntax = 21
const ResultCodeNoSuchObject = 32
const ResultCodeAliasProblem = 33
const ResultCodeInvalidDNSyntax = 34
const ResultCodeAliasDereferencingProblem = 36
const ResultCodeInappropriateAuthentication = 48
const ResultCodeInvalidCredentials = 49
const ResultCodeInsufficientAccessRights = 50
const ResultCodeBusy = 51
const ResultCodeUnavailable = 52
const ResultCodeUnwillingToPerform = 53
const ResultCodeLoopDetect = 54
const ResultCodeNamingViolation = 64
const ResultCodeObjectClassViolation = 65
const ResultCodeNotAllowedOnNonLeaf = 66
const ResultCodeNotAllowedOnRDN = 67
const ResultCodeEntryAlreadyExists = 68
const ResultCodeObjectClassModsProhibited = 69
const ResultCodeAffectsMultipleDSAs = 71
const ResultCodeOther = 80
var EnumeratedLDAPResultCode = map[ENUMERATED]string{
ResultCodeSuccess: "success",
ResultCodeOperationsError: "operationsError",
ResultCodeProtocolError: "protocolError",
ResultCodeTimeLimitExceeded: "timeLimitExceeded",
ResultCodeSizeLimitExceeded: "sizeLimitExceeded",
ResultCodeCompareFalse: "compareFalse",
ResultCodeCompareTrue: "compareTrue",
ResultCodeAuthMethodNotSupported: "authMethodNotSupported",
ResultCodeStrongerAuthRequired: "strongerAuthRequired",
// -- 9 reserved --
ResultCodeReferral: "referral",
ResultCodeAdminLimitExceeded: "adminLimitExceeded",
ResultCodeUnavailableCriticalExtension: "unavailableCriticalExtension",
ResultCodeConfidentialityRequired: "confidentialityRequired",
ResultCodeSaslBindInProgress: "saslBindInProgress",
ResultCodeNoSuchAttribute: "noSuchAttribute",
ResultCodeUndefinedAttributeType: "undefinedAttributeType",
ResultCodeInappropriateMatching: "inappropriateMatching",
ResultCodeConstraintViolation: "constraintViolation",
ResultCodeAttributeOrValueExists: "attributeOrValueExists",
ResultCodeInvalidAttributeSyntax: "invalidAttributeSyntax",
// -- 22-31 unused --
ResultCodeNoSuchObject: "noSuchObject",
ResultCodeAliasProblem: "aliasProblem",
ResultCodeInvalidDNSyntax: "invalidDNSyntax",
// -- 35 reserved for undefined isLeaf --
ResultCodeAliasDereferencingProblem: "aliasDereferencingProblem",
// -- 37-47 unused --
ResultCodeInappropriateAuthentication: "inappropriateAuthentication",
ResultCodeInvalidCredentials: "invalidCredentials",
ResultCodeInsufficientAccessRights: "insufficientAccessRights",
ResultCodeBusy: "busy",
ResultCodeUnavailable: "unavailable",
ResultCodeUnwillingToPerform: "unwillingToPerform",
ResultCodeLoopDetect: "loopDetect",
// -- 55-63 unused --
ResultCodeNamingViolation: "namingViolation",
ResultCodeObjectClassViolation: "objectClassViolation",
ResultCodeNotAllowedOnNonLeaf: "notAllowedOnNonLeaf",
ResultCodeNotAllowedOnRDN: "notAllowedOnRDN",
ResultCodeEntryAlreadyExists: "entryAlreadyExists",
ResultCodeObjectClassModsProhibited: "objectClassModsProhibited",
// -- 70 reserved for CLDAP --
ResultCodeAffectsMultipleDSAs: "affectsMultipleDSAs",
// -- 72-79 unused --
ResultCodeOther: "other",
}
// Referral ::= SEQUENCE SIZE (1..MAX) OF uri URI
type Referral []URI
//
// URI ::= LDAPString -- limited to characters permitted in
// -- URIs
type URI LDAPString
//
// Controls ::= SEQUENCE OF control Control
type Controls []Control
//
// Control ::= SEQUENCE {
// controlType LDAPOID,
// criticality BOOLEAN DEFAULT FALSE,
// controlValue OCTET STRING OPTIONAL }
type Control struct {
controlType LDAPOID
criticality BOOLEAN
controlValue *OCTETSTRING
}
//
//
//
//
//Sermersheim Standards Track [Page 56]
//
//
//RFC 4511 LDAPv3 June 2006
//
//
// BindRequest ::= [APPLICATION 0] SEQUENCE {
// version INTEGER (1 .. 127),
// name LDAPDN,
// authentication AuthenticationChoice }
const TagBindRequest = 0
const BindRequestVersionMin = 1
const BindRequestVersionMax = 127
type BindRequest struct {
version INTEGER
name LDAPDN
authentication AuthenticationChoice
}
//
// AuthenticationChoice ::= CHOICE {
// simple [0] OCTET STRING,
// -- 1 and 2 reserved
// sasl [3] SaslCredentials,
// ... }
const TagAuthenticationChoiceSimple = 0
const TagAuthenticationChoiceSaslCredentials = 3
type AuthenticationChoice interface {
sizeTagged(int) int
}
//
// SaslCredentials ::= SEQUENCE {
// mechanism LDAPString,
// credentials OCTET STRING OPTIONAL }
type SaslCredentials struct {
mechanism LDAPString
credentials *OCTETSTRING
}
//
// BindResponse ::= [APPLICATION 1] SEQUENCE {
// COMPONENTS OF LDAPResult,
// serverSaslCreds [7] OCTET STRING OPTIONAL }
const TagBindResponse = 1
const TagBindResponseServerSaslCreds = 7
type BindResponse struct {
LDAPResult
serverSaslCreds *OCTETSTRING
}
//
// UnbindRequest ::= [APPLICATION 2] NULL
const TagUnbindRequest = 2
type UnbindRequest struct {
}
//
// 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 }
const TagSearchRequest = 3
type SearchRequest struct {
baseObject LDAPDN
scope ENUMERATED
derefAliases ENUMERATED
sizeLimit INTEGER
timeLimit INTEGER
typesOnly BOOLEAN
filter Filter
attributes AttributeSelection
}
const SearchRequestScopeBaseObject = 0
const SearchRequestSingleLevel = 1
const SearchRequestHomeSubtree = 2
var EnumeratedSearchRequestScope = map[ENUMERATED]string{
SearchRequestScopeBaseObject: "baseObject",
SearchRequestSingleLevel: "singleLevel",
SearchRequestHomeSubtree: "homeSubtree",
}
const SearchRequetDerefAliasesNeverDerefAliases = 0
const SearchRequetDerefAliasesDerefInSearching = 1
const SearchRequetDerefAliasesDerefFindingBaseObj = 2
const SearchRequetDerefAliasesDerefAlways = 3
var EnumeratedSearchRequestDerefAliases = map[ENUMERATED]string{
SearchRequetDerefAliasesNeverDerefAliases: "neverDerefAliases",
SearchRequetDerefAliasesDerefInSearching: "derefInSearching",
SearchRequetDerefAliasesDerefFindingBaseObj: "derefFindingBaseObj",
SearchRequetDerefAliasesDerefAlways: "derefAlways",
}
//
// AttributeSelection ::= SEQUENCE OF selector LDAPString
// -- The LDAPString is constrained to
// -- <attributeSelector> in Section 4.5.1.8
type AttributeSelection []LDAPString
//
// 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,
//
//
//
//Sermersheim Standards Track [Page 57]
//
//
//RFC 4511 LDAPv3 June 2006
//
//
// substrings [4] SubstringFilter,
// greaterOrEqual [5] AttributeValueAssertion,
// lessOrEqual [6] AttributeValueAssertion,
// present [7] AttributeDescription,
// approxMatch [8] AttributeValueAssertion,
// extensibleMatch [9] MatchingRuleAssertion,
// ... }
const TagFilterAnd = 0
const TagFilterOr = 1
const TagFilterNot = 2
const TagFilterEqualityMatch = 3
const TagFilterSubstrings = 4
const TagFilterGreaterOrEqual = 5
const TagFilterLessOrEqual = 6
const TagFilterPresent = 7
const TagFilterApproxMatch = 8
const TagFilterExtensibleMatch = 9
type Filter interface {
size() int
write(*Bytes) int
getFilterTag() int
}
type FilterAnd []Filter
type FilterOr []Filter
type FilterNot struct {
Filter
}
type FilterEqualityMatch AttributeValueAssertion
type FilterSubstrings SubstringFilter
type FilterGreaterOrEqual AttributeValueAssertion
type FilterLessOrEqual AttributeValueAssertion
type FilterPresent AttributeDescription
type FilterApproxMatch AttributeValueAssertion
type FilterExtensibleMatch 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
// }
type SubstringFilter struct {
type_ AttributeDescription
substrings []Substring
}
type Substring interface{}
const TagSubstringInitial = 0
const TagSubstringAny = 1
const TagSubstringFinal = 2
type SubstringInitial AssertionValue
type SubstringAny AssertionValue
type SubstringFinal AssertionValue
//
// MatchingRuleAssertion ::= SEQUENCE {
// matchingRule [1] MatchingRuleId OPTIONAL,
// type [2] AttributeDescription OPTIONAL,
// matchValue [3] AssertionValue,
// dnAttributes [4] BOOLEAN DEFAULT FALSE }
type MatchingRuleAssertion struct {
matchingRule *MatchingRuleId
type_ *AttributeDescription
matchValue AssertionValue
dnAttributes BOOLEAN
}
const TagMatchingRuleAssertionMatchingRule = 1
const TagMatchingRuleAssertionType = 2
const TagMatchingRuleAssertionMatchValue = 3
const TagMatchingRuleAssertionDnAttributes = 4
//
// SearchResultEntry ::= [APPLICATION 4] SEQUENCE {
// objectName LDAPDN,
// attributes PartialAttributeList }
const TagSearchResultEntry = 4
type SearchResultEntry struct {
objectName LDAPDN
attributes PartialAttributeList
}
//
// PartialAttributeList ::= SEQUENCE OF
// partialAttribute PartialAttribute
type PartialAttributeList []PartialAttribute
//
// SearchResultReference ::= [APPLICATION 19] SEQUENCE
// SIZE (1..MAX) OF uri URI
const TagSearchResultReference = 19
type SearchResultReference []URI
//
// SearchResultDone ::= [APPLICATION 5] LDAPResult
const TagSearchResultDone = 5
type SearchResultDone LDAPResult
//
// ModifyRequest ::= [APPLICATION 6] SEQUENCE {
// object LDAPDN,
// changes SEQUENCE OF change SEQUENCE {
// operation ENUMERATED {
// add (0),
// delete (1),
// replace (2),
// ... },
// modification PartialAttribute } }
const TagModifyRequest = 6
type ModifyRequest struct {
object LDAPDN
changes []ModifyRequestChange
}
type ModifyRequestChange struct {
operation ENUMERATED
modification PartialAttribute
}
const ModifyRequestChangeOperationAdd = 0
const ModifyRequestChangeOperationDelete = 1
const ModifyRequestChangeOperationReplace = 2
var EnumeratedModifyRequestChangeOperation = map[ENUMERATED]string{
ModifyRequestChangeOperationAdd: "add",
ModifyRequestChangeOperationDelete: "delete",
ModifyRequestChangeOperationReplace: "replace",
}
//
// ModifyResponse ::= [APPLICATION 7] LDAPResult
const TagModifyResponse = 7
type ModifyResponse LDAPResult
//
//
//
//
//
//
//Sermersheim Standards Track [Page 58]
//
//
//RFC 4511 LDAPv3 June 2006
//
//
// AddRequest ::= [APPLICATION 8] SEQUENCE {
// entry LDAPDN,
// attributes AttributeList }
const TagAddRequest = 8
type AddRequest struct {
entry LDAPDN
attributes AttributeList
}
//
// AttributeList ::= SEQUENCE OF attribute Attribute
type AttributeList []Attribute
//
// AddResponse ::= [APPLICATION 9] LDAPResult
const TagAddResponse = 9
type AddResponse LDAPResult
//
// DelRequest ::= [APPLICATION 10] LDAPDN
const TagDelRequest = 10
type DelRequest LDAPDN
//
// DelResponse ::= [APPLICATION 11] LDAPResult
const TagDelResponse = 11
type DelResponse LDAPResult
//
// ModifyDNRequest ::= [APPLICATION 12] SEQUENCE {
// entry LDAPDN,
// newrdn RelativeLDAPDN,
// deleteoldrdn BOOLEAN,
// newSuperior [0] LDAPDN OPTIONAL }
const TagModifyDNRequest = 12
type ModifyDNRequest struct {
entry LDAPDN
newrdn RelativeLDAPDN
deleteoldrdn BOOLEAN
newSuperior *LDAPDN
}
const TagModifyDNRequestNewSuperior = 0
//
// ModifyDNResponse ::= [APPLICATION 13] LDAPResult
const TagModifyDNResponse = 13
type ModifyDNResponse LDAPResult
//
// CompareRequest ::= [APPLICATION 14] SEQUENCE {
// entry LDAPDN,
// ava AttributeValueAssertion }
const TagCompareRequest = 14
type CompareRequest struct {
entry LDAPDN
ava AttributeValueAssertion
}
// CompareResponse ::= [APPLICATION 15] LDAPResult
const TagCompareResponse = 15
type CompareResponse LDAPResult
//
// AbandonRequest ::= [APPLICATION 16] MessageID
const TagAbandonRequest = 16
type AbandonRequest MessageID
//
// ExtendedRequest ::= [APPLICATION 23] SEQUENCE {
// requestName [0] LDAPOID,
// requestValue [1] OCTET STRING OPTIONAL }
const TagExtendedRequest = 23
type ExtendedRequest struct {
requestName LDAPOID
requestValue *OCTETSTRING
}
const TagExtendedRequestName = 0
const TagExtendedRequestValue = 1
//
// ExtendedResponse ::= [APPLICATION 24] SEQUENCE {
// COMPONENTS OF LDAPResult,
// responseName [10] LDAPOID OPTIONAL,
// responseValue [11] OCTET STRING OPTIONAL }
const TagExtendedResponse = 24
type ExtendedResponse struct {
LDAPResult
responseName *LDAPOID
responseValue *OCTETSTRING
}
const TagExtendedResponseName = 10
const TagExtendedResponseValue = 11
//
// IntermediateResponse ::= [APPLICATION 25] SEQUENCE {
// responseName [0] LDAPOID OPTIONAL,
// responseValue [1] OCTET STRING OPTIONAL }
const TagIntermediateResponse = 25
type IntermediateResponse struct {
responseName *LDAPOID
responseValue *OCTETSTRING
}
const TagIntermediateResponseName = 0
const TagIntermediateResponseValue = 1
//
// END
//

7
goldap/struct_methods.go Normal file
View file

@ -0,0 +1,7 @@
package message
func NewLDAPMessageWithProtocolOp(po ProtocolOp) *LDAPMessage {
m := NewLDAPMessage()
m.protocolOp = po
return m
}

38
goldap/unbind_request.go Normal file
View file

@ -0,0 +1,38 @@
package message
import "fmt"
//
// UnbindRequest ::= [APPLICATION 2] NULL
func readUnbindRequest(bytes *Bytes) (unbindrequest UnbindRequest, err error) {
var tagAndLength TagAndLength
tagAndLength, err = bytes.ParseTagAndLength()
if err != nil {
err = LdapError{fmt.Sprintf("readUnbindRequest:\n%s", err.Error())}
return
}
err = tagAndLength.Expect(classApplication, TagUnbindRequest, isNotCompound)
if err != nil {
err = LdapError{fmt.Sprintf("readUnbindRequest:\n%s", err.Error())}
return
}
if tagAndLength.Length != 0 {
err = LdapError{"readUnbindRequest: expecting NULL"}
return
}
return
}
//
// UnbindRequest ::= [APPLICATION 2] NULL
func (u UnbindRequest) write(bytes *Bytes) (size int) {
size += bytes.WriteTagAndLength(classApplication, isNotCompound, TagUnbindRequest, 0)
return
}
//
// UnbindRequest ::= [APPLICATION 2] NULL
func (u UnbindRequest) size() (size int) {
size = sizeTagAndLength(TagUnbindRequest, 0)
return
}

32
goldap/uri.go Normal file
View file

@ -0,0 +1,32 @@
package message
import "fmt"
//
// URI ::= LDAPString -- limited to characters permitted in
// -- URIs
func readURI(bytes *Bytes) (uri URI, err error) {
var ldapstring LDAPString
ldapstring, err = readLDAPString(bytes)
// @TODO: check permitted chars in URI
if err != nil {
err = LdapError{fmt.Sprintf("readURI:\n%s", err.Error())}
return
}
uri = URI(ldapstring)
return
}
//
// URI ::= LDAPString -- limited to characters permitted in
// -- URIs
func (u URI) write(bytes *Bytes) int {
return LDAPString(u).write(bytes)
}
//
// URI ::= LDAPString -- limited to characters permitted in
// -- URIs
func (u URI) size() int {
return LDAPString(u).size()
}

6
goldap/write.go Normal file
View file

@ -0,0 +1,6 @@
package message
type Writable interface {
write(bytes *Bytes) int
writeTagged(bytes *Bytes, class int, tag int) int
}

19
goldap/write_test.go Normal file
View file

@ -0,0 +1,19 @@
package message
import (
"reflect"
"testing"
)
func TestWriteLDAPMessage(t *testing.T) {
var testData = getLDAPMessageTestData()
for i, test := range testData {
bytes, err := test.out.Write()
if err != nil {
t.Errorf("#%d error at offset %d (%s): %s\nEXPECTED BYTES: %#v\nWRITTEN BYTES: %#v\n", i, test.bytes.offset, test.bytes.DumpCurrentBytes(), err, test.bytes.getBytes(), bytes.getBytes())
} else if !reflect.DeepEqual(bytes.getBytes(), test.bytes.getBytes()) {
t.Errorf("#%d:\nGOT:\n%#+v\nEXPECTED:\n%#+v", i, bytes.getBytes(), test.bytes.getBytes())
}
}
}

View file

@ -6,7 +6,7 @@ import (
"sync"
"time"
ldap "github.com/lor00x/goldap/message"
ldap "bottin/goldap"
)
type UserState interface{}
@ -206,10 +206,14 @@ func (c *client) close() {
}
func (c *client) writeMessage(m *ldap.LDAPMessage) {
data, _ := m.Write()
data, err := m.Write()
if err != nil {
Logger.Errorf("bottin: unable to marshal response message: %v", err)
}
//Logger.Printf(">>> %d - %s - hex=%x", c.Numero, m.ProtocolOpName(), data.Bytes())
Logger.Tracef(">>> [%d] %#v", c.Numero, m)
Logger.Tracef("%v", data.Bytes())
c.bw.Write(data.Bytes())
c.bw.Flush()

View file

@ -1,6 +1,6 @@
package ldapserver
import ldap "github.com/lor00x/goldap/message"
import ldap "bottin/goldap"
// LDAP Application Codes
const (

View file

@ -3,7 +3,7 @@ package ldapserver
import (
"fmt"
ldap "github.com/lor00x/goldap/message"
ldap "bottin/goldap"
)
type Message struct {

View file

@ -5,7 +5,7 @@ import (
"errors"
"fmt"
ldap "github.com/lor00x/goldap/message"
ldap "bottin/goldap"
)
type messagePacket struct {
@ -141,6 +141,7 @@ func readBytes(conn *bufio.Reader, bytes *[]byte, length int) (b byte, err error
if n != length {
fmt.Errorf("%d bytes read instead of %d", n, length)
} else if err != nil {
fmt.Println("ooopsie an error occured here:", err)
return
}
*bytes = append(*bytes, newbytes...)

View file

@ -1,6 +1,6 @@
package ldapserver
import ldap "github.com/lor00x/goldap/message"
import ldap "bottin/goldap"
func NewBindResponse(resultCode int) ldap.BindResponse {
r := ldap.BindResponse{}

View file

@ -3,7 +3,7 @@ package ldapserver
import (
"strings"
ldap "github.com/lor00x/goldap/message"
ldap "bottin/goldap"
)
// Constant to LDAP Request protocol Type names

19
main.go
View file

@ -13,9 +13,9 @@ import (
"syscall"
ldap "bottin/ldapserver"
message "bottin/goldap"
consul "github.com/hashicorp/consul/api"
message "github.com/lor00x/goldap/message"
log "github.com/sirupsen/logrus"
)
@ -320,12 +320,19 @@ func (server *Server) init() error {
return err
}
admin_pass := make([]byte, 8)
_, err = rand.Read(admin_pass)
if err != nil {
return err
admin_pass_str, environnement_variable_exist := os.LookupEnv("BOTTIN_DEFAULT_ADMIN_PW")
if !environnement_variable_exist {
admin_pass := make([]byte, 8)
_, err = rand.Read(admin_pass)
if err != nil {
return err
}
admin_pass_str = base64.RawURLEncoding.EncodeToString(admin_pass)
} else {
server.logger.Debug("BOTTIN_DEFAULT_ADMIN_PW environment variable is set, using it for admin's password")
}
admin_pass_str := base64.RawURLEncoding.EncodeToString(admin_pass)
admin_pass_hash := SSHAEncode([]byte(admin_pass_str))
admin_dn := "cn=admin," + server.config.Suffix

View file

@ -6,7 +6,7 @@ import (
ldap "bottin/ldapserver"
message "github.com/lor00x/goldap/message"
message "bottin/goldap"
)
// Generic read utility functions ----------

160
test/bottin_test.go Normal file
View file

@ -0,0 +1,160 @@
package main
import (
"testing"
)
func TestAddThenDelete(t *testing.T) {
t.Parallel()
//SetUp - Create Users and Groups
inst, err := Init()
if err != nil {
t.Error(err)
}
//TearDown - Delete all the users and groups created
err = inst.Clean()
if err != nil {
t.Error(err)
}
}
func TestConfirmAddAttributes(t *testing.T) {
t.Parallel()
//SetUp - Create Users and Groups
inst, err := Init()
if err != nil {
t.Error(err)
}
//Test search_attribute to confirm the Add
if ok, err := inst.CompareOurDataWithConsul(); !ok {
t.Error(err)
}
//TearDown - Delete all the users and groups created
err = inst.Clean()
if err != nil {
t.Error(err)
}
}
//Modifyrequest Test
func TestModifyRequest(t *testing.T) {
t.Parallel()
//SetUp - Create Users and Groups
inst, err := Init()
if err != nil {
t.Error(err)
}
//Test modify all data (groups and users)
err = inst.ModifyRandomAllData()
if err != nil {
t.Error(err)
}
//TearDown - Delete all the users and groups created
err = inst.Clean()
if err != nil {
t.Error(err)
}
}
func TestModifyRequestAndCheck(t *testing.T) {
t.Parallel()
//SetUp - Create Users and Groups
inst, err := Init()
if err != nil {
t.Error(err)
}
//Test modify all data (groups and users)
err = inst.ModifyRandomAllData()
if err != nil {
t.Error(err)
}
//Check if the data was modify on Consul
if ok, err := inst.CompareOurDataWithConsul(); !ok {
t.Error(err)
}
//TearDown - Delete all the users and groups created
err = inst.Clean()
if err != nil {
t.Error(err)
}
}
func TestAddUserInGroup(t *testing.T) {
t.Parallel()
//SetUp - Create Users and Groups
inst, err := Init()
if err != nil {
t.Error(err)
}
//Add users in group
err = inst.AddAllUsersInGroup()
if err != nil {
t.Error(err)
}
//TearDown - Delete all the users and groups created
err = inst.Clean()
if err != nil {
t.Error(err)
}
}
func TestDeleteGroupsAfterAddedUsers(t *testing.T) {
t.Parallel()
//SetUp - Create Users and Groups
inst, err := Init()
if err != nil {
t.Error(err)
}
//Add users in group
err = inst.AddAllUsersInGroup()
if err != nil {
t.Error(err)
}
//Delete the half groups
number := len(inst.dataGroups) / 2
err = inst.clean(inst.dataGroups[0:number])
if err != nil {
t.Error(err)
}
inst.dataGroups = inst.dataGroups[number:len(inst.dataGroups)]
//Check all the groups in memberOf exist
ok, err := inst.CheckMemberOf()
if err != nil {
t.Error(err)
}
if !ok {
t.Errorf("Found group in memberOf that isn't in Consul.")
}
//TearDown - Delete all the users and groups created
err = inst.Clean()
if err != nil {
t.Error(err)
}
}
//Example of paralellism Test
func TestPrincipal(t *testing.T) {
t.Run("A=Add and delete", TestAddThenDelete)
t.Run("A=Modify", TestModifyRequest)
if !testing.Short() {
t.Run("B=Add attributes", TestConfirmAddAttributes)
t.Run("B=Modify and check", TestModifyRequestAndCheck)
t.Run("C=Add user in group", TestAddUserInGroup)
t.Run("C=Delete group", TestDeleteGroupsAfterAddedUsers)
}
}

13
test/config.json.test Normal file
View file

@ -0,0 +1,13 @@
{
"suffix": "dc=deuxfleurs,dc=fr",
"bind": "127.0.0.1:1389",
"acl": [
"ANONYMOUS::bind:*,ou=users,dc=deuxfleurs,dc=fr:",
"ANONYMOUS::bind:cn=admin,dc=deuxfleurs,dc=fr:",
"*,dc=deuxfleurs,dc=fr::read:*:* !userpassword",
"*::read modify:SELF:*",
"cn=admin,dc=deuxfleurs,dc=fr::read add modify delete:*:*",
"*:cn=admin,ou=groups,dc=deuxfleurs,dc=fr:read add modify delete:*:*"
]
}

281
test/create.go Normal file
View file

@ -0,0 +1,281 @@
package main
import (
"fmt"
"strings"
"sync"
"github.com/go-ldap/ldap/v3"
"github.com/sirupsen/logrus"
)
//Mux value, this value permits do not have two identicals values in the parallel instances
type StoreAllCN struct {
mu sync.Mutex
cn map[string]struct{}
}
var allNames = StoreAllCN{cn: make(map[string]struct{})}
//Type used for the tests
type attributes struct {
Name string
Data []string
}
type data_DN struct {
DN string
Attributes []attributes
}
type instance struct {
numberUsers, numberGroups int
dataGroups, dataUsers []data_DN
logging *ldap.Conn
}
//Create a new object instance
//With this instance, we can obtain an isolated container where
//we have our users and groups. It allows to run tests in parallel.
func NewInstance(numberUsers, numberGroups int) (*instance, error) {
l, err := Connect()
if err != nil {
return nil, err
}
logging.Level = logrus.InfoLevel
inst := instance{
numberUsers: numberUsers,
numberGroups: numberGroups,
dataGroups: []data_DN{},
dataUsers: []data_DN{},
logging: l,
}
err = inst.createOrganizationnalUnit()
if ldap.IsErrorWithCode(err, uint16(68)) {
logging.Warn("OrganizationnalUnit already created")
err = nil
}
if err != nil {
return nil, err
}
err = inst.CreateGroups()
if err != nil {
return nil, err
}
err = inst.CreateUsers()
if err != nil {
return nil, err
}
return &inst, nil
}
//Part: Created users or groups or OU
func (inst *instance) createOrganizationnalUnit() error {
dn := []string{"ou=groups,dc=deuxfleurs,dc=fr", "ou=users,dc=deuxfleurs,dc=fr"}
attributes := []map[string][]string{{
"description": []string{"OrganizationalUnit qui regroupe tous les groupes"},
"objectclass": []string{"organizationalUnit", "top"},
"ou": []string{"groups"},
"structuralobjectclass": []string{"organizationalUnit"},
},
{
"description": []string{"OrganizationalUnit qui regroupe tous les users"},
"objectclass": []string{"organizationalUnit", "top"},
"ou": []string{"users"},
"structuralobjectclass": []string{"organizationalUnit"},
},
}
for index := range dn {
err := inst.Add_Request(dn[index], attributes[index])
if err != nil {
return err
}
}
return nil
}
//Part: Create User or group
func (inst *instance) CreateUsers() (err error) {
dn := "cn=%s,ou=users,dc=deuxfleurs,dc=fr"
attributes := map[string][]string{
"displayname": {},
"objectclass": {"inetOrgPerson", "organizationalPerson", "person", "top"},
"structuralobjectclass": {"inetOrgPerson"},
}
du, err := inst.create(dn, []string{"displayname"}, inst.numberUsers, attributes, inst.dataUsers)
if err == nil {
inst.dataUsers = du
}
return err
}
func (inst *instance) CreateGroups() error {
dn := "cn=%s,ou=groups,dc=deuxfleurs,dc=fr"
attributes := map[string][]string{
"description": {},
"objectclass": {"groupOfNames", "top"},
"structuralobjectclass": {"groupOfNames"},
}
dg, err := inst.create(dn, []string{"description"}, inst.numberGroups, attributes, inst.dataGroups)
if err == nil {
inst.dataGroups = dg
}
return err
}
//Hard Function: She does:
//- generate an unique name
//- store the Data of each AddRequest in instance struct
//- send AddRequest to Bottin
func (inst *instance) create(dn string, unique_attr []string, number int, attributes map[string][]string, data []data_DN) ([]data_DN, error) {
for i := 0; i < number; i++ {
name := inst.GenerateName()
datDn := data_DN{DN: fmt.Sprintf(dn, name)}
for _, value := range unique_attr {
attributes[value] = []string{name}
}
datDn.Attributes = MapAttToStruct(attributes)
data = append(data, datDn)
err := inst.Add_Request(fmt.Sprintf(dn, name), attributes)
if err != nil {
return nil, err
}
}
return data, nil
}
//Part: clean
func (inst *instance) Clean() error {
err := inst.CleanGroups()
if err != nil {
return err
}
err = inst.CleanUsers()
return err
}
func (inst *instance) CleanUsers() error {
err := inst.clean(inst.dataUsers)
if err != nil {
return err
}
inst.dataUsers = []data_DN{}
return err
}
func (inst *instance) CleanGroups() error {
err := inst.clean(inst.dataGroups)
if err != nil {
return err
}
inst.dataGroups = []data_DN{}
return err
}
func (inst *instance) clean(stock []data_DN) error {
logging.Debugf("Delete %d elements.", len(stock))
for _, value := range stock {
err := inst.Delete_Request(value.DN)
if err != nil {
return err
}
}
return nil
}
//Part: Verify if a data_Dn is a group or an user
func (inst *instance) VerifyUser(user data_DN) (bool, error) {
dn := "ou=users,dc=deuxfleurs,dc=fr"
cn := strings.Split(user.DN, ",")[0]
filter := fmt.Sprintf("(%s)", cn)
res, err := inst.Search_Request(dn, filter, []string{"cn"})
return len(res.Entries) == 1, err
}
func (inst *instance) VerifyGroup(group data_DN) (bool, error) {
dn := "ou=groups,dc=deuxfleurs,dc=fr"
cn := strings.Split(group.DN, ",")[0]
filter := fmt.Sprintf("(%s)", cn)
res, err := inst.Search_Request(dn, filter, []string{"cn"})
return len(res.Entries) == 1, err
}
//Part: Add user in a group
func (inst *instance) AddUserInGroup(user, group data_DN) error {
err := inst.Modify_Request(group.DN, nil, nil, map[string][]string{
"member": {user.DN},
})
return err
}
func (inst *instance) AddUserSliceInGroup(users_cn []string, group_dn string) error {
err := inst.Modify_Request(group_dn, nil, nil, map[string][]string{
"member": users_cn,
})
return err
}
//Part: modify, add, delete data_DN struct
func AddAtt(name string, data []string, dat data_DN) data_DN {
dat.Attributes = append(dat.Attributes, attributes{
Name: name,
Data: data,
})
logging.Debug(fmt.Sprintf("Attributes %s add from %s.", name, dat.DN))
return dat
}
func DelAtt(name string, dat data_DN) data_DN {
for index, value := range dat.Attributes {
if value.Name == name {
dat.Attributes[index] = dat.Attributes[len(dat.Attributes)-1]
//tmp := dat.Attributes[:len(dat.Attributes)-1]
dat.Attributes = []attributes{}
logging.Debugf("Attributes %s delete from %s.", name, dat.DN)
return dat
}
}
logging.Debugf("Can't delete attribute %s from %s.", name, dat.DN)
return dat
}
func ReplaceAtt(name string, data []string, dat data_DN) data_DN {
for index, value := range dat.Attributes {
if value.Name == name {
dat.Attributes[index] = attributes{
Name: name,
Data: data,
}
logging.Debugf("Replace attributes %s from %s succesful..", name, dat.DN)
return dat
}
}
logging.Debugf("Can't replace attributes %s from %s.", name, dat.DN)
return dat
}

173
test/functionTest.go Normal file
View file

@ -0,0 +1,173 @@
package main
import (
"fmt"
"strings"
"github.com/go-ldap/ldap/v3"
)
const default_users, default_groups = 1000, 1000
func Init() (*instance, error) {
inst, err := NewInstance(default_users, default_groups)
return inst, err
}
//Part to compare our datas
func (inst *instance) CompareOurDataWithConsul() (bool, error) {
if ok, err := inst.VerifyOurData(inst.dataUsers); !ok {
return false, err
}
if ok, err := inst.VerifyOurData(inst.dataGroups); !ok {
return false, err
}
return true, nil
}
func (inst *instance) VerifyOurData(tabData []data_DN) (bool, error) {
for _, value := range tabData {
names := getNamesAtt(value)
cn := strings.Split(value.DN, ",")[0]
res, err := inst.Search_Request(value.DN, fmt.Sprintf("(&(%s))", cn), names)
if err != nil {
return false, err
}
if len(res.Entries) != 1 {
return false, fmt.Errorf("expected 1 entry, but found %d entry/ies", len(res.Entries))
}
if !Compare(value, res.Entries[0]) {
return false, fmt.Errorf("no match with the DN: %s", value.DN)
}
}
return true, nil
}
func Compare(dat data_DN, ent *ldap.Entry) bool {
for _, value := range dat.Attributes {
logging.Debugf("Attributes from %s is now: %s.", dat.DN, dat.Attributes)
entVal := GetAttributeValuesBottin(ent, value.Name)
logging.Debugf("Values of the Entry: attributName: %s, Values: %s.", value.Name, entVal)
if !CompareSliceString(entVal, value.Data) {
logging.Debugf("Values expected: %s, values found: %s.", value.Data, entVal)
return false
}
}
return true
}
//Part modify datas
func (inst *instance) ModifyRandomAllData() error {
dg, err := inst.ModifyRandom(inst.dataGroups, []string{"description"})
if err != nil {
return err
} else {
inst.dataGroups = dg
}
dg, err = inst.ModifyRandom(inst.dataUsers, []string{"displayname"})
if err != nil {
return err
} else {
inst.dataUsers = dg
}
return nil
}
//Function which modify random way the attributes in attName of a data_DN's slice, it can delete, replace and delete
//The function modify also in the dat object
func (inst *instance) ModifyRandom(dat []data_DN, attName []string) ([]data_DN, error) {
for index, value := range dat {
del := make(map[string][]string)
add := make(map[string][]string)
replace := make(map[string][]string)
for _, att := range attName {
switch selNumber := R.Intn(3); selNumber {
case 0:
del[att] = []string{}
value = DelAtt(att, value)
logging.Debug(fmt.Sprintf("Delete the attribute %s of the DN %s.", att, value.DN))
case 1:
name := inst.GenerateName()
value = AddAtt(name, []string{name}, value)
add[name] = []string{name}
logging.Debug(fmt.Sprintf("Add the attribute %s with value %s of the DN %s.", name, name, value.DN))
case 2:
name := inst.GenerateName()
value = ReplaceAtt(att, []string{name}, value)
replace[att] = []string{name}
logging.Debug(fmt.Sprintf("Replace the attribute %s with value %s of the DN %s.", att, name, value.DN))
}
}
err := inst.Modify_Request(value.DN, add, del, replace)
if err != nil {
return dat, err
}
dat[index] = value
}
return dat, nil
}
//Add all users in a random group
func (inst *instance) AddAllUsersInGroup() error {
for _, value := range inst.dataGroups {
valueRand := (len(inst.dataUsers) + 1) / 30
if valueRand == 0 {
valueRand = 1
}
numberOfMembers := R.Intn(valueRand) + 1
logging.Debugf("%s will be have %d members.", value.DN, numberOfMembers)
groupMemory := make(map[int]struct{})
users_cn := []string{}
for i := 0; i < numberOfMembers; i++ {
selectGroup := R.Intn(len(inst.dataUsers))
for _, ok := groupMemory[selectGroup]; ok; _, ok = groupMemory[selectGroup] {
selectGroup = R.Intn(len(inst.dataUsers))
logging.Debugf("Search an other member. The value is %d , and we have %d members available.", selectGroup, len(inst.dataUsers))
}
groupMemory[selectGroup] = struct{}{}
users_cn = append(users_cn, inst.dataGroups[selectGroup].DN)
}
err := inst.AddUserSliceInGroup(users_cn, value.DN)
if err != nil {
return err
}
}
return nil
}
//Check if the groups in memberOf exist in Consul
func (inst *instance) CheckMemberOf() (bool, error) {
for _, value := range inst.dataUsers {
cn := strings.Split(value.DN, ",")[0]
res, err := inst.Search_Request(value.DN, fmt.Sprintf("(&(%s))", cn), []string{"memberOf"})
if err != nil {
return false, err
}
if len(res.Entries) != 1 {
return false, fmt.Errorf("expected 1 entry, but found %d entry/ies", len(res.Entries))
}
attValues := GetAttributeValuesBottin(res.Entries[0], "memberOf")
for _, dnGroup := range attValues {
logging.Debugf("Verify if the group %s exist...", dnGroup)
ok, err := inst.VerifyGroup(data_DN{DN: dnGroup})
if err != nil {
return false, err
}
if !ok {
return false, fmt.Errorf("don't found the group: %s", dnGroup)
}
}
}
return true, nil
}

8
test/go.mod Normal file
View file

@ -0,0 +1,8 @@
module bottin/integration
go 1.14
require (
github.com/go-ldap/ldap/v3 v3.3.0
github.com/sirupsen/logrus v1.4.2
)

26
test/go.sum Normal file
View file

@ -0,0 +1,26 @@
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28=
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8=
github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-ldap/ldap/v3 v3.3.0 h1:lwx+SJpgOHd8tG6SumBQZXCmNX51zM8B1cfxJ5gv4tQ=
github.com/go-ldap/ldap/v3 v3.3.0/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM=
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

141
test/handler.go Normal file
View file

@ -0,0 +1,141 @@
package main
import (
"fmt"
"math/rand"
"os"
ldap "github.com/go-ldap/ldap/v3"
"github.com/sirupsen/logrus"
)
const maxlength_generateName, minlength_generateName = 25, 3
const bindusername = "cn=admin,dc=deuxfleurs,dc=fr"
const adresse = "127.0.0.1"
const port = 1389
var logging = logrus.New()
const seed = 654258
var R = rand.New(rand.NewSource(seed))
var bindpassword = "sf7yO52NCuE"
//Handler just to facilite the print error
func PrintError(LDAPError error) {
if LDAPError != nil {
logging.Fatal(LDAPError)
}
}
//Generate an unique name, which store in all_names
func (inst *instance) GenerateName() (name string) {
alphabet := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v",
"w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y",
"Z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "é", "è", "ê", "ë", "à", "@", "â", "ä", "û", "ü", "ù", "$", "£", "%", "ø", "€"}
length := R.Intn(maxlength_generateName) + minlength_generateName
//Check if this name not exist already
//Lock thhis variable because she is hared with other goroutine
allNames.mu.Lock()
for only_one := true; only_one; _, only_one = allNames.cn[name] {
//Create the name
for i := 0; i < length; i++ {
name += alphabet[R.Intn(len(alphabet))]
}
}
//Add the new name in the map to store this one
allNames.cn[name] = struct{}{}
allNames.mu.Unlock()
logging.Debug(fmt.Sprintf("Name generated: %s.", name))
return
}
//Handler to around the bug with MessageId
func (inst *instance) Reconnect() (err error) {
inst.logging.Close()
inst.logging, err = ldap.Dial("tcp", fmt.Sprintf("%s:%d", adresse, port))
if err != nil {
return
}
err = inst.logging.Bind(bindusername, bindpassword)
//logging.Debug("Reconnect succesful")
return
}
//Transform attributes in map format to the struct attributes
func MapAttToStruct(att map[string][]string) []attributes {
resultat := []attributes{}
for key, value := range att {
logging.Debug(fmt.Sprintf("Transform: key: %s, values: %s to attributes struct.\n", key, value))
resultat = append(resultat, attributes{
Name: key,
Data: value,
})
}
return resultat
}
func Connect() (*ldap.Conn, error) {
l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", adresse, port))
if err != nil {
return nil, err
}
if key, ok := os.LookupEnv("BOTTIN_DEFAULT_ADMIN_PW"); ok {
bindpassword = key
}
//l.Debug.Enable(true)
err = l.Bind(bindusername, bindpassword)
logging.Debug("Connection succesful")
return l, err
}
//Handler to get only attributes names
func getNamesAtt(dat data_DN) []string {
resultat := []string{}
for _, values := range dat.Attributes {
resultat = append(resultat, values.Name)
}
return resultat
}
//Handler to compare slice string
func CompareSliceString(string1, string2 []string) bool {
if len(string1) != len(string2) {
return false
} else {
for index := range string1 {
if string1[index] != string2[index] {
return false
}
}
}
return true
}
//Handler to remove an element in slice string
func DeleteElementSliceString(s string, sSlice []string) []string {
for index, value := range sSlice {
if value == s {
sSlice[index] = sSlice[len(sSlice)-1]
return sSlice[:len(sSlice)-1]
}
}
return sSlice
}
//Get attributes entry values bottin bug
func GetAttributeValuesBottin(ent *ldap.Entry, name string) (res []string) {
for _, val := range ent.Attributes {
if val.Name == name {
res = append(res, val.Values...)
}
}
return
}

59
test/request.go Normal file
View file

@ -0,0 +1,59 @@
package main
import (
ldap "github.com/go-ldap/ldap/v3"
)
func (inst *instance) Add_Request(dn string, attributes map[string][]string) error {
//Create the AddRequest
req := ldap.NewAddRequest(dn, nil)
for key, value := range attributes {
req.Attribute(key, value)
}
//Send the request
err := inst.logging.Add(req)
return err
}
//Use enum to select Replace,Delete,Modify
func (inst *instance) Modify_Request(dn string, add_attributes, delete_attributes, replace_attributes map[string][]string) error {
modifyReq := ldap.NewModifyRequest(dn, nil)
for key, value := range add_attributes {
modifyReq.Add(key, value)
}
for key, value := range delete_attributes {
modifyReq.Delete(key, value)
}
for key, value := range replace_attributes {
modifyReq.Replace(key, value)
}
err := inst.logging.Modify(modifyReq)
return err
}
func (inst *instance) Delete_Request(dn string) error {
del := ldap.NewDelRequest(dn, nil)
err := inst.logging.Del(del)
return err
}
func (inst *instance) Search_Request(dn, filter string, name_attributes []string) (*ldap.SearchResult, error) {
searchReq := ldap.NewSearchRequest(
dn,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
filter,
name_attributes,
nil,
)
res, err := inst.logging.Search(searchReq)
logging.Debugf("Search Request made with: dn: %s, filter: %s, attributes: %s. \n", dn, filter, name_attributes)
return res, err
}

16
test/runner.sh Executable file
View file

@ -0,0 +1,16 @@
#!/bin/sh
set -ex
echo $BOTTIN_DEFAULT_ADMIN_PW
consul agent -dev > /dev/null 2>&1 &
sleep 2
cp test/config.json.test config.json
./bottin > /tmp/bottin.log 2>&1 &
sleep 1
./test/test -test.v -test.failfast -test.short -test.run TestPrincipal
./test/test -test.v -test.failfast -test.run TestPrincipal/B=
jobs
kill %2
kill %1

View file

@ -8,7 +8,7 @@ import (
ldap "bottin/ldapserver"
consul "github.com/hashicorp/consul/api"
message "github.com/lor00x/goldap/message"
message "bottin/goldap"
)
// Generic item modification function --------