Class: Net::LDAP::Connection
- Inherits:
-
Object
- Object
- Net::LDAP::Connection
- Defined in:
- lib/net/ldap.rb
Overview
This is a private class used internally by the library. It should not be called by user code.
Constant Summary collapse
- LdapVersion =
:nodoc:
3
Instance Method Summary collapse
-
#add(args) ⇒ Object
– add TODO, need to support a time limit, in case the server fails to respond.
-
#bind(auth) ⇒ Object
– bind.
-
#close ⇒ Object
– close This is provided as a convenience method to make sure a connection object gets closed without waiting for a GC to happen.
-
#delete(args) ⇒ Object
– delete TODO, need to support a time limit, in case the server fails to respond.
-
#initialize(server) {|_self| ... } ⇒ Connection
constructor
– initialize.
-
#modify(args) ⇒ Object
– modify TODO, need to support a time limit, in case the server fails to respond.
-
#next_msgid ⇒ Object
– next_msgid.
-
#rename(args) ⇒ Object
– rename TODO, need to support a time limit, in case the server fails to respond.
-
#search(args = {}) ⇒ Object
– search Alternate implementation, this yields each search entry to the caller as it are received.
-
#setup_encryption(args) ⇒ Object
– Helper method called only from new, and only after we have a successfully-opened Depending on the received arguments, we establish SSL, potentially replacing the value of @conn accordingly.
Constructor Details
#initialize(server) {|_self| ... } ⇒ Connection
– initialize
959 960 961 962 963 964 965 966 967 968 969 970 971 |
# File 'lib/net/ldap.rb', line 959 def initialize server begin @conn = TCPsocket.new( server[:host], server[:port] ) rescue raise LdapError.new( "no connection to server" ) end if server[:encryption] setup_encryption server[:encryption] end yield self if block_given? end |
Instance Method Details
#add(args) ⇒ Object
– add TODO, need to support a time limit, in case the server fails to respond.
1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 |
# File 'lib/net/ldap.rb', line 1196 def add args add_dn = args[:dn] or raise LdapError.new("Unable to add empty DN") add_attrs = [] a = args[:attributes] and a.each {|k,v| add_attrs << [ k.to_s.to_ber, v.to_a.map {|m| m.to_ber}.to_ber_set ].to_ber_sequence } request = [add_dn.to_ber, add_attrs.to_ber_sequence].to_ber_appsequence(8) pkt = [next_msgid.to_ber, request].to_ber_sequence @conn.write pkt (be = @conn.read_ber(AsnSyntax)) && (pdu = LdapPdu.new( be )) && (pdu.app_tag == 9) or raise LdapError.new( "response missing or invalid" ) pdu.result_code end |
#bind(auth) ⇒ Object
– bind
1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 |
# File 'lib/net/ldap.rb', line 1032 def bind auth user,psw = case auth[:method] when :anonymous ["",""] when :simple [auth[:username] || auth[:dn], auth[:password]] end raise LdapError.new( "invalid binding information" ) unless (user && psw) msgid = next_msgid.to_ber request = [LdapVersion.to_ber, user.to_ber, psw.to_ber_contextspecific(0)].to_ber_appsequence(0) request_pkt = [msgid, request].to_ber_sequence @conn.write request_pkt (be = @conn.read_ber(AsnSyntax) and pdu = Net::LdapPdu.new( be )) or raise LdapError.new( "no bind result" ) pdu.result_code end |
#close ⇒ Object
– close This is provided as a convenience method to make sure a connection object gets closed without waiting for a GC to happen. Clients shouldn’t have to call it, but perhaps it will come in handy someday.
1015 1016 1017 1018 |
# File 'lib/net/ldap.rb', line 1015 def close @conn.close @conn = nil end |
#delete(args) ⇒ Object
– delete TODO, need to support a time limit, in case the server fails to respond.
1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 |
# File 'lib/net/ldap.rb', line 1234 def delete args dn = args[:dn] or raise "Unable to delete empty DN" request = dn.to_s.to_ber_application_string(10) pkt = [next_msgid.to_ber, request].to_ber_sequence @conn.write pkt (be = @conn.read_ber(AsnSyntax)) && (pdu = LdapPdu.new( be )) && (pdu.app_tag == 11) or raise LdapError.new( "response missing or invalid" ) pdu.result_code end |
#modify(args) ⇒ Object
– modify TODO, need to support a time limit, in case the server fails to respond. TODO!!! We’re throwing an exception here on empty DN. Should return a proper error instead, probaby from farther up the chain. TODO!!! If the user specifies a bogus opcode, we’ll throw a confusing error here (“to_ber_enumerated is not defined on nil”).
1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 |
# File 'lib/net/ldap.rb', line 1173 def modify args modify_dn = args[:dn] or raise "Unable to modify empty DN" modify_ops = [] a = args[:operations] and a.each {|op, attr, values| # TODO, fix the following line, which gives a bogus error # if the opcode is invalid. op_1 = {:add => 0, :delete => 1, :replace => 2} [op.to_sym].to_ber_enumerated modify_ops << [op_1, [attr.to_s.to_ber, values.to_a.map {|v| v.to_ber}.to_ber_set].to_ber_sequence].to_ber_sequence } request = [modify_dn.to_ber, modify_ops.to_ber_sequence].to_ber_appsequence(6) pkt = [next_msgid.to_ber, request].to_ber_sequence @conn.write pkt (be = @conn.read_ber(AsnSyntax)) && (pdu = LdapPdu.new( be )) && (pdu.app_tag == 7) or raise LdapError.new( "response missing or invalid" ) pdu.result_code end |
#next_msgid ⇒ Object
– next_msgid
1023 1024 1025 1026 |
# File 'lib/net/ldap.rb', line 1023 def next_msgid @msgid ||= 0 @msgid += 1 end |
#rename(args) ⇒ Object
– rename TODO, need to support a time limit, in case the server fails to respond.
1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 |
# File 'lib/net/ldap.rb', line 1216 def rename args old_dn = args[:olddn] or raise "Unable to rename empty DN" new_rdn = args[:newrdn] or raise "Unable to rename to empty RDN" delete_attrs = args[:delete_attributes] ? true : false request = [old_dn.to_ber, new_rdn.to_ber, delete_attrs.to_ber].to_ber_appsequence(12) pkt = [next_msgid.to_ber, request].to_ber_sequence @conn.write pkt (be = @conn.read_ber(AsnSyntax)) && (pdu = LdapPdu.new( be )) && (pdu.app_tag == 13) or raise LdapError.new( "response missing or invalid" ) pdu.result_code end |
#search(args = {}) ⇒ Object
– search Alternate implementation, this yields each search entry to the caller as it are received. TODO, certain search parameters are hardcoded. TODO, if we mis-parse the server results or the results are wrong, we can block forever. That’s because we keep reading results until we get a type-5 packet, which might never come. We need to support the time-limit in the protocol. – WARNING: this code substantially recapitulates the searchx method.
02May06: Well, I added support for RFC-2696-style paged searches. This is used on all queries because the extension is marked non-critical. As far as I know, only A/D uses this, but it’s required for A/D. Otherwise you won’t get more than 1000 results back from a query. This implementation is kindof clunky and should probably be refactored. Also, is it my imagination, or are A/Ds the slowest directory servers ever???
1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 |
# File 'lib/net/ldap.rb', line 1068 def search args = {} search_filter = (args && args[:filter]) || Filter.eq( "objectclass", "*" ) search_base = (args && args[:base]) || "dc=example,dc=com" search_attributes = ((args && args[:attributes]) || []).map {|attr| attr.to_s.to_ber} return_referrals = args && args[:return_referrals] == true attributes_only = (args and args[:attributes_only] == true) scope = args[:scope] || Net::LDAP::SearchScope_WholeSubtree raise LdapError.new( "invalid search scope" ) unless SearchScopes.include?(scope) # An interesting value for the size limit would be close to A/D's built-in # page limit of 1000 records, but openLDAP newer than version 2.2.0 chokes # on anything bigger than 126. You get a silent error that is easily visible # by running slapd in debug mode. Go figure. = [126, ""] result_code = 0 loop { # should collect this into a private helper to clarify the structure request = [ search_base.to_ber, scope.to_ber_enumerated, 0.to_ber_enumerated, 0.to_ber, 0.to_ber, attributes_only.to_ber, search_filter.to_ber, search_attributes.to_ber_sequence ].to_ber_appsequence(3) controls = [ [ LdapControls::PagedResults.to_ber, false.to_ber, # criticality MUST be false to interoperate with normal LDAPs. .map{|v| v.to_ber}.to_ber_sequence.to_s.to_ber ].to_ber_sequence ].to_ber_contextspecific(0) pkt = [next_msgid.to_ber, request, controls].to_ber_sequence @conn.write pkt result_code = 0 controls = [] while (be = @conn.read_ber(AsnSyntax)) && (pdu = LdapPdu.new( be )) case pdu.app_tag when 4 # search-data yield( pdu.search_entry ) if block_given? when 19 # search-referral if return_referrals if block_given? se = Net::LDAP::Entry.new se[:search_referrals] = (pdu.search_referrals || []) yield se end end #p pdu.referrals when 5 # search-result result_code = pdu.result_code controls = pdu.result_controls break else raise LdapError.new( "invalid response-type in search: #{pdu.app_tag}" ) end end # When we get here, we have seen a type-5 response. # If there is no error AND there is an RFC-2696 cookie, # then query again for the next page of results. # If not, we're done. # Don't screw this up or we'll break every search we do. more_pages = false if result_code == 0 and controls controls.each do |c| if c.oid == LdapControls::PagedResults more_pages = false # just in case some bogus server sends us >1 of these. if c.value and c.value.length > 0 = c.value.read_ber[1] if and .length > 0 [1] = more_pages = true end end end end end break unless more_pages } # loop result_code end |
#setup_encryption(args) ⇒ Object
– Helper method called only from new, and only after we have a successfully-opened Depending on the received arguments, we establish SSL, potentially replacing the value of @conn accordingly. Don’t generate any errors here if no encryption is requested. DO raise LdapError objects if encryption is requested and we have trouble setting it up. That includes if OpenSSL is not set up on the machine. (Question: how does the Ruby OpenSSL wrapper react in that case?) DO NOT filter exceptions raised by the OpenSSL library. Let them pass back to the user. That should make it easier for us to debug the problem reports. Presumably (hopefully?) that will also produce recognizable errors if someone tries to use this on a machine without OpenSSL.
The simple_tls method is intended as the simplest, stupidest, easiest solution for people who want nothing more than encrypted comms with the LDAP server. It doesn’t do any server-cert validation and requires nothing in the way of key files and root-cert files, etc etc. OBSERVE: WE REPLACE the value of @conn, which is presumed to be a connected TCPsocket object.
995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 |
# File 'lib/net/ldap.rb', line 995 def setup_encryption args case args[:method] when :simple_tls raise LdapError.new("openssl unavailable") unless $net_ldap_openssl_available ctx = OpenSSL::SSL::SSLContext.new @conn = OpenSSL::SSL::SSLSocket.new(@conn, ctx) @conn.connect @conn.sync_close = true # additional branches requiring server validation and peer certs, etc. go here. else raise LdapError.new( "unsupported encryption method #{args[:method]}" ) end end |