1 /* 2 * Copyright (c) 2002, 2014, 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 this.sockComparator = socketFactoryClass.getMethod( 90 "compare", new Class<?>[]{Object.class, Object.class}); 91 Method getDefault = socketFactoryClass.getMethod( 92 "getDefault", new Class<?>[]{}); 93 this.factory = 94 (SocketFactory)getDefault.invoke(null, new Object[]{}); 95 } catch (Exception e) { 96 // Ignore it here, the same exceptions are/will be handled by 97 // LdapPoolManager and Connection classes. 98 if (debug) { 99 System.out.println("ClientId received an exception"); 100 e.printStackTrace(); 101 } 102 } 103 } else { 104 isDefaultSockFactory = true; 105 } 106 107 // The SocketFactory field is not used in the myHash 108 // computation as there is no right way to compute the hash code 109 // for this field. There is no harm in skipping it from the hash 110 // computation 111 myHash = version + port 112 + (trace != null ? trace.hashCode() : 0) 113 + (this.hostname != null ? this.hostname.hashCode() : 0) 114 + (protocol != null ? protocol.hashCode() : 0) 115 + (ctlHash=hashCodeControls(bindCtls)); 116 } 117 118 public boolean equals(Object obj) { 119 if (!(obj instanceof ClientId)) { 120 return false; 121 } 122 123 ClientId other = (ClientId)obj; 124 125 return myHash == other.myHash 126 && version == other.version 127 && port == other.port 128 && trace == other.trace 129 && (hostname == other.hostname // null OK 130 || (hostname != null && hostname.equals(other.hostname))) 131 && (protocol == other.protocol // null OK 132 || (protocol != null && protocol.equals(other.protocol))) 133 && ctlHash == other.ctlHash 134 && (equalsControls(bindCtls, other.bindCtls)) 135 && (equalsSockFactory(other)); 136 } 137 138 public int hashCode() { 139 return myHash; 140 } 141 142 private static int hashCodeControls(Control[] c) { 143 if (c == null) { 144 return 0; 145 } 146 147 int code = 0; 148 for (int i = 0; i < c.length; i++) { 149 code = code * 31 + c[i].getID().hashCode(); 150 } 151 return code; 152 } 153 154 private static boolean equalsControls(Control[] a, Control[] b) { 155 if (a == b) { 156 return true; // both null or same 157 } 158 if (a == null || b == null) { 159 return false; // one is non-null 160 } 161 if (a.length != b.length) { 162 return false; 163 } 164 165 for (int i = 0; i < a.length; i++) { 166 if (!a[i].getID().equals(b[i].getID()) 167 || a[i].isCritical() != b[i].isCritical() 168 || !Arrays.equals(a[i].getEncodedValue(), 169 b[i].getEncodedValue())) { 170 return false; 171 } 172 } 173 return true; 174 } 175 176 private boolean equalsSockFactory(ClientId other) { 177 if (this.isDefaultSockFactory && other.isDefaultSockFactory) { 178 return true; 179 } 180 else if (!other.isDefaultSockFactory) { 181 return invokeComparator(other, this); 182 } else { 183 return invokeComparator(this, other); 184 } 185 } 186 187 // delegate the comparison work to the SocketFactory class 188 // as there is no enough information here, to do the comparison 189 private boolean invokeComparator(ClientId c1, ClientId c2) { 190 Object ret; 191 try { 192 ret = (c1.sockComparator).invoke( 193 c1.factory, c1.socketFactory, c2.socketFactory); 194 } catch(Exception e) { 195 if (debug) { 196 System.out.println("ClientId received an exception"); 197 e.printStackTrace(); 198 } 199 // Failed to invoke the comparator; flag unequality 200 return false; 201 } 202 if (((Integer) ret) == 0) { 203 return true; 204 } 205 return false; 206 } 207 208 private static String toStringControls(Control[] ctls) { 209 if (ctls == null) { 210 return ""; 211 } 212 StringBuilder str = new StringBuilder(); 213 for (int i = 0; i < ctls.length; i++) { 214 str.append(ctls[i].getID()); 215 str.append(' '); 216 } 217 return str.toString(); 218 } 219 220 public String toString() { 221 return (hostname + ":" + port + ":" + 222 (protocol != null ? protocol : "") + ":" + 223 toStringControls(bindCtls) + ":" + 224 socketFactory); 225 } 226 }