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,37 @@
1
+ #!/usr/local/bin/ruby
2
+
3
+ require 'ldap'
4
+
5
+ CHILDREN = 10
6
+ CONNECTS = 1 # per child
7
+ SEARCHES = 100 # per connection
8
+
9
+ pids = []
10
+ CHILDREN.times do
11
+ pids << fork do
12
+ CONNECTS.times do
13
+ conn = LDAP::Conn.new("localhost",1389)
14
+ conn.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3)
15
+ conn.bind
16
+ SEARCHES.times do
17
+ res = conn.search("cn=Fred Flintstone,dc=example,dc=com", LDAP::LDAP_SCOPE_BASE,
18
+ "(objectclass=*)") do |e|
19
+ #puts "#{$$} #{e.dn.inspect}"
20
+ end
21
+ end
22
+ conn.unbind
23
+ end
24
+ end
25
+ end
26
+ okcount = 0
27
+ badcount = 0
28
+ pids.each do |p|
29
+ Process.wait(p)
30
+ if $?.exitstatus == 0
31
+ okcount += 1
32
+ else
33
+ badcount += 1
34
+ end
35
+ end
36
+ puts "Children finished: #{okcount} ok, #{badcount} failed"
37
+ exit badcount > 0 ? 1 : 0
@@ -0,0 +1,4 @@
1
+ require 'ldap/server/result'
2
+ require 'ldap/server/connection'
3
+ require 'ldap/server/operation'
4
+ require 'ldap/server/server'
@@ -0,0 +1,276 @@
1
+ require 'thread'
2
+ require 'openssl'
3
+ require 'ldap/server/result'
4
+
5
+ module LDAP
6
+ class Server
7
+
8
+ # An object which handles an LDAP connection. Note that LDAP allows
9
+ # requests and responses to be exchanged asynchronously: e.g. a client
10
+ # can send three requests, and the three responses can come back in
11
+ # any order. For that reason, we start a new thread for each request,
12
+ # and we need a mutex on the io object so that multiple responses don't
13
+ # interfere with each other.
14
+
15
+ class Connection
16
+ attr_reader :binddn, :version, :opt
17
+
18
+ def initialize(io, opt={})
19
+ @io = io
20
+ @opt = opt
21
+ @mutex = Mutex.new
22
+ @active_reqs = {} # map message ID to thread object
23
+ @binddn = nil
24
+ @version = 3
25
+ @logger = @opt[:logger] || $stderr
26
+ @ssl = false
27
+
28
+ startssl if @opt[:ssl_on_connect]
29
+ end
30
+
31
+ def log(msg)
32
+ @logger << "[#{@io.peeraddr[3]}]: #{msg}\n"
33
+ end
34
+
35
+ def startssl # :yields:
36
+ @mutex.synchronize do
37
+ raise LDAP::ResultError::OperationsError if @ssl or @active_reqs.size > 0
38
+ yield if block_given?
39
+ @io = OpenSSL::SSL::SSLSocket.new(@io, @opt[:ssl_ctx])
40
+ @io.sync_close = true
41
+ @io.accept
42
+ @ssl = true
43
+ end
44
+ end
45
+
46
+ # Read one ASN1 element from the given stream.
47
+ # Return String containing the raw element.
48
+
49
+ def ber_read(io)
50
+ blk = io.read(2) # minimum: short tag, short length
51
+ throw(:close) if blk.nil?
52
+
53
+ codepoints = blk.respond_to?(:codepoints) ? blk.codepoints.to_a : blk
54
+
55
+ tag = codepoints[0] & 0x1f
56
+ len = codepoints[1]
57
+
58
+ if tag == 0x1f # long form
59
+ tag = 0
60
+ while true
61
+ ch = io.getc
62
+ blk << ch
63
+ tag = (tag << 7) | (ch & 0x7f)
64
+ break if (ch & 0x80) == 0
65
+ end
66
+ len = io.getc
67
+ blk << len
68
+ end
69
+
70
+ if (len & 0x80) != 0 # long form
71
+ len = len & 0x7f
72
+ raise LDAP::ResultError::ProtocolError, "Indefinite length encoding not supported" if len == 0
73
+ offset = blk.length
74
+ blk << io.read(len)
75
+ # is there a more efficient way of doing this?
76
+ len = 0
77
+ blk[offset..-1].each_byte { |b| len = (len << 8) | b }
78
+ end
79
+
80
+ offset = blk.length
81
+ blk << io.read(len)
82
+ return blk
83
+ # or if we wanted to keep the partial decoding we've done:
84
+ # return blk, [blk[0] >> 6, tag], offset
85
+ end
86
+
87
+ def handle_requests
88
+ operationClass = @opt[:operation_class]
89
+ ocArgs = @opt[:operation_args] || []
90
+ catch(:close) do
91
+ while true
92
+ begin
93
+ blk = ber_read(@io)
94
+ asn1 = OpenSSL::ASN1::decode(blk)
95
+ # Debugging:
96
+ # puts "Request: #{blk.unpack("H*")}\n#{asn1.inspect}" if $debug
97
+
98
+ raise LDAP::ResultError::ProtocolError, "LDAPMessage must be SEQUENCE" unless asn1.is_a?(OpenSSL::ASN1::Sequence)
99
+ raise LDAP::ResultError::ProtocolError, "Bad Message ID" unless asn1.value[0].is_a?(OpenSSL::ASN1::Integer)
100
+ messageId = asn1.value[0].value
101
+
102
+ protocolOp = asn1.value[1]
103
+ raise LDAP::ResultError::ProtocolError, "Bad protocolOp" unless protocolOp.is_a?(OpenSSL::ASN1::ASN1Data)
104
+ raise LDAP::ResultError::ProtocolError, "Bad protocolOp tag class" unless protocolOp.tag_class == :APPLICATION
105
+
106
+ # controls are not properly implemented
107
+ c = asn1.value[2]
108
+ if c.is_a?(OpenSSL::ASN1::ASN1Data) and c.tag_class == :APPLICATION and c.tag == 0
109
+ controls = c.value
110
+ end
111
+
112
+ case protocolOp.tag
113
+ when 0 # BindRequest
114
+ abandon_all
115
+ @binddn, @version = operationClass.new(self,messageId,*ocArgs).
116
+ do_bind(protocolOp, controls)
117
+
118
+ when 2 # UnbindRequest
119
+ throw(:close)
120
+
121
+ when 3 # SearchRequest
122
+ # Note: RFC 2251 4.4.4.1 says behaviour is undefined if
123
+ # client sends an overlapping request with same message ID,
124
+ # so we don't have to worry about the case where there is
125
+ # already a thread with this id in @active_reqs.
126
+ # However, to avoid a race we copy messageId/
127
+ # protocolOp/controls into thread-local variables, because
128
+ # they will change when the next request comes in.
129
+ #
130
+ # There is a theoretical race condition here: a client could
131
+ # send an abandon request before Thread.current is assigned to
132
+ # @active_reqs[thrm]. It's not a problem, because abandon isn't
133
+ # guaranteed to work anyway. Doing it this way ensures that
134
+ # @active_reqs does not leak memory on a long-lived connection.
135
+
136
+ Thread.new(messageId,protocolOp,controls) do |thrm,thrp,thrc|
137
+ begin
138
+ @active_reqs[thrm] = Thread.current
139
+ operationClass.new(self,thrm,*ocArgs).do_search(thrp, thrc)
140
+ ensure
141
+ @active_reqs.delete(thrm)
142
+ end
143
+ end
144
+
145
+ when 6 # ModifyRequest
146
+ Thread.new(messageId,protocolOp,controls) do |thrm,thrp,thrc|
147
+ begin
148
+ @active_reqs[thrm] = Thread.current
149
+ operationClass.new(self,thrm,*ocArgs).do_modify(thrp, thrc)
150
+ ensure
151
+ @active_reqs.delete(thrm)
152
+ end
153
+ end
154
+
155
+ when 8 # AddRequest
156
+ Thread.new(messageId,protocolOp,controls) do |thrm,thrp,thrc|
157
+ begin
158
+ @active_reqs[thrm] = Thread.current
159
+ operationClass.new(self,thrm,*ocArgs).do_add(thrp, thrc)
160
+ ensure
161
+ @active_reqs.delete(thrm)
162
+ end
163
+ end
164
+
165
+ when 10 # DelRequest
166
+ Thread.new(messageId,protocolOp,controls) do |thrm,thrp,thrc|
167
+ begin
168
+ @active_reqs[thrm] = Thread.current
169
+ operationClass.new(self,thrm,*ocArgs).do_del(thrp, thrc)
170
+ ensure
171
+ @active_reqs.delete(thrm)
172
+ end
173
+ end
174
+
175
+ when 12 # ModifyDNRequest
176
+ Thread.new(messageId,protocolOp,controls) do |thrm,thrp,thrc|
177
+ begin
178
+ @active_reqs[thrm] = Thread.current
179
+ operationClass.new(self,thrm,*ocArgs).do_modifydn(thrp, thrc)
180
+ ensure
181
+ @active_reqs.delete(thrm)
182
+ end
183
+ end
184
+
185
+ when 14 # CompareRequest
186
+ Thread.new(messageId,protocolOp,controls) do |thrm,thrp,thrc|
187
+ begin
188
+ @active_reqs[thrm] = Thread.current
189
+ operationClass.new(self,thrm,*ocArgs).do_compare(thrp, thrc)
190
+ ensure
191
+ @active_reqs.delete(thrm)
192
+ end
193
+ end
194
+
195
+ when 16 # AbandonRequest
196
+ abandon(protocolOp.value)
197
+
198
+ else
199
+ raise LDAP::ResultError::ProtocolError, "Unrecognised protocolOp tag #{protocolOp.tag}"
200
+ end
201
+
202
+ rescue LDAP::ResultError::ProtocolError, OpenSSL::ASN1::ASN1Error => e
203
+ send_notice_of_disconnection(LDAP::ResultError::ProtocolError.new.to_i, e.message)
204
+ throw(:close)
205
+
206
+ # all other exceptions propagate up and are caught by tcpserver
207
+ end
208
+ end
209
+ end
210
+ abandon_all
211
+ end
212
+
213
+ def write(data)
214
+ @mutex.synchronize do
215
+ @io.write(data)
216
+ @io.flush
217
+ end
218
+ end
219
+
220
+ def writelock
221
+ @mutex.synchronize do
222
+ yield @io
223
+ @io.flush
224
+ end
225
+ end
226
+
227
+ def abandon(messageID)
228
+ @mutex.synchronize do
229
+ thread = @active_reqs.delete(messageID)
230
+ thread.raise LDAP::Abandon if thread and thread.alive?
231
+ end
232
+ end
233
+
234
+ def abandon_all
235
+ return if @active_reqs.size == 0
236
+ @mutex.synchronize do
237
+ @active_reqs.each do |id, thread|
238
+ thread.raise LDAP::Abandon if thread.alive?
239
+ end
240
+ @active_reqs = {}
241
+ end
242
+ end
243
+
244
+ def send_unsolicited_notification(resultCode, opt={})
245
+ protocolOp = [
246
+ OpenSSL::ASN1::Enumerated(resultCode),
247
+ OpenSSL::ASN1::OctetString(opt[:matchedDN] || ""),
248
+ OpenSSL::ASN1::OctetString(opt[:errorMessage] || ""),
249
+ ]
250
+ if opt[:referral]
251
+ rs = opt[:referral].collect { |r| OpenSSL::ASN1::OctetString(r) }
252
+ protocolOp << OpenSSL::ASN1::Sequence(rs, 3, :IMPLICIT, :APPLICATION)
253
+ end
254
+ if opt[:responseName]
255
+ protocolOp << OpenSSL::ASN1::OctetString(opt[:responseName], 10, :IMPLICIT, :APPLICATION)
256
+ end
257
+ if opt[:response]
258
+ protocolOp << OpenSSL::ASN1::OctetString(opt[:response], 11, :IMPLICIT, :APPLICATION)
259
+ end
260
+ message = [
261
+ OpenSSL::ASN1::Integer(0),
262
+ OpenSSL::ASN1::Sequence(protocolOp, 24, :IMPLICIT, :APPLICATION),
263
+ ]
264
+ message << opt[:controls] if opt[:controls]
265
+ write(OpenSSL::ASN1::Sequence(message).to_der)
266
+ end
267
+
268
+ def send_notice_of_disconnection(resultCode, errorMessage="")
269
+ send_unsolicited_notification(resultCode,
270
+ :errorMessage=>errorMessage,
271
+ :responseName=>"1.3.6.1.4.1.1466.20036"
272
+ )
273
+ end
274
+ end
275
+ end # class Server
276
+ end # module LDAP
@@ -0,0 +1,223 @@
1
+ require 'ldap/server/result'
2
+ require 'ldap/server/match'
3
+
4
+ module LDAP
5
+ class Server
6
+
7
+ # LDAP filters are parsed into a LISP-like internal representation:
8
+ #
9
+ # [:true]
10
+ # [:false]
11
+ # [:undef]
12
+ # [:and, ..., ..., ...]
13
+ # [:or, ..., ..., ...]
14
+ # [:not, ...]
15
+ # [:present, attr]
16
+ # [:eq, attr, MO, val]
17
+ # [:approx, attr, MO, val]
18
+ # [:substrings, attr, MO, initial=nil, {any, any...}, final=nil]
19
+ # [:ge, attr, MO, val]
20
+ # [:le, attr, MO, val]
21
+ #
22
+ # This is done rather than a more object-oriented approach, in the
23
+ # hope that it will make it easier to match certain filter structures
24
+ # when converting them into something else. e.g. certain LDAP filter
25
+ # constructs can be mapped to some fixed SQL queries.
26
+ #
27
+ # See RFC 2251 4.5.1 for the three-state(!) boolean logic from LDAP
28
+ #
29
+ # If no schema is provided: 'attr' is the raw attribute name as provided
30
+ # by the client. If a schema is provided: attr is converted to its
31
+ # normalized name as listed in the schema, e.g. 'commonname' becomes 'cn',
32
+ # 'objectclass' becomes 'objectClass' etc.
33
+ # If a schema is provided, MO is a matching object which can be used to
34
+ # perform the match. If no schema is provided, this is 'nil'. In that
35
+ # case you could use LDAP::Server::MatchingRule::DefaultMatch.
36
+
37
+ class Filter
38
+
39
+ # Parse a filter in OpenSSL::ASN1 format into our own format.
40
+ #
41
+ # There are some trivial optimisations we make: e.g.
42
+ # (&(objectClass=*)(cn=foo)) -> (&(cn=foo)) -> (cn=foo)
43
+
44
+ def self.parse(asn1, schema=nil)
45
+ case asn1.tag
46
+ when 0 # and
47
+ conds = asn1.value.collect { |a| parse(a) }
48
+ conds.delete([:true])
49
+ return [:true] if conds.size == 0
50
+ return conds.first if conds.size == 1
51
+ return [:false] if conds.include?([:false])
52
+ return conds.unshift(:and)
53
+
54
+ when 1 # or
55
+ conds = asn1.value.collect { |a| parse(a) }
56
+ conds.delete([:false])
57
+ return [:false] if conds.size == 0
58
+ return conds.first if conds.size == 1
59
+ return [:true] if conds.include?([:true])
60
+ return conds.unshift(:or)
61
+
62
+ when 2 # not
63
+ cond = parse(asn1.value[0])
64
+ case cond
65
+ when [:false]; return [:true]
66
+ when [:true]; return [:false]
67
+ when [:undef]; return [:undef]
68
+ end
69
+ return [:not, cond]
70
+
71
+ when 3 # equalityMatch
72
+ attr = asn1.value[0].value
73
+ val = asn1.value[1].value
74
+ return [:true] if attr =~ /\AobjectClass\z/i and val =~ /\Atop\z/i
75
+ if schema
76
+ a = schema.find_attrtype(attr)
77
+ return [:undef] unless a.equality
78
+ return [:eq, a.to_s, a.equality, val]
79
+ end
80
+ return [:eq, attr, nil, val]
81
+
82
+ when 4 # substrings
83
+ attr = asn1.value[0].value
84
+ if schema
85
+ a = schema.find_attrtype(attr)
86
+ return [:undef] unless a.substr
87
+ res = [:substrings, a.to_s, a.substr, nil]
88
+ else
89
+ res = [:substrings, attr, nil, nil]
90
+ end
91
+ final_val = nil
92
+
93
+ asn1.value[1].value.each do |ss|
94
+ case ss.tag
95
+ when 0
96
+ res[3] = ss.value
97
+ when 1
98
+ res << ss.value
99
+ when 2
100
+ final_val = ss.value
101
+ else
102
+ raise LDAP::ResultError::ProtocolError,
103
+ "Unrecognised substring tag #{ss.tag.inspect}"
104
+ end
105
+ end
106
+ res << final_val
107
+ return res
108
+
109
+ when 5 # greaterOrEqual
110
+ attr = asn1.value[0].value
111
+ val = asn1.value[1].value
112
+ if schema
113
+ a = schema.find_attrtype(attr)
114
+ return [:undef] unless a.ordering
115
+ return [:ge, a.to_s, a.ordering, val]
116
+ end
117
+ return [:ge, attr, nil, val]
118
+
119
+ when 6 # lessOrEqual
120
+ attr = asn1.value[0].value
121
+ val = asn1.value[1].value
122
+ if schema
123
+ a = schema.find_attrtype(attr)
124
+ return [:undef] unless a.ordering
125
+ return [:le, a.to_s, a.ordering, val]
126
+ end
127
+ return [:le, attr, nil, val]
128
+
129
+ when 7 # present
130
+ attr = asn1.value
131
+ return [:true] if attr =~ /\AobjectClass\z/i
132
+ if schema
133
+ begin
134
+ a = schema.find_attrtype(attr)
135
+ return [:present, a.to_s]
136
+ rescue LDAP::ResultError::UndefinedAttributeType
137
+ return [:false]
138
+ end
139
+ end
140
+ return [:present, attr]
141
+
142
+ when 8 # approxMatch
143
+ attr = asn1.value[0].value
144
+ val = asn1.value[1].value
145
+ if schema
146
+ a = schema.find_attrtype(attr)
147
+ # I don't know how properly to deal with approxMatch. I'm assuming
148
+ # that the object will have an equality MatchingRule, and we
149
+ # can defer to that.
150
+ return [:undef] unless a.equality
151
+ return [:approx, a.to_s, a.equality, val]
152
+ end
153
+ return [:approx, attr, nil, val]
154
+
155
+ #when 9 # extensibleMatch
156
+ # FIXME
157
+
158
+ else
159
+ raise LDAP::ResultError::ProtocolError,
160
+ "Unrecognised Filter tag #{asn1.tag}"
161
+ end
162
+
163
+ # Unknown attribute type
164
+ rescue LDAP::ResultError::UndefinedAttributeType
165
+ return [:undef]
166
+ end
167
+
168
+ # Run a parsed filter against an attr=>[val] hash.
169
+ #
170
+ # Returns true, false or nil.
171
+
172
+ def self.run(filter, av)
173
+ case filter[0]
174
+ when :and
175
+ res = true
176
+ filter[1..-1].each do |elem|
177
+ r = run(elem, av)
178
+ return false if r == false
179
+ res = nil if r.nil?
180
+ end
181
+ return res
182
+
183
+ when :or
184
+ res = false
185
+ filter[1..-1].each do |elem|
186
+ r = run(elem, av)
187
+ return true if r == true
188
+ res = nil if r.nil?
189
+ end
190
+ return res
191
+
192
+ when :not
193
+ case run(filter[1], av)
194
+ when true; return false
195
+ when false; return true
196
+ else return nil
197
+ end
198
+
199
+ when :present
200
+ return av.has_key?(filter[1])
201
+
202
+ when :eq, :approx, :le, :ge, :substrings
203
+ # the filter now includes a suitable matching object
204
+ return (filter[2] || LDAP::Server::MatchingRule::DefaultMatch).send(
205
+ filter.first, av[filter[1].to_s], *filter[3..-1])
206
+
207
+ when :true
208
+ return true
209
+
210
+ when :false
211
+ return false
212
+
213
+ when :undef
214
+ return nil
215
+ end
216
+
217
+ raise LDAP::ResultError::OperationsError,
218
+ "Unimplemented filter #{filter.first.inspect}"
219
+ end
220
+
221
+ end # class Filter
222
+ end # class Server
223
+ end # module LDAP