fakeldap 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.md +3 -0
- data/lib/fakeldap.rb +46 -0
- data/lib/fakeldap/version.rb +4 -0
- data/vendor/ruby-ldapserver/COPYING +27 -0
- data/vendor/ruby-ldapserver/ChangeLog +83 -0
- data/vendor/ruby-ldapserver/Manifest.txt +32 -0
- data/vendor/ruby-ldapserver/README +222 -0
- data/vendor/ruby-ldapserver/Rakefile +22 -0
- data/vendor/ruby-ldapserver/examples/README +89 -0
- data/vendor/ruby-ldapserver/examples/mkcert.rb +31 -0
- data/vendor/ruby-ldapserver/examples/rbslapd1.rb +111 -0
- data/vendor/ruby-ldapserver/examples/rbslapd2.rb +161 -0
- data/vendor/ruby-ldapserver/examples/rbslapd3.rb +172 -0
- data/vendor/ruby-ldapserver/examples/speedtest.rb +37 -0
- data/vendor/ruby-ldapserver/lib/ldap/server.rb +4 -0
- data/vendor/ruby-ldapserver/lib/ldap/server/connection.rb +276 -0
- data/vendor/ruby-ldapserver/lib/ldap/server/filter.rb +223 -0
- data/vendor/ruby-ldapserver/lib/ldap/server/match.rb +283 -0
- data/vendor/ruby-ldapserver/lib/ldap/server/operation.rb +487 -0
- data/vendor/ruby-ldapserver/lib/ldap/server/preforkserver.rb +93 -0
- data/vendor/ruby-ldapserver/lib/ldap/server/result.rb +71 -0
- data/vendor/ruby-ldapserver/lib/ldap/server/schema.rb +592 -0
- data/vendor/ruby-ldapserver/lib/ldap/server/server.rb +89 -0
- data/vendor/ruby-ldapserver/lib/ldap/server/syntax.rb +235 -0
- data/vendor/ruby-ldapserver/lib/ldap/server/tcpserver.rb +91 -0
- data/vendor/ruby-ldapserver/lib/ldap/server/util.rb +88 -0
- data/vendor/ruby-ldapserver/lib/ldap/server/version.rb +11 -0
- data/vendor/ruby-ldapserver/test/core.schema +582 -0
- data/vendor/ruby-ldapserver/test/encoding_test.rb +279 -0
- data/vendor/ruby-ldapserver/test/filter_test.rb +107 -0
- data/vendor/ruby-ldapserver/test/match_test.rb +59 -0
- data/vendor/ruby-ldapserver/test/schema_test.rb +113 -0
- data/vendor/ruby-ldapserver/test/syntax_test.rb +40 -0
- data/vendor/ruby-ldapserver/test/test_helper.rb +2 -0
- data/vendor/ruby-ldapserver/test/util_test.rb +51 -0
- metadata +130 -0
@@ -0,0 +1,283 @@
|
|
1
|
+
require 'ldap/server/syntax'
|
2
|
+
require 'ldap/server/result'
|
3
|
+
|
4
|
+
module LDAP
|
5
|
+
class Server
|
6
|
+
|
7
|
+
# A class which holds LDAP MatchingRules. For now there is a global pool
|
8
|
+
# of MatchingRule objects (rather than each Schema object having
|
9
|
+
# its own pool)
|
10
|
+
|
11
|
+
class MatchingRule
|
12
|
+
attr_reader :oid, :names, :syntax, :desc, :obsolete
|
13
|
+
|
14
|
+
# Create a new MatchingRule object
|
15
|
+
|
16
|
+
def initialize(oid, names, syntax, desc=nil, obsolete=false, &blk)
|
17
|
+
@oid = oid
|
18
|
+
@names = names
|
19
|
+
@names = [@names] unless @names.is_a?(Array)
|
20
|
+
@desc = desc
|
21
|
+
@obsolete = obsolete
|
22
|
+
@syntax = LDAP::Server::Syntax.find(syntax) # creates new obj if reqd
|
23
|
+
@def = nil
|
24
|
+
# initialization hook
|
25
|
+
self.instance_eval(&blk) if blk
|
26
|
+
end
|
27
|
+
|
28
|
+
def name
|
29
|
+
(@names && names[0]) || @oid
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_s
|
33
|
+
(@names && names[0]) || @oid
|
34
|
+
end
|
35
|
+
|
36
|
+
def normalize(x)
|
37
|
+
x
|
38
|
+
end
|
39
|
+
|
40
|
+
# Create a new MatchingRule object, given its description string
|
41
|
+
|
42
|
+
def self.from_def(str, &blk)
|
43
|
+
m = LDAP::Server::Syntax::MatchingRuleDescription.match(str)
|
44
|
+
raise LDAP::ResultError::InvalidAttributeSyntax,
|
45
|
+
"Bad MatchingRuleDescription #{str.inspect}" unless m
|
46
|
+
new(m[1], m[2].scan(/'(.*?)'/).flatten, m[5], m[3], m[4], &blk)
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_def
|
50
|
+
return @def if @def
|
51
|
+
ans = "( #{@oid} "
|
52
|
+
if names.nil? or @names.empty?
|
53
|
+
# nothing
|
54
|
+
elsif @names.size == 1
|
55
|
+
ans << "NAME '#{@names[0]}' "
|
56
|
+
else
|
57
|
+
ans << "NAME ( "
|
58
|
+
@names.each { |n| ans << "'#{n}' " }
|
59
|
+
ans << ") "
|
60
|
+
end
|
61
|
+
ans << "DESC '#@desc' " if @desc
|
62
|
+
ans << "OBSOLETE " if @obsolete
|
63
|
+
ans << "SYNTAX #@syntax " if @syntax
|
64
|
+
ans << ")"
|
65
|
+
@def = ans
|
66
|
+
end
|
67
|
+
|
68
|
+
@@rules = {} # oid / name / alias => object
|
69
|
+
|
70
|
+
# Add a new matching rule
|
71
|
+
|
72
|
+
def self.add(*args, &blk)
|
73
|
+
s = new(*args, &blk)
|
74
|
+
@@rules[s.oid] = s
|
75
|
+
return if s.names.nil?
|
76
|
+
s.names.each do |n|
|
77
|
+
@@rules[n.downcase] = s
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Find a MatchingRule object given a name or oid, or return nil
|
82
|
+
# (? should we create one automatically, like Syntax)
|
83
|
+
|
84
|
+
def self.find(x)
|
85
|
+
return x if x.nil? or x.is_a?(LDAP::Server::MatchingRule)
|
86
|
+
@@rules[x.downcase]
|
87
|
+
end
|
88
|
+
|
89
|
+
# Return all known matching rules
|
90
|
+
|
91
|
+
def self.all_matching_rules
|
92
|
+
@@rules.values.uniq
|
93
|
+
end
|
94
|
+
|
95
|
+
# Now some things we can mixin to a MatchingRule when needed.
|
96
|
+
# Replace 'normalize' with a function which gives the canonical
|
97
|
+
# version of a value for comparison.
|
98
|
+
|
99
|
+
module Equality
|
100
|
+
def eq(vals, m)
|
101
|
+
return false if vals.nil?
|
102
|
+
m = normalize(m)
|
103
|
+
vals.each { |v| return true if normalize(v) == m }
|
104
|
+
return false
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
module Ordering
|
109
|
+
def ge(vals, m)
|
110
|
+
return false if vals.nil?
|
111
|
+
m = normalize(m)
|
112
|
+
vals.each { |v| return true if normalize(v) >= m }
|
113
|
+
return false
|
114
|
+
end
|
115
|
+
|
116
|
+
def le(vals, m)
|
117
|
+
return false if vals.nil?
|
118
|
+
m = normalize(m)
|
119
|
+
vals.each { |v| return true if normalize(v) <= m }
|
120
|
+
return false
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
module Substrings
|
125
|
+
def substrings(vals, *ss)
|
126
|
+
return false if vals.nil?
|
127
|
+
|
128
|
+
# convert the condition list into a regexp
|
129
|
+
re = []
|
130
|
+
re << "^#{Regexp.escape(normalize(ss[0]).to_s)}" if ss[0]
|
131
|
+
ss[1..-2].each { |s| re << Regexp.escape(normalize(s).to_s) }
|
132
|
+
re << "#{Regexp.escape(normalize(ss[-1]).to_s)}$" if ss[-1]
|
133
|
+
re = Regexp.new(re.join(".*"))
|
134
|
+
|
135
|
+
vals.each do |v|
|
136
|
+
v = normalize(v).to_s
|
137
|
+
return true if re.match(v)
|
138
|
+
end
|
139
|
+
return false
|
140
|
+
end
|
141
|
+
end # module Substrings
|
142
|
+
|
143
|
+
class DefaultMatchingClass
|
144
|
+
include MatchingRule::Equality
|
145
|
+
include MatchingRule::Ordering
|
146
|
+
include MatchingRule::Substrings
|
147
|
+
def normalize(x)
|
148
|
+
x
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
DefaultMatch = DefaultMatchingClass.new
|
153
|
+
|
154
|
+
end # class MatchingRule
|
155
|
+
|
156
|
+
#
|
157
|
+
# And now, here are some matching rules you can use (RFC2252 section 8)
|
158
|
+
#
|
159
|
+
|
160
|
+
class MatchingRule
|
161
|
+
|
162
|
+
add('2.5.13.0', 'objectIdentifierMatch', '1.3.6.1.4.1.1466.115.121.1.38') do
|
163
|
+
extend Equality
|
164
|
+
end
|
165
|
+
# FIXME: Filters should return undef if the OID is not in the schema
|
166
|
+
# (which means passing in the schema to every equality test)
|
167
|
+
|
168
|
+
add('2.5.13.1', 'distinguishedNameMatch', '1.3.6.1.4.1.1466.115.121.1.12') do
|
169
|
+
extend Equality
|
170
|
+
end
|
171
|
+
# FIXME: Distinguished Name matching is supposed to parse the DN into
|
172
|
+
# its parts and then apply the schema equality rules to each part
|
173
|
+
# (i.e. some parts may be case-sensitive, others case-insensitive)
|
174
|
+
# This is just one of the many nonsense design decisions in LDAP :-(
|
175
|
+
|
176
|
+
# How is a DirectoryString different to an IA5String or a PrintableString?
|
177
|
+
|
178
|
+
module StringTrim
|
179
|
+
def normalize(x); x.gsub(/^\s*|\s*$/, '').gsub(/\s+/,' '); end
|
180
|
+
end
|
181
|
+
|
182
|
+
module StringDowncase
|
183
|
+
def normalize(x); x.downcase.gsub(/^\s*|\s*$/, '').gsub(/\s+/,' '); end
|
184
|
+
end
|
185
|
+
|
186
|
+
add('2.5.13.2', 'caseIgnoreMatch', '1.3.6.1.4.1.1466.115.1') do
|
187
|
+
extend Equality
|
188
|
+
extend StringDowncase
|
189
|
+
end
|
190
|
+
|
191
|
+
module Integer
|
192
|
+
def normalize(x); x.to_i; end
|
193
|
+
end
|
194
|
+
|
195
|
+
add('2.5.13.8', 'numericStringMatch', '1.3.6.1.4.1.1466.115.121.1.36') do
|
196
|
+
extend Equality
|
197
|
+
extend Integer
|
198
|
+
end
|
199
|
+
|
200
|
+
# TODO: Add semantics for these (difficult since RFC2252 doesn't give
|
201
|
+
# them, so we presumably have to go through X.500)
|
202
|
+
add('2.5.13.11', 'caseIgnoreListMatch', '1.3.6.1.4.1.1466.115.121.1.41')
|
203
|
+
add('2.5.13.14', 'integerMatch', '1.3.6.1.4.1.1466.115.121.1.27') do
|
204
|
+
extend Equality
|
205
|
+
extend Integer
|
206
|
+
end
|
207
|
+
add('2.5.13.16', 'bitStringMatch', '1.3.6.1.4.1.1466.115.121.1.6')
|
208
|
+
add('2.5.13.20', 'telephoneNumberMatch', '1.3.6.1.4.1.1466.115.121.1.50') do
|
209
|
+
extend Equality
|
210
|
+
extend StringTrim
|
211
|
+
end
|
212
|
+
add('2.5.13.22', 'presentationAddressMatch', '1.3.6.1.4.1.1466.115.121.1.43')
|
213
|
+
add('2.5.13.23', 'uniqueMemberMatch', '1.3.6.1.4.1.1466.115.121.1.34')
|
214
|
+
add('2.5.13.24', 'protocolInformationMatch', '1.3.6.1.4.1.1466.115.121.1.42')
|
215
|
+
add('2.5.13.27', 'generalizedTimeMatch', '1.3.6.1.4.1.1466.115.121.1.24') { extend Equality }
|
216
|
+
|
217
|
+
# IA5 stuff. FIXME: What's the correct way to 'downcase' UTF8 strings?
|
218
|
+
|
219
|
+
module IA5Trim
|
220
|
+
def normalize(x); x.gsub(/^\s*|\s*$/u, '').gsub(/\s+/u,' '); end
|
221
|
+
end
|
222
|
+
|
223
|
+
module IA5Downcase
|
224
|
+
def normalize(x); x.downcase.gsub(/^\s*|\s*$/u, '').gsub(/\s+/u,' '); end
|
225
|
+
end
|
226
|
+
|
227
|
+
add('1.3.6.1.4.1.1466.109.114.1', 'caseExactIA5Match', '1.3.6.1.4.1.1466.115.121.1.26') do
|
228
|
+
extend Equality
|
229
|
+
extend IA5Trim
|
230
|
+
end
|
231
|
+
|
232
|
+
add('1.3.6.1.4.1.1466.109.114.2', 'caseIgnoreIA5Match', '1.3.6.1.4.1.1466.115.121.1.26') do
|
233
|
+
extend Equality
|
234
|
+
extend IA5Downcase
|
235
|
+
end
|
236
|
+
|
237
|
+
add('2.5.13.28', 'generalizedTimeOrderingMatch', '1.3.6.1.4.1.1466.115.121.1.24') { extend Ordering }
|
238
|
+
add('2.5.13.3', 'caseIgnoreOrderingMatch', '1.3.6.1.4.1.1466.115.121.1.15') do
|
239
|
+
extend Ordering
|
240
|
+
extend StringDowncase
|
241
|
+
end
|
242
|
+
|
243
|
+
add('2.5.13.4', 'caseIgnoreSubstringsMatch', '1.3.6.1.4.1.1466.115.121.1.58') do
|
244
|
+
extend Substrings
|
245
|
+
extend StringDowncase
|
246
|
+
end
|
247
|
+
add('2.5.13.21', 'telephoneNumberSubstringsMatch', '1.3.6.1.4.1.1466.115.121.1.58') do
|
248
|
+
extend Substrings
|
249
|
+
end
|
250
|
+
add('2.5.13.10', 'numericStringSubstringsMatch', '1.3.6.1.4.1.1466.115.121.1.58') do
|
251
|
+
extend Substrings
|
252
|
+
end
|
253
|
+
|
254
|
+
# from OpenLDAP
|
255
|
+
add('1.3.6.1.4.1.4203.1.2.1', 'caseExactIA5SubstringsMatch', '1.3.6.1.4.1.1466.115.121.1.26') do
|
256
|
+
extend Substrings
|
257
|
+
extend IA5Trim
|
258
|
+
end
|
259
|
+
add('1.3.6.1.4.1.1466.109.114.3', 'caseIgnoreIA5SubstringsMatch', '1.3.6.1.4.1.1466.115.121.1.26') do
|
260
|
+
extend Substrings
|
261
|
+
extend IA5Downcase
|
262
|
+
end
|
263
|
+
add('2.5.13.5', 'caseExactMatch', '1.3.6.1.4.1.1466.115.121.1.15') { extend Equality }
|
264
|
+
add('2.5.13.6', 'caseExactOrderingMatch', '1.3.6.1.4.1.1466.115.121.1.15') { extend Ordering }
|
265
|
+
add('2.5.13.7', 'caseExactSubstringsMatch', '1.3.6.1.4.1.1466.115.121.1.58') { extend Substrings }
|
266
|
+
add('2.5.13.9', 'numericStringOrderingMatch', '1.3.6.1.4.1.1466.115.121.1.36') { extend Ordering; extend Integer }
|
267
|
+
add('2.5.13.13', 'booleanMatch', '1.3.6.1.4.1.1466.115.121.1.7') do
|
268
|
+
extend Equality
|
269
|
+
def self.normalize(x)
|
270
|
+
return true if x == 'TRUE'
|
271
|
+
return false if x == 'FALSE'
|
272
|
+
x
|
273
|
+
end
|
274
|
+
end
|
275
|
+
add('2.5.13.15', 'integerOrderingMatch', '1.3.6.1.4.1.1466.115.121.1.27') { extend Ordering; extend Integer }
|
276
|
+
add('2.5.13.17', 'octetStringMatch', '1.3.6.1.4.1.1466.115.121.1.40') { extend Equality }
|
277
|
+
add('2.5.13.18', 'octetStringOrderingMatch', '1.3.6.1.4.1.1466.115.121.1.40') { extend Ordering }
|
278
|
+
add('2.5.13.19', 'octetStringSubstringsMatch', '1.3.6.1.4.1.1466.115.121.1.40') { extend Substrings }
|
279
|
+
|
280
|
+
end # class MatchingRule
|
281
|
+
|
282
|
+
end # class Server
|
283
|
+
end # module LDAP
|
@@ -0,0 +1,487 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
require 'ldap/server/result'
|
3
|
+
require 'ldap/server/filter'
|
4
|
+
|
5
|
+
module LDAP
|
6
|
+
class Server
|
7
|
+
|
8
|
+
# Scope
|
9
|
+
BaseObject = 0
|
10
|
+
SingleLevel = 1
|
11
|
+
WholeSubtree = 2
|
12
|
+
|
13
|
+
# DerefAliases
|
14
|
+
NeverDerefAliases = 0
|
15
|
+
DerefInSearching = 1
|
16
|
+
DerefFindingBaseObj = 2
|
17
|
+
DerefAlways = 3
|
18
|
+
|
19
|
+
# Object to handle a single LDAP request. Typically you would
|
20
|
+
# subclass this object and override methods 'simple_bind', 'search' etc.
|
21
|
+
# The do_xxx methods are internal, and handle the parsing of requests
|
22
|
+
# and the sending of responses.
|
23
|
+
|
24
|
+
class Operation
|
25
|
+
|
26
|
+
# An instance of this object is created by the Connection object
|
27
|
+
# for each operation which is requested by the client. If you subclass
|
28
|
+
# Operation, and you override initialize, make sure you call 'super'.
|
29
|
+
|
30
|
+
def initialize(connection, messageID)
|
31
|
+
@connection = connection
|
32
|
+
@respEnvelope = OpenSSL::ASN1::Sequence([
|
33
|
+
OpenSSL::ASN1::Integer(messageID),
|
34
|
+
# protocolOp,
|
35
|
+
# controls [0] OPTIONAL,
|
36
|
+
])
|
37
|
+
@schema = @connection.opt[:schema]
|
38
|
+
@server = @connection.opt[:server]
|
39
|
+
end
|
40
|
+
|
41
|
+
# Send a log message
|
42
|
+
|
43
|
+
def log(*args)
|
44
|
+
@connection.log(*args)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Send an exception report to the log
|
48
|
+
|
49
|
+
def log_exception(e)
|
50
|
+
@connection.log "#{e}: #{e.backtrace.join("\n\tfrom ")}"
|
51
|
+
end
|
52
|
+
|
53
|
+
##################################################
|
54
|
+
### Utility methods to send protocol responses ###
|
55
|
+
##################################################
|
56
|
+
|
57
|
+
def send_LDAPMessage(protocolOp, opt={}) # :nodoc:
|
58
|
+
@respEnvelope.value[1] = protocolOp
|
59
|
+
if opt[:controls]
|
60
|
+
@respEnvelope.value[2] = OpenSSL::ASN1::Set(opt[:controls], 0, :IMPLICIT, APPLICATION)
|
61
|
+
else
|
62
|
+
@respEnvelope.value.delete_at(2)
|
63
|
+
end
|
64
|
+
|
65
|
+
if false # $debug
|
66
|
+
puts "Response:"
|
67
|
+
p @respEnvelope
|
68
|
+
p @respEnvelope.to_der.unpack("H*")
|
69
|
+
end
|
70
|
+
|
71
|
+
@connection.write(@respEnvelope.to_der)
|
72
|
+
end
|
73
|
+
|
74
|
+
def send_LDAPResult(tag, resultCode, opt={}) # :nodoc:
|
75
|
+
seq = [
|
76
|
+
OpenSSL::ASN1::Enumerated(resultCode),
|
77
|
+
OpenSSL::ASN1::OctetString(opt[:matchedDN] || ""),
|
78
|
+
OpenSSL::ASN1::OctetString(opt[:errorMessage] || ""),
|
79
|
+
]
|
80
|
+
if opt[:referral]
|
81
|
+
rs = opt[:referral].collect { |r| OpenSSL::ASN1::OctetString(r) }
|
82
|
+
seq << OpenSSL::ASN1::Sequence(rs, 3, :IMPLICIT, :APPLICATION)
|
83
|
+
end
|
84
|
+
yield seq if block_given? # opportunity to add more elements
|
85
|
+
|
86
|
+
send_LDAPMessage(OpenSSL::ASN1::Sequence(seq, tag, :IMPLICIT, :APPLICATION), opt)
|
87
|
+
end
|
88
|
+
|
89
|
+
def send_BindResponse(resultCode, opt={})
|
90
|
+
send_LDAPResult(1, resultCode, opt) do |resp|
|
91
|
+
if opt[:serverSaslCreds]
|
92
|
+
resp << OpenSSL::ASN1::OctetString(opt[:serverSaslCreds], 7, :IMPLICIT, :APPLICATION)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Send a found entry. Avs are {attr1=>val1, attr2=>[val2,val3]}
|
98
|
+
# If schema given, return operational attributes only if
|
99
|
+
# explicitly requested
|
100
|
+
|
101
|
+
def send_SearchResultEntry(dn, avs, opt={})
|
102
|
+
@rescount += 1
|
103
|
+
if @sizelimit
|
104
|
+
raise LDAP::ResultError::SizeLimitExceeded if @rescount > @sizelimit
|
105
|
+
end
|
106
|
+
|
107
|
+
if @schema
|
108
|
+
# normalize the attribute names
|
109
|
+
@attributes = @attributes.collect { |a| @schema.find_attrtype(a).to_s }
|
110
|
+
end
|
111
|
+
|
112
|
+
sendall = @attributes == [] || @attributes.include?("*")
|
113
|
+
avseq = []
|
114
|
+
|
115
|
+
avs.each do |attr, vals|
|
116
|
+
if !@attributes.include?(attr)
|
117
|
+
next unless sendall
|
118
|
+
if @schema
|
119
|
+
a = @schema.find_attrtype(attr)
|
120
|
+
next unless a and (a.usage.nil? or a.usage == :userApplications)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
if @typesOnly
|
125
|
+
vals = []
|
126
|
+
else
|
127
|
+
vals = [vals] unless vals.kind_of?(Array)
|
128
|
+
# FIXME: optionally do a value_to_s conversion here?
|
129
|
+
# FIXME: handle attribute;binary
|
130
|
+
end
|
131
|
+
|
132
|
+
avseq << OpenSSL::ASN1::Sequence([
|
133
|
+
OpenSSL::ASN1::OctetString(attr),
|
134
|
+
OpenSSL::ASN1::Set(vals.collect { |v| OpenSSL::ASN1::OctetString(v.to_s) })
|
135
|
+
])
|
136
|
+
end
|
137
|
+
|
138
|
+
send_LDAPMessage(OpenSSL::ASN1::Sequence([
|
139
|
+
OpenSSL::ASN1::OctetString(dn),
|
140
|
+
OpenSSL::ASN1::Sequence(avseq),
|
141
|
+
], 4, :IMPLICIT, :APPLICATION), opt)
|
142
|
+
end
|
143
|
+
|
144
|
+
def send_SearchResultReference(urls, opt={})
|
145
|
+
send_LDAPMessage(OpenSSL::ASN1::Sequence(
|
146
|
+
urls.collect { |url| OpenSSL::ASN1::OctetString(url) }
|
147
|
+
),
|
148
|
+
opt
|
149
|
+
)
|
150
|
+
end
|
151
|
+
|
152
|
+
def send_SearchResultDone(resultCode, opt={})
|
153
|
+
send_LDAPResult(5, resultCode, opt)
|
154
|
+
end
|
155
|
+
|
156
|
+
def send_ModifyResponse(resultCode, opt={})
|
157
|
+
send_LDAPResult(7, resultCode, opt)
|
158
|
+
end
|
159
|
+
|
160
|
+
def send_AddResponse(resultCode, opt={})
|
161
|
+
send_LDAPResult(9, resultCode, opt)
|
162
|
+
end
|
163
|
+
|
164
|
+
def send_DelResponse(resultCode, opt={})
|
165
|
+
send_LDAPResult(11, resultCode, opt)
|
166
|
+
end
|
167
|
+
|
168
|
+
def send_ModifyDNResponse(resultCode, opt={})
|
169
|
+
send_LDAPResult(13, resultCode, opt)
|
170
|
+
end
|
171
|
+
|
172
|
+
def send_CompareResponse(resultCode, opt={})
|
173
|
+
send_LDAPResult(15, resultCode, opt)
|
174
|
+
end
|
175
|
+
|
176
|
+
def send_ExtendedResponse(resultCode, opt={})
|
177
|
+
send_LDAPResult(24, resultCode, opt) do |resp|
|
178
|
+
if opt[:responseName]
|
179
|
+
resp << OpenSSL::ASN1::OctetString(opt[:responseName], 10, :IMPLICIT, :APPLICATION)
|
180
|
+
end
|
181
|
+
if opt[:response]
|
182
|
+
resp << OpenSSL::ASN1::OctetString(opt[:response], 11, :IMPLICIT, :APPLICATION)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
##########################################
|
188
|
+
### Methods to parse each request type ###
|
189
|
+
##########################################
|
190
|
+
|
191
|
+
def do_bind(protocolOp, controls) # :nodoc:
|
192
|
+
version = protocolOp.value[0].value
|
193
|
+
dn = protocolOp.value[1].value
|
194
|
+
dn = nil if dn == ""
|
195
|
+
authentication = protocolOp.value[2]
|
196
|
+
|
197
|
+
case authentication.tag # tag_class == :CONTEXT_SPECIFIC (check why)
|
198
|
+
when 0
|
199
|
+
simple_bind(version, dn, authentication.value)
|
200
|
+
when 3
|
201
|
+
mechanism = authentication.value[0].value
|
202
|
+
credentials = authentication.value[1].value
|
203
|
+
# sasl_bind(version, dn, mechanism, credentials)
|
204
|
+
# FIXME: needs to exchange further BindRequests
|
205
|
+
raise LDAP::ResultError::AuthMethodNotSupported
|
206
|
+
else
|
207
|
+
raise LDAP::ResultError::ProtocolError, "BindRequest bad AuthenticationChoice"
|
208
|
+
end
|
209
|
+
send_BindResponse(0)
|
210
|
+
return dn, version
|
211
|
+
|
212
|
+
rescue LDAP::ResultError => e
|
213
|
+
send_BindResponse(e.to_i, :errorMessage=>e.message)
|
214
|
+
return nil, version
|
215
|
+
end
|
216
|
+
|
217
|
+
# reformat ASN1 into {attr=>[vals], attr=>[vals]}
|
218
|
+
#
|
219
|
+
# AttributeList ::= SEQUENCE OF SEQUENCE {
|
220
|
+
# type AttributeDescription,
|
221
|
+
# vals SET OF AttributeValue }
|
222
|
+
|
223
|
+
def attributelist(set) # :nodoc:
|
224
|
+
av = {}
|
225
|
+
set.value.each do |seq|
|
226
|
+
a = seq.value[0].value
|
227
|
+
if @schema
|
228
|
+
a = @schema.find_attrtype(a).to_s
|
229
|
+
end
|
230
|
+
v = seq.value[1].value.collect { |asn1| asn1.value }
|
231
|
+
# Not clear from the spec whether the same attribute (with
|
232
|
+
# distinct values) can appear more than once in AttributeList
|
233
|
+
raise LDAP::ResultError::AttributeOrValueExists, a if av[a]
|
234
|
+
av[a] = v
|
235
|
+
end
|
236
|
+
return av
|
237
|
+
end
|
238
|
+
|
239
|
+
def do_search(protocolOp, controls) # :nodoc:
|
240
|
+
baseObject = protocolOp.value[0].value
|
241
|
+
scope = protocolOp.value[1].value
|
242
|
+
deref = protocolOp.value[2].value
|
243
|
+
client_sizelimit = protocolOp.value[3].value
|
244
|
+
client_timelimit = protocolOp.value[4].value
|
245
|
+
@typesOnly = protocolOp.value[5].value
|
246
|
+
filter = Filter::parse(protocolOp.value[6], @schema)
|
247
|
+
@attributes = protocolOp.value[7].value.collect {|x| x.value}
|
248
|
+
|
249
|
+
@rescount = 0
|
250
|
+
@sizelimit = server_sizelimit
|
251
|
+
@sizelimit = client_sizelimit if client_sizelimit > 0 and
|
252
|
+
(@sizelimit.nil? or client_sizelimit < @sizelimit)
|
253
|
+
|
254
|
+
if baseObject.empty? and scope == BaseObject
|
255
|
+
send_SearchResultEntry("", @server.root_dse) if
|
256
|
+
@server.root_dse and LDAP::Server::Filter.run(filter, @server.root_dse)
|
257
|
+
send_SearchResultDone(0)
|
258
|
+
return
|
259
|
+
elsif @schema and baseObject == @schema.subschema_dn
|
260
|
+
send_SearchResultEntry(baseObject, @schema.subschema_subentry) if
|
261
|
+
@schema and @schema.subschema_subentry and
|
262
|
+
LDAP::Server::Filter.run(filter, @schema.subschema_subentry)
|
263
|
+
send_SearchResultDone(0)
|
264
|
+
return
|
265
|
+
end
|
266
|
+
|
267
|
+
t = server_timelimit || 10
|
268
|
+
t = client_timelimit if client_timelimit > 0 and client_timelimit < t
|
269
|
+
|
270
|
+
Timeout::timeout(t, LDAP::ResultError::TimeLimitExceeded) do
|
271
|
+
search(baseObject, scope, deref, filter)
|
272
|
+
end
|
273
|
+
send_SearchResultDone(0)
|
274
|
+
|
275
|
+
# Note that TimeLimitExceeded is a subclass of LDAP::ResultError
|
276
|
+
rescue LDAP::ResultError => e
|
277
|
+
send_SearchResultDone(e.to_i, :errorMessage=>e.message)
|
278
|
+
|
279
|
+
rescue Abandon
|
280
|
+
# send no response
|
281
|
+
|
282
|
+
# Since this Operation is running in its own thread, we have to
|
283
|
+
# catch all other exceptions. Otherwise, in the event of a programming
|
284
|
+
# error, this thread will silently terminate and the client will wait
|
285
|
+
# forever for a response.
|
286
|
+
|
287
|
+
rescue Exception => e
|
288
|
+
log_exception(e)
|
289
|
+
send_SearchResultDone(LDAP::ResultError::OperationsError.new.to_i, :errorMessage=>e.message)
|
290
|
+
end
|
291
|
+
|
292
|
+
def do_modify(protocolOp, controls) # :nodoc:
|
293
|
+
dn = protocolOp.value[0].value
|
294
|
+
modinfo = {}
|
295
|
+
protocolOp.value[1].value.each do |seq|
|
296
|
+
attr = seq.value[1].value[0].value
|
297
|
+
if @schema
|
298
|
+
attr = @schema.find_attrtype(attr).to_s
|
299
|
+
end
|
300
|
+
vals = seq.value[1].value[1].value.collect { |v| v.value }
|
301
|
+
case seq.value[0].value
|
302
|
+
when 0
|
303
|
+
modinfo[attr] = [:add] + vals
|
304
|
+
when 1
|
305
|
+
modinfo[attr] = [:delete] + vals
|
306
|
+
when 2
|
307
|
+
modinfo[attr] = [:replace] + vals
|
308
|
+
else
|
309
|
+
raise LDAP::ResultError::ProtocolError, "Bad modify operation #{seq.value[0].value}"
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
modify(dn, modinfo)
|
314
|
+
send_ModifyResponse(0)
|
315
|
+
|
316
|
+
rescue LDAP::ResultError => e
|
317
|
+
send_ModifyResponse(e.to_i, :errorMessage=>e.message)
|
318
|
+
rescue Abandon
|
319
|
+
# no response
|
320
|
+
rescue Exception => e
|
321
|
+
log_exception(e)
|
322
|
+
send_ModifyResponse(LDAP::ResultCode::OperationsError.new.to_i, :errorMessage=>e.message)
|
323
|
+
end
|
324
|
+
|
325
|
+
def do_add(protocolOp, controls) # :nodoc:
|
326
|
+
dn = protocolOp.value[0].value
|
327
|
+
av = attributelist(protocolOp.value[1])
|
328
|
+
add(dn, av)
|
329
|
+
send_AddResponse(0)
|
330
|
+
|
331
|
+
rescue LDAP::ResultError => e
|
332
|
+
send_AddResponse(e.to_i, :errorMessage=>e.message)
|
333
|
+
rescue Abandon
|
334
|
+
# no response
|
335
|
+
rescue Exception => e
|
336
|
+
log_exception(e)
|
337
|
+
send_AddResponse(LDAP::ResultCode::OperationsError.new.to_i, :errorMessage=>e.message)
|
338
|
+
end
|
339
|
+
|
340
|
+
def do_del(protocolOp, controls) # :nodoc:
|
341
|
+
dn = protocolOp.value
|
342
|
+
del(dn)
|
343
|
+
send_DelResponse(0)
|
344
|
+
|
345
|
+
rescue LDAP::ResultError => e
|
346
|
+
send_DelResponse(e.to_i, :errorMessage=>e.message)
|
347
|
+
rescue Abandon
|
348
|
+
# no response
|
349
|
+
rescue Exception => e
|
350
|
+
log_exception(e)
|
351
|
+
send_DelResponse(LDAP::ResultCode::OperationsError.new.to_i, :errorMessage=>e.message)
|
352
|
+
end
|
353
|
+
|
354
|
+
def do_modifydn(protocolOp, controls) # :nodoc:
|
355
|
+
entry = protocolOp.value[0].value
|
356
|
+
newrdn = protocolOp.value[1].value
|
357
|
+
deleteoldrdn = protocolOp.value[2].value
|
358
|
+
if protocolOp.value.size > 3 and protocolOp.value[3].tag == 0
|
359
|
+
newSuperior = protocolOp.value[3].value
|
360
|
+
end
|
361
|
+
modifydn(entry, newrdn, deleteoldrdn, newSuperior)
|
362
|
+
send_ModifyDNResponse(0)
|
363
|
+
|
364
|
+
rescue LDAP::ResultError => e
|
365
|
+
send_ModifyDNResponse(e.to_i, :errorMessage=>e.message)
|
366
|
+
rescue Abandon
|
367
|
+
# no response
|
368
|
+
rescue Exception => e
|
369
|
+
log_exception(e)
|
370
|
+
send_ModifyDNResponse(LDAP::ResultCode::OperationsError.new.to_i, :errorMessage=>e.message)
|
371
|
+
end
|
372
|
+
|
373
|
+
def do_compare(protocolOp, controls) # :nodoc:
|
374
|
+
entry = protocolOp.value[0].value
|
375
|
+
ava = protocolOp.value[1].value
|
376
|
+
attr = ava[0].value
|
377
|
+
if @schema
|
378
|
+
attr = @schema.find_attrtype(attr).to_s
|
379
|
+
end
|
380
|
+
val = ava[1].value
|
381
|
+
if compare(entry, attr, val)
|
382
|
+
send_CompareResponse(6) # compareTrue
|
383
|
+
else
|
384
|
+
send_CompareResponse(5) # compareFalse
|
385
|
+
end
|
386
|
+
|
387
|
+
rescue LDAP::ResultError => e
|
388
|
+
send_CompareResponse(e.to_i, :errorMessage=>e.message)
|
389
|
+
rescue Abandon
|
390
|
+
# no response
|
391
|
+
rescue Exception => e
|
392
|
+
log_exception(e)
|
393
|
+
send_CompareResponse(LDAP::ResultCode::OperationsError.new.to_i, :errorMessage=>e.message)
|
394
|
+
end
|
395
|
+
|
396
|
+
############################################################
|
397
|
+
### Methods to get parameters related to this connection ###
|
398
|
+
############################################################
|
399
|
+
|
400
|
+
# Server-set maximum time limit. Override for more complex behaviour
|
401
|
+
# (e.g. limit depends on @connection.binddn). Nil uses hardcoded default.
|
402
|
+
|
403
|
+
def server_timelimit
|
404
|
+
@connection.opt[:timelimit]
|
405
|
+
end
|
406
|
+
|
407
|
+
# Server-set maximum size limit. Override for more complex behaviour
|
408
|
+
# (e.g. limit depends on @connection.binddn). Return nil for unlimited.
|
409
|
+
|
410
|
+
def server_sizelimit
|
411
|
+
@connection.opt[:sizelimit]
|
412
|
+
end
|
413
|
+
|
414
|
+
######################################################
|
415
|
+
### Methods to actually perform the work requested ###
|
416
|
+
######################################################
|
417
|
+
|
418
|
+
# Handle a simple bind request; raise an exception if the bind is
|
419
|
+
# not acceptable, otherwise just return to accept the bind.
|
420
|
+
#
|
421
|
+
# Override this method in your own subclass.
|
422
|
+
|
423
|
+
def simple_bind(version, dn, password)
|
424
|
+
if version != 3
|
425
|
+
raise LDAP::ResultError::ProtocolError, "version 3 only"
|
426
|
+
end
|
427
|
+
if dn
|
428
|
+
raise LDAP::ResultError::InappropriateAuthentication, "This server only supports anonymous bind"
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
# Handle a search request; override this.
|
433
|
+
#
|
434
|
+
# Call send_SearchResultEntry for each result found. Raise an exception
|
435
|
+
# if there is a problem. timeLimit, sizeLimit and typesOnly are taken
|
436
|
+
# care of, but you need to perform all authorisation checks yourself,
|
437
|
+
# using @connection.binddn
|
438
|
+
|
439
|
+
def search(basedn, scope, deref, filter, attrs)
|
440
|
+
raise LDAP::ResultError::UnwillingToPerform, "search not implemented"
|
441
|
+
end
|
442
|
+
|
443
|
+
# Handle a modify request; override this
|
444
|
+
#
|
445
|
+
# dn is the object to modify; modification is a hash of
|
446
|
+
# attr => [:add, val, val...] -- add operation
|
447
|
+
# attr => [:replace, val, val...] -- replace operation
|
448
|
+
# attr => [:delete, val, val...] -- delete these values
|
449
|
+
# attr => [:delete] -- delete all values
|
450
|
+
|
451
|
+
def modify(dn, modification)
|
452
|
+
raise LDAP::ResultError::UnwillingToPerform, "modify not implemented"
|
453
|
+
end
|
454
|
+
|
455
|
+
# Handle an add request; override this
|
456
|
+
#
|
457
|
+
# Parameters are the dn of the entry to add, and a hash of
|
458
|
+
# attr=>[val...]
|
459
|
+
# Raise an exception if there is a problem; it is up to you to check
|
460
|
+
# that the connection has sufficient authorisation using @connection.binddn
|
461
|
+
|
462
|
+
def add(dn, av)
|
463
|
+
raise LDAP::ResultError::UnwillingToPerform, "add not implemented"
|
464
|
+
end
|
465
|
+
|
466
|
+
# Handle a del request; override this
|
467
|
+
|
468
|
+
def del(dn)
|
469
|
+
raise LDAP::ResultError::UnwillingToPerform, "delete not implemented"
|
470
|
+
end
|
471
|
+
|
472
|
+
# Handle a modifydn request; override this
|
473
|
+
|
474
|
+
def modifydn(entry, newrdn, deleteoldrdn, newSuperior)
|
475
|
+
raise LDAP::ResultError::UnwillingToPerform, "modifydn not implemented"
|
476
|
+
end
|
477
|
+
|
478
|
+
# Handle a compare request; override this. Return true or false,
|
479
|
+
# or raise an exception for errors.
|
480
|
+
|
481
|
+
def compare(entry, attr, val)
|
482
|
+
raise LDAP::ResultError::UnwillingToPerform, "compare not implemented"
|
483
|
+
end
|
484
|
+
|
485
|
+
end # class Operation
|
486
|
+
end # class Server
|
487
|
+
end # module LDAP
|