pruby-net-ldap 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +5 -0
- data/COPYING +272 -0
- data/ChangeLog +58 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +16 -0
- data/LICENCE +55 -0
- data/LICENSE.txt +20 -0
- data/README +32 -0
- data/README.rdoc +62 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/lib/net/ber.rb +294 -0
- data/lib/net/ldap/dataset.rb +108 -0
- data/lib/net/ldap/entry.rb +165 -0
- data/lib/net/ldap/filter.rb +387 -0
- data/lib/net/ldap/pdu.rb +205 -0
- data/lib/net/ldap/psw.rb +64 -0
- data/lib/net/ldap.rb +1311 -0
- data/lib/net/ldif.rb +39 -0
- data/lib/pruby-net-ldap.rb +1 -0
- data/pre-setup.rb +46 -0
- data/pruby-net-ldap.gemspec +74 -0
- data/setup.rb +1366 -0
- data/tests/testber.rb +42 -0
- data/tests/testdata.ldif +101 -0
- data/tests/testem.rb +12 -0
- data/tests/testfilter.rb +37 -0
- data/tests/testldap.rb +190 -0
- data/tests/testldif.rb +69 -0
- data/tests/testpsw.rb +28 -0
- metadata +112 -0
data/lib/net/ber.rb
ADDED
@@ -0,0 +1,294 @@
|
|
1
|
+
# $Id: ber.rb 142 2006-07-26 12:20:33Z blackhedd $
|
2
|
+
#
|
3
|
+
# NET::BER
|
4
|
+
# Mixes ASN.1/BER convenience methods into several standard classes.
|
5
|
+
# Also provides BER parsing functionality.
|
6
|
+
#
|
7
|
+
#----------------------------------------------------------------------------
|
8
|
+
#
|
9
|
+
# Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
|
10
|
+
#
|
11
|
+
# Gmail: garbagecat10
|
12
|
+
#
|
13
|
+
# This program is free software; you can redistribute it and/or modify
|
14
|
+
# it under the terms of the GNU General Public License as published by
|
15
|
+
# the Free Software Foundation; either version 2 of the License, or
|
16
|
+
# (at your option) any later version.
|
17
|
+
#
|
18
|
+
# This program is distributed in the hope that it will be useful,
|
19
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
20
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
21
|
+
# GNU General Public License for more details.
|
22
|
+
#
|
23
|
+
# You should have received a copy of the GNU General Public License
|
24
|
+
# along with this program; if not, write to the Free Software
|
25
|
+
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
26
|
+
#
|
27
|
+
#---------------------------------------------------------------------------
|
28
|
+
#
|
29
|
+
#
|
30
|
+
|
31
|
+
|
32
|
+
|
33
|
+
|
34
|
+
module Net
|
35
|
+
|
36
|
+
module BER
|
37
|
+
|
38
|
+
class BerError < Exception; end
|
39
|
+
|
40
|
+
|
41
|
+
# This module is for mixing into IO and IO-like objects.
|
42
|
+
module BERParser
|
43
|
+
|
44
|
+
# The order of these follows the class-codes in BER.
|
45
|
+
# Maybe this should have been a hash.
|
46
|
+
TagClasses = [:universal, :application, :context_specific, :private]
|
47
|
+
|
48
|
+
BuiltinSyntax = {
|
49
|
+
:universal => {
|
50
|
+
:primitive => {
|
51
|
+
1 => :boolean,
|
52
|
+
2 => :integer,
|
53
|
+
4 => :string,
|
54
|
+
10 => :integer,
|
55
|
+
},
|
56
|
+
:constructed => {
|
57
|
+
16 => :array,
|
58
|
+
17 => :array
|
59
|
+
}
|
60
|
+
}
|
61
|
+
}
|
62
|
+
|
63
|
+
#
|
64
|
+
# read_ber
|
65
|
+
# TODO: clean this up so it works properly with partial
|
66
|
+
# packets coming from streams that don't block when
|
67
|
+
# we ask for more data (like StringIOs). At it is,
|
68
|
+
# this can throw TypeErrors and other nasties.
|
69
|
+
#
|
70
|
+
def read_ber syntax=nil
|
71
|
+
return nil if eof?
|
72
|
+
|
73
|
+
id = getc # don't trash this value, we'll use it later
|
74
|
+
tag = id & 31
|
75
|
+
tag < 31 or raise BerError.new( "unsupported tag encoding: #{id}" )
|
76
|
+
tagclass = TagClasses[ id >> 6 ]
|
77
|
+
encoding = (id & 0x20 != 0) ? :constructed : :primitive
|
78
|
+
|
79
|
+
n = getc
|
80
|
+
lengthlength,contentlength = if n <= 127
|
81
|
+
[1,n]
|
82
|
+
else
|
83
|
+
j = (0...(n & 127)).inject(0) {|mem,x| mem = (mem << 8) + getc}
|
84
|
+
[1 + (n & 127), j]
|
85
|
+
end
|
86
|
+
|
87
|
+
newobj = read contentlength
|
88
|
+
|
89
|
+
objtype = nil
|
90
|
+
[syntax, BuiltinSyntax].each {|syn|
|
91
|
+
if syn && (ot = syn[tagclass]) && (ot = ot[encoding]) && ot[tag]
|
92
|
+
objtype = ot[tag]
|
93
|
+
break
|
94
|
+
end
|
95
|
+
}
|
96
|
+
|
97
|
+
obj = case objtype
|
98
|
+
when :boolean
|
99
|
+
newobj != "\000"
|
100
|
+
when :string
|
101
|
+
(newobj || "").dup
|
102
|
+
when :integer
|
103
|
+
j = 0
|
104
|
+
newobj.each_byte {|b| j = (j << 8) + b}
|
105
|
+
j
|
106
|
+
when :array
|
107
|
+
seq = []
|
108
|
+
sio = StringIO.new( newobj || "" )
|
109
|
+
# Interpret the subobject, but note how the loop
|
110
|
+
# is built: nil ends the loop, but false (a valid
|
111
|
+
# BER value) does not!
|
112
|
+
while (e = sio.read_ber(syntax)) != nil
|
113
|
+
seq << e
|
114
|
+
end
|
115
|
+
seq
|
116
|
+
else
|
117
|
+
raise BerError.new( "unsupported object type: class=#{tagclass}, encoding=#{encoding}, tag=#{tag}" )
|
118
|
+
end
|
119
|
+
|
120
|
+
# Add the identifier bits into the object if it's a String or an Array.
|
121
|
+
# We can't add extra stuff to Fixnums and booleans, not that it makes much sense anyway.
|
122
|
+
obj and ([String,Array].include? obj.class) and obj.instance_eval "def ber_identifier; #{id}; end"
|
123
|
+
obj
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
end # module BERParser
|
128
|
+
end # module BER
|
129
|
+
|
130
|
+
end # module Net
|
131
|
+
|
132
|
+
|
133
|
+
class IO
|
134
|
+
include Net::BER::BERParser
|
135
|
+
end
|
136
|
+
|
137
|
+
require "stringio"
|
138
|
+
class StringIO
|
139
|
+
include Net::BER::BERParser
|
140
|
+
end
|
141
|
+
|
142
|
+
begin
|
143
|
+
require 'openssl'
|
144
|
+
class OpenSSL::SSL::SSLSocket
|
145
|
+
include Net::BER::BERParser
|
146
|
+
end
|
147
|
+
rescue LoadError
|
148
|
+
# Ignore LoadError.
|
149
|
+
# DON'T ignore NameError, which means the SSLSocket class
|
150
|
+
# is somehow unavailable on this implementation of Ruby's openssl.
|
151
|
+
# This may be WRONG, however, because we don't yet know how Ruby's
|
152
|
+
# openssl behaves on machines with no OpenSSL library. I suppose
|
153
|
+
# it's possible they do not fail to require 'openssl' but do not
|
154
|
+
# create the classes. So this code is provisional.
|
155
|
+
# Also, you might think that OpenSSL::SSL::SSLSocket inherits from
|
156
|
+
# IO so we'd pick it up above. But you'd be wrong.
|
157
|
+
end
|
158
|
+
|
159
|
+
class String
|
160
|
+
def read_ber syntax=nil
|
161
|
+
StringIO.new(self).read_ber(syntax)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
|
166
|
+
|
167
|
+
#----------------------------------------------
|
168
|
+
|
169
|
+
|
170
|
+
class FalseClass
|
171
|
+
#
|
172
|
+
# to_ber
|
173
|
+
#
|
174
|
+
def to_ber
|
175
|
+
"\001\001\000"
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
|
180
|
+
class TrueClass
|
181
|
+
#
|
182
|
+
# to_ber
|
183
|
+
#
|
184
|
+
def to_ber
|
185
|
+
"\001\001\001"
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
|
190
|
+
|
191
|
+
class Fixnum
|
192
|
+
#
|
193
|
+
# to_ber
|
194
|
+
#
|
195
|
+
def to_ber
|
196
|
+
i = [self].pack('w')
|
197
|
+
[2, i.length].pack("CC") + i
|
198
|
+
end
|
199
|
+
|
200
|
+
#
|
201
|
+
# to_ber_enumerated
|
202
|
+
#
|
203
|
+
def to_ber_enumerated
|
204
|
+
i = [self].pack('w')
|
205
|
+
[10, i.length].pack("CC") + i
|
206
|
+
end
|
207
|
+
|
208
|
+
#
|
209
|
+
# to_ber_length_encoding
|
210
|
+
#
|
211
|
+
def to_ber_length_encoding
|
212
|
+
if self <= 127
|
213
|
+
[self].pack('C')
|
214
|
+
else
|
215
|
+
i = [self].pack('N').sub(/^[\0]+/,"")
|
216
|
+
[0x80 + i.length].pack('C') + i
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
end # class Fixnum
|
221
|
+
|
222
|
+
|
223
|
+
class Bignum
|
224
|
+
|
225
|
+
def to_ber
|
226
|
+
i = [self].pack('w')
|
227
|
+
i.length > 126 and raise Net::BER::BerError.new( "range error in bignum" )
|
228
|
+
[2, i.length].pack("CC") + i
|
229
|
+
end
|
230
|
+
|
231
|
+
end
|
232
|
+
|
233
|
+
|
234
|
+
|
235
|
+
class String
|
236
|
+
#
|
237
|
+
# to_ber
|
238
|
+
# A universal octet-string is tag number 4,
|
239
|
+
# but others are possible depending on the context, so we
|
240
|
+
# let the caller give us one.
|
241
|
+
# The preferred way to do this in user code is via to_ber_application_sring
|
242
|
+
# and to_ber_contextspecific.
|
243
|
+
#
|
244
|
+
def to_ber code = 4
|
245
|
+
[code].pack('C') + length.to_ber_length_encoding + self
|
246
|
+
end
|
247
|
+
|
248
|
+
#
|
249
|
+
# to_ber_application_string
|
250
|
+
#
|
251
|
+
def to_ber_application_string code
|
252
|
+
to_ber( 0x40 + code )
|
253
|
+
end
|
254
|
+
|
255
|
+
#
|
256
|
+
# to_ber_contextspecific
|
257
|
+
#
|
258
|
+
def to_ber_contextspecific code
|
259
|
+
to_ber( 0x80 + code )
|
260
|
+
end
|
261
|
+
|
262
|
+
end # class String
|
263
|
+
|
264
|
+
|
265
|
+
|
266
|
+
class Array
|
267
|
+
#
|
268
|
+
# to_ber_appsequence
|
269
|
+
# An application-specific sequence usually gets assigned
|
270
|
+
# a tag that is meaningful to the particular protocol being used.
|
271
|
+
# This is different from the universal sequence, which usually
|
272
|
+
# gets a tag value of 16.
|
273
|
+
# Now here's an interesting thing: We're adding the X.690
|
274
|
+
# "application constructed" code at the top of the tag byte (0x60),
|
275
|
+
# but some clients, notably ldapsearch, send "context-specific
|
276
|
+
# constructed" (0xA0). The latter would appear to violate RFC-1777,
|
277
|
+
# but what do I know? We may need to change this.
|
278
|
+
#
|
279
|
+
|
280
|
+
def to_ber id = 0; to_ber_seq_internal( 0x30 + id ); end
|
281
|
+
def to_ber_set id = 0; to_ber_seq_internal( 0x31 + id ); end
|
282
|
+
def to_ber_sequence id = 0; to_ber_seq_internal( 0x30 + id ); end
|
283
|
+
def to_ber_appsequence id = 0; to_ber_seq_internal( 0x60 + id ); end
|
284
|
+
def to_ber_contextspecific id = 0; to_ber_seq_internal( 0xA0 + id ); end
|
285
|
+
|
286
|
+
private
|
287
|
+
def to_ber_seq_internal code
|
288
|
+
s = self.to_s
|
289
|
+
[code].pack('C') + s.length.to_ber_length_encoding + s
|
290
|
+
end
|
291
|
+
|
292
|
+
end # class Array
|
293
|
+
|
294
|
+
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# $Id: dataset.rb 78 2006-04-26 02:57:34Z blackhedd $
|
2
|
+
#
|
3
|
+
#
|
4
|
+
#----------------------------------------------------------------------------
|
5
|
+
#
|
6
|
+
# Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
|
7
|
+
#
|
8
|
+
# Gmail: garbagecat10
|
9
|
+
#
|
10
|
+
# This program is free software; you can redistribute it and/or modify
|
11
|
+
# it under the terms of the GNU General Public License as published by
|
12
|
+
# the Free Software Foundation; either version 2 of the License, or
|
13
|
+
# (at your option) any later version.
|
14
|
+
#
|
15
|
+
# This program is distributed in the hope that it will be useful,
|
16
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
17
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
18
|
+
# GNU General Public License for more details.
|
19
|
+
#
|
20
|
+
# You should have received a copy of the GNU General Public License
|
21
|
+
# along with this program; if not, write to the Free Software
|
22
|
+
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
23
|
+
#
|
24
|
+
#---------------------------------------------------------------------------
|
25
|
+
#
|
26
|
+
#
|
27
|
+
|
28
|
+
|
29
|
+
|
30
|
+
|
31
|
+
module Net
|
32
|
+
class LDAP
|
33
|
+
|
34
|
+
class Dataset < Hash
|
35
|
+
|
36
|
+
attr_reader :comments
|
37
|
+
|
38
|
+
|
39
|
+
def Dataset::read_ldif io
|
40
|
+
ds = Dataset.new
|
41
|
+
|
42
|
+
line = io.gets && chomp
|
43
|
+
dn = nil
|
44
|
+
|
45
|
+
while line
|
46
|
+
io.gets and chomp
|
47
|
+
if $_ =~ /^[\s]+/
|
48
|
+
line << " " << $'
|
49
|
+
else
|
50
|
+
nextline = $_
|
51
|
+
|
52
|
+
if line =~ /^\#/
|
53
|
+
ds.comments << line
|
54
|
+
elsif line =~ /^dn:[\s]*/i
|
55
|
+
dn = $'
|
56
|
+
ds[dn] = Hash.new {|k,v| k[v] = []}
|
57
|
+
elsif line.length == 0
|
58
|
+
dn = nil
|
59
|
+
elsif line =~ /^([^:]+):([\:]?)[\s]*/
|
60
|
+
# $1 is the attribute name
|
61
|
+
# $2 is a colon iff the attr-value is base-64 encoded
|
62
|
+
# $' is the attr-value
|
63
|
+
# Avoid the Base64 class because not all Ruby versions have it.
|
64
|
+
attrvalue = ($2 == ":") ? $'.unpack('m').shift : $'
|
65
|
+
ds[dn][$1.downcase.intern] << attrvalue
|
66
|
+
end
|
67
|
+
|
68
|
+
line = nextline
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
ds
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
def initialize
|
77
|
+
@comments = []
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
def to_ldif
|
82
|
+
ary = []
|
83
|
+
ary += (@comments || [])
|
84
|
+
|
85
|
+
keys.sort.each {|dn|
|
86
|
+
ary << "dn: #{dn}"
|
87
|
+
|
88
|
+
self[dn].keys.map {|sym| sym.to_s}.sort.each {|attr|
|
89
|
+
self[dn][attr.intern].each {|val|
|
90
|
+
ary << "#{attr}: #{val}"
|
91
|
+
}
|
92
|
+
}
|
93
|
+
|
94
|
+
ary << ""
|
95
|
+
}
|
96
|
+
|
97
|
+
block_given? and ary.each {|line| yield line}
|
98
|
+
|
99
|
+
ary
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
end # Dataset
|
104
|
+
|
105
|
+
end # LDAP
|
106
|
+
end # Net
|
107
|
+
|
108
|
+
|
@@ -0,0 +1,165 @@
|
|
1
|
+
# $Id: entry.rb 123 2006-05-18 03:52:38Z blackhedd $
|
2
|
+
#
|
3
|
+
# LDAP Entry (search-result) support classes
|
4
|
+
#
|
5
|
+
#
|
6
|
+
#----------------------------------------------------------------------------
|
7
|
+
#
|
8
|
+
# Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
|
9
|
+
#
|
10
|
+
# Gmail: garbagecat10
|
11
|
+
#
|
12
|
+
# This program is free software; you can redistribute it and/or modify
|
13
|
+
# it under the terms of the GNU General Public License as published by
|
14
|
+
# the Free Software Foundation; either version 2 of the License, or
|
15
|
+
# (at your option) any later version.
|
16
|
+
#
|
17
|
+
# This program is distributed in the hope that it will be useful,
|
18
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
19
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
20
|
+
# GNU General Public License for more details.
|
21
|
+
#
|
22
|
+
# You should have received a copy of the GNU General Public License
|
23
|
+
# along with this program; if not, write to the Free Software
|
24
|
+
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
25
|
+
#
|
26
|
+
#---------------------------------------------------------------------------
|
27
|
+
#
|
28
|
+
|
29
|
+
|
30
|
+
|
31
|
+
|
32
|
+
module Net
|
33
|
+
class LDAP
|
34
|
+
|
35
|
+
|
36
|
+
# Objects of this class represent individual entries in an LDAP
|
37
|
+
# directory. User code generally does not instantiate this class.
|
38
|
+
# Net::LDAP#search provides objects of this class to user code,
|
39
|
+
# either as block parameters or as return values.
|
40
|
+
#
|
41
|
+
# In LDAP-land, an "entry" is a collection of attributes that are
|
42
|
+
# uniquely and globally identified by a DN ("Distinguished Name").
|
43
|
+
# Attributes are identified by short, descriptive words or phrases.
|
44
|
+
# Although a directory is
|
45
|
+
# free to implement any attribute name, most of them follow rigorous
|
46
|
+
# standards so that the range of commonly-encountered attribute
|
47
|
+
# names is not large.
|
48
|
+
#
|
49
|
+
# An attribute name is case-insensitive. Most directories also
|
50
|
+
# restrict the range of characters allowed in attribute names.
|
51
|
+
# To simplify handling attribute names, Net::LDAP::Entry
|
52
|
+
# internally converts them to a standard format. Therefore, the
|
53
|
+
# methods which take attribute names can take Strings or Symbols,
|
54
|
+
# and work correctly regardless of case or capitalization.
|
55
|
+
#
|
56
|
+
# An attribute consists of zero or more data items called
|
57
|
+
# <i>values.</i> An entry is the combination of a unique DN, a set of attribute
|
58
|
+
# names, and a (possibly-empty) array of values for each attribute.
|
59
|
+
#
|
60
|
+
# Class Net::LDAP::Entry provides convenience methods for dealing
|
61
|
+
# with LDAP entries.
|
62
|
+
# In addition to the methods documented below, you may access individual
|
63
|
+
# attributes of an entry simply by giving the attribute name as
|
64
|
+
# the name of a method call. For example:
|
65
|
+
# ldap.search( ... ) do |entry|
|
66
|
+
# puts "Common name: #{entry.cn}"
|
67
|
+
# puts "Email addresses:"
|
68
|
+
# entry.mail.each {|ma| puts ma}
|
69
|
+
# end
|
70
|
+
# If you use this technique to access an attribute that is not present
|
71
|
+
# in a particular Entry object, a NoMethodError exception will be raised.
|
72
|
+
#
|
73
|
+
#--
|
74
|
+
# Ugly problem to fix someday: We key off the internal hash with
|
75
|
+
# a canonical form of the attribute name: convert to a string,
|
76
|
+
# downcase, then take the symbol. Unfortunately we do this in
|
77
|
+
# at least three places. Should do it in ONE place.
|
78
|
+
class Entry
|
79
|
+
|
80
|
+
# This constructor is not generally called by user code.
|
81
|
+
def initialize dn = nil # :nodoc:
|
82
|
+
@myhash = Hash.new {|k,v| k[v] = [] }
|
83
|
+
@myhash[:dn] = [dn]
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
def []= name, value # :nodoc:
|
88
|
+
sym = name.to_s.downcase.intern
|
89
|
+
@myhash[sym] = value
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
#--
|
94
|
+
# We have to deal with this one as we do with []=
|
95
|
+
# because this one and not the other one gets called
|
96
|
+
# in formulations like entry["CN"] << cn.
|
97
|
+
#
|
98
|
+
def [] name # :nodoc:
|
99
|
+
name = name.to_s.downcase.intern unless name.is_a?(Symbol)
|
100
|
+
@myhash[name]
|
101
|
+
end
|
102
|
+
|
103
|
+
# Returns the dn of the Entry as a String.
|
104
|
+
def dn
|
105
|
+
self[:dn][0]
|
106
|
+
end
|
107
|
+
|
108
|
+
# Returns an array of the attribute names present in the Entry.
|
109
|
+
def attribute_names
|
110
|
+
@myhash.keys
|
111
|
+
end
|
112
|
+
|
113
|
+
# Accesses each of the attributes present in the Entry.
|
114
|
+
# Calls a user-supplied block with each attribute in turn,
|
115
|
+
# passing two arguments to the block: a Symbol giving
|
116
|
+
# the name of the attribute, and a (possibly empty)
|
117
|
+
# Array of data values.
|
118
|
+
#
|
119
|
+
def each
|
120
|
+
if block_given?
|
121
|
+
attribute_names.each {|a|
|
122
|
+
attr_name,values = a,self[a]
|
123
|
+
yield attr_name, values
|
124
|
+
}
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
alias_method :each_attribute, :each
|
129
|
+
|
130
|
+
|
131
|
+
#--
|
132
|
+
# Convenience method to convert unknown method names
|
133
|
+
# to attribute references. Of course the method name
|
134
|
+
# comes to us as a symbol, so let's save a little time
|
135
|
+
# and not bother with the to_s.downcase two-step.
|
136
|
+
# Of course that means that a method name like mAIL
|
137
|
+
# won't work, but we shouldn't be encouraging that
|
138
|
+
# kind of bad behavior in the first place.
|
139
|
+
# Maybe we should thow something if the caller sends
|
140
|
+
# arguments or a block...
|
141
|
+
#
|
142
|
+
def method_missing *args, &block # :nodoc:
|
143
|
+
s = args[0].to_s.downcase.intern
|
144
|
+
if attribute_names.include?(s)
|
145
|
+
self[s]
|
146
|
+
elsif s.to_s[-1] == 61 and s.to_s.length > 1
|
147
|
+
value = args[1] or raise RuntimeError.new( "unable to set value" )
|
148
|
+
value = [value] unless value.is_a?(Array)
|
149
|
+
name = s.to_s[0..-2].intern
|
150
|
+
self[name] = value
|
151
|
+
else
|
152
|
+
raise NoMethodError.new( "undefined method '#{s}'" )
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def write
|
157
|
+
end
|
158
|
+
|
159
|
+
end # class Entry
|
160
|
+
|
161
|
+
|
162
|
+
end # class LDAP
|
163
|
+
end # module Net
|
164
|
+
|
165
|
+
|