dim-ruby-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.
@@ -0,0 +1,58 @@
1
+ = Net::LDAP for Ruby
2
+
3
+ * http://github.com/dim/ruby-net-ldap
4
+
5
+ == DESCRIPTION:
6
+
7
+ Pure Ruby LDAP library.
8
+
9
+ A LDAP client library written in pure Ruby.
10
+
11
+ == FEATURES/PROBLEMS:
12
+
13
+ The Lightweight Directory Access Protocol (LDAP) is an Internet protocol
14
+ for accessing distributed directory services.
15
+
16
+ Net::LDAP is a feature-complete LDAP support library written in pure Ruby.
17
+ It supports most LDAP client features and a subset of server features as
18
+ well.
19
+
20
+ * Standards-based (going for RFC 4511)
21
+ * Portable: 100% Ruby
22
+
23
+ == INSTALL:
24
+
25
+ Net::LDAP is a pure Ruby library. It does not require any external
26
+ libraries.
27
+
28
+ You can install the RubyGems version of Net::LDAP available at
29
+ GitHub:
30
+
31
+ * gem install dim-ruby-net-ldap --source=http://gems.github.com
32
+
33
+ == SYNOPSIS:
34
+
35
+ See Net::LDAP for documentation and usage samples.
36
+
37
+ == CREDITS:
38
+
39
+ Net::LDAP was originally developed by:
40
+
41
+ * Francis Cianfrocca <garbagecat10@gmail.com>
42
+
43
+ Contributions since:
44
+
45
+ * Austin Ziegler <halostatue@gmail.com>
46
+ * Emiel van de Laar <gemiel@gmail.com>
47
+ * Dimitrij Denissenko <contact@dvisionfactory.com>
48
+
49
+ == LICENSE:
50
+
51
+ Copyright (C) 2006 by Francis Cianfrocca
52
+
53
+ Please read the file LICENSE for licensing restrictions on this library. In
54
+ the simplest terms, this library is available under the same terms as Ruby
55
+ itself.
56
+
57
+ Available under the same terms as Ruby. See LICENSE in the main
58
+ distribution for full licensing information.
@@ -0,0 +1,26 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+
5
+ # Add 'lib' to load path.
6
+ $LOAD_PATH.unshift( "#{File.dirname(__FILE__)}/lib" )
7
+
8
+ # Pull in local 'net/ldap' as opposed to an installed version.
9
+ require 'net/ldap'
10
+
11
+ begin
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |spec|
14
+ spec.name = "ruby-net-ldap"
15
+ spec.summary = "Pure Ruby LDAP library"
16
+ spec.email = "contact@dvisionfactory.com"
17
+ spec.homepage = "http://github.com/dim/ruby-net-ldap"
18
+ spec.description = "Net::LDAP is a feature-complete LDAP support library written in pure Ruby. It supports most LDAP client features and a subset of server features as well."
19
+ spec.authors = ["Francis Cianfrocca", "Emiel van de Laar", "Tom Copeland", "Austin Ziegler", "Dimitrij Denissenko"]
20
+ spec.test_files = Dir.glob('test*/**')
21
+ end
22
+ rescue LoadError
23
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
24
+ end
25
+
26
+ # vim: syntax=Ruby
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 1
4
+ :patch: 0
@@ -0,0 +1,558 @@
1
+ # $Id$
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
+ module Net
33
+
34
+ module BER
35
+
36
+ class BerError < StandardError; end
37
+
38
+ class BerIdentifiedString < String
39
+ attr_accessor :ber_identifier
40
+ def initialize args
41
+ super args
42
+ end
43
+ end
44
+
45
+ class BerIdentifiedArray < Array
46
+ attr_accessor :ber_identifier
47
+ def initialize
48
+ super
49
+ end
50
+ end
51
+
52
+ class BerIdentifiedNull
53
+ attr_accessor :ber_identifier
54
+ def to_ber
55
+ "\005\000"
56
+ end
57
+ end
58
+
59
+ class BerIdentifiedOid
60
+ attr_accessor :ber_identifier
61
+
62
+ def initialize oid
63
+ if oid.is_a?(String)
64
+ oid = oid.split(/\./).map {|s| s.to_i }
65
+ end
66
+ @value = oid
67
+ end
68
+
69
+ def to_ber
70
+ # Provisional implementation.
71
+ # We ASSUME that our incoming value is an array, and we
72
+ # use the Array#to_ber_oid method defined below.
73
+ # We probably should obsolete that method, actually, in
74
+ # and move the code here.
75
+ # WE ARE NOT CURRENTLY ENCODING THE BER-IDENTIFIER.
76
+ # This implementation currently hardcodes 6, the universal OID tag.
77
+ ary = @value.dup
78
+ first = ary.shift
79
+ raise Net::BER::BerError.new(" invalid OID" ) unless [0,1,2].include?(first)
80
+ first = first * 40 + ary.shift
81
+ ary.unshift first
82
+ oid = ary.pack("w*")
83
+ [6, oid.length].pack("CC") + oid
84
+ end
85
+ end
86
+
87
+ #--
88
+ # This condenses our nicely self-documenting ASN hashes down
89
+ # to an array for fast lookups.
90
+ # Scoped to be called as a module method, but not intended for
91
+ # user code to call.
92
+ #
93
+ def self.compile_syntax syn
94
+ out = [nil] * 256
95
+ syn.each do |tclass,tclasses|
96
+ tagclass = {:universal=>0, :application=>64, :context_specific=>128, :private=>192} [tclass]
97
+ tclasses.each do |codingtype,codings|
98
+ encoding = {:primitive=>0, :constructed=>32} [codingtype]
99
+ codings.each do |tag,objtype|
100
+ out[tagclass + encoding + tag] = objtype
101
+ end
102
+ end
103
+ end
104
+ out
105
+ end
106
+
107
+ # This module is for mixing into IO and IO-like objects.
108
+ module BERParser
109
+
110
+ # The order of these follows the class-codes in BER.
111
+ # Maybe this should have been a hash.
112
+ TagClasses = [:universal, :application, :context_specific, :private]
113
+
114
+ BuiltinSyntax = BER.compile_syntax(
115
+ :universal => {
116
+ :primitive => {
117
+ 1 => :boolean,
118
+ 2 => :integer,
119
+ 4 => :string,
120
+ 5 => :null,
121
+ 6 => :oid,
122
+ 10 => :integer,
123
+ 13 => :string # (relative OID)
124
+ },
125
+ :constructed => {
126
+ 16 => :array,
127
+ 17 => :array
128
+ }
129
+ },
130
+ :context_specific => {
131
+ :primitive => {
132
+ 10 => :integer
133
+ }
134
+ }
135
+ )
136
+
137
+ #
138
+ # read_ber
139
+ # TODO: clean this up so it works properly with partial
140
+ # packets coming from streams that don't block when
141
+ # we ask for more data (like StringIOs). At it is,
142
+ # this can throw TypeErrors and other nasties.
143
+ #--
144
+ # BEWARE, this violates DRY and is largely equal in functionality to
145
+ # read_ber_from_string. Eventually that method may subsume the functionality
146
+ # of this one.
147
+ #
148
+ def read_ber syntax=nil
149
+ # don't bother with this line, since IO#getc by definition returns nil on eof.
150
+ #return nil if eof?
151
+
152
+ id = getc or return nil # don't trash this value, we'll use it later
153
+ id = id.ord
154
+ #tag = id & 31
155
+ #tag < 31 or raise BerError.new( "unsupported tag encoding: #{id}" )
156
+ #tagclass = TagClasses[ id >> 6 ]
157
+ #encoding = (id & 0x20 != 0) ? :constructed : :primitive
158
+
159
+ n = getc.ord
160
+ lengthlength,contentlength = if n <= 127
161
+ [1,n]
162
+ else
163
+ # Replaced the inject because it profiles hot.
164
+ #j = (0...(n & 127)).inject(0) {|mem,x| mem = (mem << 8) + getc}
165
+ j = 0
166
+ read( n & 127 ).each_byte {|n1| j = (j << 8) + n1}
167
+ [1 + (n & 127), j]
168
+ end
169
+
170
+ newobj = read contentlength
171
+
172
+ # This exceptionally clever and clear bit of code is verrrry slow.
173
+ objtype = (syntax && syntax[id]) || BuiltinSyntax[id]
174
+
175
+
176
+ # == is expensive so sort this if/else so the common cases are at the top.
177
+ obj = if objtype == :string
178
+ #(newobj || "").dup
179
+ s = BerIdentifiedString.new( newobj || "" )
180
+ s.ber_identifier = id
181
+ s
182
+ elsif objtype == :integer
183
+ j = 0
184
+ newobj.each_byte {|b| j = (j << 8) + b}
185
+ j
186
+ elsif objtype == :oid
187
+ # cf X.690 pgh 8.19 for an explanation of this algorithm.
188
+ # Potentially not good enough. We may need a BerIdentifiedOid
189
+ # as a subclass of BerIdentifiedArray, to get the ber identifier
190
+ # and also a to_s method that produces the familiar dotted notation.
191
+ oid = newobj.unpack("w*")
192
+ f = oid.shift
193
+ g = if f < 40
194
+ [0, f]
195
+ elsif f < 80
196
+ [1, f-40]
197
+ else
198
+ [2, f-80] # f-80 can easily be > 80. What a weird optimization.
199
+ end
200
+ oid.unshift g.last
201
+ oid.unshift g.first
202
+ oid
203
+ elsif objtype == :array
204
+ #seq = []
205
+ seq = BerIdentifiedArray.new
206
+ seq.ber_identifier = id
207
+ sio = StringIO.new( newobj || "" )
208
+ # Interpret the subobject, but note how the loop
209
+ # is built: nil ends the loop, but false (a valid
210
+ # BER value) does not!
211
+ while (e = sio.read_ber(syntax)) != nil
212
+ seq << e
213
+ end
214
+ seq
215
+ elsif objtype == :boolean
216
+ newobj != "\000"
217
+ elsif objtype == :null
218
+ n = BerIdentifiedNull.new
219
+ n.ber_identifier = id
220
+ n
221
+ else
222
+ #raise BerError.new( "unsupported object type: class=#{tagclass}, encoding=#{encoding}, tag=#{tag}" )
223
+ raise BerError.new( "unsupported object type: id=#{id}" )
224
+ end
225
+
226
+ # Add the identifier bits into the object if it's a String or an Array.
227
+ # We can't add extra stuff to Fixnums and booleans, not that it makes much sense anyway.
228
+ # Replaced this mechanism with subclasses because the instance_eval profiled too hot.
229
+ #obj and ([String,Array].include? obj.class) and obj.instance_eval "def ber_identifier; #{id}; end"
230
+ #obj.ber_identifier = id if obj.respond_to?(:ber_identifier)
231
+ obj
232
+
233
+ end
234
+
235
+ #--
236
+ # Violates DRY! This replicates the functionality of #read_ber.
237
+ # Eventually this method may replace that one.
238
+ # This version of #read_ber behaves properly in the face of incomplete
239
+ # data packets. If a full BER object is detected, we return an array containing
240
+ # the detected object and the number of bytes consumed from the string.
241
+ # If we don't detect a complete packet, return nil.
242
+ #
243
+ # Observe that weirdly we recursively call the original #read_ber in here.
244
+ # That needs to be fixed if we ever obsolete the original method in favor of this one.
245
+ def read_ber_from_string str, syntax=nil
246
+ id = str[0] || return
247
+ id = id.ord if RUBY_VERSION.to_f >= 1.9
248
+
249
+ n = str[1] || return
250
+ n = n.ord if RUBY_VERSION.to_f >= 1.9
251
+
252
+ n_consumed = 2
253
+ lengthlength,contentlength = if n <= 127
254
+ [1,n]
255
+ else
256
+ n1 = n & 127
257
+ return nil unless str.length >= (n_consumed + n1)
258
+ j = 0
259
+ n1.times do
260
+ j = (j << 8) + str[n_consumed]
261
+ n_consumed += 1
262
+ end
263
+ [1 + (n1), j]
264
+ end
265
+
266
+ return nil unless str.length >= (n_consumed + contentlength)
267
+ newobj = str[n_consumed...(n_consumed + contentlength)]
268
+ n_consumed += contentlength
269
+
270
+ objtype = (syntax && syntax[id]) || BuiltinSyntax[id]
271
+
272
+ # == is expensive so sort this if/else so the common cases are at the top.
273
+ obj = if objtype == :array
274
+ seq = BerIdentifiedArray.new
275
+ seq.ber_identifier = id
276
+ sio = StringIO.new( newobj || "" )
277
+ # Interpret the subobject, but note how the loop
278
+ # is built: nil ends the loop, but false (a valid
279
+ # BER value) does not!
280
+ # Also, we can use the standard read_ber method because
281
+ # we know for sure we have enough data. (Although this
282
+ # might be faster than the standard method.)
283
+ while (e = sio.read_ber(syntax)) != nil
284
+ seq << e
285
+ end
286
+ seq
287
+ elsif objtype == :string
288
+ s = BerIdentifiedString.new( newobj || "" )
289
+ s.ber_identifier = id
290
+ s
291
+ elsif objtype == :integer
292
+ j = 0
293
+ newobj.each_byte {|b| j = (j << 8) + b}
294
+ j
295
+ elsif objtype == :oid
296
+ # cf X.690 pgh 8.19 for an explanation of this algorithm.
297
+ # Potentially not good enough. We may need a BerIdentifiedOid
298
+ # as a subclass of BerIdentifiedArray, to get the ber identifier
299
+ # and also a to_s method that produces the familiar dotted notation.
300
+ oid = newobj.unpack("w*")
301
+ f = oid.shift
302
+ g = if f < 40
303
+ [0,f]
304
+ elsif f < 80
305
+ [1, f-40]
306
+ else
307
+ [2, f-80] # f-80 can easily be > 80. What a weird optimization.
308
+ end
309
+ oid.unshift g.last
310
+ oid.unshift g.first
311
+ oid
312
+ elsif objtype == :boolean
313
+ newobj != "\000"
314
+ elsif objtype == :null
315
+ n = BerIdentifiedNull.new
316
+ n.ber_identifier = id
317
+ n
318
+ else
319
+ raise BerError.new( "unsupported object type: id=#{id}" )
320
+ end
321
+
322
+ [obj, n_consumed]
323
+ end
324
+
325
+ end # module BERParser
326
+ end # module BER
327
+
328
+ end # module Net
329
+
330
+
331
+ class IO
332
+ include Net::BER::BERParser
333
+ end
334
+
335
+ require "stringio"
336
+ class StringIO
337
+ include Net::BER::BERParser
338
+ end
339
+
340
+ begin
341
+ require 'openssl'
342
+ class OpenSSL::SSL::SSLSocket
343
+ include Net::BER::BERParser
344
+ end
345
+ rescue LoadError
346
+ # Ignore LoadError.
347
+ # DON'T ignore NameError, which means the SSLSocket class
348
+ # is somehow unavailable on this implementation of Ruby's openssl.
349
+ # This may be WRONG, however, because we don't yet know how Ruby's
350
+ # openssl behaves on machines with no OpenSSL library. I suppose
351
+ # it's possible they do not fail to require 'openssl' but do not
352
+ # create the classes. So this code is provisional.
353
+ # Also, you might think that OpenSSL::SSL::SSLSocket inherits from
354
+ # IO so we'd pick it up above. But you'd be wrong.
355
+ end
356
+
357
+
358
+
359
+ class String
360
+ include Net::BER::BERParser
361
+ def read_ber syntax=nil
362
+ StringIO.new(self).read_ber(syntax)
363
+ end
364
+
365
+ def read_ber! syntax=nil
366
+ obj,n_consumed = read_ber_from_string(self, syntax)
367
+ if n_consumed
368
+ self.slice!(0...n_consumed)
369
+ obj
370
+ else
371
+ nil
372
+ end
373
+ end
374
+ end
375
+
376
+ #----------------------------------------------
377
+
378
+
379
+ class FalseClass
380
+ #
381
+ # to_ber
382
+ #
383
+ def to_ber
384
+ "\001\001\000"
385
+ end
386
+ end
387
+
388
+
389
+ class TrueClass
390
+ #
391
+ # to_ber
392
+ #
393
+ def to_ber
394
+ "\001\001\001"
395
+ end
396
+ end
397
+
398
+
399
+
400
+ class Fixnum
401
+ #
402
+ # to_ber
403
+ #
404
+ def to_ber
405
+ "\002" + to_ber_internal
406
+ end
407
+
408
+ #
409
+ # to_ber_enumerated
410
+ #
411
+ def to_ber_enumerated
412
+ "\012" + to_ber_internal
413
+ end
414
+
415
+ #
416
+ # to_ber_length_encoding
417
+ #
418
+ def to_ber_length_encoding
419
+ if self <= 127
420
+ [self].pack('C')
421
+ else
422
+ i = [self].pack('N').sub(/^[\0]+/,"")
423
+ [0x80 + i.length].pack('C') + i
424
+ end
425
+ end
426
+
427
+ # Generate a BER-encoding for an application-defined INTEGER.
428
+ # Example: SNMP's Counter, Gauge, and TimeTick types.
429
+ #
430
+ def to_ber_application tag
431
+ [0x40 + tag].pack("C") + to_ber_internal
432
+ end
433
+
434
+ #--
435
+ # Called internally to BER-encode the length and content bytes of a Fixnum.
436
+ # The caller will prepend the tag byte.
437
+ def to_ber_internal
438
+ # PLEASE optimize this code path. It's awfully ugly and probably slow.
439
+ # It also doesn't understand negative numbers yet.
440
+ raise Net::BER::BerError.new( "range error in fixnum" ) unless self >= 0
441
+ z = [self].pack("N")
442
+ zlen = if self < 0x80
443
+ 1
444
+ elsif self < 0x8000
445
+ 2
446
+ elsif self < 0x800000
447
+ 3
448
+ else
449
+ 4
450
+ end
451
+ [zlen].pack("C") + z[0-zlen,zlen]
452
+ end
453
+ private :to_ber_internal
454
+
455
+ end # class Fixnum
456
+
457
+
458
+ class Bignum
459
+
460
+ def to_ber
461
+ #i = [self].pack('w')
462
+ #i.length > 126 and raise Net::BER::BerError.new( "range error in bignum" )
463
+ #[2, i.length].pack("CC") + i
464
+
465
+ # Ruby represents Bignums as two's-complement numbers so we may actually be
466
+ # good as far as representing negatives goes.
467
+ # I'm sure this implementation can be improved performance-wise if necessary.
468
+ # Ruby's Bignum#size returns the number of bytes in the internal representation
469
+ # of the number, but it can and will include leading zero bytes on at least
470
+ # some implementations. Evidently Ruby stores these as sets of quadbytes.
471
+ # It's not illegal in BER to encode all of the leading zeroes but let's strip
472
+ # them out anyway.
473
+ #
474
+ out = [0] * size
475
+ (size * 8).times do |bit|
476
+ next unless self[bit] == 1
477
+ out[bit/8] += (1 << (bit % 8))
478
+ end
479
+ out = out.pack('C*')
480
+ out.slice!(-1,1) while out.length > 1 and out[-1].ord.zero?
481
+ [2, out.length].pack("CC") + out.reverse
482
+ end
483
+
484
+ end
485
+
486
+
487
+
488
+ class String
489
+ #
490
+ # to_ber
491
+ # A universal octet-string is tag number 4,
492
+ # but others are possible depending on the context, so we
493
+ # let the caller give us one.
494
+ # The preferred way to do this in user code is via to_ber_application_sring
495
+ # and to_ber_contextspecific.
496
+ #
497
+ def to_ber code = 4
498
+ [code].pack('C') + length.to_ber_length_encoding + self
499
+ end
500
+
501
+ #
502
+ # to_ber_application_string
503
+ #
504
+ def to_ber_application_string code
505
+ to_ber( 0x40 + code )
506
+ end
507
+
508
+ #
509
+ # to_ber_contextspecific
510
+ #
511
+ def to_ber_contextspecific code
512
+ to_ber( 0x80 + code )
513
+ end
514
+
515
+ end # class String
516
+
517
+
518
+
519
+ class Array
520
+ #
521
+ # to_ber_appsequence
522
+ # An application-specific sequence usually gets assigned
523
+ # a tag that is meaningful to the particular protocol being used.
524
+ # This is different from the universal sequence, which usually
525
+ # gets a tag value of 16.
526
+ # Now here's an interesting thing: We're adding the X.690
527
+ # "application constructed" code at the top of the tag byte (0x60),
528
+ # but some clients, notably ldapsearch, send "context-specific
529
+ # constructed" (0xA0). The latter would appear to violate RFC-1777,
530
+ # but what do I know? We may need to change this.
531
+ #
532
+
533
+ def to_ber id = 0; to_ber_seq_internal( 0x30 + id ); end
534
+ def to_ber_set id = 0; to_ber_seq_internal( 0x31 + id ); end
535
+ def to_ber_sequence id = 0; to_ber_seq_internal( 0x30 + id ); end
536
+ def to_ber_appsequence id = 0; to_ber_seq_internal( 0x60 + id ); end
537
+ def to_ber_contextspecific id = 0; to_ber_seq_internal( 0xA0 + id ); end
538
+
539
+ def to_ber_oid
540
+ ary = self.dup
541
+ first = ary.shift
542
+ raise Net::BER::BerError.new( "invalid OID" ) unless [0,1,2].include?(first)
543
+ first = first * 40 + ary.shift
544
+ ary.unshift first
545
+ oid = ary.pack("w*")
546
+ [6, oid.length].pack("CC") + oid
547
+ end
548
+
549
+ private
550
+ def to_ber_seq_internal code
551
+ s = self.join
552
+ [code].pack('C') + s.length.to_ber_length_encoding + s
553
+ end
554
+
555
+
556
+ end # class Array
557
+
558
+