using System; using System.Collections; using System.IO; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; using TGSupport.FileUtilities; namespace TGNetServices.Protocols { /// /// SocketAsync Summary /// public class SocketAsync { // Private members private Socket m_Socket = null; // Client socket private string m_SessionID = ""; // Unique socket session ID private string m_RemoteIP = ""; // Remote IP address private string m_RemotePort = ""; // Remote port number private string m_LogFile = ""; // Fully qualified file name for log messages private bool m_LogCmds = false; // Enables commands writes to log file private int m_LogLength = LOGLEN_DEF; // Maximum log message length // Callback members internal byte[] m_RecvBuff = new byte[BUF_SIZE]; // Receive buffer internal MemoryStream m_RecvStream = null; // Received data raw byte stream internal long m_RecvMax = 0; // Received data maximum byte count internal bool m_RecvOK = false; // Did socket complete successfully? internal string m_RecvEnd = ""; // End of transmission command internal long m_SendCount = 0; // Received data output byte count internal bool m_SendOK = false; // Did socket complete successfully? // Semaphores internal ManualResetEvent RecvDone = new ManualResetEvent(false); internal ManualResetEvent SendDone = new ManualResetEvent(false); // Constants private const int BUF_SIZE = 1024; // Size of receive buffer. private const int RECEIVE_TIMEOUT = 5000; // 5 Second timeout private const int SEND_TIMEOUT = 5000; // 5 Second timeout private const string CMD_CRLF = "\r\n"; // Carriage return + Linefeed private const int LOGLEN_DEF = 100; // Maximum log message length default #region Constructors /// /// SocketAsync Summary /// public SocketAsync(string sLogFile, bool bLogCmds) { // Initialize the logging information m_LogFile = sLogFile; m_LogCmds = bLogCmds; m_SessionID = this.GetHashCode().ToString();; } #endregion #region function Open / Close /// /// Open Summary /// public void Open(Socket Socket) { // Initialize the connection identification information m_Socket = Socket; // Get the remote endpoints m_RemoteIP = ((IPEndPoint)m_Socket.RemoteEndPoint).Address.ToString(); m_RemotePort = ((IPEndPoint)m_Socket.RemoteEndPoint).Port.ToString(); // Bound sockets do not appear to report their LocalEndPoint correctly // The LocalEndPoint can be null for the entire communication // if (m_Socket.LocalEndPoint != null) // { // The LocalEndPoint is *TOTALLY* unreliable. After spending a lot // of time debugging, I cannot determine when this field is valid // m_LocalIP = ((IPEndPoint)m_Socket.LocalEndPoint).Address.ToString(); // m_LocalPort = ((IPEndPoint)m_Socket.LocalEndPoint).Port.ToString(); // } } /// /// Close Summary /// public void Close() { // Initialize the connection identification information if(m_Socket != null) { m_Socket.Shutdown(SocketShutdown.Both); m_Socket.Close(); m_Socket = null; } } #endregion #region function DataReceive /// /// DataReceive Summary /// public bool DataReceive(string sCmdEnd, long lMaxLength) { // Has the connection been initialized? if (m_Socket == null) return (false); // Initialize the incoming data stream m_RecvStream = new MemoryStream(); m_RecvMax = lMaxLength; m_RecvEnd = sCmdEnd; // Reset the semaphore to wait state RecvDone.Reset(); // Begin receiving data try { // Initialize and start the asynchronous socket m_Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, RECEIVE_TIMEOUT); m_Socket.BeginReceive(m_RecvBuff, 0, BUF_SIZE, 0, new AsyncCallback(RecvCallback), this); } catch (Exception e) { // Log error if requested if(m_LogCmds) { FileUtilities.LogWrite(m_LogFile, m_SessionID, m_RemoteIP, "SMTP DataReceive: ", e.Message); } // Error occurred, incoming data stream still empty return false; } // Wait until read is completed before continuing. RecvDone.WaitOne(); // Read complete, return data if (m_RecvOK) { // Log incoming client data if(m_LogCmds) { if (m_RecvStream.Length <= m_LogLength) { // Convert any embedded CRLF strings string sData = ReceiveStringTail(m_LogLength); sData = sData.Replace(CMD_CRLF,""); FileUtilities.LogWrite(m_LogFile, m_SessionID, m_RemoteIP, "C", sData); } else { FileUtilities.LogWrite(m_LogFile, m_SessionID, m_RemoteIP, "C", ""); } } } else { // Timeout - Log the tail of the data stream if(m_LogCmds) { string sData = ReceiveStringTail(m_LogLength); sData = sData.Replace(CMD_CRLF,""); FileUtilities.LogWrite(m_LogFile, m_SessionID, m_RemoteIP, "SMTP DataReceive Timeout " + m_RecvStream.Length.ToString() + " bytes", sData); } } // Return to calling thread return (m_RecvOK); } internal bool DataReceiveHidden(string sCmdEnd, long lMaxLength) { // Hides logging for passwords and other sensitive information int iCurrent = m_LogLength; m_LogLength = 0; DataReceive(sCmdEnd, lMaxLength); m_LogLength = iCurrent; return (m_RecvOK); } private void RecvCallback(IAsyncResult ar) { int bytesRead = 0; SocketAsync pSockA = null; // Catch asynchronous errors locally & log them try { // Retrieve the handler socket from the async state object pSockA = (SocketAsync) ar.AsyncState; // Read data from the client socket bytesRead = pSockA.m_Socket.EndReceive(ar); } catch (Exception e) { // Error occurred, zero out byte count bytesRead = 0; // Log error if requested if(pSockA.m_LogCmds) { FileUtilities.LogWrite(pSockA.m_LogFile, pSockA.m_SessionID, pSockA.m_RemoteIP, "SMTP SendCallback: ", e.Message); } } // Any data received? if (bytesRead <= 0) { // Socket timed out pSockA.m_RecvOK = false; // Signal the main thread to continue pSockA.RecvDone.Set(); // Terminate asynchronous operation return; } // Process incoming data stream // There might be more data, so store what has been received so far pSockA.m_RecvStream.Seek(0, SeekOrigin.End); pSockA.m_RecvStream.Write(pSockA.m_RecvBuff, 0, bytesRead); // Look for end message '' or '.' at tail if (pSockA.m_RecvEnd == pSockA.ReceiveStringTail(pSockA.m_RecvEnd.Length)) { // Remove trailing terminator - end of message pSockA.m_RecvStream.SetLength (pSockA.m_RecvStream.Length - pSockA.m_RecvEnd.Length); // All the data has been read from the client pSockA.m_RecvOK = true; // Signal the main thread to continue pSockA.RecvDone.Set(); // Terminate asynchronous operation return; } // Maximum message length exceeded? if (pSockA.m_RecvStream.Length >= pSockA.m_RecvMax) { // Truncate incoming message pSockA.ReceiveStringAppend(pSockA.m_RecvEnd); // Data lost from the client pSockA.m_RecvOK = false; // Signal the main thread to continue pSockA.RecvDone.Set(); // Terminate asynchronous operation return; } // Not all data received, get more try { pSockA.m_Socket.BeginReceive(pSockA.m_RecvBuff, 0, BUF_SIZE, 0, new AsyncCallback(RecvCallback), pSockA); } catch (Exception e) { // Log error if requested if(pSockA.m_LogCmds) { FileUtilities.LogWrite(pSockA.m_LogFile, pSockA.m_SessionID, pSockA.m_RemoteIP, "SMTP SendCallback: ", e.Message); } // Data lost from the client pSockA.m_RecvOK = false; // Signal the main thread to continue pSockA.RecvDone.Set(); // Terminate asynchronous operation return; } } #endregion #region function DataSend /// /// DataSend Summary /// public bool DataSend(byte[] bData, string sCmdEnd) { // Has the connection been initialized? if (m_Socket == null) return (false); // Begin sending the data to the remote device. SendDone.Reset(); m_SendCount = bData.Length; // Initialize and start the asynchronous socket m_Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, SEND_TIMEOUT); m_Socket.BeginSend(bData, 0, bData.Length, 0, new AsyncCallback(SendCallback), this); // Wait until read is completed before continuing. SendDone.WaitOne(); // Log outgoing client data if(m_LogCmds) { if(m_SendOK) FileUtilities.LogWrite(m_LogFile, m_SessionID, m_RemoteIP, "S", ""); else FileUtilities.LogWrite(m_LogFile, m_SessionID, m_RemoteIP, "DataSend", "Failed to send " + bData.Length.ToString() + " bytes>"); } // Continue? if (!m_SendOK) return false; // Send the command terminator // Convert the string data to byte data using ASCII encoding SendDone.Reset(); byte[] bCmdEnd = Encoding.ASCII.GetBytes(sCmdEnd); m_SendCount = bCmdEnd.Length; m_Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, SEND_TIMEOUT); m_Socket.BeginSend(bCmdEnd, 0, bCmdEnd.Length, 0, new AsyncCallback(SendCallback), this); // Wait until read is completed before continuing. SendDone.WaitOne(); // Return to calling thread return(m_SendOK); } internal bool DataSendCommand(string sData) { // Has the connection been initialized? if (m_Socket == null) return (false); // Append CRLF for all transmissions // Convert the string data to byte data using ASCII encoding byte[] SendBuffer = Encoding.ASCII.GetBytes(sData + CMD_CRLF); // Log outgoing client data if(m_LogCmds) { // Log the head of the data stream string sHead = sData.Substring (0, Math.Min(sData.Length, m_LogLength)); // Convert any embedded , ignore trailing which is only in SendBuffer sHead = sHead.Replace(CMD_CRLF,""); FileUtilities.LogWrite(m_LogFile, m_SessionID, m_RemoteIP, "S", sHead); } // Send data DataSendStart(SendBuffer); // Return to calling thread return(m_SendOK); } internal bool DataSendCommandHidden(string sData) { // Hides logging for passwords and other sensitive information int iCurrent = m_LogLength; m_LogLength = 0; DataSendCommand(sData); m_LogLength = iCurrent; return(m_SendOK); } private void DataSendStart(byte[] SendBuffer) { // Initialize transmission variables SendDone.Reset(); m_SendCount = SendBuffer.Length; // Begin sending data try { // Initialize and start the asynchronous socket m_Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, SEND_TIMEOUT); m_Socket.BeginSend(SendBuffer, 0, SendBuffer.Length, 0, new AsyncCallback(SendCallback), this); } catch (Exception e) { // Log error if requested if(m_LogCmds) { FileUtilities.LogWrite(m_LogFile, m_SessionID, m_RemoteIP, "SMTP DataSendStart: ", e.Message); } // Indicate that an error occurred and return m_SendOK = false; return; } // Wait until read is completed before continuing. SendDone.WaitOne(); } private void SendCallback(IAsyncResult ar) { int bytesSent = 0; // Retrieve the handler socket from the async state object SocketAsync pSockA = (SocketAsync) ar.AsyncState; // Catch asynchronous errors locally & log them try { // Retrieve the socket from the state object. Socket Socket = pSockA.m_Socket; // Complete sending the data to the remote device. bytesSent = Socket.EndSend(ar); } catch (Exception e) { // Error occurred, zero out byte count bytesSent = 0; // Log error if requested if(pSockA.m_LogCmds) { FileUtilities.LogWrite(pSockA.m_LogFile, pSockA.m_SessionID, pSockA.m_RemoteIP, "SMTP SendCallback: ", e.Message); } } finally { // Indicate timeout if sent bytes not equal to total pSockA.m_SendOK = (bytesSent == pSockA.m_SendCount); // Signal the main thread to continue pSockA.SendDone.Set(); } } #endregion #region Properties /// /// Gets Session ID /// public string SessionID { get {return m_SessionID;} } /// /// Turn command logging on or off /// public bool LogFileActive { get {return m_LogCmds;} set {m_LogCmds = value;} } /// /// Gets log file name /// public string LogFileFullName { get {return (m_LogFile);} } /// /// Gets Remote Connection IPAddress /// public string RemoteIP { get {return m_RemoteIP;} } /// /// Gets Remote Connection Port Number /// public string RemotePort { get {return m_RemotePort;} } /// /// ReceiveBytes Summary /// public byte[] ReceiveBytes { // Return the incoming memory stream as a string get {return m_RecvStream.ToArray();} } /// /// ReceiveString Summary /// public string ReceiveString { // Return the incoming memory stream as a string get {return Encoding.ASCII.GetString(m_RecvStream.ToArray());} } /// /// ReceiveStringAppend Summary /// public void ReceiveStringAppend (string sString) { // Append a string to the incoming memory stream byte[] bufTail = Encoding.ASCII.GetBytes(sString); m_RecvStream.Seek(0, SeekOrigin.End); m_RecvStream.Write(bufTail, 0, bufTail.Length); } /// /// ReceiveStringTail Summary /// public string ReceiveStringTail (long lReqLen) { // Return the tail of the incoming data stream lReqLen = Math.Min (lReqLen, m_RecvStream.Length); byte[] bufTail = new byte[lReqLen]; m_RecvStream.Position = m_RecvStream.Length - lReqLen; m_RecvStream.Read(bufTail, 0, (int) lReqLen); return (Encoding.ASCII.GetString(bufTail)); } #endregion } }