J. Duke | 319a3b9 | 2007-12-01 00:00:00 +0000 | [diff] [blame^] | 1 | /* |
| 2 | * Copyright 2002-2003 Sun Microsystems, Inc. All Rights Reserved. |
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| 4 | * |
| 5 | * This code is free software; you can redistribute it and/or modify it |
| 6 | * under the terms of the GNU General Public License version 2 only, as |
| 7 | * published by the Free Software Foundation. Sun designates this |
| 8 | * particular file as subject to the "Classpath" exception as provided |
| 9 | * by Sun in the LICENSE file that accompanied this code. |
| 10 | * |
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT |
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| 14 | * version 2 for more details (a copy is included in the LICENSE file that |
| 15 | * accompanied this code). |
| 16 | * |
| 17 | * You should have received a copy of the GNU General Public License version |
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, |
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| 20 | * |
| 21 | * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, |
| 22 | * CA 95054 USA or visit www.sun.com if you need additional information or |
| 23 | * have any questions. |
| 24 | */ |
| 25 | |
| 26 | package com.sun.jndi.ldap; |
| 27 | |
| 28 | import java.util.Arrays; |
| 29 | import java.util.Enumeration; |
| 30 | import java.util.Hashtable; |
| 31 | import java.util.NoSuchElementException; |
| 32 | import java.util.Random; |
| 33 | import java.util.StringTokenizer; |
| 34 | import java.util.List; |
| 35 | |
| 36 | import javax.naming.*; |
| 37 | import javax.naming.directory.*; |
| 38 | import javax.naming.spi.NamingManager; |
| 39 | import javax.naming.ldap.LdapName; |
| 40 | import javax.naming.ldap.Rdn; |
| 41 | |
| 42 | import com.sun.jndi.ldap.LdapURL; |
| 43 | |
| 44 | /** |
| 45 | * This class discovers the location of LDAP services by querying DNS. |
| 46 | * See http://www.ietf.org/internet-drafts/draft-ietf-ldapext-locate-07.txt |
| 47 | */ |
| 48 | |
| 49 | class ServiceLocator { |
| 50 | |
| 51 | private static final String SRV_RR = "SRV"; |
| 52 | |
| 53 | private static final String[] SRV_RR_ATTR = new String[]{SRV_RR}; |
| 54 | |
| 55 | private static final Random random = new Random(); |
| 56 | |
| 57 | private ServiceLocator() { |
| 58 | } |
| 59 | |
| 60 | /** |
| 61 | * Maps a distinguished name (RFC 2253) to a fully qualified domain name. |
| 62 | * Processes a sequence of RDNs having a DC attribute. |
| 63 | * The special RDN "DC=." denotes the root of the domain tree. |
| 64 | * Multi-valued RDNs, non-DC attributes, binary-valued attributes and the |
| 65 | * RDN "DC=." all reset the domain name and processing continues. |
| 66 | * |
| 67 | * @param dn A string distinguished name (RFC 2253). |
| 68 | * @return A domain name or null if none can be derived. |
| 69 | * @throw InvalidNameException If the distinugished name is invalid. |
| 70 | */ |
| 71 | static String mapDnToDomainName(String dn) throws InvalidNameException { |
| 72 | if (dn == null) { |
| 73 | return null; |
| 74 | } |
| 75 | StringBuffer domain = new StringBuffer(); |
| 76 | LdapName ldapName = new LdapName(dn); |
| 77 | |
| 78 | // process RDNs left-to-right |
| 79 | //List<Rdn> rdnList = ldapName.getRdns(); |
| 80 | |
| 81 | List rdnList = ldapName.getRdns(); |
| 82 | for (int i = rdnList.size() - 1; i >= 0; i--) { |
| 83 | //Rdn rdn = rdnList.get(i); |
| 84 | Rdn rdn = (Rdn) rdnList.get(i); |
| 85 | |
| 86 | // single-valued RDN with a DC attribute |
| 87 | if ((rdn.size() == 1) && |
| 88 | ("dc".equalsIgnoreCase(rdn.getType()) )) { |
| 89 | Object attrval = rdn.getValue(); |
| 90 | if (attrval instanceof String) { |
| 91 | if (attrval.equals(".") || |
| 92 | (domain.length() == 1 && domain.charAt(0) == '.')) { |
| 93 | domain.setLength(0); // reset (when current or previous |
| 94 | // RDN value is "DC=.") |
| 95 | } |
| 96 | if (domain.length() > 0) { |
| 97 | domain.append('.'); |
| 98 | } |
| 99 | domain.append(attrval); |
| 100 | } else { |
| 101 | domain.setLength(0); // reset (when binary-valued attribute) |
| 102 | } |
| 103 | } else { |
| 104 | domain.setLength(0); // reset (when multi-valued RDN or non-DC) |
| 105 | } |
| 106 | } |
| 107 | return (domain.length() != 0) ? domain.toString() : null; |
| 108 | } |
| 109 | |
| 110 | /** |
| 111 | * Locates the LDAP service for a given domain. |
| 112 | * Queries DNS for a list of LDAP Service Location Records (SRV) for a |
| 113 | * given domain name. |
| 114 | * |
| 115 | * @param domainName A string domain name. |
| 116 | * @param environment The possibly null environment of the context. |
| 117 | * @return An ordered list of hostports for the LDAP service or null if |
| 118 | * the service has not been located. |
| 119 | */ |
| 120 | static String[] getLdapService(String domainName, Hashtable environment) { |
| 121 | |
| 122 | if (domainName == null || domainName.length() == 0) { |
| 123 | return null; |
| 124 | } |
| 125 | |
| 126 | String dnsUrl = "dns:///_ldap._tcp." + domainName; |
| 127 | String[] hostports = null; |
| 128 | |
| 129 | try { |
| 130 | // Create the DNS context using NamingManager rather than using |
| 131 | // the initial context constructor. This avoids having the initial |
| 132 | // context constructor call itself (when processing the URL |
| 133 | // argument in the getAttributes call). |
| 134 | Context ctx = NamingManager.getURLContext("dns", environment); |
| 135 | if (!(ctx instanceof DirContext)) { |
| 136 | return null; // cannot create a DNS context |
| 137 | } |
| 138 | Attributes attrs = |
| 139 | ((DirContext)ctx).getAttributes(dnsUrl, SRV_RR_ATTR); |
| 140 | Attribute attr; |
| 141 | |
| 142 | if (attrs != null && ((attr = attrs.get(SRV_RR)) != null)) { |
| 143 | int numValues = attr.size(); |
| 144 | int numRecords = 0; |
| 145 | SrvRecord[] srvRecords = new SrvRecord[numValues]; |
| 146 | |
| 147 | // create the service records |
| 148 | int i = 0; |
| 149 | int j = 0; |
| 150 | while (i < numValues) { |
| 151 | try { |
| 152 | srvRecords[j] = new SrvRecord((String) attr.get(i)); |
| 153 | j++; |
| 154 | } catch (Exception e) { |
| 155 | // ignore bad value |
| 156 | } |
| 157 | i++; |
| 158 | } |
| 159 | numRecords = j; |
| 160 | |
| 161 | // trim |
| 162 | if (numRecords < numValues) { |
| 163 | SrvRecord[] trimmed = new SrvRecord[numRecords]; |
| 164 | System.arraycopy(srvRecords, 0, trimmed, 0, numRecords); |
| 165 | srvRecords = trimmed; |
| 166 | } |
| 167 | |
| 168 | // Sort the service records in ascending order of their |
| 169 | // priority value. For records with equal priority, move |
| 170 | // those with weight 0 to the top of the list. |
| 171 | if (numRecords > 1) { |
| 172 | Arrays.sort(srvRecords); |
| 173 | } |
| 174 | |
| 175 | // extract the host and port number from each service record |
| 176 | hostports = extractHostports(srvRecords); |
| 177 | } |
| 178 | } catch (NamingException e) { |
| 179 | // ignore |
| 180 | } |
| 181 | return hostports; |
| 182 | } |
| 183 | |
| 184 | /** |
| 185 | * Extract hosts and port numbers from a list of SRV records. |
| 186 | * An array of hostports is returned or null if none were found. |
| 187 | */ |
| 188 | private static String[] extractHostports(SrvRecord[] srvRecords) { |
| 189 | String[] hostports = null; |
| 190 | |
| 191 | int head = 0; |
| 192 | int tail = 0; |
| 193 | int sublistLength = 0; |
| 194 | int k = 0; |
| 195 | for (int i = 0; i < srvRecords.length; i++) { |
| 196 | if (hostports == null) { |
| 197 | hostports = new String[srvRecords.length]; |
| 198 | } |
| 199 | // find the head and tail of the list of records having the same |
| 200 | // priority value. |
| 201 | head = i; |
| 202 | while (i < srvRecords.length - 1 && |
| 203 | srvRecords[i].priority == srvRecords[i + 1].priority) { |
| 204 | i++; |
| 205 | } |
| 206 | tail = i; |
| 207 | |
| 208 | // select hostports from the sublist |
| 209 | sublistLength = (tail - head) + 1; |
| 210 | for (int j = 0; j < sublistLength; j++) { |
| 211 | hostports[k++] = selectHostport(srvRecords, head, tail); |
| 212 | } |
| 213 | } |
| 214 | return hostports; |
| 215 | } |
| 216 | |
| 217 | /* |
| 218 | * Randomly select a service record in the range [head, tail] and return |
| 219 | * its hostport value. Follows the algorithm in RFC 2782. |
| 220 | */ |
| 221 | private static String selectHostport(SrvRecord[] srvRecords, int head, |
| 222 | int tail) { |
| 223 | if (head == tail) { |
| 224 | return srvRecords[head].hostport; |
| 225 | } |
| 226 | |
| 227 | // compute the running sum for records between head and tail |
| 228 | int sum = 0; |
| 229 | for (int i = head; i <= tail; i++) { |
| 230 | if (srvRecords[i] != null) { |
| 231 | sum += srvRecords[i].weight; |
| 232 | srvRecords[i].sum = sum; |
| 233 | } |
| 234 | } |
| 235 | String hostport = null; |
| 236 | |
| 237 | // If all records have zero weight, select first available one; |
| 238 | // otherwise, randomly select a record according to its weight |
| 239 | int target = (sum == 0 ? 0 : random.nextInt(sum + 1)); |
| 240 | for (int i = head; i <= tail; i++) { |
| 241 | if (srvRecords[i] != null && srvRecords[i].sum >= target) { |
| 242 | hostport = srvRecords[i].hostport; |
| 243 | srvRecords[i] = null; // make this record unavailable |
| 244 | break; |
| 245 | } |
| 246 | } |
| 247 | return hostport; |
| 248 | } |
| 249 | |
| 250 | /** |
| 251 | * This class holds a DNS service (SRV) record. |
| 252 | * See http://www.ietf.org/rfc/rfc2782.txt |
| 253 | */ |
| 254 | |
| 255 | static class SrvRecord implements Comparable { |
| 256 | |
| 257 | int priority; |
| 258 | int weight; |
| 259 | int sum; |
| 260 | String hostport; |
| 261 | |
| 262 | /** |
| 263 | * Creates a service record object from a string record. |
| 264 | * DNS supplies the string record in the following format: |
| 265 | * <pre> |
| 266 | * <Priority> " " <Weight> " " <Port> " " <Host> |
| 267 | * </pre> |
| 268 | */ |
| 269 | SrvRecord(String srvRecord) throws Exception { |
| 270 | StringTokenizer tokenizer = new StringTokenizer(srvRecord, " "); |
| 271 | String port; |
| 272 | |
| 273 | if (tokenizer.countTokens() == 4) { |
| 274 | priority = Integer.parseInt(tokenizer.nextToken()); |
| 275 | weight = Integer.parseInt(tokenizer.nextToken()); |
| 276 | port = tokenizer.nextToken(); |
| 277 | hostport = tokenizer.nextToken() + ":" + port; |
| 278 | } else { |
| 279 | throw new IllegalArgumentException(); |
| 280 | } |
| 281 | } |
| 282 | |
| 283 | /* |
| 284 | * Sort records in ascending order of priority value. For records with |
| 285 | * equal priority move those with weight 0 to the top of the list. |
| 286 | */ |
| 287 | public int compareTo(Object o) { |
| 288 | SrvRecord that = (SrvRecord) o; |
| 289 | if (priority > that.priority) { |
| 290 | return 1; // this > that |
| 291 | } else if (priority < that.priority) { |
| 292 | return -1; // this < that |
| 293 | } else if (weight == 0 && that.weight != 0) { |
| 294 | return -1; // this < that |
| 295 | } else if (weight != 0 && that.weight == 0) { |
| 296 | return 1; // this > that |
| 297 | } else { |
| 298 | return 0; // this == that |
| 299 | } |
| 300 | } |
| 301 | } |
| 302 | } |