1 /* 2 * Copyright (c) 2002, 2011, Oracle and/or its affiliates. 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.jndi.ldap; 27 28 import java.util.Locale; 29 import java.util.Arrays; // JDK 1.2 30 import java.io.OutputStream; 31 import javax.naming.ldap.Control; 32 import java.lang.reflect.Method; 33 import javax.net.SocketFactory; 34 35 /** 36 * Represents identity information about an anonymous LDAP connection. 37 * This base class contains the following information: 38 * - protocol version number 39 * - server's hostname (case-insensitive) 40 * - server's port number 41 * - prototype type (plain or ssl) 42 * - controls to be sent with the LDAP bind request 43 * 44 * All other identity classes must be a subclass of ClientId. 45 * Identity subclasses would add more distinguishing information, depending 46 * on the type of authentication that the connection is to have. 47 * 48 * The equals() and hashCode() methods of this class and its subclasses are 49 * important because they are used to determine whether two requests for 50 * the same connection are identical, and thus whether the same connection 51 * may be shared. This is especially important for authenticated connections 52 * because a mistake would result in a serious security violation. 53 * 54 * @author Rosanna Lee 55 */ 56 class ClientId { 57 final private int version; 58 final private String hostname; 59 final private int port; 60 final private String protocol; 61 final private Control[] bindCtls; 62 final private OutputStream trace; 63 final private String socketFactory; 64 final private int myHash; 65 final private int ctlHash; 66 67 private SocketFactory factory = null; 68 private Method sockComparator = null; 69 private boolean isDefaultSockFactory = false; 70 final public static boolean debug = false; 71 72 ClientId(int version, String hostname, int port, String protocol, 73 Control[] bindCtls, OutputStream trace, String socketFactory) { 74 this.version = version; 75 this.hostname = hostname.toLowerCase(Locale.ENGLISH); // ignore case 76 this.port = port; 77 this.protocol = protocol; 78 this.bindCtls = (bindCtls != null ? bindCtls.clone() : null); 79 this.trace = trace; 80 // 81 // Needed for custom socket factory pooling 82 // 83 this.socketFactory = socketFactory; 84 if ((socketFactory != null) && 85 !socketFactory.equals(LdapCtx.DEFAULT_SSL_FACTORY)) { 86 try { 87 Class<?> socketFactoryClass = 88 Obj.helper.loadClass(socketFactory); 89 Class<?> objClass = Class.forName("java.lang.Object"); 90 this.sockComparator = socketFactoryClass.getMethod( 91 "compare", new Class<?>[]{objClass, objClass}); 92 Method getDefault = socketFactoryClass.getMethod( 93 "getDefault", new Class<?>[]{}); 94 this.factory = 95 (SocketFactory)getDefault.invoke(null, new Object[]{}); 96 } catch (Exception e) { 97 // Ignore it here, the same exceptions are/will be handled by 98 // LdapPoolManager and Connection classes. 99 if (debug) { 100 System.out.println("ClientId received an exception"); 101 e.printStackTrace(); 102 } 103 } 104 } else { 105 isDefaultSockFactory = true; 106 } 107 108 // The SocketFactory field is not used in the myHash 109 // computation as there is no right way to compute the hash code 110 // for this field. There is no harm in skipping it from the hash 111 // computation 112 myHash = version + port 113 + (trace != null ? trace.hashCode() : 0) 114 + (this.hostname != null ? this.hostname.hashCode() : 0) 115 + (protocol != null ? protocol.hashCode() : 0) 116 + (ctlHash=hashCodeControls(bindCtls)); 117 } 118 119 public boolean equals(Object obj) { 120 if (!(obj instanceof ClientId)) { 121 return false; 122 } 123 124 ClientId other = (ClientId)obj; 125 126 return myHash == other.myHash 127 && version == other.version 128 && port == other.port 129 && trace == other.trace 130 && (hostname == other.hostname // null OK 131 || (hostname != null && hostname.equals(other.hostname))) 132 && (protocol == other.protocol // null OK 133 || (protocol != null && protocol.equals(other.protocol))) 134 && ctlHash == other.ctlHash 135 && (equalsControls(bindCtls, other.bindCtls)) 136 && (equalsSockFactory(other)); 137 } 138 139 public int hashCode() { 140 return myHash; 141 } 142 143 private static int hashCodeControls(Control[] c) { 144 if (c == null) { 145 return 0; 146 } 147 148 int code = 0; 149 for (int i = 0; i < c.length; i++) { 150 code = code * 31 + c[i].getID().hashCode(); 151 } 152 return code; 153 } 154 155 private static boolean equalsControls(Control[] a, Control[] b) { 156 if (a == b) { 157 return true; // both null or same 158 } 159 if (a == null || b == null) { 160 return false; // one is non-null 161 } 162 if (a.length != b.length) { 163 return false; 164 } 165 166 for (int i = 0; i < a.length; i++) { 167 if (!a[i].getID().equals(b[i].getID()) 168 || a[i].isCritical() != b[i].isCritical() 169 || !Arrays.equals(a[i].getEncodedValue(), 170 b[i].getEncodedValue())) { 171 return false; 172 } 173 } 174 return true; 175 } 176 177 private boolean equalsSockFactory(ClientId other) { 178 if (this.isDefaultSockFactory && other.isDefaultSockFactory) { 179 return true; 180 } 181 else if (!other.isDefaultSockFactory) { 182 return invokeComparator(other, this); 183 } else { 184 return invokeComparator(this, other); 185 } 186 } 187 188 // delegate the comparison work to the SocketFactory class 189 // as there is no enough information here, to do the comparison 190 private boolean invokeComparator(ClientId c1, ClientId c2) { 191 Object ret; 192 try { 193 ret = (c1.sockComparator).invoke( 194 c1.factory, c1.socketFactory, c2.socketFactory); 195 } catch(Exception e) { 196 if (debug) { 197 System.out.println("ClientId received an exception"); 198 e.printStackTrace(); 199 } 200 // Failed to invoke the comparator; flag unequality 201 return false; 202 } 203 if (((Integer) ret) == 0) { 204 return true; 205 } 206 return false; 207 } 208 209 private static String toStringControls(Control[] ctls) { 210 if (ctls == null) { 211 return ""; 212 } 213 StringBuilder str = new StringBuilder(); 214 for (int i = 0; i < ctls.length; i++) { 215 str.append(ctls[i].getID()); 216 str.append(' '); 217 } 218 return str.toString(); 219 } 220 221 public String toString() { 222 return (hostname + ":" + port + ":" + 223 (protocol != null ? protocol : "") + ":" + 224 toStringControls(bindCtls) + ":" + 225 socketFactory); 226 } 227 }