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
}
}