1 /******************************************************************************
3 * Copyright (c) 2003 Novell Inc. www.novell.com
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the Software), to deal
7 * in the Software without restriction, including without limitation the rights
8 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 * copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
12 * The above copyright notice and this permission notice shall be included in
13 * all copies or substantial portions of the Software.
15 * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 *******************************************************************************/
24 // Novell.Directory.Ldap.Connection.cs
27 // Sunil Kumar (Sunilk@novell.com)
29 // (C) 2003 Novell, Inc (http://www.novell.com)
33 using System.Threading;
34 using Novell.Directory.Ldap.Asn1;
35 using Novell.Directory.Ldap.Rfc2251;
36 using Novell.Directory.Ldap.Utilclass;
37 using Mono.Security.Protocol.Tls;
38 using Mono.Security.X509.Extensions;
39 using Syscert = System.Security.Cryptography.X509Certificates;
40 using System.Security.Cryptography;
42 using System.Net.Sockets;
43 using System.Collections;
46 using Mono.Security.X509;
47 using System.Text.RegularExpressions;
48 using System.Globalization;
49 using System.Reflection;
51 namespace Novell.Directory.Ldap
53 public delegate bool CertificateValidationCallback(
54 Syscert.X509Certificate certificate,
55 int[] certificateErrors);
57 /// <summary> The class that creates a connection to the Ldap server. After the
58 /// connection is made, a thread is created that reads data from the
61 /// The application's thread sends a request to the MessageAgent class, which
62 /// creates a Message class. The Message class calls the writeMessage method
63 /// of this class to send the request to the server. The application thread
64 /// will then query the MessageAgent class for a response.
66 /// The reader thread multiplexes response messages received from the
67 /// server to the appropriate Message class. Each Message class
68 /// has its own message queue.
70 /// Unsolicited messages are process separately, and if the application
71 /// has registered a handler, a separate thread is created for that
72 /// application's handler to process the message.
74 /// Note: the reader thread must not be a "selfish" thread, since some
75 /// operating systems do not time slice.
79 sealed class Connection
81 public event CertificateValidationCallback OnCertificateValidation;
82 public enum CertificateProblem : long
84 CertEXPIRED = 0x800B0101,
85 CertVALIDITYPERIODNESTING = 0x800B0102,
86 CertROLE = 0x800B0103,
87 CertPATHLENCONST = 0x800B0104,
88 CertCRITICAL = 0x800B0105,
89 CertPURPOSE = 0x800B0106,
90 CertISSUERCHAINING = 0x800B0107,
91 CertMALFORMED = 0x800B0108,
92 CertUNTRUSTEDROOT = 0x800B0109,
93 CertCHAINING = 0x800B010A,
94 CertREVOKED = 0x800B010C,
95 CertUNTRUSTEDTESTROOT = 0x800B010D,
96 CertREVOCATION_FAILURE = 0x800B010E,
97 CertCN_NO_MATCH = 0x800B010F,
98 CertWRONG_USAGE = 0x800B0110,
99 CertUNTRUSTEDCA = 0x800B0112
101 private static String GetProblemMessage(CertificateProblem Problem)
103 String ProblemMessage = "";
104 String ProblemCodeName = CertificateProblem.GetName(typeof(CertificateProblem), Problem);
105 if(ProblemCodeName != null)
106 ProblemMessage = ProblemMessage + ProblemCodeName;
108 ProblemMessage = "Unknown Certificate Problem";
109 return ProblemMessage;
112 private ArrayList handshakeProblemsEncountered = new ArrayList();
113 private void InitBlock()
115 writeSemaphore = new System.Object();
116 encoder = new LBEREncoder();
117 decoder = new LBERDecoder();
118 stopReaderMessageID = CONTINUE_READING;
119 messages = new MessageVector(5, 5);
120 unsolicitedListeners = new System.Collections.ArrayList(3);
122 /// <summary> Indicates whether clones exist for LdapConnection
125 /// <returns> true if clones exist, false otherwise.
133 return (cloneCount > 0);
151 /// <summary> gets the host used for this connection</summary>
152 internal System.String Host
162 /// <summary> gets the port used for this connection</summary>
173 /// <summary> gets the writeSemaphore id used for active bind operation</summary>
174 /// <summary> sets the writeSemaphore id used for active bind operation</summary>
175 internal int BindSemId
181 return bindSemaphoreId;
188 bindSemaphoreId = value;
193 /// <summary> checks if the writeSemaphore id used for active bind operation is clear</summary>
194 internal bool BindSemIdClear
200 if (bindSemaphoreId == 0)
208 /// <summary> Return whether the application is bound to this connection.
209 /// Note: an anonymous bind returns false - not bound
217 if (bindProperties != null)
219 // Bound if not anonymous
220 return (!bindProperties.Anonymous);
226 /// <summary> Return whether a connection has been made</summary>
227 internal bool Connected
233 return (in_Renamed != null);
238 /// Sets the authentication credentials in the object
239 /// and set flag indicating successful bind.
244 /// <returns> The BindProperties object for this connection.
247 /// Sets the authentication credentials in the object
248 /// and set flag indicating successful bind.
253 /// <param name="bindProps"> The BindProperties object to set.
255 internal BindProperties BindProperties
261 return bindProperties;
268 bindProperties = value;
273 /// <summary> Gets the current referral active on this connection if created to
274 /// follow referrals.
277 /// <returns> the active referral url
279 /// <summary> Sets the current referral active on this connection if created to
280 /// follow referrals.
282 internal ReferralInfo ActiveReferral
288 return activeReferral;
295 activeReferral = value;
301 /// <summary> Returns the name of this Connection, used for debug only
304 /// <returns> the name of this connection
306 internal System.String ConnectionName
317 private System.Object writeSemaphore;
318 private int writeSemaphoreOwner = 0;
319 private int writeSemaphoreCount = 0;
321 // We need a message number for disconnect to grab the semaphore,
322 // but may not have one, so we invent a unique one.
323 private int ephemeralId = - 1;
324 private BindProperties bindProperties = null;
325 private int bindSemaphoreId = 0; // 0 is never used by to lock a semaphore
327 private Thread reader = null; // New thread that reads data from the server.
328 private Thread deadReader = null; // Identity of last reader thread
329 private System.IO.IOException deadReaderException = null; // Last exception of reader
331 private LBEREncoder encoder;
332 private LBERDecoder decoder;
335 * socket is the current socket being used.
336 * nonTLSBackup is the backup socket if startTLS is called.
337 * if nonTLSBackup is null then startTLS has not been called,
338 * or stopTLS has been called to end TLS protection
340 private System.Net.Sockets.Socket sock = null;
341 private System.Net.Sockets.TcpClient socket = null;
342 private System.Net.Sockets.TcpClient nonTLSBackup = null;
344 private System.IO.Stream in_Renamed = null;
345 private System.IO.Stream out_Renamed = null;
346 // When set to true the client connection is up and running
347 private bool clientActive = true;
349 private bool ssl = false;
351 // Indicates we have received a server shutdown unsolicited notification
352 private bool unsolSvrShutDnNotification = false;
354 // Ldap message IDs are all positive numbers so we can use negative
355 // numbers as flags. This are flags assigned to stopReaderMessageID
356 // to tell the reader what state we are in.
357 private const int CONTINUE_READING = - 99;
358 private const int STOP_READING = - 98;
360 // Stops the reader thread when a Message with the passed-in ID is read.
361 // This parameter is set by stopReaderOnReply and stopTLS
362 private int stopReaderMessageID;
365 // Place to save message information classes
366 private MessageVector messages;
368 // Connection created to follow referral
369 private ReferralInfo activeReferral = null;
371 // Place to save unsolicited message listeners
372 private System.Collections.ArrayList unsolicitedListeners;
374 // The LdapSocketFactory to be used as the default to create new connections
375 // private static LdapSocketFactory socketFactory = null;
376 // The LdapSocketFactory used for this connection
377 // private LdapSocketFactory mySocketFactory;
378 private System.String host = null;
379 private int port = 0;
380 // Number of clones in addition to original LdapConnection using this
382 private int cloneCount = 0;
383 // Connection number & name used only for debug
384 private System.String name = "";
385 private static System.Object nameLock; // protect connNum
386 private static int connNum = 0;
388 // These attributes can be retreived using the getProperty
389 // method in LdapConnection. Future releases might require
390 // these to be local variables that can be modified using
391 // the setProperty method.
393 internal static System.String sdk;
395 internal static System.Int32 protocol;
397 internal static System.String security = "simple";
399 /// <summary> Create a new Connection object
402 /// <param name="factory">specifies the factory to use to produce SSL sockets.
405 // internal Connection(LdapSocketFactory factory)
406 internal Connection()
412 /// <summary> Copy this Connection object.
414 /// This is not a true clone, but creates a new object encapsulating
415 /// part of the connection information from the original object.
416 /// The new object will have the same default socket factory,
417 /// designated socket factory, host, port, and protocol version
418 /// as the original object.
419 /// The new object is NOT be connected to the host.
422 /// <returns> a shallow copy of this object
425 internal System.Object copy()
427 Connection c = new Connection();
430 Novell.Directory.Ldap.Connection.protocol = Connection.protocol;
434 /// <summary> Acquire a simple counting semaphore that synchronizes state affecting
435 /// bind. This method generates an ephemeral message id (negative number).
437 /// We bind using the message ID because a different thread may unlock
438 /// the semaphore than the one that set it. It is cleared when the
439 /// response to the bind is processed, or when the bind operation times out.
441 /// Returns when the semaphore is acquired
444 /// <returns> the ephemeral message id that identifies semaphore's owner
447 internal int acquireWriteSemaphore()
449 return acquireWriteSemaphore(0);
452 /// <summary> Acquire a simple counting semaphore that synchronizes state affecting
453 /// bind. The semaphore is held by setting a value in writeSemaphoreOwner.
455 /// We bind using the message ID because a different thread may unlock
456 /// the semaphore than the one that set it. It is cleared when the
457 /// response to the bind is processed, or when the bind operation times out.
458 /// Returns when the semaphore is acquired.
461 /// <param name="msgId">a value that identifies the owner of this semaphore. A
462 /// value of zero means assign a unique semaphore value.
465 /// <returns> the semaphore value used to acquire the lock
468 internal int acquireWriteSemaphore(int msgId)
471 lock (writeSemaphore)
475 ephemeralId = ((ephemeralId == System.Int32.MinValue)?(ephemeralId = - 1):--ephemeralId);
480 if (writeSemaphoreOwner == 0)
482 // we have acquired the semahpore
483 writeSemaphoreOwner = id;
488 if (writeSemaphoreOwner == id)
490 // we already own the semahpore
495 // Keep trying for the lock
496 System.Threading.Monitor.Wait(writeSemaphore);
499 catch (System.Threading.ThreadInterruptedException ex)
501 // Keep trying for the lock
506 writeSemaphoreCount++;
511 /// <summary> Release a simple counting semaphore that synchronizes state affecting
512 /// bind. Frees the semaphore when number of acquires and frees for this
516 /// <param name="msgId">a value that identifies the owner of this semaphore
519 internal void freeWriteSemaphore(int msgId)
521 lock (writeSemaphore)
523 if (writeSemaphoreOwner == 0)
525 throw new System.SystemException("Connection.freeWriteSemaphore(" + msgId + "): semaphore not owned by any thread");
527 else if (writeSemaphoreOwner != msgId)
529 throw new System.SystemException("Connection.freeWriteSemaphore(" + msgId + "): thread does not own the semaphore, owned by " + writeSemaphoreOwner);
531 // if all instances of this semaphore for this thread are released,
532 // wake up all threads waiting.
533 if (--writeSemaphoreCount == 0)
535 writeSemaphoreOwner = 0;
536 System.Threading.Monitor.Pulse(writeSemaphore);
543 * Wait until the reader thread ID matches the specified parameter.
544 * Null = wait for the reader to terminate
545 * Non Null = wait for the reader to start
546 * Returns when the ID matches, i.e. reader stopped, or reader started.
548 * @param the thread id to match
550 private void waitForReader(Thread thread)
552 // wait for previous reader thread to terminate
553 System.Threading.Thread rInst;
554 System.Threading.Thread tInst;
572 // while (reader != thread)
573 while (!Object.Equals(rInst,tInst))
575 // Don't initialize connection while previous reader thread still
580 * The reader thread may start and immediately terminate.
581 * To prevent the waitForReader from waiting forever
582 * for the dead to rise, we leave traces of the deceased.
583 * If the thread is already gone, we throw an exception.
585 if (thread == deadReader)
588 /* then we wanted a shutdown */
590 System.IO.IOException lex = deadReaderException;
591 deadReaderException = null;
593 // Reader thread terminated
594 throw new LdapException(ExceptionMessages.CONNECTION_READER, LdapException.CONNECT_ERROR, null, lex);
598 System.Threading.Monitor.Wait(this, TimeSpan.FromMilliseconds(5));
601 catch (System.Threading.ThreadInterruptedException ex)
624 deadReaderException = null;
629 /// <summary> Constructs a TCP/IP connection to a server specified in host and port.
632 /// <param name="host">The host to connect to.
635 /// <param name="port">The port on the host to connect to.
638 internal void connect(System.String host, int port)
640 connect(host, port, 0);
645 /****************************************************************************/
646 public bool ServerCertificateValidation(
647 Syscert.X509Certificate certificate,
648 int[] certificateErrors)
650 if (null != OnCertificateValidation)
652 return OnCertificateValidation(certificate, certificateErrors);
655 return DefaultCertificateValidationHandler(certificate, certificateErrors);
658 public bool DefaultCertificateValidationHandler(
659 Syscert.X509Certificate certificate,
660 int[] certificateErrors)
664 if (certificateErrors != null &&
665 certificateErrors.Length > 0)
667 if( certificateErrors.Length==1 && certificateErrors[0] == -2146762481)
673 Console.WriteLine("Detected errors in the Server Certificate:");
675 for (int i = 0; i < certificateErrors.Length; i++)
677 handshakeProblemsEncountered.Add((CertificateProblem)((uint)certificateErrors[i]));
678 Console.WriteLine(certificateErrors[i]);
689 // Skip the server cert errors.
694 /***********************************************************************/
695 /// <summary> Constructs a TCP/IP connection to a server specified in host and port.
696 /// Starts the reader thread.
699 /// <param name="host">The host to connect to.
702 /// <param name="port">The port on the host to connect to.
705 /// <param name="semaphoreId">The write semaphore ID to use for the connect
707 private void connect(System.String host, int port, int semaphoreId)
709 /* Synchronized so all variables are in a consistant state and
710 * so that another thread isn't doing a connect, disconnect, or clone
713 // Wait for active reader to terminate
716 // Clear the server shutdown notification flag. This should already
717 // be false unless of course we are reusing the same Connection object
718 // after a server shutdown notification
719 unsolSvrShutDnNotification = false;
721 int semId = acquireWriteSemaphore(semaphoreId);
724 // Make socket connection to specified host and port
727 port = 389;//LdapConnection.DEFAULT_PORT;
732 if ((in_Renamed == null) || (out_Renamed == null))
738 this.sock = new Socket ( AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);
739 IPAddress hostadd = Dns.Resolve(host).AddressList[0];
740 IPEndPoint ephost = new IPEndPoint(hostadd,port);
741 sock.Connect(ephost);
742 NetworkStream nstream = new NetworkStream(sock,true);
743 // Load Mono.Security.dll
747 a = Assembly.LoadWithPartialName("Mono.Security");
749 catch(System.IO.FileNotFoundException)
751 throw new LdapException(ExceptionMessages.SSL_PROVIDER_MISSING, LdapException.SSL_PROVIDER_NOT_FOUND, null);
753 Type tSslClientStream = a.GetType("Mono.Security.Protocol.Tls.SslClientStream");
754 BindingFlags flags = (BindingFlags.NonPublic | BindingFlags.Public |
755 BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly);
757 object[] consArgs = new object[4];
758 consArgs[0] = nstream;
761 Type tSecurityProtocolType = a.GetType("Mono.Security.Protocol.Tls.SecurityProtocolType");
762 Enum objSPType = (Enum)(Activator.CreateInstance(tSecurityProtocolType));
763 int nSsl3Val = (int) Enum.Parse(tSecurityProtocolType, "Ssl3");
764 int nTlsVal = (int) Enum.Parse(tSecurityProtocolType, "Tls");
765 consArgs[3] = Enum.ToObject(tSecurityProtocolType, nSsl3Val | nTlsVal);
767 object objSslClientStream =
768 Activator.CreateInstance(tSslClientStream, consArgs);
770 // Register ServerCertValidationDelegate handler
771 PropertyInfo pi = tSslClientStream.GetProperty("ServerCertValidationDelegate");
772 pi.SetValue(objSslClientStream,
773 Delegate.CreateDelegate(pi.PropertyType, this, "ServerCertificateValidation"),
776 // Get the in and out streams
777 in_Renamed = (System.IO.Stream) objSslClientStream;
778 out_Renamed = (System.IO.Stream) objSslClientStream;
780 SslClientStream sslstream = new SslClientStream(
784 Mono.Security.Protocol.Tls.SecurityProtocolType.Ssl3|Mono.Security.Protocol.Tls.SecurityProtocolType.Tls);
785 sslstream.ServerCertValidationDelegate += new CertificateValidationCallback(ServerCertificateValidation);*/
786 // byte[] buffer = new byte[0];
787 // sslstream.Read(buffer, 0, buffer.Length);
788 // sslstream.doHandshake();
790 in_Renamed = (System.IO.Stream) sslstream;
791 out_Renamed = (System.IO.Stream) sslstream;*/
794 socket = new System.Net.Sockets.TcpClient(host, port);
795 in_Renamed = (System.IO.Stream) socket.GetStream();
796 out_Renamed = (System.IO.Stream) socket.GetStream();
801 Console.WriteLine( "connect input/out Stream specified");
805 catch (System.Net.Sockets.SocketException se)
807 // Unable to connect to server host:port
810 throw new LdapException(ExceptionMessages.CONNECTION_ERROR, new System.Object[] { host, port }, LdapException.CONNECT_ERROR, null, se);
812 catch (System.IO.IOException ioe)
814 // Unable to connect to server host:port
817 throw new LdapException(ExceptionMessages.CONNECTION_ERROR, new System.Object[]{host, port}, LdapException.CONNECT_ERROR, null, ioe);
822 // start the reader thread
824 clientActive = true; // Client is up
826 freeWriteSemaphore(semId);
832 /// <summary> Increments the count of cloned connections</summary>
834 internal void incrCloneCount()
843 /// <summary> Destroys a clone of <code>LdapConnection</code>.
845 /// This method first determines if only one <code>LdapConnection</code>
846 /// object is associated with this connection, i.e. if no clone exists.
848 /// If no clone exists, the socket is closed, and the current
849 /// <code>Connection</code> object is returned.
851 /// If multiple <code>LdapConnection</code> objects are associated
852 /// with this connection, i.e. clones exist, a {@link #copy} of the
853 /// this object is made, but is not connected to any host. This
854 /// disassociates that clone from the original connection. The new
855 /// <code>Connection</code> object is returned.
857 /// Only one destroyClone instance is allowed to run at any one time.
859 /// If the connection is closed, any threads waiting for operations
860 /// on that connection will wake with an LdapException indicating
861 /// the connection is closed.
864 /// <param name="apiCall"><code>true</code> indicates the application is closing the
865 /// connection or or creating a new one by calling either the
866 /// <code>connect</code> or <code>disconnect</code> methods
867 /// of <code>LdapConnection</code>. <code>false</code>
868 /// indicates that <code>LdapConnection</code> is being finalized.
871 /// <returns> a Connection object or null if finalizing.
874 internal Connection destroyClone(bool apiCall)
878 Connection conn = this;
883 // This is a clone, set a new connection object.
886 conn = (Connection) this.copy();
895 if (in_Renamed != null)
897 // Not a clone and connected
899 * Either the application has called disconnect or connect
900 * resulting in the current connection being closed. If the
901 * application has any queues waiting on messages, we
902 * need wake these up so the application does not hang.
903 * The boolean flag indicates whether the close came
904 * from an API call or from the object being finalized.
906 InterThreadException notify = new InterThreadException((apiCall?ExceptionMessages.CONNECTION_CLOSED:ExceptionMessages.CONNECTION_FINALIZED), null, LdapException.CONNECT_ERROR, null, null);
907 // Destroy old connection
908 shutdown("destroy clone", 0, notify);
915 /// <summary> sets the default socket factory
918 /// <param name="factory">the default factory to set
921 /// <summary> gets the socket factory used for this connection
924 /// <returns> the default factory for this connection
928 /// <summary> clears the writeSemaphore id used for active bind operation</summary>
930 internal void clearBindSemId()
936 /// <summary> Writes an LdapMessage to the Ldap server over a socket.
939 /// <param name="info">the Message containing the message to write.
942 internal void writeMessage(Message info)
944 ExceptionMessages em = new ExceptionMessages();
945 System.Object [][]contents = em.getContents();
947 // For bind requests, if not connected, attempt to reconnect
948 if (info.BindRequest && (Connected == false) && ((System.Object) host != null))
950 connect(host, port, info.MessageID);
952 if(Connected == true)
954 LdapMessage msg = info.Request;
961 for(errorcount=0;errorcount<contents.Length;errorcount++)
962 if(contents[errorcount][0]=="CONNECTION_CLOSED")
964 throw new LdapException(ExceptionMessages.CONNECTION_CLOSED, new System.Object[]{host, port}, LdapException.CONNECT_ERROR, (String)contents[errorcount][1]);
969 /// <summary> Writes an LdapMessage to the Ldap server over a socket.
972 /// <param name="msg">the message to write.
975 internal void writeMessage(LdapMessage msg)
978 // Get the correct semaphore id for bind operations
979 if (bindSemaphoreId == 0)
981 // Semaphore id for normal operations
986 // Semaphore id for sasl bind operations
987 id = bindSemaphoreId;
989 System.IO.Stream myOut = out_Renamed;
991 acquireWriteSemaphore(id);
996 throw new System.IO.IOException("Output stream not initialized");
998 if (!(myOut.CanWrite))
1002 sbyte[] ber = msg.Asn1Object.getEncoding(encoder);
1003 myOut.Write(SupportClass.ToByteArray(ber), 0, ber.Length);
1006 catch (System.IO.IOException ioe)
1008 if ((msg.Type == LdapMessage.BIND_REQUEST) &&
1011 string strMsg = "Following problem(s) occurred while establishing SSL based Connection : ";
1012 if (handshakeProblemsEncountered.Count > 0)
1014 strMsg += GetProblemMessage((CertificateProblem)handshakeProblemsEncountered[0]);
1015 for (int nProbIndex = 1; nProbIndex < handshakeProblemsEncountered.Count; nProbIndex++)
1017 strMsg += ", " + GetProblemMessage((CertificateProblem)handshakeProblemsEncountered[nProbIndex]);
1022 strMsg += "Unknown Certificate Problem";
1024 throw new LdapException(strMsg, new System.Object[]{host, port}, LdapException.SSL_HANDSHAKE_FAILED, null, ioe);
1027 * IOException could be due to a server shutdown notification which
1028 * caused our Connection to quit. If so we send back a slightly
1029 * different error message. We could have checked this a little
1030 * earlier in the method but that would be an expensive check each
1031 * time we send out a message. Since this shutdown request is
1032 * going to be an infrequent occurence we check for it only when
1033 * we get an IOException. shutdown() will do the cleanup.
1037 // We beliefe the connection was alive
1038 if (unsolSvrShutDnNotification)
1040 // got server shutdown
1041 throw new LdapException(ExceptionMessages.SERVER_SHUTDOWN_REQ, new System.Object[]{host, port}, LdapException.CONNECT_ERROR, null, ioe);
1044 // Other I/O Exceptions on host:port are reported as is
1045 throw new LdapException(ExceptionMessages.IO_EXCEPTION, new System.Object[]{host, port}, LdapException.CONNECT_ERROR, null, ioe);
1050 freeWriteSemaphore(id);
1051 handshakeProblemsEncountered.Clear();
1056 /// <summary> Returns the message agent for this msg ID</summary>
1058 internal MessageAgent getMessageAgent(int msgId)
1060 Message info = messages.findMessageById(msgId);
1061 return info.MessageAgent;
1064 /// <summary> Removes a Message class from the Connection's list
1067 /// <param name="info">the Message class to remove from the list
1070 internal void removeMessage(Message info)
1072 bool done = SupportClass.VectorRemoveElement(messages, info);
1076 /// <summary> Cleans up resources associated with this connection.</summary>
1079 shutdown("Finalize", 0, null);
1082 /// <summary> Cleans up resources associated with this connection.
1083 /// This method may be called by finalize() for the connection, or it may
1084 /// be called by LdapConnection.disconnect().
1085 /// Should not have a writeSemaphore lock in place, as deadlock can occur
1086 /// while abandoning connections.
1088 private void shutdown(System.String reason, int semaphoreId, InterThreadException notifyUser)
1090 Message info = null;
1095 clientActive = false;
1098 // remove messages from connection list and send abandon
1101 System.Object temp_object;
1102 temp_object = messages[0];
1103 messages.RemoveAt(0);
1104 info = (Message) temp_object;
1106 catch (ArgumentOutOfRangeException ex)
1111 info.Abandon(null, notifyUser); // also notifies the application
1114 int semId = acquireWriteSemaphore(semaphoreId);
1115 // Now send unbind if socket not closed
1116 if ((bindProperties != null) && (out_Renamed != null) && (out_Renamed.CanWrite) && (!bindProperties.Anonymous))
1120 LdapMessage msg = new LdapUnbindRequest(null);
1121 sbyte[] ber = msg.Asn1Object.getEncoding(encoder);
1122 out_Renamed.Write(SupportClass.ToByteArray(ber), 0, ber.Length);
1123 out_Renamed.Flush();
1124 out_Renamed.Close();
1126 catch (System.Exception ex)
1128 ; // don't worry about error
1131 bindProperties = null;
1133 if (socket != null || sock != null)
1135 // Just before closing the sockets, abort the reader thread
1136 if ((reader != null) && (reason != "reader: thread stopping"))
1144 sock.Shutdown(SocketShutdown.Both);
1150 if(in_Renamed != null)
1157 // ignore problem closing socket
1164 freeWriteSemaphore(semId);
1168 /// <summary> This tests to see if there are any outstanding messages. If no messages
1169 /// are in the queue it returns true. Each message will be tested to
1170 /// verify that it is complete.
1171 /// <I>The writeSemaphore must be set for this method to be reliable!</I>
1174 /// <returns> true if no outstanding messages
1177 internal bool areMessagesComplete()
1179 System.Object[] messages = this.messages.ObjectArray;
1180 int length = messages.Length;
1182 // Check if SASL bind in progress
1183 if (bindSemaphoreId != 0)
1188 // Check if any messages queued
1194 for (int i = 0; i < length; i++)
1196 if (((Message) messages[i]).Complete == false)
1202 /// <summary> The reader thread will stop when a reply is read with an ID equal
1203 /// to the messageID passed in to this method. This is used by
1204 /// LdapConnection.StartTLS.
1207 internal void stopReaderOnReply(int messageID)
1210 this.stopReaderMessageID = messageID;
1214 /// <summary>startReader
1215 /// startReader should be called when socket and io streams have been
1216 /// set or changed. In particular after client.Connection.startTLS()
1217 /// It assumes the reader thread is not running.
1220 internal void startReader()
1222 // Start Reader Thread
1223 Thread r = new Thread(new ThreadStart(new ReaderThread(this).Run));
1224 r.IsBackground = true; // If the last thread running, allow exit.
1230 /// <summary> Indicates if the conenction is using TLS protection
1232 /// Return true if using TLS protection
1238 return (this.nonTLSBackup != null);
1242 /// <summary> StartsTLS, in this package, assumes the caller has:
1243 /// 1) Acquired the writeSemaphore
1244 /// 2) Stopped the reader thread
1245 /// 3) checked that no messages are outstanding on this connection.
1247 /// After calling this method upper layers should start the reader
1248 /// by calling startReader()
1250 /// In the client.Connection, StartTLS assumes Ldap.LdapConnection will
1251 /// stop and start the reader thread. Connection.StopTLS will stop
1252 /// and start the reader thread.
1255 internal void startTLS()
1259 waitForReader(null);
1260 this.nonTLSBackup = this.socket;
1261 /* this.sock = new Socket ( AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);
1262 IPAddress hostadd = Dns.Resolve(host).AddressList[0];
1263 IPEndPoint ephost = new IPEndPoint(hostadd,port);
1264 sock.Connect(ephost);
1266 // NetworkStream nstream = new NetworkStream(this.socket,true);
1267 // Load Mono.Security.dll
1271 a = Assembly.LoadFrom("Mono.Security.dll");
1273 catch(System.IO.FileNotFoundException)
1275 throw new LdapException(ExceptionMessages.SSL_PROVIDER_MISSING, LdapException.SSL_PROVIDER_NOT_FOUND, null);
1277 Type tSslClientStream = a.GetType("Mono.Security.Protocol.Tls.SslClientStream");
1278 BindingFlags flags = (BindingFlags.NonPublic | BindingFlags.Public |
1279 BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly);
1281 object[] consArgs = new object[4];
1282 consArgs[0] = socket.GetStream();
1284 consArgs[2] = false;
1285 Type tSecurityProtocolType = a.GetType("Mono.Security.Protocol.Tls.SecurityProtocolType");
1286 Enum objSPType = (Enum)(Activator.CreateInstance(tSecurityProtocolType));
1287 int nSsl3Val = (int) Enum.Parse(tSecurityProtocolType, "Ssl3");
1288 int nTlsVal = (int) Enum.Parse(tSecurityProtocolType, "Tls");
1289 consArgs[3] = Enum.ToObject(tSecurityProtocolType, nSsl3Val | nTlsVal);
1291 object objSslClientStream =
1292 Activator.CreateInstance(tSslClientStream, consArgs);
1294 // Register ServerCertValidationDelegate handler
1295 EventInfo ei = tSslClientStream.GetEvent("ServerCertValidationDelegate");
1296 ei.AddEventHandler(objSslClientStream,
1297 Delegate.CreateDelegate(ei.EventHandlerType, this, "ServerCertificateValidation"));
1299 // Get the in and out streams
1300 in_Renamed = (System.IO.Stream) objSslClientStream;
1301 out_Renamed = (System.IO.Stream) objSslClientStream;
1304 SslClientStream sslstream = new SslClientStream(
1309 Mono.Security.Protocol.Tls.SecurityProtocolType.Ssl3| Mono.Security.Protocol.Tls.SecurityProtocolType.Tls);
1310 sslstream.ServerCertValidationDelegate = new CertificateValidationCallback(ServerCertificateValidation);
1311 this.in_Renamed = (System.IO.Stream) sslstream;
1312 this.out_Renamed = (System.IO.Stream) sslstream;*/
1314 catch (System.IO.IOException ioe)
1316 this.nonTLSBackup = null;
1317 throw new LdapException("Could not negotiate a secure connection", LdapException.CONNECT_ERROR, null, ioe);
1319 catch (System.Exception uhe)
1321 this.nonTLSBackup = null;
1322 throw new LdapException("The host is unknown", LdapException.CONNECT_ERROR, null, uhe);
1330 * StopTLS, in this package, assumes the caller has:
1331 * 1) blocked writing (acquireWriteSemaphore).
1332 * 2) checked that no messages are outstanding.
1334 * StopTLS Needs to do the following:
1335 * 1) close the current socket
1336 * - This stops the reader thread
1337 * - set STOP_READING flag on stopReaderMessageID so that
1338 * the reader knows that the IOException is planned.
1339 * 2) replace the current socket with nonTLSBackup,
1340 * 3) and set nonTLSBackup to null;
1341 * 4) reset input and outputstreams
1342 * 5) start the reader thread by calling startReader
1344 * Note: Sun's JSSE doesn't allow the nonTLSBackup socket to be
1345 * used any more, even though autoclose was false: you get an IOException.
1346 * IBM's JSSE hangs when you close the JSSE socket.
1349 internal void stopTLS()
1353 this.stopReaderMessageID = Connection.STOP_READING;
1354 this.out_Renamed.Close();
1355 this.in_Renamed.Close();
1356 // this.sock.Shutdown(SocketShutdown.Both);
1357 // this.sock.Close();
1358 waitForReader(null);
1359 this.socket = this.nonTLSBackup;
1360 this.in_Renamed = (System.IO.Stream) this.socket.GetStream();
1361 this.out_Renamed = (System.IO.Stream) this.socket.GetStream();
1362 // Allow the new reader to start
1363 this.stopReaderMessageID = Connection.CONTINUE_READING;
1365 catch (System.IO.IOException ioe)
1367 throw new LdapException(ExceptionMessages.STOPTLS_ERROR, LdapException.CONNECT_ERROR, null, ioe);
1371 this.nonTLSBackup = null;
1376 ///TLS not supported in first release
1378 internal Stream InputStream
1380 get { return in_Renamed; }
1383 internal Stream OutputStream
1385 get { return out_Renamed; }
1388 internal void ReplaceStreams(Stream newIn, Stream newOut)
1390 // wait for reader to stop, see LdapConnection.Bind
1391 waitForReader(null);
1393 out_Renamed = newOut;
1397 public class ReaderThread
1399 private void InitBlock(Connection enclosingInstance)
1401 this.enclosingInstance = enclosingInstance;
1403 private Connection enclosingInstance;
1404 public Connection Enclosing_Instance
1408 return enclosingInstance;
1412 public ReaderThread(Connection enclosingInstance)
1414 InitBlock(enclosingInstance);
1418 /// <summary> This thread decodes and processes RfcLdapMessage's from the server.
1420 /// Note: This thread needs a graceful shutdown implementation.
1422 public virtual void Run()
1425 System.String reason = "reader: thread stopping";
1426 InterThreadException notify = null;
1427 Message info = null;
1428 System.IO.IOException ioex = null;
1429 this.enclosingInstance.reader = System.Threading.Thread.CurrentThread;
1430 // Enclosing_Instance.reader = SupportClass.ThreadClass.Current();
1431 // Console.WriteLine("Inside run:" + this.enclosingInstance.reader.Name);
1436 // -------------------------------------------------------
1437 // Decode an RfcLdapMessage directly from the socket.
1438 // -------------------------------------------------------
1439 Asn1Identifier asn1ID;
1440 System.IO.Stream myIn;
1441 /* get current value of in, keep value consistant
1442 * though the loop, i.e. even during shutdown
1444 myIn = this.enclosingInstance.in_Renamed;
1449 asn1ID = new Asn1Identifier(myIn);
1450 int tag = asn1ID.Tag;
1451 if (asn1ID.Tag != Asn1Sequence.TAG)
1453 continue; // loop looking for an RfcLdapMessage identifier
1456 // Turn the message into an RfcMessage class
1457 Asn1Length asn1Len = new Asn1Length(myIn);
1459 RfcLdapMessage msg = new RfcLdapMessage(this.enclosingInstance.decoder, myIn, asn1Len.Length);
1461 // ------------------------------------------------------------
1462 // Process the decoded RfcLdapMessage.
1463 // ------------------------------------------------------------
1464 int msgId = msg.MessageID;
1466 // Find the message which requested this response.
1467 // It is possible to receive a response for a request which
1468 // has been abandoned. If abandoned, throw it away
1471 info = this.enclosingInstance.messages.findMessageById(msgId);
1472 info.putReply(msg); // queue & wake up waiting thread
1474 catch (System.FieldAccessException ex)
1478 * We get the NoSuchFieldException when we could not find
1479 * a matching message id. First check to see if this is
1480 * an unsolicited notification (msgID == 0). If it is not
1481 * we throw it away. If it is we call any unsolicited
1482 * listeners that might have been registered to listen for these
1487 /* Note the location of this code. We could have required
1488 * that message ID 0 be just like other message ID's but
1489 * since message ID 0 has to be treated specially we have
1490 * a separate check for message ID 0. Also note that
1491 * this test is after the regular message list has been
1492 * checked for. We could have always checked the list
1493 * of messages after checking if this is an unsolicited
1494 * notification but that would have inefficient as
1495 * message ID 0 is a rare event (as of this time).
1501 // Notify any listeners that might have been registered
1502 this.enclosingInstance.notifyAllUnsolicitedListeners(msg);
1505 * Was this a server shutdown unsolicited notification.
1506 * IF so we quit. Actually calling the return will
1507 * first transfer control to the finally clause which
1508 * will do the necessary clean up.
1510 if (this.enclosingInstance.unsolSvrShutDnNotification)
1512 notify = new InterThreadException(ExceptionMessages.SERVER_SHUTDOWN_REQ, new System.Object[]{this.enclosingInstance.host, this.enclosingInstance.port}, LdapException.CONNECT_ERROR, null, null);
1522 if ((this.enclosingInstance.stopReaderMessageID == msgId) || (this.enclosingInstance.stopReaderMessageID == Novell.Directory.Ldap.Connection.STOP_READING))
1524 // Stop the reader Thread.
1529 catch(ThreadAbortException tae)
1531 // Abort has been called on reader
1532 // before closing sockets, from shutdown
1535 catch (System.IO.IOException ioe)
1539 if ((this.enclosingInstance.stopReaderMessageID != Novell.Directory.Ldap.Connection.STOP_READING) && this.enclosingInstance.clientActive)
1541 // Connection lost waiting for results from host:port
1542 notify = new InterThreadException(ExceptionMessages.CONNECTION_WAIT, new System.Object[]{this.enclosingInstance.host, this.enclosingInstance.port}, LdapException.CONNECT_ERROR, ioe, info);
1544 // The connection is no good, don't use it any more
1545 this.enclosingInstance.in_Renamed = null;
1546 this.enclosingInstance.out_Renamed = null;
1551 * There can be four states that the reader can be in at this point:
1552 * 1) We are starting TLS and will be restarting the reader
1553 * after we have negotiated TLS.
1554 * - Indicated by whether stopReaderMessageID does not
1555 * equal CONTINUE_READING.
1556 * - Don't call Shutdown.
1557 * 2) We are stoping TLS and will be restarting after TLS is
1559 * - Indicated by an IOException AND stopReaderMessageID equals
1560 * STOP_READING - in which case notify will be null.
1561 * - Don't call Shutdown
1562 * 3) We receive a Server Shutdown notification.
1563 * - Indicated by messageID equal to 0.
1565 * 4) Another error occured
1566 * - Indicated by an IOException AND notify is not NULL
1569 if ((!this.enclosingInstance.clientActive) || (notify != null))
1572 this.enclosingInstance.shutdown(reason, 0, notify);
1576 this.enclosingInstance.stopReaderMessageID = Novell.Directory.Ldap.Connection.CONTINUE_READING;
1579 this.enclosingInstance.deadReaderException = ioex;
1580 this.enclosingInstance.deadReader = this.enclosingInstance.reader;
1581 this.enclosingInstance.reader = null;
1584 } // End class ReaderThread
1586 /// <summary>Add the specific object to the list of listeners that want to be
1587 /// notified when an unsolicited notification is received.
1590 internal void AddUnsolicitedNotificationListener(LdapUnsolicitedNotificationListener listener)
1592 unsolicitedListeners.Add(listener);
1596 /// <summary>Remove the specific object from current list of listeners</summary>
1598 internal void RemoveUnsolicitedNotificationListener(LdapUnsolicitedNotificationListener listener)
1600 SupportClass.VectorRemoveElement(unsolicitedListeners, listener);
1604 /// <summary>Inner class defined so that we can spawn off each unsolicited
1605 /// listener as a seperate thread. We did not want to call the
1606 /// unsolicited listener method directly as this would have tied up our
1607 /// deamon listener thread in the applications unsolicited listener method.
1608 /// Since we do not know what the application unsolicited listener
1609 /// might be doing and how long it will take to process the uncoslicited
1610 /// notification. We use this class to spawn off the unsolicited
1611 /// notification as a separate thread
1613 private class UnsolicitedListenerThread:SupportClass.ThreadClass
1615 private void InitBlock(Connection enclosingInstance)
1617 this.enclosingInstance = enclosingInstance;
1619 private Connection enclosingInstance;
1620 public Connection Enclosing_Instance
1624 return enclosingInstance;
1628 private LdapUnsolicitedNotificationListener listenerObj;
1629 private LdapExtendedResponse unsolicitedMsg;
1632 internal UnsolicitedListenerThread(Connection enclosingInstance, LdapUnsolicitedNotificationListener l, LdapExtendedResponse m)
1634 InitBlock(enclosingInstance);
1635 this.listenerObj = l;
1636 this.unsolicitedMsg = m;
1640 public override void Run()
1642 listenerObj.messageReceived(unsolicitedMsg);
1647 private void notifyAllUnsolicitedListeners(RfcLdapMessage message)
1651 // MISSING: If this is a shutdown notification from the server
1652 // set a flag in the Connection class so that we can throw an
1653 // appropriate LdapException to the application
1654 LdapMessage extendedLdapMessage = new LdapExtendedResponse(message);
1655 System.String notificationOID = ((LdapExtendedResponse) extendedLdapMessage).ID;
1656 if (notificationOID.Equals(LdapConnection.SERVER_SHUTDOWN_OID))
1660 unsolSvrShutDnNotification = true;
1663 int numOfListeners = unsolicitedListeners.Count;
1665 // Cycle through all the listeners
1666 for (int i = 0; i < numOfListeners; i++)
1669 // Get next listener
1670 LdapUnsolicitedNotificationListener listener = (LdapUnsolicitedNotificationListener) unsolicitedListeners[i];
1673 // Create a new ExtendedResponse each time as we do not want each listener
1674 // to have its own copy of the message
1675 LdapExtendedResponse tempLdapMessage = new LdapExtendedResponse(message);
1677 // Spawn a new thread for each listener to go process the message
1678 // The reason we create a new thread rather than just call the
1679 // the messageReceived method directly is beacuse we do not know
1680 // what kind of processing the notification listener class will
1681 // do. We do not want our deamon thread to block waiting for
1682 // the notification listener method to return.
1683 UnsolicitedListenerThread u = new UnsolicitedListenerThread(this, listener, tempLdapMessage);
1692 nameLock = new System.Object();
1693 sdk = new System.Text.StringBuilder("2.1.8").ToString();