fakeldap 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/LICENSE +20 -0
  2. data/README.md +3 -0
  3. data/lib/fakeldap.rb +46 -0
  4. data/lib/fakeldap/version.rb +4 -0
  5. data/vendor/ruby-ldapserver/COPYING +27 -0
  6. data/vendor/ruby-ldapserver/ChangeLog +83 -0
  7. data/vendor/ruby-ldapserver/Manifest.txt +32 -0
  8. data/vendor/ruby-ldapserver/README +222 -0
  9. data/vendor/ruby-ldapserver/Rakefile +22 -0
  10. data/vendor/ruby-ldapserver/examples/README +89 -0
  11. data/vendor/ruby-ldapserver/examples/mkcert.rb +31 -0
  12. data/vendor/ruby-ldapserver/examples/rbslapd1.rb +111 -0
  13. data/vendor/ruby-ldapserver/examples/rbslapd2.rb +161 -0
  14. data/vendor/ruby-ldapserver/examples/rbslapd3.rb +172 -0
  15. data/vendor/ruby-ldapserver/examples/speedtest.rb +37 -0
  16. data/vendor/ruby-ldapserver/lib/ldap/server.rb +4 -0
  17. data/vendor/ruby-ldapserver/lib/ldap/server/connection.rb +276 -0
  18. data/vendor/ruby-ldapserver/lib/ldap/server/filter.rb +223 -0
  19. data/vendor/ruby-ldapserver/lib/ldap/server/match.rb +283 -0
  20. data/vendor/ruby-ldapserver/lib/ldap/server/operation.rb +487 -0
  21. data/vendor/ruby-ldapserver/lib/ldap/server/preforkserver.rb +93 -0
  22. data/vendor/ruby-ldapserver/lib/ldap/server/result.rb +71 -0
  23. data/vendor/ruby-ldapserver/lib/ldap/server/schema.rb +592 -0
  24. data/vendor/ruby-ldapserver/lib/ldap/server/server.rb +89 -0
  25. data/vendor/ruby-ldapserver/lib/ldap/server/syntax.rb +235 -0
  26. data/vendor/ruby-ldapserver/lib/ldap/server/tcpserver.rb +91 -0
  27. data/vendor/ruby-ldapserver/lib/ldap/server/util.rb +88 -0
  28. data/vendor/ruby-ldapserver/lib/ldap/server/version.rb +11 -0
  29. data/vendor/ruby-ldapserver/test/core.schema +582 -0
  30. data/vendor/ruby-ldapserver/test/encoding_test.rb +279 -0
  31. data/vendor/ruby-ldapserver/test/filter_test.rb +107 -0
  32. data/vendor/ruby-ldapserver/test/match_test.rb +59 -0
  33. data/vendor/ruby-ldapserver/test/schema_test.rb +113 -0
  34. data/vendor/ruby-ldapserver/test/syntax_test.rb +40 -0
  35. data/vendor/ruby-ldapserver/test/test_helper.rb +2 -0
  36. data/vendor/ruby-ldapserver/test/util_test.rb +51 -0
  37. 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