ruby-ldapserver 0.5.3 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- checksums.yaml.gz.sig +3 -0
- data/.github/workflows/ci.yml +43 -0
- data/.gitignore +1 -0
- data/{ChangeLog → CHANGELOG.md} +28 -13
- data/README.md +141 -0
- data/examples/rbslapd1.rb +5 -4
- data/examples/rbslapd2.rb +1 -1
- data/examples/rbslapd3.rb +4 -4
- data/examples/rbslapd4.rb +90 -0
- data/examples/rbslapd5.rb +73 -0
- data/examples/rbslapd6.rb +75 -0
- data/lib/ldap/server/connection.rb +16 -10
- data/lib/ldap/server/dn.rb +220 -0
- data/lib/ldap/server/filter.rb +1 -1
- data/lib/ldap/server/operation.rb +47 -9
- data/lib/ldap/server/request.rb +166 -0
- data/lib/ldap/server/result.rb +1 -1
- data/lib/ldap/server/router.rb +220 -0
- data/lib/ldap/server/server.rb +25 -10
- data/lib/ldap/server/syntax.rb +1 -1
- data/lib/ldap/server/tcpserver.rb +16 -3
- data/lib/ldap/server/trie.rb +92 -0
- data/lib/ldap/server/version.rb +1 -1
- data/ruby-ldapserver.gemspec +9 -9
- data/test/dn_test.rb +149 -0
- data/test/encoding_test.rb +142 -179
- data/test/trie_test.rb +60 -0
- data.tar.gz.sig +2 -0
- metadata +84 -22
- metadata.gz.sig +0 -0
- data/README +0 -222
@@ -31,11 +31,11 @@ class Server
|
|
31
31
|
def log(msg, severity = Logger::INFO)
|
32
32
|
@logger.add(severity, msg, @io.peeraddr[3])
|
33
33
|
end
|
34
|
-
|
34
|
+
|
35
35
|
def debug msg
|
36
36
|
log msg, Logger::DEBUG
|
37
37
|
end
|
38
|
-
|
38
|
+
|
39
39
|
def log_exception(e)
|
40
40
|
log "#{e}: #{e.backtrace.join("\n\tfrom ")}", Logger::ERROR
|
41
41
|
end
|
@@ -93,8 +93,6 @@ class Server
|
|
93
93
|
end
|
94
94
|
|
95
95
|
def handle_requests
|
96
|
-
operationClass = @opt[:operation_class]
|
97
|
-
ocArgs = @opt[:operation_args] || []
|
98
96
|
catch(:close) do
|
99
97
|
while true
|
100
98
|
begin
|
@@ -120,9 +118,14 @@ class Server
|
|
120
118
|
case protocolOp.tag
|
121
119
|
when 0 # BindRequest
|
122
120
|
abandon_all
|
123
|
-
|
124
|
-
|
125
|
-
|
121
|
+
if @opt[:router]
|
122
|
+
@binddn, @version = @opt[:router].do_bind(self, messageId, protocolOp, controls)
|
123
|
+
else
|
124
|
+
operationClass = @opt[:operation_class]
|
125
|
+
ocArgs = @opt[:operation_args] || []
|
126
|
+
@binddn, @version = operationClass.new(self,messageId,*ocArgs).
|
127
|
+
do_bind(protocolOp, controls)
|
128
|
+
end
|
126
129
|
when 2 # UnbindRequest
|
127
130
|
throw(:close)
|
128
131
|
|
@@ -171,14 +174,17 @@ class Server
|
|
171
174
|
# client sends an overlapping request with same message ID,
|
172
175
|
# so we don't have to worry about the case where there is
|
173
176
|
# already a thread with this messageId in @threadgroup.
|
174
|
-
|
175
177
|
def start_op(messageId,protocolOp,controls,meth)
|
176
178
|
operationClass = @opt[:operation_class]
|
177
179
|
ocArgs = @opt[:operation_args] || []
|
178
180
|
thr = Thread.new do
|
179
181
|
begin
|
180
|
-
|
181
|
-
|
182
|
+
if @opt[:router]
|
183
|
+
@opt[:router].send meth, self, messageId, protocolOp, controls
|
184
|
+
else
|
185
|
+
operationClass.new(self,messageId,*ocArgs).
|
186
|
+
send(meth,protocolOp,controls)
|
187
|
+
end
|
182
188
|
rescue Exception => e
|
183
189
|
log_exception e
|
184
190
|
end
|
@@ -0,0 +1,220 @@
|
|
1
|
+
require 'ldap/server/util'
|
2
|
+
|
3
|
+
module LDAP
|
4
|
+
class Server
|
5
|
+
|
6
|
+
class DN
|
7
|
+
include Enumerable
|
8
|
+
|
9
|
+
attr_reader :dname
|
10
|
+
|
11
|
+
# Combines a set of elements to a syntactically correct DN
|
12
|
+
# elements is [elements, ...] where elements
|
13
|
+
# can be either { attr => val } or [attr, val]
|
14
|
+
def self.join(elements)
|
15
|
+
LDAP::Server::Operation.join_dn(elements)
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(dn)
|
19
|
+
@dname = LDAP::Server::Operation.split_dn(dn)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns the value of the first occurrence of attr (bottom-up)
|
23
|
+
def find_first(attr)
|
24
|
+
@dname.each do |pair|
|
25
|
+
return pair[attr.to_s] if pair[attr.to_s]
|
26
|
+
end
|
27
|
+
nil
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns the value of the last occurrence of attr (bottom-up)
|
31
|
+
def find_last(attr)
|
32
|
+
@dname.reverse_each do |pair|
|
33
|
+
return pair[attr.to_s] if pair[attr.to_s]
|
34
|
+
end
|
35
|
+
nil
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns all values of all occurrences of attr (bottom-up)
|
39
|
+
def find(attr)
|
40
|
+
result = []
|
41
|
+
@dname.each do |pair|
|
42
|
+
result << pair[attr.to_s] if pair[attr.to_s]
|
43
|
+
end
|
44
|
+
result
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns the value of the n-th occurrence of attr (top-down, 0 is first element)
|
48
|
+
def find_nth(attr, n)
|
49
|
+
i = 0
|
50
|
+
@dname.each do |pair|
|
51
|
+
if pair[attr.to_s]
|
52
|
+
return pair[attr.to_s] if i == n
|
53
|
+
i += 1
|
54
|
+
end
|
55
|
+
end
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
|
59
|
+
# Whether or not the DN starts with dn (bottom-up)
|
60
|
+
# dn is a string
|
61
|
+
def start_with?(dn)
|
62
|
+
needle = LDAP::Server::Operation.split_dn(dn)
|
63
|
+
|
64
|
+
# Needle is longer than haystack
|
65
|
+
return false if needle.length > @dname.length
|
66
|
+
|
67
|
+
needle_index = 0
|
68
|
+
haystack_index = 0
|
69
|
+
|
70
|
+
while needle_index < needle.length
|
71
|
+
return false if @dname[haystack_index] != needle[needle_index]
|
72
|
+
needle_index += 1
|
73
|
+
haystack_index += 1
|
74
|
+
end
|
75
|
+
true
|
76
|
+
end
|
77
|
+
|
78
|
+
# Whether or not the DN starts with a format (bottom-up) (values are ignored)
|
79
|
+
# dn is a string
|
80
|
+
def start_with_format?(dn)
|
81
|
+
needle = LDAP::Server::Operation.split_dn(dn)
|
82
|
+
|
83
|
+
# Needle is longer than haystack
|
84
|
+
return false if needle.length > @dname.length
|
85
|
+
|
86
|
+
needle_index = 0
|
87
|
+
haystack_index = 0
|
88
|
+
|
89
|
+
while needle_index < needle.length
|
90
|
+
return false if @dname[haystack_index].keys != needle[needle_index].keys
|
91
|
+
needle_index += 1
|
92
|
+
haystack_index += 1
|
93
|
+
end
|
94
|
+
true
|
95
|
+
end
|
96
|
+
|
97
|
+
# Whether or not the DN ends with dn (top-down)
|
98
|
+
# dn is a string
|
99
|
+
def end_with?(dn)
|
100
|
+
needle = LDAP::Server::Operation.split_dn(dn)
|
101
|
+
|
102
|
+
# Needle is longer than haystack
|
103
|
+
return false if needle.length > @dname.length
|
104
|
+
|
105
|
+
needle_index = needle.length - 1
|
106
|
+
haystack_index = @dname.length - 1
|
107
|
+
|
108
|
+
while needle_index >= 0
|
109
|
+
return false if @dname[haystack_index] != needle[needle_index]
|
110
|
+
needle_index -= 1
|
111
|
+
haystack_index -= 1
|
112
|
+
end
|
113
|
+
true
|
114
|
+
end
|
115
|
+
|
116
|
+
# Whether or not the DN ends with format (top-down) (values are ignored)
|
117
|
+
# dn is a string
|
118
|
+
def end_with_format?(dn)
|
119
|
+
needle = LDAP::Server::Operation.split_dn(dn)
|
120
|
+
|
121
|
+
# Needle is longer than haystack
|
122
|
+
return false if needle.length > @dname.length
|
123
|
+
|
124
|
+
needle_index = needle.length - 1
|
125
|
+
haystack_index = @dname.length - 1
|
126
|
+
|
127
|
+
while needle_index >= 0
|
128
|
+
return false if @dname[haystack_index].keys != needle[needle_index].keys
|
129
|
+
needle_index -= 1
|
130
|
+
haystack_index -= 1
|
131
|
+
end
|
132
|
+
true
|
133
|
+
end
|
134
|
+
|
135
|
+
# Whether or not the DN equals dn (values are case sensitive)
|
136
|
+
# dn is a string
|
137
|
+
def equal?(dn)
|
138
|
+
split_dn = LDAP::Server::Operation.split_dn(dn)
|
139
|
+
|
140
|
+
return false if split_dn.length != @dname.length
|
141
|
+
|
142
|
+
@dname.each_with_index do |pair, index|
|
143
|
+
return false if pair != split_dn[index]
|
144
|
+
end
|
145
|
+
true
|
146
|
+
end
|
147
|
+
|
148
|
+
# Whether or not the DN equals dn's format (values are ignored) (case insensitive)
|
149
|
+
# dn is a string
|
150
|
+
def equal_format?(dn)
|
151
|
+
split_dn = LDAP::Server::Operation.split_dn(dn)
|
152
|
+
|
153
|
+
return false if split_dn.length != @dname.length
|
154
|
+
|
155
|
+
@dname.each_with_index do |pair, index|
|
156
|
+
return false if pair.keys != split_dn[index].keys
|
157
|
+
end
|
158
|
+
true
|
159
|
+
end
|
160
|
+
|
161
|
+
# Whether or not the DN constains a substring equal to dn (values are case sensitive)
|
162
|
+
# dn is a string
|
163
|
+
def include?(dn)
|
164
|
+
split_dn = LDAP::Server::Operation.split_dn(dn)
|
165
|
+
return false if split_dn.length > @dname.length
|
166
|
+
LDAP::Server::Operation.join_dn(@dname).include?(LDAP::Server::Operation.join_dn(split_dn))
|
167
|
+
end
|
168
|
+
|
169
|
+
# Whether or not the DN constains a substring format equal to dn (values are ignored) (case insensitive)
|
170
|
+
# dn is a string
|
171
|
+
def include_format?(dn)
|
172
|
+
split_dn = LDAP::Server::Operation.split_dn(dn)
|
173
|
+
|
174
|
+
return false if split_dn.length > @dname.length
|
175
|
+
|
176
|
+
haystack = []
|
177
|
+
@dname.each { |pair| haystack << pair.keys }
|
178
|
+
|
179
|
+
needle = []
|
180
|
+
split_dn.each { |pair| needle << pair.keys }
|
181
|
+
|
182
|
+
haystack.join.include?(needle.join)
|
183
|
+
end
|
184
|
+
|
185
|
+
# Generates a mapping for variables
|
186
|
+
# For example:
|
187
|
+
# > dn = LDAP::Server.DN.new("uid=user,ou=Users,dc=mydomain,dc=com")
|
188
|
+
# > dn.parse("uid=:uid, ou=:category, dc=mydomain, dc=com")
|
189
|
+
# => { :uid => "user", :category => "Users" }
|
190
|
+
def parse(template_dn)
|
191
|
+
result = {}
|
192
|
+
template = LDAP::Server::Operation.split_dn(template_dn)
|
193
|
+
template.reverse.zip(@dname.reverse).each do |temp, const|
|
194
|
+
break if const and temp.keys.first != const.keys.first
|
195
|
+
if temp.values.first.start_with?(':')
|
196
|
+
sym = temp.values.first[1..-1].to_sym
|
197
|
+
if const
|
198
|
+
result[sym] = const.values.first unless result[sym]
|
199
|
+
else
|
200
|
+
result[sym] = nil
|
201
|
+
end
|
202
|
+
elsif temp.values.first != const.values.first
|
203
|
+
break
|
204
|
+
end
|
205
|
+
end
|
206
|
+
result
|
207
|
+
end
|
208
|
+
|
209
|
+
def each(&block)
|
210
|
+
@dname.each(&block)
|
211
|
+
end
|
212
|
+
|
213
|
+
def reverse_each(&block)
|
214
|
+
@dname.reverse_each(&block)
|
215
|
+
end
|
216
|
+
|
217
|
+
end
|
218
|
+
|
219
|
+
end
|
220
|
+
end
|
data/lib/ldap/server/filter.rb
CHANGED
@@ -202,7 +202,7 @@ class Server
|
|
202
202
|
when :eq, :approx, :le, :ge, :substrings
|
203
203
|
# the filter now includes a suitable matching object
|
204
204
|
return (filter[2] || LDAP::Server::MatchingRule::DefaultMatch).send(
|
205
|
-
filter.first, av[filter[1].to_s], *filter[3..-1])
|
205
|
+
filter.first, Array(av[filter[1].to_s]), *filter[3..-1])
|
206
206
|
|
207
207
|
when :true
|
208
208
|
return true
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'timeout'
|
2
|
+
require 'openssl'
|
2
3
|
require 'ldap/server/result'
|
3
4
|
require 'ldap/server/filter'
|
4
5
|
|
@@ -36,6 +37,7 @@ class Server
|
|
36
37
|
])
|
37
38
|
@schema = @connection.opt[:schema]
|
38
39
|
@server = @connection.opt[:server]
|
40
|
+
@attribute_range_limit = @connection.opt[:attribute_range_limit]
|
39
41
|
end
|
40
42
|
|
41
43
|
def log msg, severity = Logger::INFO
|
@@ -45,9 +47,9 @@ class Server
|
|
45
47
|
def debug msg
|
46
48
|
@connection.debug msg
|
47
49
|
end
|
48
|
-
|
50
|
+
|
49
51
|
# Send an exception report to the log
|
50
|
-
|
52
|
+
|
51
53
|
def log_exception msg
|
52
54
|
@connection.log_exception msg
|
53
55
|
end
|
@@ -84,7 +86,7 @@ class Server
|
|
84
86
|
seq << OpenSSL::ASN1::Sequence(rs, 3, :IMPLICIT, :APPLICATION)
|
85
87
|
end
|
86
88
|
yield seq if block_given? # opportunity to add more elements
|
87
|
-
|
89
|
+
|
88
90
|
send_LDAPMessage(OpenSSL::ASN1::Sequence(seq, tag, :IMPLICIT, :APPLICATION), opt)
|
89
91
|
end
|
90
92
|
|
@@ -96,6 +98,9 @@ class Server
|
|
96
98
|
end
|
97
99
|
end
|
98
100
|
|
101
|
+
|
102
|
+
AttributeRange = Struct.new :start, :end
|
103
|
+
|
99
104
|
# Send a found entry. Avs are {attr1=>val1, attr2=>[val2,val3]}
|
100
105
|
# If schema given, return operational attributes only if
|
101
106
|
# explicitly requested
|
@@ -114,23 +119,43 @@ class Server
|
|
114
119
|
sendall = @attributes == [] || @attributes.include?("*")
|
115
120
|
avseq = []
|
116
121
|
|
117
|
-
avs.
|
118
|
-
|
122
|
+
avs.each_with_index do |(attr, vals), aidx|
|
123
|
+
query_attr_idx = @attributes.index(attr)
|
124
|
+
if !query_attr_idx
|
119
125
|
next unless sendall
|
120
126
|
if @schema
|
121
127
|
a = @schema.find_attrtype(attr)
|
122
128
|
next unless a and (a.usage.nil? or a.usage == :userApplications)
|
123
129
|
end
|
124
130
|
end
|
131
|
+
query_attr = query_attr_idx && @attribute_ranges[query_attr_idx]
|
125
132
|
|
126
133
|
if @typesOnly
|
127
|
-
vals = []
|
134
|
+
vals = []
|
128
135
|
else
|
129
136
|
vals = [vals] unless vals.kind_of?(Array)
|
130
137
|
# FIXME: optionally do a value_to_s conversion here?
|
131
138
|
# FIXME: handle attribute;binary
|
132
139
|
end
|
133
140
|
|
141
|
+
if (@attribute_range_limit && vals.size > @attribute_range_limit) || query_attr&.start
|
142
|
+
if query_attr&.start
|
143
|
+
range_start = query_attr.start.to_i
|
144
|
+
range_end = query_attr.end == "*" ? -1 : query_attr.end.to_i
|
145
|
+
else
|
146
|
+
range_start = 0
|
147
|
+
range_end = @attribute_range_limit ? @attribute_range_limit - 1 : -1
|
148
|
+
end
|
149
|
+
range_end = range_start + @attribute_range_limit - 1 if @attribute_range_limit && (vals.size - range_start > @attribute_range_limit)
|
150
|
+
range_end = -1 if vals.size <= range_end
|
151
|
+
rvals = vals[range_start .. range_end]
|
152
|
+
vals = []
|
153
|
+
avseq << OpenSSL::ASN1::Sequence([
|
154
|
+
OpenSSL::ASN1::OctetString("#{attr};range=#{range_start}-#{range_end == -1 ? "*" : range_end}"),
|
155
|
+
OpenSSL::ASN1::Set(rvals.collect { |v| OpenSSL::ASN1::OctetString(v.to_s) })
|
156
|
+
])
|
157
|
+
end
|
158
|
+
|
134
159
|
avseq << OpenSSL::ASN1::Sequence([
|
135
160
|
OpenSSL::ASN1::OctetString(attr),
|
136
161
|
OpenSSL::ASN1::Set(vals.collect { |v| OpenSSL::ASN1::OctetString(v.to_s) })
|
@@ -200,8 +225,8 @@ class Server
|
|
200
225
|
when 0
|
201
226
|
simple_bind(version, dn, authentication.value)
|
202
227
|
when 3
|
203
|
-
mechanism = authentication.value[0].value
|
204
|
-
credentials = authentication.value[1].value
|
228
|
+
# mechanism = authentication.value[0].value
|
229
|
+
# credentials = authentication.value[1].value
|
205
230
|
# sasl_bind(version, dn, mechanism, credentials)
|
206
231
|
# FIXME: needs to exchange further BindRequests
|
207
232
|
raise LDAP::ResultError::AuthMethodNotSupported
|
@@ -246,7 +271,20 @@ class Server
|
|
246
271
|
client_timelimit = protocolOp.value[4].value.to_i
|
247
272
|
@typesOnly = protocolOp.value[5].value
|
248
273
|
filter = Filter::parse(protocolOp.value[6], @schema)
|
249
|
-
|
274
|
+
attributes = protocolOp.value[7].value.collect {|x| x.value}
|
275
|
+
attributes = attributes.map do |attr|
|
276
|
+
if attr =~ /(.*);range=(\d+)-(\d+|\*)\z/
|
277
|
+
[$1, $2, $3]
|
278
|
+
else
|
279
|
+
attr
|
280
|
+
end
|
281
|
+
end
|
282
|
+
@attributes = attributes.map do |name, |
|
283
|
+
name
|
284
|
+
end
|
285
|
+
@attribute_ranges = attributes.map do |_, range_start, range_end|
|
286
|
+
range_start && AttributeRange.new(range_start, range_end)
|
287
|
+
end
|
250
288
|
|
251
289
|
@rescount = 0
|
252
290
|
@sizelimit = server_sizelimit
|
@@ -0,0 +1,166 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
module LDAP
|
4
|
+
class Server
|
5
|
+
|
6
|
+
class Request
|
7
|
+
attr_accessor :connection, :typesOnly, :attributes, :rescount, :sizelimit
|
8
|
+
|
9
|
+
# Object to handle a single LDAP request. This object is created on
|
10
|
+
# every request by the router, and is passed as argument to the defined
|
11
|
+
# routes.
|
12
|
+
|
13
|
+
def initialize(connection, messageId)
|
14
|
+
@connection = connection
|
15
|
+
@respEnvelope = OpenSSL::ASN1::Sequence([
|
16
|
+
OpenSSL::ASN1::Integer(messageId),
|
17
|
+
# protocolOp,
|
18
|
+
# controls [0] OPTIONAL,
|
19
|
+
])
|
20
|
+
@schema = @connection.opt[:schema]
|
21
|
+
@server = @connection.opt[:server]
|
22
|
+
@rescount = 0
|
23
|
+
end
|
24
|
+
|
25
|
+
##################################################
|
26
|
+
### Utility methods to send protocol responses ###
|
27
|
+
##################################################
|
28
|
+
|
29
|
+
def send_LDAPMessage(protocolOp, opt={}) # :nodoc:
|
30
|
+
@respEnvelope.value[1] = protocolOp
|
31
|
+
if opt[:controls]
|
32
|
+
@respEnvelope.value[2] = OpenSSL::ASN1::Set(opt[:controls], 0, :IMPLICIT, APPLICATION)
|
33
|
+
else
|
34
|
+
@respEnvelope.value.delete_at(2)
|
35
|
+
end
|
36
|
+
|
37
|
+
@connection.write(@respEnvelope.to_der)
|
38
|
+
end
|
39
|
+
|
40
|
+
def send_LDAPResult(tag, resultCode, opt={}) # :nodoc:
|
41
|
+
seq = [
|
42
|
+
OpenSSL::ASN1::Enumerated(resultCode),
|
43
|
+
OpenSSL::ASN1::OctetString(opt[:matchedDN] || ""),
|
44
|
+
OpenSSL::ASN1::OctetString(opt[:errorMessage] || ""),
|
45
|
+
]
|
46
|
+
if opt[:referral]
|
47
|
+
rs = opt[:referral].collect { |r| OpenSSL::ASN1::OctetString(r) }
|
48
|
+
seq << OpenSSL::ASN1::Sequence(rs, 3, :IMPLICIT, :APPLICATION)
|
49
|
+
end
|
50
|
+
yield seq if block_given? # opportunity to add more elements
|
51
|
+
|
52
|
+
send_LDAPMessage(OpenSSL::ASN1::Sequence(seq, tag, :IMPLICIT, :APPLICATION), opt)
|
53
|
+
end
|
54
|
+
|
55
|
+
def send_BindResponse(resultCode, opt={})
|
56
|
+
send_LDAPResult(1, resultCode, opt) do |resp|
|
57
|
+
if opt[:serverSaslCreds]
|
58
|
+
resp << OpenSSL::ASN1::OctetString(opt[:serverSaslCreds], 7, :IMPLICIT, :APPLICATION)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Send a found entry. Avs are {attr1=>val1, attr2=>[val2,val3]}
|
64
|
+
# If schema given, return operational attributes only if
|
65
|
+
# explicitly requested
|
66
|
+
|
67
|
+
def send_SearchResultEntry(dn, avs, opt={})
|
68
|
+
@rescount += 1
|
69
|
+
if @sizelimit
|
70
|
+
raise LDAP::ResultError::SizeLimitExceeded if @rescount > @sizelimit
|
71
|
+
end
|
72
|
+
|
73
|
+
if @schema
|
74
|
+
# normalize the attribute names
|
75
|
+
@attributes = @attributes.map { |a| a == '*' ? a : @schema.find_attrtype(a).to_s }
|
76
|
+
end
|
77
|
+
|
78
|
+
sendall = @attributes == [] || @attributes.include?("*")
|
79
|
+
avseq = []
|
80
|
+
|
81
|
+
avs.each do |attr, vals|
|
82
|
+
if !@attributes.include?(attr)
|
83
|
+
next unless sendall
|
84
|
+
if @schema
|
85
|
+
a = @schema.find_attrtype(attr)
|
86
|
+
next unless a and (a.usage.nil? or a.usage == :userApplications)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
if @typesOnly
|
91
|
+
vals = []
|
92
|
+
else
|
93
|
+
vals = [vals] unless vals.kind_of?(Array)
|
94
|
+
# FIXME: optionally do a value_to_s conversion here?
|
95
|
+
# FIXME: handle attribute;binary
|
96
|
+
end
|
97
|
+
|
98
|
+
avseq << OpenSSL::ASN1::Sequence([
|
99
|
+
OpenSSL::ASN1::OctetString(attr),
|
100
|
+
OpenSSL::ASN1::Set(vals.collect { |v| OpenSSL::ASN1::OctetString(v.to_s) })
|
101
|
+
])
|
102
|
+
end
|
103
|
+
|
104
|
+
send_LDAPMessage(OpenSSL::ASN1::Sequence([
|
105
|
+
OpenSSL::ASN1::OctetString(dn),
|
106
|
+
OpenSSL::ASN1::Sequence(avseq),
|
107
|
+
], 4, :IMPLICIT, :APPLICATION), opt)
|
108
|
+
end
|
109
|
+
|
110
|
+
def send_SearchResultDone(resultCode, opt={})
|
111
|
+
send_LDAPResult(5, resultCode, opt)
|
112
|
+
end
|
113
|
+
|
114
|
+
def send_ModifyResponse(resultCode, opt={})
|
115
|
+
send_LDAPResult(7, resultCode, opt)
|
116
|
+
end
|
117
|
+
|
118
|
+
def send_AddResponse(resultCode, opt={})
|
119
|
+
send_LDAPResult(9, resultCode, opt)
|
120
|
+
end
|
121
|
+
|
122
|
+
def send_DelResponse(resultCode, opt={})
|
123
|
+
send_LDAPResult(11, resultCode, opt)
|
124
|
+
end
|
125
|
+
|
126
|
+
def send_ModifyDNResponse(resultCode, opt={})
|
127
|
+
send_LDAPResult(13, resultCode, opt)
|
128
|
+
end
|
129
|
+
|
130
|
+
def send_CompareResponse(resultCode, opt={})
|
131
|
+
send_LDAPResult(15, resultCode, opt)
|
132
|
+
end
|
133
|
+
|
134
|
+
def send_ExtendedResponse(resultCode, opt={})
|
135
|
+
send_LDAPResult(24, resultCode, opt) do |resp|
|
136
|
+
if opt[:responseName]
|
137
|
+
resp << OpenSSL::ASN1::OctetString(opt[:responseName], 10, :IMPLICIT, :APPLICATION)
|
138
|
+
end
|
139
|
+
if opt[:response]
|
140
|
+
resp << OpenSSL::ASN1::OctetString(opt[:response], 11, :IMPLICIT, :APPLICATION)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
############################################################
|
146
|
+
### Methods to get parameters related to this connection ###
|
147
|
+
############################################################
|
148
|
+
|
149
|
+
# Server-set maximum time limit. Override for more complex behaviour
|
150
|
+
# (e.g. limit depends on @connection.binddn). Nil uses hardcoded default.
|
151
|
+
|
152
|
+
def server_timelimit
|
153
|
+
@connection.opt[:timelimit]
|
154
|
+
end
|
155
|
+
|
156
|
+
# Server-set maximum size limit. Override for more complex behaviour
|
157
|
+
# (e.g. limit depends on @connection.binddn). Return nil for unlimited.
|
158
|
+
|
159
|
+
def server_sizelimit
|
160
|
+
@connection.opt[:sizelimit]
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
end
|