net-ldap 0.1.1 → 0.2

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.

Potentially problematic release.


This version of net-ldap might be problematic. Click here for more details.

Files changed (58) hide show
  1. data/.autotest +11 -0
  2. data/.gemtest +0 -0
  3. data/.rspec +2 -0
  4. data/Contributors.rdoc +21 -0
  5. data/Hacking.rdoc +68 -0
  6. data/{History.txt → History.rdoc} +65 -1
  7. data/License.rdoc +29 -0
  8. data/Manifest.txt +25 -14
  9. data/README.rdoc +52 -0
  10. data/Rakefile +52 -96
  11. data/autotest/discover.rb +1 -0
  12. data/lib/net-ldap.rb +1 -0
  13. data/lib/net/ber.rb +302 -81
  14. data/lib/net/ber/ber_parser.rb +153 -97
  15. data/lib/net/ber/core_ext.rb +62 -0
  16. data/lib/net/ber/core_ext/array.rb +82 -0
  17. data/lib/net/ber/core_ext/bignum.rb +22 -0
  18. data/lib/net/ber/core_ext/false_class.rb +10 -0
  19. data/lib/net/ber/core_ext/fixnum.rb +66 -0
  20. data/lib/net/ber/core_ext/string.rb +48 -0
  21. data/lib/net/ber/core_ext/true_class.rb +12 -0
  22. data/lib/net/ldap.rb +1455 -1475
  23. data/lib/net/ldap/dataset.rb +134 -79
  24. data/lib/net/ldap/dn.rb +225 -0
  25. data/lib/net/ldap/entry.rb +168 -249
  26. data/lib/net/ldap/filter.rb +654 -387
  27. data/lib/net/ldap/password.rb +31 -0
  28. data/lib/net/ldap/pdu.rb +232 -233
  29. data/lib/net/snmp.rb +4 -31
  30. data/net-ldap.gemspec +59 -0
  31. data/spec/integration/ssl_ber_spec.rb +3 -0
  32. data/spec/spec_helper.rb +2 -2
  33. data/spec/unit/ber/ber_spec.rb +82 -6
  34. data/spec/unit/ber/core_ext/string_spec.rb +51 -0
  35. data/spec/unit/ldap/dn_spec.rb +80 -0
  36. data/spec/unit/ldap/entry_spec.rb +51 -0
  37. data/spec/unit/ldap/filter_spec.rb +84 -0
  38. data/spec/unit/ldap_spec.rb +48 -0
  39. data/test/test_entry.rb +54 -2
  40. data/test/test_filter.rb +93 -54
  41. data/test/test_ldap_connection.rb +24 -0
  42. data/test/test_ldif.rb +31 -23
  43. data/test/test_rename.rb +77 -0
  44. data/test/test_snmp.rb +34 -33
  45. metadata +88 -52
  46. data/COPYING +0 -272
  47. data/LICENSE +0 -56
  48. data/README.txt +0 -68
  49. data/lib/net/ldap/core_ext/all.rb +0 -43
  50. data/lib/net/ldap/core_ext/array.rb +0 -42
  51. data/lib/net/ldap/core_ext/bignum.rb +0 -25
  52. data/lib/net/ldap/core_ext/false_class.rb +0 -11
  53. data/lib/net/ldap/core_ext/fixnum.rb +0 -74
  54. data/lib/net/ldap/core_ext/string.rb +0 -40
  55. data/lib/net/ldap/core_ext/true_class.rb +0 -11
  56. data/lib/net/ldap/psw.rb +0 -57
  57. data/lib/net/ldif.rb +0 -34
  58. data/test/test_ber.rb +0 -78
@@ -1,99 +1,154 @@
1
- #----------------------------------------------------------------------------
2
- #
3
- # Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
4
- #
5
- # Gmail: garbagecat10
6
- #
7
- # This program is free software; you can redistribute it and/or modify
8
- # it under the terms of the GNU General Public License as published by
9
- # the Free Software Foundation; either version 2 of the License, or
10
- # (at your option) any later version.
11
- #
12
- # This program is distributed in the hope that it will be useful,
13
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
- # GNU General Public License for more details.
16
- #
17
- # You should have received a copy of the GNU General Public License
18
- # along with this program; if not, write to the Free Software
19
- # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20
- #
21
- #---------------------------------------------------------------------------
22
-
23
- module Net
24
- class LDAP
25
- class Dataset < Hash
26
- attr_reader :comments
27
-
28
- class IOFilter
29
- def initialize(io)
30
- @io = io
31
- end
32
- def gets
33
- s = @io.gets
34
- s.chomp if s
35
- end
36
- end
1
+ # -*- ruby encoding: utf-8 -*-
2
+ ##
3
+ # An LDAP Dataset. Used primarily as an intermediate format for converting
4
+ # to and from LDIF strings and Net::LDAP::Entry objects.
5
+ class Net::LDAP::Dataset < Hash
6
+ ##
7
+ # Dataset object comments.
8
+ attr_reader :comments
37
9
 
38
- def self.read_ldif io
39
- ds = Dataset.new
40
- io = IOFilter.new(io)
10
+ def initialize(*args, &block) # :nodoc:
11
+ super
12
+ @comments = []
13
+ end
41
14
 
42
- line = io.gets
43
- dn = nil
15
+ ##
16
+ # Outputs an LDAP Dataset as an array of strings representing LDIF
17
+ # entries.
18
+ def to_ldif
19
+ ary = []
20
+ ary += @comments unless @comments.empty?
21
+ keys.sort.each do |dn|
22
+ ary << "dn: #{dn}"
44
23
 
45
- while line
46
- new_line = io.gets
47
- if new_line =~ /^[\s]+/
48
- line << " " << $'
24
+ attributes = self[dn].keys.map { |attr| attr.to_s }.sort
25
+ attributes.each do |attr|
26
+ self[dn][attr.to_sym].each do |value|
27
+ if attr == "userpassword" or value_is_binary?(value)
28
+ value = [value].pack("m").chomp.gsub(/\n/m, "\n ")
29
+ ary << "#{attr}:: #{value}"
49
30
  else
50
- nextline = new_line
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
31
+ ary << "#{attr}: #{value}"
69
32
  end
70
33
  end
34
+ end
35
+
36
+ ary << ""
37
+ end
38
+ block_given? and ary.each { |line| yield line}
39
+
40
+ ary
41
+ end
42
+
43
+ ##
44
+ # Outputs an LDAP Dataset as an LDIF string.
45
+ def to_ldif_string
46
+ to_ldif.join("\n")
47
+ end
71
48
 
72
- ds
49
+ ##
50
+ # Convert the parsed LDIF objects to Net::LDAP::Entry objects.
51
+ def to_entries
52
+ ary = []
53
+ keys.each do |dn|
54
+ entry = Net::LDAP::Entry.new(dn)
55
+ self[dn].each do |attr, value|
56
+ entry[attr] = value
73
57
  end
58
+ ary << entry
59
+ end
60
+ ary
61
+ end
74
62
 
63
+ ##
64
+ # This is an internal convenience method to determine if a value requires
65
+ # base64-encoding before conversion to LDIF output. The standard approach
66
+ # in most LDAP tools is to check whether the value is a password, or if
67
+ # the first or last bytes are non-printable. Microsoft Active Directory,
68
+ # on the other hand, sometimes sends values that are binary in the middle.
69
+ #
70
+ # In the worst cases, this could be a nasty performance killer, which is
71
+ # why we handle the simplest cases first. Ideally, we would also test the
72
+ # first/last byte, but it's a bit harder to do this in a way that's
73
+ # compatible with both 1.8.6 and 1.8.7.
74
+ def value_is_binary?(value) # :nodoc:
75
+ value = value.to_s
76
+ return true if value[0] == ?: or value[0] == ?<
77
+ value.each_byte { |byte| return true if (byte < 32) || (byte > 126) }
78
+ false
79
+ end
80
+ private :value_is_binary?
75
81
 
76
- def initialize
77
- @comments = []
82
+ class << self
83
+ class ChompedIO # :nodoc:
84
+ def initialize(io)
85
+ @io = io
78
86
  end
87
+ def gets
88
+ s = @io.gets
89
+ s.chomp if s
90
+ end
91
+ end
92
+
93
+ ##
94
+ # Creates a Dataset object from an Entry object. Used mostly to assist
95
+ # with the conversion of
96
+ def from_entry(entry)
97
+ dataset = Net::LDAP::Dataset.new
98
+ hash = { }
99
+ entry.each_attribute do |attribute, value|
100
+ next if attribute == :dn
101
+ hash[attribute] = value
102
+ end
103
+ dataset[entry.dn] = hash
104
+ dataset
105
+ end
79
106
 
107
+ ##
108
+ # Reads an object that returns data line-wise (using #gets) and parses
109
+ # LDIF data into a Dataset object.
110
+ def read_ldif(io)
111
+ ds = Net::LDAP::Dataset.new
112
+ io = ChompedIO.new(io)
80
113
 
81
- def to_ldif
82
- ary = []
83
- ary += (@comments || [])
84
- keys.sort.each do |dn|
85
- ary << "dn: #{dn}"
114
+ line = io.gets
115
+ dn = nil
86
116
 
87
- self[dn].keys.map {|sym| sym.to_s}.sort.each do |attr|
88
- self[dn][attr.intern].each {|val| ary << "#{attr}: #{val}" }
117
+ while line
118
+ new_line = io.gets
119
+
120
+ if new_line =~ /^[\s]+/
121
+ line << " " << $'
122
+ else
123
+ nextline = new_line
124
+
125
+ if line =~ /^#/
126
+ ds.comments << line
127
+ yield :comment, line if block_given?
128
+ elsif line =~ /^dn:[\s]*/i
129
+ dn = $'
130
+ ds[dn] = Hash.new { |k,v| k[v] = [] }
131
+ yield :dn, dn if block_given?
132
+ elsif line.empty?
133
+ dn = nil
134
+ yield :end, nil if block_given?
135
+ elsif line =~ /^([^:]+):([\:]?)[\s]*/
136
+ # $1 is the attribute name
137
+ # $2 is a colon iff the attr-value is base-64 encoded
138
+ # $' is the attr-value
139
+ # Avoid the Base64 class because not all Ruby versions have it.
140
+ attrvalue = ($2 == ":") ? $'.unpack('m').shift : $'
141
+ ds[dn][$1.downcase.to_sym] << attrvalue
142
+ yield :attr, [$1.downcase.to_sym, attrvalue] if block_given?
89
143
  end
90
144
 
91
- ary << ""
145
+ line = nextline
92
146
  end
93
- block_given? and ary.each {|line| yield line}
94
- ary
95
147
  end
96
148
 
97
- end
98
- end
149
+ ds
150
+ end
151
+ end
99
152
  end
153
+
154
+ require 'net/ldap/entry' unless defined? Net::LDAP::Entry
@@ -0,0 +1,225 @@
1
+ # -*- ruby encoding: utf-8 -*-
2
+
3
+ ##
4
+ # Objects of this class represent an LDAP DN ("Distinguished Name"). A DN
5
+ # ("Distinguished Name") is a unique identifier for an entry within an LDAP
6
+ # directory. It is made up of a number of other attributes strung together,
7
+ # to identify the entry in the tree.
8
+ #
9
+ # Each attribute that makes up a DN needs to have its value escaped so that
10
+ # the DN is valid. This class helps take care of that.
11
+ #
12
+ # A fully escaped DN needs to be unescaped when analysing its contents. This
13
+ # class also helps take care of that.
14
+ class Net::LDAP::DN
15
+ ##
16
+ # Initialize a DN, escaping as required. Pass in attributes in name/value
17
+ # pairs. If there is a left over argument, it will be appended to the dn
18
+ # without escaping (useful for a base string).
19
+ #
20
+ # Most uses of this class will be to escape a DN, rather than to parse it,
21
+ # so storing the dn as an escaped String and parsing parts as required
22
+ # with a state machine seems sensible.
23
+ def initialize(*args)
24
+ buffer = StringIO.new
25
+
26
+ args.each_index do |index|
27
+ buffer << "=" if index % 2 == 1
28
+ buffer << "," if index % 2 == 0 && index != 0
29
+
30
+ if index < args.length - 1 || index % 2 == 1
31
+ buffer << Net::LDAP::DN.escape(args[index])
32
+ else
33
+ buffer << args[index]
34
+ end
35
+ end
36
+
37
+ @dn = buffer.string
38
+ end
39
+
40
+ ##
41
+ # Parse a DN into key value pairs using ASN from
42
+ # http://tools.ietf.org/html/rfc2253 section 3.
43
+ def each_pair
44
+ state = :key
45
+ key = StringIO.new
46
+ value = StringIO.new
47
+ hex_buffer = ""
48
+
49
+ @dn.each_char do |char|
50
+ case state
51
+ when :key then
52
+ case char
53
+ when 'a'..'z', 'A'..'Z' then
54
+ state = :key_normal
55
+ key << char
56
+ when '0'..'9' then
57
+ state = :key_oid
58
+ key << char
59
+ when ' ' then state = :key
60
+ else raise "DN badly formed"
61
+ end
62
+ when :key_normal then
63
+ case char
64
+ when '=' then state = :value
65
+ when 'a'..'z', 'A'..'Z', '0'..'9', '-', ' ' then key << char
66
+ else raise "DN badly formed"
67
+ end
68
+ when :key_oid then
69
+ case char
70
+ when '=' then state = :value
71
+ when '0'..'9', '.', ' ' then key << char
72
+ else raise "DN badly formed"
73
+ end
74
+ when :value then
75
+ case char
76
+ when '\\' then state = :value_normal_escape
77
+ when '"' then state = :value_quoted
78
+ when ' ' then state = :value
79
+ when '#' then
80
+ state = :value_hexstring
81
+ value << char
82
+ when ',' then
83
+ state = :key
84
+ yield key.string.strip, value.string.rstrip
85
+ key = StringIO.new
86
+ value = StringIO.new;
87
+ else
88
+ state = :value_normal
89
+ value << char
90
+ end
91
+ when :value_normal then
92
+ case char
93
+ when '\\' then state = :value_normal_escape
94
+ when ',' then
95
+ state = :key
96
+ yield key.string.strip, value.string.rstrip
97
+ key = StringIO.new
98
+ value = StringIO.new;
99
+ else value << char
100
+ end
101
+ when :value_normal_escape then
102
+ case char
103
+ when '0'..'9', 'a'..'f', 'A'..'F' then
104
+ state = :value_normal_escape_hex
105
+ hex_buffer = char
106
+ else state = :value_normal; value << char
107
+ end
108
+ when :value_normal_escape_hex then
109
+ case char
110
+ when '0'..'9', 'a'..'f', 'A'..'F' then
111
+ state = :value_normal
112
+ value << "#{hex_buffer}#{char}".to_i(16).chr
113
+ else raise "DN badly formed"
114
+ end
115
+ when :value_quoted then
116
+ case char
117
+ when '\\' then state = :value_quoted_escape
118
+ when '"' then state = :value_end
119
+ else value << char
120
+ end
121
+ when :value_quoted_escape then
122
+ case char
123
+ when '0'..'9', 'a'..'f', 'A'..'F' then
124
+ state = :value_quoted_escape_hex
125
+ hex_buffer = char
126
+ else
127
+ state = :value_quoted;
128
+ value << char
129
+ end
130
+ when :value_quoted_escape_hex then
131
+ case char
132
+ when '0'..'9', 'a'..'f', 'A'..'F' then
133
+ state = :value_quoted
134
+ value << "#{hex_buffer}#{char}".to_i(16).chr
135
+ else raise "DN badly formed"
136
+ end
137
+ when :value_hexstring then
138
+ case char
139
+ when '0'..'9', 'a'..'f', 'A'..'F' then
140
+ state = :value_hexstring_hex
141
+ value << char
142
+ when ' ' then state = :value_end
143
+ when ',' then
144
+ state = :key
145
+ yield key.string.strip, value.string.rstrip
146
+ key = StringIO.new
147
+ value = StringIO.new;
148
+ else raise "DN badly formed"
149
+ end
150
+ when :value_hexstring_hex then
151
+ case char
152
+ when '0'..'9', 'a'..'f', 'A'..'F' then
153
+ state = :value_hexstring
154
+ value << char
155
+ else raise "DN badly formed"
156
+ end
157
+ when :value_end then
158
+ case char
159
+ when ' ' then state = :value_end
160
+ when ',' then
161
+ state = :key
162
+ yield key.string.strip, value.string.rstrip
163
+ key = StringIO.new
164
+ value = StringIO.new;
165
+ else raise "DN badly formed"
166
+ end
167
+ else raise "Fell out of state machine"
168
+ end
169
+ end
170
+
171
+ # Last pair
172
+ if [:value, :value_normal, :value_hexstring, :value_end].include? state
173
+ yield key.string.strip, value.string.rstrip
174
+ else
175
+ raise "DN badly formed"
176
+ end
177
+ end
178
+
179
+ ##
180
+ # Returns the DN as an array in the form expected by the constructor.
181
+ def to_a
182
+ a = []
183
+ self.each_pair { |key, value| a << key << value }
184
+ a
185
+ end
186
+
187
+ ##
188
+ # Return the DN as an escaped string.
189
+ def to_s
190
+ @dn
191
+ end
192
+
193
+ # http://tools.ietf.org/html/rfc2253 section 2.4 lists these exceptions
194
+ # for dn values. All of the following must be escaped in any normal string
195
+ # using a single backslash ('\') as escape.
196
+ ESCAPES = {
197
+ ',' => ',',
198
+ '+' => '+',
199
+ '"' => '"',
200
+ '\\' => '\\',
201
+ '<' => '<',
202
+ '>' => '>',
203
+ ';' => ';',
204
+ }
205
+
206
+ # Compiled character class regexp using the keys from the above hash, and
207
+ # checking for a space or # at the start, or space at the end, of the
208
+ # string.
209
+ ESCAPE_RE = Regexp.new("(^ |^#| $|[" +
210
+ ESCAPES.keys.map { |e| Regexp.escape(e) }.join +
211
+ "])")
212
+
213
+ ##
214
+ # Escape a string for use in a DN value
215
+ def self.escape(string)
216
+ string.gsub(ESCAPE_RE) { |char| "\\" + ESCAPES[char] }
217
+ end
218
+
219
+ ##
220
+ # Proxy all other requests to the string object, because a DN is mainly
221
+ # used within the library as a string
222
+ def method_missing(method, *args, &block)
223
+ @dn.send(method, *args, &block)
224
+ end
225
+ end