net-ldap 0.1.1 → 0.2

Sign up to get free protection for your applications and to get access to all the features.

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