/// <summary> /// Is called when TLS handshake has completed. /// </summary> /// <param name="op">Asynchronous operation.</param> /// <exception cref="ArgumentNullException">Is raised when <b>op</b> is null reference.</exception> private void SwitchToSecureCompleted(SwitchToSecureAsyncOP op) { if(op == null){ throw new ArgumentNullException("op"); } try{ if(op.Error != null){ m_pException = op.Error; m_pSmtpClient.LogAddException("Exception: " + m_pException.Message,m_pException); } else{ // Log m_pSmtpClient.LogAddText("TLS handshake completed sucessfully."); } } catch(Exception x){ m_pException = x; m_pSmtpClient.LogAddException("Exception: " + m_pException.Message,m_pException); } op.Dispose(); SetState(AsyncOP_State.Completed); }
private void STARTTLS(string cmdTag,string cmdText) { /* RFC 3501 6.2.1. STARTTLS Command. Arguments: none Responses: no specific response for this command Result: OK - starttls completed, begin TLS negotiation BAD - command unknown or arguments invalid A [TLS] negotiation begins immediately after the CRLF at the end of the tagged OK response from the server. Once a client issues a STARTTLS command, it MUST NOT issue further commands until a server response is seen and the [TLS] negotiation is complete. The server remains in the non-authenticated state, even if client credentials are supplied during the [TLS] negotiation. This does not preclude an authentication mechanism such as EXTERNAL (defined in [SASL]) from using client identity determined by the [TLS] negotiation. Once [TLS] has been started, the client MUST discard cached information about server capabilities and SHOULD re-issue the CAPABILITY command. This is necessary to protect against man-in- the-middle attacks which alter the capabilities list prior to STARTTLS. The server MAY advertise different capabilities after STARTTLS. Example: C: a001 CAPABILITY S: * CAPABILITY IMAP4rev1 STARTTLS LOGINDISABLED S: a001 OK CAPABILITY completed C: a002 STARTTLS S: a002 OK Begin TLS negotiation now <TLS negotiation, further commands are under [TLS] layer> C: a003 CAPABILITY S: * CAPABILITY IMAP4rev1 AUTH=PLAIN S: a003 OK CAPABILITY completed C: a004 LOGIN joe password S: a004 OK LOGIN completed */ if(m_SessionRejected){ m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Bad sequence of commands: Session rejected.")); return; } if(this.IsAuthenticated){ m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","This ommand is only valid in not-authenticated state.")); return; } if(this.IsSecureConnection){ m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","Bad sequence of commands: Connection is already secure.")); return; } if(this.Certificate == null){ m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"NO","TLS not available: Server has no SSL certificate.")); return; } m_pResponseSender.SendResponseAsync(new IMAP_r_ServerStatus(cmdTag,"OK","Begin TLS negotiation now.")); try{ DateTime startTime = DateTime.Now; // Create delegate which is called when SwitchToSecureAsync has completed. Action<SwitchToSecureAsyncOP> switchSecureCompleted = delegate(SwitchToSecureAsyncOP e){ try{ // Operation failed. if(e.Error != null){ LogAddException(e.Error); Disconnect(); } // Operation suceeded. else{ // Log LogAddText("SSL negotiation completed successfully in " + (DateTime.Now - startTime).TotalSeconds.ToString("f2") + " seconds."); BeginReadCmd(); } } catch(Exception x){ LogAddException(x); Disconnect(); } }; SwitchToSecureAsyncOP op = new SwitchToSecureAsyncOP(); op.CompletedAsync += delegate(object sender,EventArgs<TCP_ServerSession.SwitchToSecureAsyncOP> e){ switchSecureCompleted(op); }; // Switch to secure completed synchronously. if(!SwitchToSecureAsync(op)){ switchSecureCompleted(op); } } catch(Exception x){ LogAddException(x); Disconnect(); } }
/// <summary> /// Is called when STARTTLS command response reading has completed. /// </summary> /// <param name="op">Asynchronous operation.</param> /// <exception cref="ArgumentNullException">Is raised when <b>op</b> is null reference.</exception> private void StartTlsReadResponseCompleted(ReadResponseAsyncOP op) { if(op == null){ throw new ArgumentNullException("op"); } try{ if(op.Error != null){ m_pException = op.Error; } else{ // STARTTLS accepted. if(op.ReplyLines[0].ReplyCode == 220){ /* RFC 3207 4. The format for the STARTTLS command is: STARTTLS with no parameters. After the client gives the STARTTLS command, the server responds with one of the following reply codes: 220 Ready to start TLS 501 Syntax error (no parameters allowed) 454 TLS not available due to temporary reason */ // Log m_pSmtpClient.LogAddText("Starting TLS handshake."); SwitchToSecureAsyncOP switchSecureOP = new SwitchToSecureAsyncOP(m_pCertCallback); switchSecureOP.CompletedAsync += delegate(object s,EventArgs<SwitchToSecureAsyncOP> e){ SwitchToSecureCompleted(switchSecureOP); }; if(!m_pSmtpClient.SwitchToSecureAsync(switchSecureOP)){ SwitchToSecureCompleted(switchSecureOP); } } // STARTTLS failed. else{ m_pException = new SMTP_ClientException(op.ReplyLines); } } } catch(Exception x){ m_pException = x; } op.Dispose(); if(m_pException != null){ m_pSmtpClient.LogAddException("Exception: " + m_pException.Message,m_pException); SetState(AsyncOP_State.Completed); } }
/// <summary> /// Starts switching connection to secure. /// </summary> /// <param name="op">Asynchronous operation.</param> /// <returns>Returns true if aynchronous operation is pending (The <see cref="SwitchToSecureAsyncOP.CompletedAsync"/> event is raised upon completion of the operation). /// Returns false if operation completed synchronously.</returns> /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and and this method is accessed.</exception> /// <exception cref="InvalidOperationException">Is raised when TCP client is not connected or connection is already secure.</exception> /// <exception cref="ArgumentNullException">Is raised when <b>op</b> is null reference.</exception> protected bool SwitchToSecureAsync(SwitchToSecureAsyncOP op) { if(this.IsDisposed){ throw new ObjectDisposedException(this.GetType().Name); } if(!this.IsConnected){ throw new InvalidOperationException("You must connect first."); } if(this.IsSecureConnection){ throw new InvalidOperationException("Connection is already secure."); } if(op == null){ throw new ArgumentNullException("op"); } if(op.State != AsyncOP_State.WaitingForStart){ throw new ArgumentException("Invalid argument 'op' state, 'op' must be in 'AsyncOP_State.WaitingForStart' state.","op"); } return op.Start(this); }
/// <summary> /// Starts switching connection to secure. /// </summary> /// <param name="op">Asynchronous operation.</param> /// <returns>Returns true if aynchronous operation is pending (The <see cref="SwitchToSecureAsyncOP.CompletedAsync"/> event is raised upon completion of the operation). /// Returns false if operation completed synchronously.</returns> /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and and this method is accessed.</exception> /// <exception cref="InvalidOperationException">Is raised when connection is already secure or when SSL certificate is not specified.</exception> /// <exception cref="ArgumentNullException">Is raised when <b>op</b> is null reference.</exception> public bool SwitchToSecureAsync(SwitchToSecureAsyncOP op) { if(this.IsDisposed){ throw new ObjectDisposedException(this.GetType().Name); } if(this.IsSecureConnection){ throw new InvalidOperationException("Connection is already secure."); } if(m_pCertificate == null){ throw new InvalidOperationException("There is no certificate specified."); } if(op == null){ throw new ArgumentNullException("op"); } if(op.State != AsyncOP_State.WaitingForStart){ throw new ArgumentException("Invalid argument 'op' state, 'op' must be in 'AsyncOP_State.WaitingForStart' state.","op"); } return op.Start(this); }
/// <summary> /// Switches session to secure connection. /// </summary> /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this method is accessed.</exception> /// <exception cref="InvalidOperationException">Is raised when connection is already secure or when SSL certificate is not specified.</exception> public void SwitchToSecure() { if(m_IsDisposed){ throw new ObjectDisposedException("TCP_ServerSession"); } if(m_IsSecure){ throw new InvalidOperationException("Session is already SSL/TLS."); } if(m_pCertificate == null){ throw new InvalidOperationException("There is no certificate specified."); } ManualResetEvent wait = new ManualResetEvent(false); using(SwitchToSecureAsyncOP op = new SwitchToSecureAsyncOP()){ op.CompletedAsync += delegate(object s1,EventArgs<SwitchToSecureAsyncOP> e1){ wait.Set(); }; if(!this.SwitchToSecureAsync(op)){ wait.Set(); } wait.WaitOne(); wait.Close(); if(op.Error != null){ throw op.Error; } } }
/// <summary> /// This method is called from TCP server when session should start processing incoming connection. /// </summary> internal void StartI() { if(m_IsSsl){ // Log LogAddText("Starting SSL negotiation now."); DateTime startTime = DateTime.Now; // Create delegate which is called when SwitchToSecureAsync has completed. Action<SwitchToSecureAsyncOP> switchSecureCompleted = delegate(SwitchToSecureAsyncOP e){ try{ // Operation failed. if(e.Error != null){ LogAddException(e.Error); if(!this.IsDisposed){ Disconnect(); } } // Operation suceeded. else{ // Log LogAddText("SSL negotiation completed successfully in " + (DateTime.Now - startTime).TotalSeconds.ToString("f2") + " seconds."); Start(); } } catch(Exception x){ LogAddException(x); if(!this.IsDisposed){ Disconnect(); } } }; SwitchToSecureAsyncOP op = new SwitchToSecureAsyncOP(); op.CompletedAsync += delegate(object sender,EventArgs<TCP_ServerSession.SwitchToSecureAsyncOP> e){ switchSecureCompleted(op); }; // Switch to secure completed synchronously. if(!SwitchToSecureAsync(op)){ switchSecureCompleted(op); } } else{ Start(); } }
/// <summary> /// Is called when POP3 server STLS response reading has completed. /// </summary> /// <param name="op">Asynchronous operation.</param> private void StlsReadResponseCompleted(SmartStream.ReadLineAsyncOP op) { try{ // Operation failed. if(op.Error != null){ m_pException = op.Error; m_pPop3Client.LogAddException("Exception: " + op.Error.Message,op.Error); SetState(AsyncOP_State.Completed); } // Operation succeeded. else{ // Log m_pPop3Client.LogAddRead(op.BytesInBuffer,op.LineUtf8); // Server returned success response. if(string.Equals(op.LineUtf8.Split(new char[]{' '},2)[0],"+OK",StringComparison.InvariantCultureIgnoreCase)){ // Log m_pPop3Client.LogAddText("Starting TLS handshake."); SwitchToSecureAsyncOP switchSecureOP = new SwitchToSecureAsyncOP(m_pCertCallback); switchSecureOP.CompletedAsync += delegate(object s,EventArgs<SwitchToSecureAsyncOP> e){ SwitchToSecureCompleted(switchSecureOP); }; if(!m_pPop3Client.SwitchToSecureAsync(switchSecureOP)){ SwitchToSecureCompleted(switchSecureOP); } } // Server returned error response. else{ m_pException = new POP3_ClientException(op.LineUtf8); SetState(AsyncOP_State.Completed); } } } catch(Exception x){ m_pException = x; m_pPop3Client.LogAddException("Exception: " + x.Message,x); SetState(AsyncOP_State.Completed); } op.Dispose(); }