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