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 }