1// Copyright 2015 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4//
5// File contains DN parsing functionality
6//
7// https://tools.ietf.org/html/rfc4514
8//
9//   distinguishedName = [ relativeDistinguishedName
10//         *( COMMA relativeDistinguishedName ) ]
11//     relativeDistinguishedName = attributeTypeAndValue
12//         *( PLUS attributeTypeAndValue )
13//     attributeTypeAndValue = attributeType EQUALS attributeValue
14//     attributeType = descr / numericoid
15//     attributeValue = string / hexstring
16//
17//     ; The following characters are to be escaped when they appear
18//     ; in the value to be encoded: ESC, one of <escaped>, leading
19//     ; SHARP or SPACE, trailing SPACE, and NULL.
20//     string =   [ ( leadchar / pair ) [ *( stringchar / pair )
21//        ( trailchar / pair ) ] ]
22//
23//     leadchar = LUTF1 / UTFMB
24//     LUTF1 = %x01-1F / %x21 / %x24-2A / %x2D-3A /
25//        %x3D / %x3F-5B / %x5D-7F
26//
27//     trailchar  = TUTF1 / UTFMB
28//     TUTF1 = %x01-1F / %x21 / %x23-2A / %x2D-3A /
29//        %x3D / %x3F-5B / %x5D-7F
30//
31//     stringchar = SUTF1 / UTFMB
32//     SUTF1 = %x01-21 / %x23-2A / %x2D-3A /
33//        %x3D / %x3F-5B / %x5D-7F
34//
35//     pair = ESC ( ESC / special / hexpair )
36//     special = escaped / SPACE / SHARP / EQUALS
37//     escaped = DQUOTE / PLUS / COMMA / SEMI / LANGLE / RANGLE
38//     hexstring = SHARP 1*hexpair
39//     hexpair = HEX HEX
40//
41//  where the productions <descr>, <numericoid>, <COMMA>, <DQUOTE>,
42//  <EQUALS>, <ESC>, <HEX>, <LANGLE>, <NULL>, <PLUS>, <RANGLE>, <SEMI>,
43//  <SPACE>, <SHARP>, and <UTFMB> are defined in [RFC4512].
44//
45
46package ldap
47
48import (
49	"bytes"
50	enchex "encoding/hex"
51	"errors"
52	"fmt"
53	"strings"
54
55	"gopkg.in/asn1-ber.v1"
56)
57
58// AttributeTypeAndValue represents an attributeTypeAndValue from https://tools.ietf.org/html/rfc4514
59type AttributeTypeAndValue struct {
60	// Type is the attribute type
61	Type string
62	// Value is the attribute value
63	Value string
64}
65
66// RelativeDN represents a relativeDistinguishedName from https://tools.ietf.org/html/rfc4514
67type RelativeDN struct {
68	Attributes []*AttributeTypeAndValue
69}
70
71// DN represents a distinguishedName from https://tools.ietf.org/html/rfc4514
72type DN struct {
73	RDNs []*RelativeDN
74}
75
76// ParseDN returns a distinguishedName or an error
77func ParseDN(str string) (*DN, error) {
78	dn := new(DN)
79	dn.RDNs = make([]*RelativeDN, 0)
80	rdn := new(RelativeDN)
81	rdn.Attributes = make([]*AttributeTypeAndValue, 0)
82	buffer := bytes.Buffer{}
83	attribute := new(AttributeTypeAndValue)
84	escaping := false
85
86	unescapedTrailingSpaces := 0
87	stringFromBuffer := func() string {
88		s := buffer.String()
89		s = s[0 : len(s)-unescapedTrailingSpaces]
90		buffer.Reset()
91		unescapedTrailingSpaces = 0
92		return s
93	}
94
95	for i := 0; i < len(str); i++ {
96		char := str[i]
97		if escaping {
98			unescapedTrailingSpaces = 0
99			escaping = false
100			switch char {
101			case ' ', '"', '#', '+', ',', ';', '<', '=', '>', '\\':
102				buffer.WriteByte(char)
103				continue
104			}
105			// Not a special character, assume hex encoded octet
106			if len(str) == i+1 {
107				return nil, errors.New("Got corrupted escaped character")
108			}
109
110			dst := []byte{0}
111			n, err := enchex.Decode([]byte(dst), []byte(str[i:i+2]))
112			if err != nil {
113				return nil, fmt.Errorf("Failed to decode escaped character: %s", err)
114			} else if n != 1 {
115				return nil, fmt.Errorf("Expected 1 byte when un-escaping, got %d", n)
116			}
117			buffer.WriteByte(dst[0])
118			i++
119		} else if char == '\\' {
120			unescapedTrailingSpaces = 0
121			escaping = true
122		} else if char == '=' {
123			attribute.Type = stringFromBuffer()
124			// Special case: If the first character in the value is # the
125			// following data is BER encoded so we can just fast forward
126			// and decode.
127			if len(str) > i+1 && str[i+1] == '#' {
128				i += 2
129				index := strings.IndexAny(str[i:], ",+")
130				data := str
131				if index > 0 {
132					data = str[i : i+index]
133				} else {
134					data = str[i:]
135				}
136				rawBER, err := enchex.DecodeString(data)
137				if err != nil {
138					return nil, fmt.Errorf("Failed to decode BER encoding: %s", err)
139				}
140				packet := ber.DecodePacket(rawBER)
141				buffer.WriteString(packet.Data.String())
142				i += len(data) - 1
143			}
144		} else if char == ',' || char == '+' {
145			// We're done with this RDN or value, push it
146			if len(attribute.Type) == 0 {
147				return nil, errors.New("incomplete type, value pair")
148			}
149			attribute.Value = stringFromBuffer()
150			rdn.Attributes = append(rdn.Attributes, attribute)
151			attribute = new(AttributeTypeAndValue)
152			if char == ',' {
153				dn.RDNs = append(dn.RDNs, rdn)
154				rdn = new(RelativeDN)
155				rdn.Attributes = make([]*AttributeTypeAndValue, 0)
156			}
157		} else if char == ' ' && buffer.Len() == 0 {
158			// ignore unescaped leading spaces
159			continue
160		} else {
161			if char == ' ' {
162				// Track unescaped spaces in case they are trailing and we need to remove them
163				unescapedTrailingSpaces++
164			} else {
165				// Reset if we see a non-space char
166				unescapedTrailingSpaces = 0
167			}
168			buffer.WriteByte(char)
169		}
170	}
171	if buffer.Len() > 0 {
172		if len(attribute.Type) == 0 {
173			return nil, errors.New("DN ended with incomplete type, value pair")
174		}
175		attribute.Value = stringFromBuffer()
176		rdn.Attributes = append(rdn.Attributes, attribute)
177		dn.RDNs = append(dn.RDNs, rdn)
178	}
179	return dn, nil
180}
181
182// Equal returns true if the DNs are equal as defined by rfc4517 4.2.15 (distinguishedNameMatch).
183// Returns true if they have the same number of relative distinguished names
184// and corresponding relative distinguished names (by position) are the same.
185func (d *DN) Equal(other *DN) bool {
186	if len(d.RDNs) != len(other.RDNs) {
187		return false
188	}
189	for i := range d.RDNs {
190		if !d.RDNs[i].Equal(other.RDNs[i]) {
191			return false
192		}
193	}
194	return true
195}
196
197// AncestorOf returns true if the other DN consists of at least one RDN followed by all the RDNs of the current DN.
198// "ou=widgets,o=acme.com" is an ancestor of "ou=sprockets,ou=widgets,o=acme.com"
199// "ou=widgets,o=acme.com" is not an ancestor of "ou=sprockets,ou=widgets,o=foo.com"
200// "ou=widgets,o=acme.com" is not an ancestor of "ou=widgets,o=acme.com"
201func (d *DN) AncestorOf(other *DN) bool {
202	if len(d.RDNs) >= len(other.RDNs) {
203		return false
204	}
205	// Take the last `len(d.RDNs)` RDNs from the other DN to compare against
206	otherRDNs := other.RDNs[len(other.RDNs)-len(d.RDNs):]
207	for i := range d.RDNs {
208		if !d.RDNs[i].Equal(otherRDNs[i]) {
209			return false
210		}
211	}
212	return true
213}
214
215// Equal returns true if the RelativeDNs are equal as defined by rfc4517 4.2.15 (distinguishedNameMatch).
216// Relative distinguished names are the same if and only if they have the same number of AttributeTypeAndValues
217// and each attribute of the first RDN is the same as the attribute of the second RDN with the same attribute type.
218// The order of attributes is not significant.
219// Case of attribute types is not significant.
220func (r *RelativeDN) Equal(other *RelativeDN) bool {
221	if len(r.Attributes) != len(other.Attributes) {
222		return false
223	}
224	return r.hasAllAttributes(other.Attributes) && other.hasAllAttributes(r.Attributes)
225}
226
227func (r *RelativeDN) hasAllAttributes(attrs []*AttributeTypeAndValue) bool {
228	for _, attr := range attrs {
229		found := false
230		for _, myattr := range r.Attributes {
231			if myattr.Equal(attr) {
232				found = true
233				break
234			}
235		}
236		if !found {
237			return false
238		}
239	}
240	return true
241}
242
243// Equal returns true if the AttributeTypeAndValue is equivalent to the specified AttributeTypeAndValue
244// Case of the attribute type is not significant
245func (a *AttributeTypeAndValue) Equal(other *AttributeTypeAndValue) bool {
246	return strings.EqualFold(a.Type, other.Type) && a.Value == other.Value
247}
248