All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.novell.ldap.Connection Maven / Gradle / Ivy

/* **************************************************************************
 * $OpenLDAP$
 *
 * Copyright (C) 1999 - 2002 Novell, Inc. All Rights Reserved.
 *
 * THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND
 * TREATIES. USE, MODIFICATION, AND REDISTRIBUTION OF THIS WORK IS SUBJECT
 * TO VERSION 2.0.1 OF THE OPENLDAP PUBLIC LICENSE, A COPY OF WHICH IS
 * AVAILABLE AT HTTP://WWW.OPENLDAP.ORG/LICENSE.HTML OR IN THE FILE "LICENSE"
 * IN THE TOP-LEVEL DIRECTORY OF THE DISTRIBUTION. ANY USE OR EXPLOITATION
 * OF THIS WORK OTHER THAN AS AUTHORIZED IN VERSION 2.0.1 OF THE OPENLDAP
 * PUBLIC LICENSE, OR OTHER PRIOR WRITTEN CONSENT FROM NOVELL, COULD SUBJECT
 * THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY.
 ******************************************************************************/

package com.novell.ldap;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;


import com.novell.ldap.asn1.*;
import com.novell.ldap.client.*;
import com.novell.ldap.rfc2251.*;
import com.novell.ldap.resources.*;

/**
 * The class that creates a connection to the LDAP server. After the
 * connection is made, a thread is created that reads data from the
 * connection.
 * 

* The application's thread sends a request to the MessageAgent class, which * creates a Message class. The Message class calls the writeMessage method * of this class to send the request to the server. The application thread * will then query the MessageAgent class for a response. *

* The reader thread multiplexes response messages received from the * server to the appropriate Message class. Each Message class * has its own message queue. *

* Unsolicited messages are process separately, and if the application * has registered a handler, a separate thread is created for that * application's handler to process the message. *

* Note: the reader thread must not be a "selfish" thread, since some * operating systems do not time slice. * */ /*package*/ final class Connection { private Object writeSemaphore = new Object(); private int writeSemaphoreOwner = 0; private int writeSemaphoreCount = 0; // We need a message number for disconnect to grab the semaphore, // but may not have one, so we invent a unique one. private int ephemeralId = -1; private BindProperties bindProperties = null; private int bindSemaphoreId = 0; // 0 is never used by to lock a semaphore private Thread reader = null; // New thread that reads data from the server. private Thread deadReader = null; // Identity of last reader thread private IOException deadReaderException = null; // Last exception of reader private LBEREncoder encoder = new LBEREncoder(); private LBERDecoder decoder = new LBERDecoder(); /* * socket is the current socket being used. * nonTLSBackup is the backup socket if startTLS is called. * if nonTLSBackup is null then startTLS has not been called, * or stopTLS has been called to end TLS protection */ private Socket socket = null; private Socket nonTLSBackup = null; private InputStream in = null; private OutputStream out = null; // When set to true the client connection is up and running private boolean clientActive = true; // Indicates we have received a server shutdown unsolicited notification private boolean unsolSvrShutDnNotification = false; // LDAP message IDs are all positive numbers so we can use negative // numbers as flags. This are flags assigned to stopReaderMessageID // to tell the reader what state we are in. private final static int CONTINUE_READING = -99; private final static int STOP_READING = -98; // Stops the reader thread when a Message with the passed-in ID is read. // This parameter is set by stopReaderOnReply and stopTLS private int stopReaderMessageID = CONTINUE_READING; // Place to save message information classes private MessageVector messages = new MessageVector(5,5); // Connection created to follow referral private ReferralInfo activeReferral = null; // Place to save unsolicited message listeners private java.util.Vector unsolicitedListeners = new java.util.Vector(3,3); // The LDAPSocketFactory to be used as the default to create new connections static private LDAPSocketFactory socketFactory = null; // The LDAPSocketFactory used for this connection private LDAPSocketFactory mySocketFactory = null; private int myTimeOut = 0; private String host = null; private int port = 0; // Number of clones in addition to original LDAPConnection using this // connection. private int cloneCount = 0; // Connection number & name used only for debug private String name = ""; private static Object nameLock = new Object(); // protect connNum private static int connNum = 0; // These attributes can be retreived using the getProperty // method in LDAPConnection. Future releases might require // these to be local variables that can be modified using // the setProperty method. /* package */ static String sdk = new String("4.6"); /* package */ static Integer protocol = new Integer(3); /* package */ static String security = "simple"; /** * Create a new Connection object * * @param factory specifies the factory to use to produce SSL sockets. */ /* package */ Connection( LDAPSocketFactory factory) { if( factory != null) { /* verify the 'setFactory' permision is set */ SecurityManager security = System.getSecurityManager(); if (security != null){ security.checkSetFactory(); } // save socket factory mySocketFactory = factory; } else { mySocketFactory = socketFactory; } if( Debug.LDAP_DEBUG) { synchronized(nameLock) { name = "Connection(" + ++connNum + "): "; } Debug.trace( Debug.messages, name + "Created"); } return; } /** * Copy this Connection object. * *

This is not a true clone, but creates a new object encapsulating * part of the connection information from the original object. * The new object will have the same default socket factory, * designated socket factory, host, port, and protocol version * as the original object. * The new object is NOT be connected to the host.

* * @return a shallow copy of this object */ /* package */ Object copy() { Connection c = new Connection(this.mySocketFactory); c.host = this.host; c.port = this.port; c.protocol = this.protocol; return c; } /** * Create a new Connection object * * @param timeout specifies the socket timeout to be used when the server is stalled. */ Connection( int timeout) { myTimeOut = timeout; mySocketFactory = socketFactory; if( Debug.LDAP_DEBUG) { synchronized(nameLock) { name = "Connection(" + ++connNum + "): "; } Debug.trace( Debug.messages, name + "Created"); } return; } /** * Copy this Connection object. * *

This is not a true clone, but creates a new object encapsulating * part of the connection information from the original object. * The new object will have the same default socket factory, * designated socket factory, host, port, and protocol version * as the original object. * The new object is NOT be connected to the host.

* * @return a shallow copy of this object */ /* package */ Object copy_timeout() { Connection c = new Connection(this.myTimeOut); c.host = this.host; c.port = this.port; c.protocol = this.protocol; return c; } /** * Acquire a simple counting semaphore that synchronizes state affecting * bind. This method generates an ephemeral message id (negative number). * * We bind using the message ID because a different thread may unlock * the semaphore than the one that set it. It is cleared when the * response to the bind is processed, or when the bind operation times out. * * Returns when the semaphore is acquired * * @return the ephemeral message id that identifies semaphore's owner */ /* package */ final int acquireWriteSemaphore() { return acquireWriteSemaphore(0); } /** * Acquire a simple counting semaphore that synchronizes state affecting * bind. The semaphore is held by setting a value in writeSemaphoreOwner. * * We bind using the message ID because a different thread may unlock * the semaphore than the one that set it. It is cleared when the * response to the bind is processed, or when the bind operation times out. * Returns when the semaphore is acquired. * * @param msgId a value that identifies the owner of this semaphore. A * value of zero means assign a unique semaphore value. * * @return the semaphore value used to acquire the lock */ /* package */ final int acquireWriteSemaphore(int msgId) { int id = msgId; synchronized( writeSemaphore) { if( id == 0) { ephemeralId = ((ephemeralId == Integer.MIN_VALUE) ? (ephemeralId = -1) : --ephemeralId); id = ephemeralId; } while( true) { if( writeSemaphoreOwner == 0) { // we have acquired the semahpore writeSemaphoreOwner = id; break; } else { if( writeSemaphoreOwner == id) { // we already own the semahpore break; } try { // Keep trying for the lock writeSemaphore.wait(); continue; } catch( InterruptedException ex) { // Keep trying for the lock continue; } } } writeSemaphoreCount++; } if( Debug.LDAP_DEBUG) { Debug.trace( Debug.bindSemaphore, name + "Acquired Socket Write Semaphore(" + id + ") count " + writeSemaphoreCount); } return id; } /** * Release a simple counting semaphore that synchronizes state affecting * bind. Frees the semaphore when number of acquires and frees for this * thread match. * * @param msgId a value that identifies the owner of this semaphore */ /* package */ final void freeWriteSemaphore(int msgId) { if( Debug.LDAP_DEBUG) { Debug.trace( Debug.bindSemaphore, name + "Free'd Socket Write Semaphore(" + msgId + ") count " + (writeSemaphoreCount - 1)); } synchronized( writeSemaphore) { if( writeSemaphoreOwner == 0) { throw new RuntimeException("Connection.freeWriteSemaphore(" + msgId + "): semaphore not owned by any thread"); } else if( writeSemaphoreOwner != msgId) { throw new RuntimeException("Connection.freeWriteSemaphore(" + msgId + "): thread does not own the semaphore, owned by " + writeSemaphoreOwner); } // if all instances of this semaphore for this thread are released, // wake up all threads waiting. if( --writeSemaphoreCount == 0) { writeSemaphoreOwner = 0; writeSemaphore.notify(); } } return; } /* * Wait until the reader thread ID matches the specified parameter. * Null = wait for the reader to terminate * Non Null = wait for the reader to start * Returns when the ID matches, i.e. reader stopped, or reader started. * * @param the thread id to match */ private void waitForReader( Thread thread) throws LDAPException { // wait for previous reader thread to terminate while( reader != thread) { // Don't initialize connection while previous reader thread still // active. try { if( Debug.LDAP_DEBUG) { if( thread == null) { Debug.trace( Debug.messages, name + "waiting for reader thread to exit"); } else { Debug.trace( Debug.messages, name + "waiting for reader thread to start"); } } /* * The reader thread may start and immediately terminate. * To prevent the waitForReader from waiting forever * for the dead to rise, we leave traces of the deceased. * If the thread is already gone, we throw an exception. */ if( thread == deadReader) { if (thread == null) /* then we wanted a shutdown */ return; if( Debug.LDAP_DEBUG) { Debug.trace( Debug.messages, name + "reader already terminated, throw exception"); } IOException lex = deadReaderException; deadReaderException = null; deadReader = null; // Reader thread terminated throw new LDAPException( ExceptionMessages.CONNECTION_READER, LDAPException.CONNECT_ERROR, null, lex); } synchronized( this) { this.wait(5); } } catch ( InterruptedException ex) { ; } } deadReaderException = null; deadReader = null; return; } /** * Constructs a TCP/IP connection to a server specified in host and port. * * @param host The host to connect to. *

* @param port The port on the host to connect to. */ /* package */ void connect(String host, int port) throws LDAPException { connect( host, port, 0); return; } /** * Constructs a TCP/IP connection to a server specified in host and port. * Starts the reader thread. * * @param host The host to connect to. *

* @param port The port on the host to connect to. *

* @param semaphoreId The write semaphore ID to use for the connect */ private void connect(String host, int port, int semaphoreId) throws LDAPException { /* Synchronized so all variables are in a consistant state and * so that another thread isn't doing a connect, disconnect, or clone * at the same time. */ if( Debug.LDAP_DEBUG) { Debug.trace( Debug.messages, name + "connect(" + host + "," + port + ")"); } // Wait for active reader to terminate waitForReader(null); // Clear the server shutdown notification flag. This should already // be false unless of course we are reusing the same Connection object // after a server shutdown notification unsolSvrShutDnNotification = false; int semId = acquireWriteSemaphore( semaphoreId); // Make socket connection to specified host and port if( port == 0) { port = LDAPConnection.DEFAULT_PORT; } try { if( (in == null) || (out == null) ) { if(mySocketFactory != null) { if( Debug.LDAP_DEBUG) { Debug.trace( Debug.messages, name + "connect(socketFactory specified)"); } socket = mySocketFactory.createSocket(host, port); } else { socket = new Socket(host, port); if(myTimeOut > 0) { socket.setSoTimeout(myTimeOut); } } in = socket.getInputStream(); out = socket.getOutputStream(); } else { if( Debug.LDAP_DEBUG) { Debug.trace( Debug.messages, name + "connect(input/out Stream specified)"); } } }catch(IOException ioe) { // Unable to connect to server host:port freeWriteSemaphore(semId); throw new LDAPException( ExceptionMessages.CONNECTION_ERROR, new Object[] { host, new Integer(port) }, LDAPException.CONNECT_ERROR, null, ioe); } // Set host and port this.host = host; this.port = port; // start the reader thread this.startReader(); freeWriteSemaphore(semId); if( Debug.LDAP_DEBUG) { Debug.trace( Debug.messages, name + " connect: setup complete"); } clientActive = true; // Client is up return; } /** * Indicates whether clones exist for LDAPConnection * * @return true if clones exist, false otherwise. */ /* package */ final boolean isCloned() { return( cloneCount > 0); } /** * Increments the count of cloned connections */ /* package */ synchronized final void incrCloneCount() { cloneCount++; if( Debug.LDAP_DEBUG) { Debug.trace( Debug.messages, name + "incrCloneCount(" + cloneCount + ")"); } return; } /** * Destroys a clone of LDAPConnection. * *

This method first determines if only one LDAPConnection * object is associated with this connection, i.e. if no clone exists.

* *

If no clone exists, the socket is closed, and the current * Connection object is returned.

* *

If multiple LDAPConnection objects are associated * with this connection, i.e. clones exist, a {@link #copy} of the * this object is made, but is not connected to any host. This * disassociates that clone from the original connection. The new * Connection object is returned. * *

Only one destroyClone instance is allowed to run at any one time.

* *

If the connection is closed, any threads waiting for operations * on that connection will wake with an LDAPException indicating * the connection is closed.

* * @param apiCall true indicates the application is closing the * connection or or creating a new one by calling either the * connect or disconnect methods * of LDAPConnection. false * indicates that LDAPConnection is being finalized. * * @return a Connection object or null if finalizing. */ /* package */ synchronized final Connection destroyClone( boolean apiCall) { if( Debug.LDAP_DEBUG) { Debug.trace( Debug.messages, name + "destroyClone(" + apiCall + ")"); } Connection conn = this; if( cloneCount > 0) { cloneCount--; // This is a clone, set a new connection object. if( Debug.LDAP_DEBUG) { Debug.trace( Debug.messages, name + "destroyClone(" + cloneCount + ") create new connection"); } if( apiCall) { conn = (Connection)this.copy(); } else { conn = null; } } else { if( in != null) { // Not a clone and connected if( Debug.LDAP_DEBUG) { Debug.trace( Debug.messages, name + "destroyClone(" + cloneCount + ") destroy old connection"); } /* * Either the application has called disconnect or connect * resulting in the current connection being closed. If the * application has any queues waiting on messages, we * need wake these up so the application does not hang. * The boolean flag indicates whether the close came * from an API call or from the object being finalized. */ InterThreadException notify = new InterThreadException( (apiCall ? ExceptionMessages.CONNECTION_CLOSED : ExceptionMessages.CONNECTION_FINALIZED), null, LDAPException.CONNECT_ERROR, null, null); // Destroy old connection shutdown("destroy clone", 0, notify); } } return conn; } /** * sets the default socket factory * * @param factory the default factory to set */ /* package */ final static void setSocketFactory( LDAPSocketFactory factory) { /* verify the 'setFactory' permision is set */ SecurityManager security = System.getSecurityManager(); if (security != null){ security.checkSetFactory(); } socketFactory = factory; return; } /** * gets the socket factory used for this connection * * @return the default factory for this connection */ /* package */ final LDAPSocketFactory getSocketFactory() { return mySocketFactory; } /** * gets the host used for this connection */ /* package */ final String getHost() { return host; } /** * gets the port used for this connection */ /* package */ final int getPort() { return port; } /** * gets the writeSemaphore id used for active bind operation */ /* package */ int getBindSemId() { return bindSemaphoreId; } /** * sets the writeSemaphore id used for active bind operation */ /* package */ void setBindSemId(int id) { bindSemaphoreId = id; return; } /** * clears the writeSemaphore id used for active bind operation */ /* package */ void clearBindSemId() { bindSemaphoreId = 0; return; } /** * Gets SocketTimeOut value set. * * If not set, returns 0. * */ final int getSocketTimeOut() { return myTimeOut; } /** * Sets the SocketTimeOut value. * */ final void setSocketTimeOut(int timeout) { try { if(socket!=null) socket.setSoTimeout(timeout); myTimeOut = timeout; } catch(SocketException e) {} return; } /** * checks if the writeSemaphore id used for active bind operation is clear */ /* package */ boolean isBindSemIdClear() { if( bindSemaphoreId == 0) { return true; } return false; } /** * Writes an LDAPMessage to the LDAP server over a socket. * * @param info the Message containing the message to write. */ /* package */ void writeMessage(Message info) throws LDAPException { messages.addElement( info); // For bind requests, if not connected, attempt to reconnect if( info.isBindRequest() && (isConnected() == false) && (host != null)){ connect( host, port, info.getMessageID()); } if(isConnected()) { LDAPMessage msg = info.getRequest(); writeMessage( msg); return; } else throw new LDAPException(ExceptionMessages.CONNECTION_CLOSED, new Object[] { host, new Integer(port) }, LDAPException.CONNECT_ERROR, null,new IOException()); } /** * Writes an LDAPMessage to the LDAP server over a socket. * * @param msg the message to write. */ /* package */ void writeMessage(LDAPMessage msg) throws LDAPException { int id; // Get the correct semaphore id for bind operations if( bindSemaphoreId == 0) { // Semaphore id for normal operations id = msg.getMessageID(); } else { // Semaphore id for sasl bind operations id = bindSemaphoreId; } OutputStream myOut = out; if( Debug.LDAP_DEBUG) { Debug.trace( Debug.messages, name + "Writing Message(" + msg.getMessageID() + ")"); Debug.trace( Debug.rawInput, name + "RawWrite: " + msg.getASN1Object().toString()); } acquireWriteSemaphore(id); try { if( myOut == null) { throw new IOException("Output stream not initialized"); } byte[] ber = msg.getASN1Object().getEncoding(encoder); myOut.write(ber, 0, ber.length); myOut.flush(); } catch( IOException ioe) { if( Debug.LDAP_DEBUG ) { Debug.trace( Debug.messages, name + "I/O Exception on host" + host + ":" + port + " " + ioe.toString()); } /* * IOException could be due to a server shutdown notification which * caused our Connection to quit. If so we send back a slightly * different error message. We could have checked this a little * earlier in the method but that would be an expensive check each * time we send out a message. Since this shutdown request is * going to be an infrequent occurence we check for it only when * we get an IOException. shutdown() will do the cleanup. */ if( clientActive) { // We beliefe the connection was alive if (unsolSvrShutDnNotification) { // got server shutdown throw new LDAPException( ExceptionMessages.SERVER_SHUTDOWN_REQ, new Object[] { host, new Integer(port)}, LDAPException.CONNECT_ERROR, null, ioe); } // Other I/O Exceptions on host:port are reported as is throw new LDAPException(ExceptionMessages.IO_EXCEPTION, new Object[] {host, new Integer(port)}, LDAPException.CONNECT_ERROR, null, ioe); } } finally { freeWriteSemaphore(id); } return; } /** * Returns the message agent for this msg ID */ /* package */ final MessageAgent getMessageAgent( int msgId) throws NoSuchFieldException { Message info = messages.findMessageById( msgId); return info.getMessageAgent(); } /** * Return whether the application is bound to this connection. * Note: an anonymous bind returns false - not bound */ /* package */ final boolean isBound() { if( bindProperties != null) { // Bound if not anonymous return( ! bindProperties.isAnonymous()); } return false; } /** * Return whether a connection has been made */ /* package */ final boolean isConnected() { return (in != null); } /** * Checks whether a connection is still alive or not by sending data to * the server on this connection's socket.If the connection is not alive * the send will generate an IOException and the function will return * false. * @return true If connection is alive * false If connection is not alive. */ final boolean isConnectionAlive() { boolean isConn=false; int id; LDAPExtendedOperation op=null; if ( in!= null ) { isConn=true; op= new LDAPExtendedOperation("0.0.0.0",null); LDAPMessage msg =new LDAPExtendedRequest(op, null); id = msg.getMessageID(); acquireWriteSemaphore(id); OutputStream myOut = out; try { if( myOut == null) { throw new IOException("Output stream not initialized"); } byte[] ber = msg.getASN1Object().getEncoding(encoder); myOut.write(ber, 0, ber.length); myOut.flush(); } catch( IOException ioe) { isConn=false; } finally { freeWriteSemaphore(id); } } return isConn; } /** * Removes a Message class from the Connection's list * * @param info the Message class to remove from the list */ /* package */ final void removeMessage( Message info) { boolean done = messages.removeElement(info); if( Debug.LDAP_DEBUG) { if( done) { Debug.trace( Debug.messages, name + "Removed Message(" + info.getMessageID() + ")"); } else { Debug.trace( Debug.messages, name + "Removing Message(" + info.getMessageID() + ") - not found"); } } return; } /** * Cleans up resources associated with this connection. */ protected void finalize() { if( Debug.LDAP_DEBUG) { Debug.trace( Debug.messages, name + "finalize: shutdown connection"); } shutdown("Finalize",0, null); return; } /** * Cleans up resources associated with this connection. * This method may be called by finalize() for the connection, or it may * be called by LDAPConnection.disconnect(). * Should not have a writeSemaphore lock in place, as deadlock can occur * while abandoning connections. */ private void shutdown( String reason, int semaphoreId, InterThreadException notifyUser) { Message info = null; if( ! clientActive) { if( Debug.LDAP_DEBUG) { Debug.trace( Debug.messages, name + "shutdown: already shutdown - " + reason); } return; } if( Debug.LDAP_DEBUG) { Debug.trace( Debug.messages, name + "shutdown: Shutting down connection - " + reason); } clientActive = false; while( true ) { // remove messages from connection list and send abandon try { info = (Message)messages.remove(0); if( Debug.LDAP_DEBUG) { Debug.trace( Debug.messages, name + "Shutdown removed message(" + info.getMessageID() + ")"); } } catch( ArrayIndexOutOfBoundsException ex) { // No more messages if( Debug.LDAP_DEBUG) { Debug.trace( Debug.messages, name + "Shutdown no messages to remove"); } break; } info.abandon( null, notifyUser); // also notifies the application } int semId = acquireWriteSemaphore( semaphoreId); // Now send unbind if socket not closed if( (bindProperties != null) && (out != null) && (! bindProperties.isAnonymous())) { try { LDAPMessage msg = new LDAPUnbindRequest( null); if( Debug.LDAP_DEBUG) { Debug.trace( Debug.messages, name + "Writing unbind request (" + msg.getMessageID() + ")"); Debug.trace( Debug.rawInput, name + "RawWrite: " + msg.getASN1Object().toString()); } byte[] ber = msg.getASN1Object().getEncoding(encoder); out.write(ber, 0, ber.length); out.flush(); } catch( Exception ex) { ; // don't worry about error } } bindProperties = null; in = null; out = null; if( socket != null) { // Close the socket try { socket.close(); } catch(java.io.IOException ie) { // ignore problem closing socket } socket = null; } // wait until reader threads stops completely try { if (reader!= Thread.currentThread()) reader.join(); // reader.join(); reader=null; } catch(InterruptedException iex) { ; } catch(NullPointerException npe) { ; } freeWriteSemaphore( semId); return; } /** * * Sets the authentication credentials in the object * and set flag indicating successful bind. * * *

* @param bindProps The BindProperties object to set. */ /* package */ final void setBindProperties( BindProperties bindProps) { bindProperties = bindProps; return; } /** * * Sets the authentication credentials in the object * and set flag indicating successful bind. * * *

* @return The BindProperties object for this connection. */ /* package */ final BindProperties getBindProperties() { return bindProperties; } /** * This tests to see if there are any outstanding messages. If no messages * are in the queue it returns true. Each message will be tested to * verify that it is complete. * The writeSemaphore must be set for this method to be reliable! * * @return true if no outstanding messages */ /* package */ final boolean areMessagesComplete(){ Object[] messages = this.messages.getObjectArray(); int length = messages.length; if( Debug.LDAP_DEBUG) { Debug.trace( Debug.TLS, "startTLS: areMessagesComplete? " + "MessageVector size = " + length + ", bindSemaphoreId=" + bindSemaphoreId); } // Check if SASL bind in progress if( bindSemaphoreId != 0) { return false; } // Check if any messages queued if(length == 0) { return true; } for(int i=0; i




© 2015 - 2024 Weber Informatics LLC | Privacy Policy