/****************************************************************************** * The MIT License * Copyright (c) 2003 Novell Inc. www.novell.com * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the Software), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. *******************************************************************************/ // // Novell.Directory.Ldap.Connection.cs // // Author: // Sunil Kumar (Sunilk@novell.com) // // (C) 2003 Novell, Inc (http://www.novell.com) // using System; using System.Threading; using Novell.Directory.Ldap.Asn1; using Novell.Directory.Ldap.Rfc2251; using Novell.Directory.Ldap.Utilclass; using Mono.Security.Protocol.Tls; using Mono.Security.X509.Extensions; using Syscert = System.Security.Cryptography.X509Certificates; using System.Security.Cryptography; using System.Net; using System.Net.Sockets; using System.Collections; using System.IO; using System.Text; using Mono.Security.X509; using System.Text.RegularExpressions; using System.Globalization; using System.Reflection; namespace Novell.Directory.Ldap { public delegate bool CertificateValidationCallback( Syscert.X509Certificate certificate, int[] certificateErrors); /// 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*/ sealed class Connection { public event CertificateValidationCallback OnCertificateValidation; public enum CertificateProblem : long { CertEXPIRED = 0x800B0101, CertVALIDITYPERIODNESTING = 0x800B0102, CertROLE = 0x800B0103, CertPATHLENCONST = 0x800B0104, CertCRITICAL = 0x800B0105, CertPURPOSE = 0x800B0106, CertISSUERCHAINING = 0x800B0107, CertMALFORMED = 0x800B0108, CertUNTRUSTEDROOT = 0x800B0109, CertCHAINING = 0x800B010A, CertREVOKED = 0x800B010C, CertUNTRUSTEDTESTROOT = 0x800B010D, CertREVOCATION_FAILURE = 0x800B010E, CertCN_NO_MATCH = 0x800B010F, CertWRONG_USAGE = 0x800B0110, CertUNTRUSTEDCA = 0x800B0112 } private static String GetProblemMessage(CertificateProblem Problem) { String ProblemMessage = ""; String ProblemCodeName = CertificateProblem.GetName(typeof(CertificateProblem), Problem); if(ProblemCodeName != null) ProblemMessage = ProblemMessage + ProblemCodeName; else ProblemMessage = "Unknown Certificate Problem"; return ProblemMessage; } private ArrayList handshakeProblemsEncountered = new ArrayList(); private void InitBlock() { writeSemaphore = new System.Object(); encoder = new LBEREncoder(); decoder = new LBERDecoder(); stopReaderMessageID = CONTINUE_READING; messages = new MessageVector(5, 5); unsolicitedListeners = new System.Collections.ArrayList(3); } /// Indicates whether clones exist for LdapConnection /// /// /// true if clones exist, false otherwise. /// internal bool Cloned { /* package */ get { return (cloneCount > 0); } } internal bool Ssl { get { return ssl; } set { ssl=value; } } /// gets the host used for this connection internal System.String Host { /* package */ get { return host; } } /// gets the port used for this connection internal int Port { /* package */ get { return port; } } /// gets the writeSemaphore id used for active bind operation /// sets the writeSemaphore id used for active bind operation internal int BindSemId { /* package */ get { return bindSemaphoreId; } /* package */ set { bindSemaphoreId = value; return ; } } /// checks if the writeSemaphore id used for active bind operation is clear internal bool BindSemIdClear { /* package */ get { if (bindSemaphoreId == 0) { return true; } return false; } } /// Return whether the application is bound to this connection. /// Note: an anonymous bind returns false - not bound /// internal bool Bound { /* package */ get { if (bindProperties != null) { // Bound if not anonymous return (!bindProperties.Anonymous); } return false; } } /// Return whether a connection has been made internal bool Connected { /* package */ get { return (in_Renamed != null); } } /// /// Sets the authentication credentials in the object /// and set flag indicating successful bind. /// /// /// /// /// The BindProperties object for this connection. /// /// /// Sets the authentication credentials in the object /// and set flag indicating successful bind. /// /// /// /// /// The BindProperties object to set. /// internal BindProperties BindProperties { /* package */ get { return bindProperties; } /* package */ set { bindProperties = value; return ; } } /// Gets the current referral active on this connection if created to /// follow referrals. /// /// /// the active referral url /// /// Sets the current referral active on this connection if created to /// follow referrals. /// internal ReferralInfo ActiveReferral { get { return activeReferral; } set { activeReferral = value; return ; } } /// Returns the name of this Connection, used for debug only /// /// /// the name of this connection /// internal System.String ConnectionName { /*package*/ get { return name; } } private System.Object writeSemaphore; 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 System.IO.IOException deadReaderException = null; // Last exception of reader private LBEREncoder encoder; private LBERDecoder decoder; /* * 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 System.Net.Sockets.Socket sock = null; private System.Net.Sockets.TcpClient socket = null; private System.Net.Sockets.TcpClient nonTLSBackup = null; private System.IO.Stream in_Renamed = null; private System.IO.Stream out_Renamed = null; // When set to true the client connection is up and running private bool clientActive = true; private bool ssl = false; // Indicates we have received a server shutdown unsolicited notification private bool 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 const int CONTINUE_READING = - 99; private const 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; // Place to save message information classes private MessageVector messages; // Connection created to follow referral private ReferralInfo activeReferral = null; // Place to save unsolicited message listeners private System.Collections.ArrayList unsolicitedListeners; // The LdapSocketFactory to be used as the default to create new connections // private static LdapSocketFactory socketFactory = null; // The LdapSocketFactory used for this connection // private LdapSocketFactory mySocketFactory; private System.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 System.String name = ""; private static System.Object nameLock; // 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 */ internal static System.String sdk; /* package */ internal static System.Int32 protocol; /* package */ internal static System.String security = "simple"; /// Create a new Connection object /// /// /// specifies the factory to use to produce SSL sockets. /// /* package */ // internal Connection(LdapSocketFactory factory) internal Connection() { InitBlock(); 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. /// /// /// a shallow copy of this object /// /* package */ internal System.Object copy() { Connection c = new Connection(); c.host = this.host; c.port = this.port; Novell.Directory.Ldap.Connection.protocol = Connection.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 /// /// /// the ephemeral message id that identifies semaphore's owner /// /* package */ internal 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. /// /// /// a value that identifies the owner of this semaphore. A /// value of zero means assign a unique semaphore value. /// /// /// the semaphore value used to acquire the lock /// /* package */ internal int acquireWriteSemaphore(int msgId) { int id = msgId; lock (writeSemaphore) { if (id == 0) { ephemeralId = ((ephemeralId == System.Int32.MinValue)?(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 System.Threading.Monitor.Wait(writeSemaphore); continue; } catch (System.Threading.ThreadInterruptedException ex) { // Keep trying for the lock continue; } } } 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. /// /// /// a value that identifies the owner of this semaphore /// /* package */ internal void freeWriteSemaphore(int msgId) { lock (writeSemaphore) { if (writeSemaphoreOwner == 0) { throw new System.SystemException("Connection.freeWriteSemaphore(" + msgId + "): semaphore not owned by any thread"); } else if (writeSemaphoreOwner != msgId) { throw new System.SystemException("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; System.Threading.Monitor.Pulse(writeSemaphore); } } 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) { // wait for previous reader thread to terminate System.Threading.Thread rInst; System.Threading.Thread tInst; if(reader!=null) { rInst=reader; } else { rInst=null; } if(thread!=null) { tInst=thread; } else { tInst=null; } // while (reader != thread) while (!Object.Equals(rInst,tInst)) { // Don't initialize connection while previous reader thread still // active. try { /* * 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 ; System.IO.IOException lex = deadReaderException; deadReaderException = null; deadReader = null; // Reader thread terminated throw new LdapException(ExceptionMessages.CONNECTION_READER, LdapException.CONNECT_ERROR, null, lex); } lock (this) { System.Threading.Monitor.Wait(this, TimeSpan.FromMilliseconds(5)); } } catch (System.Threading.ThreadInterruptedException ex) { ; } if(reader!=null) { rInst=reader; } else { rInst=null; } if(thread!=null) { tInst=thread; } else { tInst=null; } } deadReaderException = null; deadReader = null; return ; } /// Constructs a TCP/IP connection to a server specified in host and port. /// /// /// The host to connect to. /// /// /// The port on the host to connect to. /// /* package */ internal void connect(System.String host, int port) { connect(host, port, 0); return ; } /****************************************************************************/ public bool ServerCertificateValidation( Syscert.X509Certificate certificate, int[] certificateErrors) { if (null != OnCertificateValidation) { return OnCertificateValidation(certificate, certificateErrors); } return DefaultCertificateValidationHandler(certificate, certificateErrors); } public bool DefaultCertificateValidationHandler( Syscert.X509Certificate certificate, int[] certificateErrors) { bool retFlag=false; if (certificateErrors != null && certificateErrors.Length > 0) { if( certificateErrors.Length==1 && certificateErrors[0] == -2146762481) { retFlag = true; } else { Console.WriteLine("Detected errors in the Server Certificate:"); for (int i = 0; i < certificateErrors.Length; i++) { handshakeProblemsEncountered.Add((CertificateProblem)((uint)certificateErrors[i])); Console.WriteLine(certificateErrors[i]); } retFlag = false; } } else { retFlag = true; } // Skip the server cert errors. return retFlag; } /***********************************************************************/ /// Constructs a TCP/IP connection to a server specified in host and port. /// Starts the reader thread. /// /// /// The host to connect to. /// /// /// The port on the host to connect to. /// /// /// The write semaphore ID to use for the connect /// private void connect(System.String host, int port, int semaphoreId) { /* 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. */ // 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); try { // Make socket connection to specified host and port if (port == 0) { port = 389;//LdapConnection.DEFAULT_PORT; } try { if ((in_Renamed == null) || (out_Renamed == null)) { if(Ssl) { this.host = host; this.port = port; this.sock = new Socket ( AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP); IPAddress hostadd = Dns.Resolve(host).AddressList[0]; IPEndPoint ephost = new IPEndPoint(hostadd,port); sock.Connect(ephost); NetworkStream nstream = new NetworkStream(sock,true); // Load Mono.Security.dll Assembly a; try { a = Assembly.LoadWithPartialName("Mono.Security"); } catch(System.IO.FileNotFoundException) { throw new LdapException(ExceptionMessages.SSL_PROVIDER_MISSING, LdapException.SSL_PROVIDER_NOT_FOUND, null); } Type tSslClientStream = a.GetType("Mono.Security.Protocol.Tls.SslClientStream"); BindingFlags flags = (BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly); object[] consArgs = new object[4]; consArgs[0] = nstream; consArgs[1] = host; consArgs[2] = false; Type tSecurityProtocolType = a.GetType("Mono.Security.Protocol.Tls.SecurityProtocolType"); Enum objSPType = (Enum)(Activator.CreateInstance(tSecurityProtocolType)); int nSsl3Val = (int) Enum.Parse(tSecurityProtocolType, "Ssl3"); int nTlsVal = (int) Enum.Parse(tSecurityProtocolType, "Tls"); consArgs[3] = Enum.ToObject(tSecurityProtocolType, nSsl3Val | nTlsVal); object objSslClientStream = Activator.CreateInstance(tSslClientStream, consArgs); // Register ServerCertValidationDelegate handler PropertyInfo pi = tSslClientStream.GetProperty("ServerCertValidationDelegate"); pi.SetValue(objSslClientStream, Delegate.CreateDelegate(pi.PropertyType, this, "ServerCertificateValidation"), null); // Get the in and out streams in_Renamed = (System.IO.Stream) objSslClientStream; out_Renamed = (System.IO.Stream) objSslClientStream; /* SslClientStream sslstream = new SslClientStream( nstream, host, false, Mono.Security.Protocol.Tls.SecurityProtocolType.Ssl3|Mono.Security.Protocol.Tls.SecurityProtocolType.Tls); sslstream.ServerCertValidationDelegate += new CertificateValidationCallback(ServerCertificateValidation);*/ // byte[] buffer = new byte[0]; // sslstream.Read(buffer, 0, buffer.Length); // sslstream.doHandshake(); /* in_Renamed = (System.IO.Stream) sslstream; out_Renamed = (System.IO.Stream) sslstream;*/ } else{ socket = new System.Net.Sockets.TcpClient(host, port); in_Renamed = (System.IO.Stream) socket.GetStream(); out_Renamed = (System.IO.Stream) socket.GetStream(); } } else { Console.WriteLine( "connect input/out Stream specified"); } } catch (System.Net.Sockets.SocketException se) { // Unable to connect to server host:port sock = null; socket = null; throw new LdapException(ExceptionMessages.CONNECTION_ERROR, new System.Object[] { host, port }, LdapException.CONNECT_ERROR, null, se); } catch (System.IO.IOException ioe) { // Unable to connect to server host:port sock = null; socket = null; throw new LdapException(ExceptionMessages.CONNECTION_ERROR, new System.Object[]{host, port}, LdapException.CONNECT_ERROR, null, ioe); } // Set host and port this.host = host; this.port = port; // start the reader thread this.startReader(); clientActive = true; // Client is up } finally { freeWriteSemaphore(semId); } return; } /// Increments the count of cloned connections /* package */ internal void incrCloneCount() { lock (this) { 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. /// /// /// 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. /// /// /// a Connection object or null if finalizing. /// /* package */ internal Connection destroyClone(bool apiCall) { lock (this) { Connection conn = this; if (cloneCount > 0) { cloneCount--; // This is a clone, set a new connection object. if (apiCall) { conn = (Connection) this.copy(); } else { conn = null; } } else { if (in_Renamed != null) { // Not a clone and connected /* * 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 /// /// /// the default factory to set /// /* package */ /// gets the socket factory used for this connection /// /// /// the default factory for this connection /// /* package */ /// clears the writeSemaphore id used for active bind operation /* package */ internal void clearBindSemId() { bindSemaphoreId = 0; return ; } /// Writes an LdapMessage to the Ldap server over a socket. /// /// /// the Message containing the message to write. /// /* package */ internal void writeMessage(Message info) { ExceptionMessages em = new ExceptionMessages(); System.Object [][]contents = em.getContents(); messages.Add(info); // For bind requests, if not connected, attempt to reconnect if (info.BindRequest && (Connected == false) && ((System.Object) host != null)) { connect(host, port, info.MessageID); } if(Connected == true) { LdapMessage msg = info.Request; writeMessage(msg); return ; } else { int errorcount=0; for(errorcount=0;errorcount Writes an LdapMessage to the Ldap server over a socket. /// /// /// the message to write. /// /* package */ internal void writeMessage(LdapMessage msg) { int id; // Get the correct semaphore id for bind operations if (bindSemaphoreId == 0) { // Semaphore id for normal operations id = msg.MessageID; } else { // Semaphore id for sasl bind operations id = bindSemaphoreId; } System.IO.Stream myOut = out_Renamed; acquireWriteSemaphore(id); try { if (myOut == null) { throw new System.IO.IOException("Output stream not initialized"); } if (!(myOut.CanWrite)) { return; } sbyte[] ber = msg.Asn1Object.getEncoding(encoder); myOut.Write(SupportClass.ToByteArray(ber), 0, ber.Length); myOut.Flush(); } catch (System.IO.IOException ioe) { if ((msg.Type == LdapMessage.BIND_REQUEST) && (ssl)) { string strMsg = "Following problem(s) occurred while establishing SSL based Connection : "; if (handshakeProblemsEncountered.Count > 0) { strMsg += GetProblemMessage((CertificateProblem)handshakeProblemsEncountered[0]); for (int nProbIndex = 1; nProbIndex < handshakeProblemsEncountered.Count; nProbIndex++) { strMsg += ", " + GetProblemMessage((CertificateProblem)handshakeProblemsEncountered[nProbIndex]); } } else { strMsg += "Unknown Certificate Problem"; } throw new LdapException(strMsg, new System.Object[]{host, port}, LdapException.SSL_HANDSHAKE_FAILED, null, ioe); } /* * 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 System.Object[]{host, port}, LdapException.CONNECT_ERROR, null, ioe); } // Other I/O Exceptions on host:port are reported as is throw new LdapException(ExceptionMessages.IO_EXCEPTION, new System.Object[]{host, port}, LdapException.CONNECT_ERROR, null, ioe); } } finally { freeWriteSemaphore(id); handshakeProblemsEncountered.Clear(); } return ; } /// Returns the message agent for this msg ID /* package */ internal MessageAgent getMessageAgent(int msgId) { Message info = messages.findMessageById(msgId); return info.MessageAgent; } /// Removes a Message class from the Connection's list /// /// /// the Message class to remove from the list /// /* package */ internal void removeMessage(Message info) { bool done = SupportClass.VectorRemoveElement(messages, info); return ; } /// Cleans up resources associated with this connection. ~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(System.String reason, int semaphoreId, InterThreadException notifyUser) { Message info = null; if (!clientActive) { return ; } clientActive = false; while (true) { // remove messages from connection list and send abandon try { System.Object temp_object; temp_object = messages[0]; messages.RemoveAt(0); info = (Message) temp_object; } catch (ArgumentOutOfRangeException ex) { // No more messages break; } info.Abandon(null, notifyUser); // also notifies the application } int semId = acquireWriteSemaphore(semaphoreId); // Now send unbind if socket not closed if ((bindProperties != null) && (out_Renamed != null) && (out_Renamed.CanWrite) && (!bindProperties.Anonymous)) { try { LdapMessage msg = new LdapUnbindRequest(null); sbyte[] ber = msg.Asn1Object.getEncoding(encoder); out_Renamed.Write(SupportClass.ToByteArray(ber), 0, ber.Length); out_Renamed.Flush(); out_Renamed.Close(); } catch (System.Exception ex) { ; // don't worry about error } } bindProperties = null; if (socket != null || sock != null) { // Just before closing the sockets, abort the reader thread if ((reader != null) && (reason != "reader: thread stopping")) reader.Abort(); // Close the socket try { if(Ssl) { try { sock.Shutdown(SocketShutdown.Both); } catch {} sock.Close(); } else { if(in_Renamed != null) in_Renamed.Close(); socket.Close(); } } catch (Exception) { // ignore problem closing socket } socket = null; sock = null; in_Renamed=null; out_Renamed=null; } freeWriteSemaphore(semId); return ; } /// 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! /// /// /// true if no outstanding messages /// /* package */ internal bool areMessagesComplete() { System.Object[] messages = this.messages.ObjectArray; int length = messages.Length; // 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 < length; i++) { if (((Message) messages[i]).Complete == false) return false; } return true; } /// The reader thread will stop when a reply is read with an ID equal /// to the messageID passed in to this method. This is used by /// LdapConnection.StartTLS. /// /* package */ internal void stopReaderOnReply(int messageID) { this.stopReaderMessageID = messageID; return ; } /// startReader /// startReader should be called when socket and io streams have been /// set or changed. In particular after client.Connection.startTLS() /// It assumes the reader thread is not running. /// /* package */ internal void startReader() { // Start Reader Thread Thread r = new Thread(new ThreadStart(new ReaderThread(this).Run)); r.IsBackground = true; // If the last thread running, allow exit. r.Start(); waitForReader(r); return ; } /// Indicates if the conenction is using TLS protection /// /// Return true if using TLS protection /// internal bool TLS { get { return (this.nonTLSBackup != null); } } /// StartsTLS, in this package, assumes the caller has: /// 1) Acquired the writeSemaphore /// 2) Stopped the reader thread /// 3) checked that no messages are outstanding on this connection. /// /// After calling this method upper layers should start the reader /// by calling startReader() /// /// In the client.Connection, StartTLS assumes Ldap.LdapConnection will /// stop and start the reader thread. Connection.StopTLS will stop /// and start the reader thread. /// /* package */ internal void startTLS() { try { waitForReader(null); this.nonTLSBackup = this.socket; /* this.sock = new Socket ( AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP); IPAddress hostadd = Dns.Resolve(host).AddressList[0]; IPEndPoint ephost = new IPEndPoint(hostadd,port); sock.Connect(ephost); */ // NetworkStream nstream = new NetworkStream(this.socket,true); // Load Mono.Security.dll Assembly a = null; try { a = Assembly.LoadFrom("Mono.Security.dll"); } catch(System.IO.FileNotFoundException) { throw new LdapException(ExceptionMessages.SSL_PROVIDER_MISSING, LdapException.SSL_PROVIDER_NOT_FOUND, null); } Type tSslClientStream = a.GetType("Mono.Security.Protocol.Tls.SslClientStream"); BindingFlags flags = (BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly); object[] consArgs = new object[4]; consArgs[0] = socket.GetStream(); consArgs[1] = host; consArgs[2] = false; Type tSecurityProtocolType = a.GetType("Mono.Security.Protocol.Tls.SecurityProtocolType"); Enum objSPType = (Enum)(Activator.CreateInstance(tSecurityProtocolType)); int nSsl3Val = (int) Enum.Parse(tSecurityProtocolType, "Ssl3"); int nTlsVal = (int) Enum.Parse(tSecurityProtocolType, "Tls"); consArgs[3] = Enum.ToObject(tSecurityProtocolType, nSsl3Val | nTlsVal); object objSslClientStream = Activator.CreateInstance(tSslClientStream, consArgs); // Register ServerCertValidationDelegate handler EventInfo ei = tSslClientStream.GetEvent("ServerCertValidationDelegate"); ei.AddEventHandler(objSslClientStream, Delegate.CreateDelegate(ei.EventHandlerType, this, "ServerCertificateValidation")); // Get the in and out streams in_Renamed = (System.IO.Stream) objSslClientStream; out_Renamed = (System.IO.Stream) objSslClientStream; /* SslClientStream sslstream = new SslClientStream( socket.GetStream(), nstream, host, false, Mono.Security.Protocol.Tls.SecurityProtocolType.Ssl3| Mono.Security.Protocol.Tls.SecurityProtocolType.Tls); sslstream.ServerCertValidationDelegate = new CertificateValidationCallback(ServerCertificateValidation); this.in_Renamed = (System.IO.Stream) sslstream; this.out_Renamed = (System.IO.Stream) sslstream;*/ } catch (System.IO.IOException ioe) { this.nonTLSBackup = null; throw new LdapException("Could not negotiate a secure connection", LdapException.CONNECT_ERROR, null, ioe); } catch (System.Exception uhe) { this.nonTLSBackup = null; throw new LdapException("The host is unknown", LdapException.CONNECT_ERROR, null, uhe); } return ; } /* * Stops TLS. * * StopTLS, in this package, assumes the caller has: * 1) blocked writing (acquireWriteSemaphore). * 2) checked that no messages are outstanding. * * StopTLS Needs to do the following: * 1) close the current socket * - This stops the reader thread * - set STOP_READING flag on stopReaderMessageID so that * the reader knows that the IOException is planned. * 2) replace the current socket with nonTLSBackup, * 3) and set nonTLSBackup to null; * 4) reset input and outputstreams * 5) start the reader thread by calling startReader * * Note: Sun's JSSE doesn't allow the nonTLSBackup socket to be * used any more, even though autoclose was false: you get an IOException. * IBM's JSSE hangs when you close the JSSE socket. */ /* package */ internal void stopTLS() { try { this.stopReaderMessageID = Connection.STOP_READING; this.out_Renamed.Close(); this.in_Renamed.Close(); // this.sock.Shutdown(SocketShutdown.Both); // this.sock.Close(); waitForReader(null); this.socket = this.nonTLSBackup; this.in_Renamed = (System.IO.Stream) this.socket.GetStream(); this.out_Renamed = (System.IO.Stream) this.socket.GetStream(); // Allow the new reader to start this.stopReaderMessageID = Connection.CONTINUE_READING; } catch (System.IO.IOException ioe) { throw new LdapException(ExceptionMessages.STOPTLS_ERROR, LdapException.CONNECT_ERROR, null, ioe); } finally { this.nonTLSBackup = null; startReader(); } return ; } ///TLS not supported in first release internal Stream InputStream { get { return in_Renamed; } } internal Stream OutputStream { get { return out_Renamed; } } internal void ReplaceStreams(Stream newIn, Stream newOut) { // wait for reader to stop, see LdapConnection.Bind waitForReader(null); in_Renamed = newIn; out_Renamed = newOut; startReader(); } public class ReaderThread { private void InitBlock(Connection enclosingInstance) { this.enclosingInstance = enclosingInstance; } private Connection enclosingInstance; public Connection Enclosing_Instance { get { return enclosingInstance; } } public ReaderThread(Connection enclosingInstance) { InitBlock(enclosingInstance); return ; } /// This thread decodes and processes RfcLdapMessage's from the server. /// /// Note: This thread needs a graceful shutdown implementation. /// public virtual void Run() { System.String reason = "reader: thread stopping"; InterThreadException notify = null; Message info = null; System.IO.IOException ioex = null; this.enclosingInstance.reader = System.Threading.Thread.CurrentThread; // Enclosing_Instance.reader = SupportClass.ThreadClass.Current(); // Console.WriteLine("Inside run:" + this.enclosingInstance.reader.Name); try { for (; ; ) { // ------------------------------------------------------- // Decode an RfcLdapMessage directly from the socket. // ------------------------------------------------------- Asn1Identifier asn1ID; System.IO.Stream myIn; /* get current value of in, keep value consistant * though the loop, i.e. even during shutdown */ myIn = this.enclosingInstance.in_Renamed; if (myIn == null) { break; } asn1ID = new Asn1Identifier(myIn); int tag = asn1ID.Tag; if (asn1ID.Tag != Asn1Sequence.TAG) { continue; // loop looking for an RfcLdapMessage identifier } // Turn the message into an RfcMessage class Asn1Length asn1Len = new Asn1Length(myIn); RfcLdapMessage msg = new RfcLdapMessage(this.enclosingInstance.decoder, myIn, asn1Len.Length); // ------------------------------------------------------------ // Process the decoded RfcLdapMessage. // ------------------------------------------------------------ int msgId = msg.MessageID; // Find the message which requested this response. // It is possible to receive a response for a request which // has been abandoned. If abandoned, throw it away try { info = this.enclosingInstance.messages.findMessageById(msgId); info.putReply(msg); // queue & wake up waiting thread } catch (System.FieldAccessException ex) { /* * We get the NoSuchFieldException when we could not find * a matching message id. First check to see if this is * an unsolicited notification (msgID == 0). If it is not * we throw it away. If it is we call any unsolicited * listeners that might have been registered to listen for these * messages. */ /* Note the location of this code. We could have required * that message ID 0 be just like other message ID's but * since message ID 0 has to be treated specially we have * a separate check for message ID 0. Also note that * this test is after the regular message list has been * checked for. We could have always checked the list * of messages after checking if this is an unsolicited * notification but that would have inefficient as * message ID 0 is a rare event (as of this time). */ if (msgId == 0) { // Notify any listeners that might have been registered this.enclosingInstance.notifyAllUnsolicitedListeners(msg); /* * Was this a server shutdown unsolicited notification. * IF so we quit. Actually calling the return will * first transfer control to the finally clause which * will do the necessary clean up. */ if (this.enclosingInstance.unsolSvrShutDnNotification) { notify = new InterThreadException(ExceptionMessages.SERVER_SHUTDOWN_REQ, new System.Object[]{this.enclosingInstance.host, this.enclosingInstance.port}, LdapException.CONNECT_ERROR, null, null); return ; } } else { } } if ((this.enclosingInstance.stopReaderMessageID == msgId) || (this.enclosingInstance.stopReaderMessageID == Novell.Directory.Ldap.Connection.STOP_READING)) { // Stop the reader Thread. return ; } } } catch(ThreadAbortException tae) { // Abort has been called on reader // before closing sockets, from shutdown return; } catch (System.IO.IOException ioe) { ioex = ioe; if ((this.enclosingInstance.stopReaderMessageID != Novell.Directory.Ldap.Connection.STOP_READING) && this.enclosingInstance.clientActive) { // Connection lost waiting for results from host:port notify = new InterThreadException(ExceptionMessages.CONNECTION_WAIT, new System.Object[]{this.enclosingInstance.host, this.enclosingInstance.port}, LdapException.CONNECT_ERROR, ioe, info); } // The connection is no good, don't use it any more this.enclosingInstance.in_Renamed = null; this.enclosingInstance.out_Renamed = null; } finally { /* * There can be four states that the reader can be in at this point: * 1) We are starting TLS and will be restarting the reader * after we have negotiated TLS. * - Indicated by whether stopReaderMessageID does not * equal CONTINUE_READING. * - Don't call Shutdown. * 2) We are stoping TLS and will be restarting after TLS is * stopped. * - Indicated by an IOException AND stopReaderMessageID equals * STOP_READING - in which case notify will be null. * - Don't call Shutdown * 3) We receive a Server Shutdown notification. * - Indicated by messageID equal to 0. * - call Shutdown. * 4) Another error occured * - Indicated by an IOException AND notify is not NULL * - call Shutdown. */ if ((!this.enclosingInstance.clientActive) || (notify != null)) { //#3 & 4 this.enclosingInstance.shutdown(reason, 0, notify); } else { this.enclosingInstance.stopReaderMessageID = Novell.Directory.Ldap.Connection.CONTINUE_READING; } } this.enclosingInstance.deadReaderException = ioex; this.enclosingInstance.deadReader = this.enclosingInstance.reader; this.enclosingInstance.reader = null; return ; } } // End class ReaderThread /// Add the specific object to the list of listeners that want to be /// notified when an unsolicited notification is received. /// /* package */ internal void AddUnsolicitedNotificationListener(LdapUnsolicitedNotificationListener listener) { unsolicitedListeners.Add(listener); return ; } /// Remove the specific object from current list of listeners /* package */ internal void RemoveUnsolicitedNotificationListener(LdapUnsolicitedNotificationListener listener) { SupportClass.VectorRemoveElement(unsolicitedListeners, listener); return ; } /// Inner class defined so that we can spawn off each unsolicited /// listener as a seperate thread. We did not want to call the /// unsolicited listener method directly as this would have tied up our /// deamon listener thread in the applications unsolicited listener method. /// Since we do not know what the application unsolicited listener /// might be doing and how long it will take to process the uncoslicited /// notification. We use this class to spawn off the unsolicited /// notification as a separate thread /// private class UnsolicitedListenerThread:SupportClass.ThreadClass { private void InitBlock(Connection enclosingInstance) { this.enclosingInstance = enclosingInstance; } private Connection enclosingInstance; public Connection Enclosing_Instance { get { return enclosingInstance; } } private LdapUnsolicitedNotificationListener listenerObj; private LdapExtendedResponse unsolicitedMsg; /* package */ internal UnsolicitedListenerThread(Connection enclosingInstance, LdapUnsolicitedNotificationListener l, LdapExtendedResponse m) { InitBlock(enclosingInstance); this.listenerObj = l; this.unsolicitedMsg = m; return ; } public override void Run() { listenerObj.messageReceived(unsolicitedMsg); return ; } } private void notifyAllUnsolicitedListeners(RfcLdapMessage message) { // MISSING: If this is a shutdown notification from the server // set a flag in the Connection class so that we can throw an // appropriate LdapException to the application LdapMessage extendedLdapMessage = new LdapExtendedResponse(message); System.String notificationOID = ((LdapExtendedResponse) extendedLdapMessage).ID; if (notificationOID.Equals(LdapConnection.SERVER_SHUTDOWN_OID)) { unsolSvrShutDnNotification = true; } int numOfListeners = unsolicitedListeners.Count; // Cycle through all the listeners for (int i = 0; i < numOfListeners; i++) { // Get next listener LdapUnsolicitedNotificationListener listener = (LdapUnsolicitedNotificationListener) unsolicitedListeners[i]; // Create a new ExtendedResponse each time as we do not want each listener // to have its own copy of the message LdapExtendedResponse tempLdapMessage = new LdapExtendedResponse(message); // Spawn a new thread for each listener to go process the message // The reason we create a new thread rather than just call the // the messageReceived method directly is beacuse we do not know // what kind of processing the notification listener class will // do. We do not want our deamon thread to block waiting for // the notification listener method to return. UnsolicitedListenerThread u = new UnsolicitedListenerThread(this, listener, tempLdapMessage); u.Start(); } return ; } static Connection() { nameLock = new System.Object(); sdk = new System.Text.StringBuilder("2.1.8").ToString(); protocol = 3; } } }