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/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
+