Cleaned & Modernized SMTPProxy.cs to add extra error catching.

This commit is contained in:
2026-08-17 10:32:08 +10:00
parent 44e40c7846
commit 67ea57cd9e

View File

@@ -14,26 +14,18 @@ using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
namespace smtpproxy.net; namespace smtpproxy.net
public class SMTPProxy : ServiceBase
{ {
public class SMTPProxy : ServiceBase
{
private ConcurrentQueue<Email> EmailQueue; private ConcurrentQueue<Email> EmailQueue;
private ManualResetEvent mre; private ManualResetEvent mre;
private bool bRunning; private bool bRunning;
private Thread Sending; private Thread Sending;
private Thread Listening; private Thread Listening;
private long ThreadCount; private long ThreadCount;
private bool ConsoleMode; private bool ConsoleMode;
private static readonly object _syncObject = new object(); private static readonly object _syncObject = new object();
private IContainer components; private IContainer components;
public SMTPProxy() public SMTPProxy()
@@ -42,25 +34,25 @@ public class SMTPProxy : ServiceBase
base.ServiceName = "SMTPProxy.net"; base.ServiceName = "SMTPProxy.net";
ConsoleMode = false; ConsoleMode = false;
EmailQueue = new ConcurrentQueue<Email>(); EmailQueue = new ConcurrentQueue<Email>();
mre = new ManualResetEvent(initialState: false); mre = new ManualResetEvent(false);
bRunning = true; bRunning = true;
ThreadCount = 0L; ThreadCount = 0L;
} }
protected override void OnStart(string[] args) protected override void OnStart(string[] args)
{ {
Listening = new Thread(StartListening); Listening = new Thread(StartListening) { IsBackground = true };
Listening.Start(); Listening.Start();
Sending = new Thread(StartSending); Sending = new Thread(StartSending) { IsBackground = true };
Sending.Start(); Sending.Start();
} }
public void Start() public void Start()
{ {
ConsoleMode = true; ConsoleMode = true;
Listening = new Thread(StartListening); Listening = new Thread(StartListening) { IsBackground = true };
Listening.Start(); Listening.Start();
Sending = new Thread(StartSending); Sending = new Thread(StartSending) { IsBackground = true };
Sending.Start(); Sending.Start();
} }
@@ -70,24 +62,41 @@ public class SMTPProxy : ServiceBase
mre.Set(); mre.Set();
} }
public void Reload(string Folder) public void Reload(string folder)
{ {
foreach (string item in Directory.EnumerateFiles(Folder, "*.eml").ToList()) if (!Directory.Exists(folder)) return;
foreach (string item in Directory.EnumerateFiles(folder, "*.eml"))
{ {
Email email = new Email(); try
email.sDATA = File.ReadAllText(item);
email.sHELO = File.ReadAllText(item.Replace(".eml", ".HELO"));
email.sMAIL = File.ReadAllText(item.Replace(".eml", ".MAIL"));
foreach (string item2 in File.ReadAllLines(item.Replace(".eml", ".RCPTS")).ToList())
{ {
if (item2.Length > 0) Email email = new Email
{ {
email.sRCPTs.Add(item2 + "\r\n"); sDATA = File.ReadAllText(item),
sHELO = File.Exists(item.Replace(".eml", ".HELO")) ? File.ReadAllText(item.Replace(".eml", ".HELO")) : "HELO localhost\r\n",
sMAIL = File.Exists(item.Replace(".eml", ".MAIL")) ? File.ReadAllText(item.Replace(".eml", ".MAIL")) : "",
FromIP = "127.0.0.1"
};
string rcptFile = item.Replace(".eml", ".RCPTS");
if (File.Exists(rcptFile))
{
foreach (string rcpt in File.ReadAllLines(rcptFile))
{
if (!string.IsNullOrWhiteSpace(rcpt))
{
email.sRCPTs.Add(rcpt.Trim() + "\r\n");
} }
} }
email.FromIP = "127.0.0.1"; }
EmailQueue.Enqueue(email); EmailQueue.Enqueue(email);
} }
catch (Exception ex)
{
Log($"Failed to reload email file {item}: {ex.Message}");
}
}
SendQueuedEmails(); SendQueuedEmails();
} }
@@ -101,457 +110,561 @@ public class SMTPProxy : ServiceBase
public void StartListening() public void StartListening()
{ {
Dns.GetHostEntry(Dns.GetHostName()); try
IPAddress iPAddress = IPAddress.Parse(ConfigurationManager.AppSettings["ListeningAddress"]); {
IPEndPoint localEP = new IPEndPoint(iPAddress, 25); string listenSetting = ConfigurationManager.AppSettings["ListeningAddress"] ?? "0.0.0.0";
Socket socket = new Socket(iPAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp); IPAddress ipAddress = IPAddress.Parse(listenSetting);
Log("Started Listening on port 25 at " + iPAddress.ToString()); IPEndPoint localEP = new IPEndPoint(ipAddress, 25);
Socket socket = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
socket.Bind(localEP); socket.Bind(localEP);
socket.Listen(100); socket.Listen(100);
while (true)
Log($"Started Listening on port 25 at {ipAddress}");
while (bRunning)
{ {
try try
{ {
Socket socket2 = socket.Accept(); Socket clientSocket = socket.Accept();
if (socket2.Connected) if (clientSocket.Connected)
{ {
Log("New Connection:" + DateTime.Now.ToString("G", CultureInfo.CreateSpecificCulture("en-AU"))); Log($"New Connection: {DateTime.Now.ToString("G", CultureInfo.CreateSpecificCulture("en-AU"))}");
ThreadPool.QueueUserWorkItem(ProcessMailClient, socket2); ThreadPool.QueueUserWorkItem(ProcessMailClient, clientSocket);
} }
} }
catch (SocketException ex) catch (SocketException ex)
{ {
Log($"SocketException : {ex.ToString()}"); if (!bRunning) break;
Log($"SocketException accepting client: {ex.Message}");
} }
catch (Exception ex2) catch (Exception ex)
{ {
Log($"Unexpected exception : {ex2.ToString()}"); Log($"Unexpected exception accepting client: {ex.Message}");
} }
} }
} }
catch (Exception ex)
{
Log($"Fatal listening error: {ex}");
}
}
private void ProcessMailClient(object stateinfo) private void ProcessMailClient(object stateInfo)
{ {
Socket socket = (Socket)stateinfo; Socket socket = (Socket)stateInfo;
socket.ReceiveTimeout = 180000; socket.ReceiveTimeout = 180000;
socket.SendTimeout = 180000; socket.SendTimeout = 180000;
byte[] bytes = Encoding.ASCII.GetBytes("220 Connected to SBC SMTPProxy.net\r\n");
socket.Send(bytes); IPEndPoint remoteEndPoint = socket.RemoteEndPoint as IPEndPoint;
IPEndPoint iPEndPoint = socket.RemoteEndPoint as IPEndPoint; string clientIp = remoteEndPoint?.Address.ToString() ?? "0.0.0.0";
string text = ConfigurationManager.AppSettings["LogFolder"] + "in";
Directory.CreateDirectory(text); string logDir = Path.Combine(ConfigurationManager.AppSettings["LogFolder"] ?? "C:\\Logs\\", "in");
string logFileName = $"{text}\\{Guid.NewGuid()}_{iPEndPoint.Address}.log"; Directory.CreateDirectory(logDir);
Log("New Client Connected from " + iPEndPoint.Address, logFileName); string logFileName = Path.Combine(logDir, $"{Guid.NewGuid()}_{clientIp}.log");
new List<Email>();
List<string> list = new List<string>(); Log($"New Client Connected from {clientIp}", logFileName);
byte[] array = new byte[2000];
StringBuilder stringBuilder = new StringBuilder(); byte[] welcome = Encoding.ASCII.GetBytes("220 Connected to SBC SMTPProxy.net\r\n");
string text2 = ""; socket.Send(welcome);
string text3 = "";
string text4 = ""; List<string> rcptList = new List<string>();
string text5 = ""; byte[] buffer = new byte[4096];
string text6 = ""; StringBuilder commandBuffer = new StringBuilder();
int num = 0;
bool flag = false; string heloHeader = "";
do string mailFrom = "";
{ string dataBody = "";
bool receivingData = false;
try try
{ {
num = socket.Receive(array, 1999, SocketFlags.None); while (socket.Connected)
if (num > 0)
{ {
array[num] = 0; int bytesRead = socket.Receive(buffer, SocketFlags.None);
string text7 = Encoding.Default.GetString(array, 0, num); if (bytesRead == 0)
text3 = text2 + text7; {
text2 = text7; Log("Client disconnected gracefully.", logFileName);
stringBuilder.Append(text7); break;
if (num == 1999 || !text3.Contains("\r\n")) }
string chunk = Encoding.Default.GetString(buffer, 0, bytesRead);
commandBuffer.Append(chunk);
if (receivingData)
{
dataBody += chunk;
if (dataBody.EndsWith("\r\n.\r\n") || dataBody == ".\r\n")
{
receivingData = false;
Email email = new Email
{
sHELO = heloHeader,
sMAIL = mailFrom,
sRCPTs = new List<string>(rcptList),
sDATA = dataBody,
FromIP = clientIp
};
EmailQueue.Enqueue(email);
Log("Queued inbound message.", logFileName);
mailFrom = "";
dataBody = "";
rcptList.Clear();
commandBuffer.Clear();
byte[] okBytes = Encoding.ASCII.GetBytes("250 OK\r\n");
socket.Send(okBytes);
Log("250 OK", logFileName);
}
continue;
}
// Process complete commands ending in newline
string fullBuffer = commandBuffer.ToString();
if (!fullBuffer.Contains("\r\n"))
{ {
continue; continue;
} }
string text8 = stringBuilder.ToString();
string text9 = ((text8.Length < 4) ? text8 : text8.Substring(0, 4)); string[] lines = fullBuffer.Split(new[] { "\r\n" }, StringSplitOptions.None);
string text10 = "500 Invalid Command\r\n"; // Keep uncompleted line in buffer
bool flag2 = false; commandBuffer.Clear();
if (flag) commandBuffer.Append(lines[lines.Length - 1]);
for (int i = 0; i < lines.Length - 1; i++)
{ {
text6 += text8; string line = lines[i].Trim();
if (text8 == ".\r\n" || text8.Contains("\r\n.\r\n")) if (string.IsNullOrEmpty(line)) continue;
{
flag = false; string verb = line.Length >= 4 ? line.Substring(0, 4).ToUpperInvariant() : line.ToUpperInvariant();
text10 = "250 OK\r\n"; string response = "500 Invalid Command\r\n";
Email email = new Email();
email.sHELO = text4; switch (verb)
email.sMAIL = text5;
email.sRCPTs = new List<string>(list);
email.sDATA = text6;
email.FromIP = iPEndPoint.Address.ToString();
EmailQueue.Enqueue(email);
text5 = (text6 = "");
list.Clear();
}
}
else
{
switch (text9)
{ {
case "HELO": case "HELO":
case "EHLO": case "EHLO":
text4 = stringBuilder.ToString(); heloHeader = line + "\r\n";
text10 = "250 Hi\r\n"; response = "250 Hi\r\n";
break; break;
case "MAIL": case "MAIL":
if (text4.Length == 0) if (string.IsNullOrEmpty(heloHeader))
{ {
text10 = "503 Do HELO first\r\n"; response = "503 Do HELO first\r\n";
break;
} }
if (text5.Length > 0) else if (!string.IsNullOrEmpty(mailFrom))
{ {
text10 = "503 Already have MAIL\r\n"; response = "503 Already have MAIL\r\n";
break; }
else
{
mailFrom = line + "\r\n";
response = "250 MAIL command recorded\r\n";
} }
text5 = stringBuilder.ToString();
text10 = "250 MAIL command recorded\r\n";
break; break;
case "RCPT": case "RCPT":
if (text4.Length == 0) if (string.IsNullOrEmpty(heloHeader))
{ {
text10 = "503 Do HELO first\r\n"; response = "503 Do HELO first\r\n";
break;
} }
if (text5.Length == 0) else if (string.IsNullOrEmpty(mailFrom))
{ {
text10 = "503 Do MAIL first\r\n"; response = "503 Do MAIL first\r\n";
break; }
else
{
rcptList.Add(line + "\r\n");
response = "250 RCPT command recorded\r\n";
} }
list.Add(stringBuilder.ToString());
text10 = "250 RCPT command recorded\r\n";
break; break;
case "DATA": case "DATA":
if (text4.Length == 0) if (string.IsNullOrEmpty(heloHeader))
{ {
text10 = "503 Do HELO first\r\n"; response = "503 Do HELO first\r\n";
} }
else if (text5.Length == 0) else if (string.IsNullOrEmpty(mailFrom))
{ {
text10 = "503 Do MAIL first\r\n"; response = "503 Do MAIL first\r\n";
} }
else if (list.Count == 0) else if (rcptList.Count == 0)
{ {
text10 = "503 Do RCPT first\r\n"; response = "503 Do RCPT first\r\n";
}
else
{
response = "354 Start mail input; end with <CRLF>.<CRLF>\r\n";
receivingData = true;
dataBody = "";
} }
text10 = "354 DATA\r\n";
flag2 = true;
break; break;
case "RSET": case "RSET":
text10 = "250 OK\r\n"; response = "250 OK\r\n";
text5 = (text6 = ""); mailFrom = "";
list.Clear(); dataBody = "";
rcptList.Clear();
break; break;
case "QUIT": case "QUIT":
byte[] quitBytes = Encoding.ASCII.GetBytes("221 Bye\r\n");
socket.Send(quitBytes);
socket.Shutdown(SocketShutdown.Both); socket.Shutdown(SocketShutdown.Both);
socket.Close(); socket.Close();
num = 0; return;
goto end_IL_00ed;
default: default:
Log("Unknown:" + text9, logFileName); Log($"Unknown command: {line}", logFileName);
break; break;
} }
byte[] respBytes = Encoding.ASCII.GetBytes(response);
socket.Send(respBytes);
Log(response.TrimEnd(), logFileName);
} }
if (!flag) }
}
catch (Exception ex)
{ {
byte[] bytes2 = Encoding.ASCII.GetBytes(text10); Log($"Session exception: {ex.Message}", logFileName);
socket.Send(bytes2);
Log(text10, logFileName);
} }
if (flag2) finally
{ {
flag = true; try
} {
stringBuilder.Clear(); if (socket.Connected)
array[0] = 0;
continue;
}
if (num == 0)
{ {
Log("Client disconnected.", logFileName);
continue;
}
Log("Client reset the connection.", logFileName);
socket.Shutdown(SocketShutdown.Both); socket.Shutdown(SocketShutdown.Both);
socket.Close(); socket.Close();
num = 0;
end_IL_00ed:;
}
catch (SocketException ex)
{
Log($"SocketException : {ex.ToString()}", logFileName);
num = 0;
}
catch (Exception ex2)
{
Log($"Unexpected exception : {ex2.ToString()}", logFileName);
num = 0;
} }
} }
while (socket.Connected && num > 0); catch { }
if (EmailQueue.Count > 5 && Interlocked.Read(ref ThreadCount) < Convert.ToUInt32(ConfigurationManager.AppSettings["ThreadMax"])) }
// Trigger worker threads if backlog grows
int threadMax = int.TryParse(ConfigurationManager.AppSettings["ThreadMax"], out var tm) ? tm : 5;
if (EmailQueue.Count > 5 && Interlocked.Read(ref ThreadCount) < threadMax)
{ {
Interlocked.Increment(ref ThreadCount); Interlocked.Increment(ref ThreadCount);
SendQueuedEmails(); ThreadPool.QueueUserWorkItem(state =>
Interlocked.Decrement(ref ThreadCount); {
try { SendQueuedEmails(); }
finally { Interlocked.Decrement(ref ThreadCount); }
});
} }
} }
private void Log(string info, string LogFileName = null) private void Log(string info, string logFileName = null)
{ {
if (ConsoleMode) if (ConsoleMode)
{ {
Console.WriteLine(info.Replace("\r\n", "")); Console.WriteLine(info?.Replace("\r\n", ""));
} }
if (!string.IsNullOrEmpty(LogFileName))
if (!string.IsNullOrEmpty(logFileName))
{ {
using (StreamWriter streamWriter = File.AppendText(LogFileName)) try
{ {
streamWriter.WriteLine("{0}", info); using (StreamWriter sw = File.AppendText(logFileName))
streamWriter.Flush(); {
sw.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} - {info}");
}
return; return;
} }
catch { }
} }
lock (_syncObject) lock (_syncObject)
{ {
DateTime now = DateTime.Now; try
using StreamWriter streamWriter2 = File.AppendText(string.Format("{0}\\{1:00}{2:00}{3:00}.log", ConfigurationManager.AppSettings["LogFolder"] + "in", now.Year, now.Month, now.Day)); {
streamWriter2.WriteLine("{0}", info); string logDir = Path.Combine(ConfigurationManager.AppSettings["LogFolder"] ?? "C:\\Logs\\", "in");
streamWriter2.Flush(); Directory.CreateDirectory(logDir);
string path = Path.Combine(logDir, $"{DateTime.Now:yyyyMMdd}.log");
using (StreamWriter sw = File.AppendText(path))
{
sw.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} - {info}");
}
}
catch { }
} }
} }
private void SendQueuedEmails() private void SendQueuedEmails()
{ {
JToken val2 = default(JToken); string profilesBase = ConfigurationManager.AppSettings["Profiles"] ?? "C:\\Profiles\\";
while (!EmailQueue.IsEmpty)
while (EmailQueue.TryDequeue(out Email email))
{ {
if (!EmailQueue.TryDequeue(out var result)) string cleanSender = email.sMAIL
.Replace(" ", "")
.Replace("MAILFROM:", "")
.Replace("<", "")
.Replace(">", "")
.Replace("\r", "")
.Replace("\n", "");
string profilePath = Path.Combine(profilesBase, email.FromIP, $"{cleanSender}.json");
if (!File.Exists(profilePath))
{ {
int atIndex = cleanSender.IndexOf('@');
string domain = atIndex >= 0 ? cleanSender.Substring(atIndex + 1) : "";
profilePath = Path.Combine(profilesBase, email.FromIP, $"default@{domain}.json");
if (!File.Exists(profilePath))
{
profilePath = Path.Combine(profilesBase, email.FromIP, "default.json");
}
}
if (!File.Exists(profilePath))
{
Log($"No configuration profile found for IP {email.FromIP} and sender {cleanSender}");
continue; continue;
} }
string sMAIL = result.sMAIL;
sMAIL = sMAIL.Replace(" ", ""); JObject config;
sMAIL = sMAIL.Replace("MAILFROM:", "");
sMAIL = sMAIL.Replace("<", "");
sMAIL = sMAIL.Replace(">", "");
sMAIL = sMAIL.Replace("\r", "");
sMAIL = sMAIL.Replace("\n", "");
string path = ConfigurationManager.AppSettings["Profiles"] + result.FromIP + "\\" + sMAIL + ".json";
if (!File.Exists(path))
{
int num = sMAIL.IndexOf('@');
string text = sMAIL.Substring(num + 1);
path = ConfigurationManager.AppSettings["Profiles"] + result.FromIP + "\\default@" + text + ".json";
if (!File.Exists(path))
{
path = ConfigurationManager.AppSettings["Profiles"] + result.FromIP + "\\default.json";
}
}
JObject val;
try try
{ {
val = JObject.Parse(File.ReadAllText(path)); config = JObject.Parse(File.ReadAllText(profilePath));
} }
catch (Exception ex) catch (Exception ex)
{ {
Log(ex.Message); Log($"Error parsing JSON profile at {profilePath}: {ex.Message}");
continue; continue;
} }
int num2 = Convert.ToInt32(val["logging"][(object)"enabled"]);
bool bHideRecipients = Convert.ToInt32(val["HideRecipients"]) == 1; bool loggingEnabled = config["logging"]?["enabled"]?.Value<int>() == 1;
bool num3 = num2 == 1; string logBasePath = config["logging"]?["path"]?.ToString() ?? "C:\\Logs\\";
string text2 = ""; bool hideRecipients = config["HideRecipients"]?.Value<int>() == 1;
string text3 = ((object)val["logging"][(object)"path"]).ToString() + "\\log";
Directory.CreateDirectory(text3); string logDir = Path.Combine(logBasePath, "log");
string outDir = Path.Combine(logBasePath, "out");
Directory.CreateDirectory(logDir);
Directory.CreateDirectory(outDir);
Guid guid = Guid.NewGuid(); Guid guid = Guid.NewGuid();
string recipientEmail = result.GetRecipientEmail(result.sRCPTs[0]); string primaryRcpt = email.sRCPTs.Count > 0 ? email.GetRecipientEmail(email.sRCPTs[0]) : "unknown";
text2 = $"{text3}\\{recipientEmail}.{guid}.log"; string mailLogPath = Path.Combine(logDir, $"{primaryRcpt}.{guid}.log");
string text4 = ((object)val["logging"][(object)"path"]).ToString() + "\\out";
Directory.CreateDirectory(text4); if (loggingEnabled)
if (num3)
{ {
File.WriteAllText($"{text4}\\{recipientEmail}.{guid}.eml", result.sDATA); File.WriteAllText(Path.Combine(outDir, $"{primaryRcpt}.{guid}.eml"), email.sDATA);
File.WriteAllText($"{text4}\\{recipientEmail}.{guid}.HELO", result.sHELO); File.WriteAllText(Path.Combine(outDir, $"{primaryRcpt}.{guid}.HELO"), email.sHELO);
File.WriteAllText($"{text4}\\{recipientEmail}.{guid}.MAIL", result.sMAIL); File.WriteAllText(Path.Combine(outDir, $"{primaryRcpt}.{guid}.MAIL"), email.sMAIL);
File.WriteAllLines($"{text4}\\{recipientEmail}.{guid}.RCPTS", result.sRCPTs); File.WriteAllLines(Path.Combine(outDir, $"{primaryRcpt}.{guid}.RCPTS"), email.sRCPTs);
} }
IPAddress iPAddress = Dns.GetHostEntry(((object)val["smtpserver"]).ToString()).AddressList[0];
IPEndPoint remoteEP = new IPEndPoint(iPAddress, Convert.ToInt32(((object)val["smtpserverport"]).ToString())); string smtpHost = config["smtpserver"]?.ToString();
int num4 = Convert.ToInt32(val["recipientreplacement"][(object)"enabled"]); int smtpPort = config["smtpserverport"]?.Value<int>() ?? 25;
List<string> list = val["recipientreplacement"][(object)"replacements"].ToObject<List<string>>();
int num5 = Convert.ToInt32(val["recipientfilter"][(object)"enabled"]); if (string.IsNullOrEmpty(smtpHost))
List<string> list2 = val["recipientfilter"][(object)"whitelist"].ToObject<List<string>>();
bool flag = false;
List<string> list3 = null;
if (val.TryGetValue("bcc", ref val2))
{ {
flag = Extensions.Value<int>((IEnumerable<JToken>)val2[(object)"enabled"]) == 1; Log("Missing smtpserver target in configuration profile.", mailLogPath);
list3 = val2[(object)"recipients"].ToObject<List<string>>(); continue;
} }
Socket socket = null;
bool flag2 = num4 == 1; bool replaceRecipients = config["recipientreplacement"]?["enabled"]?.Value<int>() == 1;
bool flag3 = num5 == 1; List<string> replacementList = config["recipientreplacement"]?["replacements"]?.ToObject<List<string>>() ?? new List<string>();
bool filterRecipients = config["recipientfilter"]?["enabled"]?.Value<int>() == 1;
List<string> whitelist = config["recipientfilter"]?["whitelist"]?.ToObject<List<string>>() ?? new List<string>();
bool bccEnabled = config["bcc"]?["enabled"]?.Value<int>() == 1;
List<string> bccRecipients = config["bcc"]?["recipients"]?.ToObject<List<string>>() ?? new List<string>();
Socket relaySocket = null;
try try
{ {
socket = new Socket(iPAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp); IPAddress targetIp;
socket.Connect(remoteEP); if (!IPAddress.TryParse(smtpHost, out targetIp))
if (!CheckAndSend("220", result.sHELO, socket, text2) || !CheckAndSend("250", result.sMAIL, socket, text2)) {
var hostEntry = Dns.GetHostEntry(smtpHost);
targetIp = hostEntry.AddressList.FirstOrDefault(a => a.AddressFamily == AddressFamily.InterNetwork) ?? hostEntry.AddressList[0];
}
relaySocket = new Socket(targetIp.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
relaySocket.ReceiveTimeout = 60000;
relaySocket.SendTimeout = 60000;
relaySocket.Connect(new IPEndPoint(targetIp, smtpPort));
if (!CheckAndSend("220", email.sHELO, relaySocket, mailLogPath) ||
!CheckAndSend("250", email.sMAIL, relaySocket, mailLogPath))
{ {
continue; continue;
} }
if (flag2)
bool hasValidRecipients = false;
if (replaceRecipients)
{ {
foreach (string item in list) foreach (string rep in replacementList)
{ {
string nextCommand = $"RCPT TO: {item}\r\n"; if (CheckAndSend("250", $"RCPT TO: <{rep}>\r\n", relaySocket, mailLogPath))
CheckAndSend("250", nextCommand, socket, text2); {
hasValidRecipients = true;
} }
goto IL_053d;
} }
int num6 = 0; }
foreach (string sRCPT in result.sRCPTs) else
{ {
if (sRCPT.Length < 8) foreach (string rcptLine in email.sRCPTs)
{ {
if (rcptLine.Trim().Length < 8) continue;
if (filterRecipients)
{
string extractedEmail = email.GetRecipientEmail(rcptLine);
if (!whitelist.Any(w => w.Equals(extractedEmail, StringComparison.OrdinalIgnoreCase)))
{
Log($"Filtering recipient: {extractedEmail}", mailLogPath);
continue; continue;
} }
if (flag3) Log($"Recipient approved by whitelist: {extractedEmail}", mailLogPath);
}
if (CheckAndSend("250", rcptLine, relaySocket, mailLogPath))
{ {
string r = result.GetRecipientEmail(sRCPT); hasValidRecipients = true;
if (!list2.Exists((string prd) => prd.Equals(r))) }
}
}
if (!hasValidRecipients)
{ {
Log("Filtering Recipient " + r, text2); Log("No valid recipients accepted by upstream relay. Aborting.", mailLogPath);
continue; continue;
} }
Log("Recipient " + r + " OK", text2);
} if (bccEnabled)
if (CheckAndSend("250", sRCPT, socket, text2))
{ {
num6++; foreach (string bcc in bccRecipients)
}
}
if (num6 != 0)
{ {
goto IL_053d; CheckAndSend("250", $"RCPT TO: <{bcc}>\r\n", relaySocket, mailLogPath);
} }
Log("No Valid recipients, aborting", text2); }
socket.Shutdown(SocketShutdown.Both);
socket.Close(); if (CheckAndSend("250", "DATA\r\n", relaySocket, mailLogPath))
goto end_IL_03a8;
IL_053d:
if (flag)
{ {
foreach (string item2 in list3) string cleansedData = CleanseDataBlock(email.sDATA, hideRecipients);
if (!cleansedData.EndsWith("\r\n")) cleansedData += "\r\n";
if (!cleansedData.EndsWith("\r\n.\r\n")) cleansedData += ".\r\n";
if (CheckAndSend("354", cleansedData, relaySocket, mailLogPath))
{ {
string nextCommand2 = $"RCPT TO: {item2}\r\n"; CheckAndSend("250", "RSET\r\n", relaySocket, mailLogPath);
CheckAndSend("250", nextCommand2, socket, text2); CheckAndSend("250", "QUIT\r\n", relaySocket, mailLogPath);
} }
} }
if (CheckAndSend("250", "DATA\r\n", socket, text2) && CheckAndSend("354", CleanseDataBlock(result.sDATA, bHideRecipients), socket, text2) && CheckAndSend("250", "RSET\r\n", socket, text2) && CheckAndSend("250", "QUIT\r\n", socket, text2)) }
catch (Exception ex)
{ {
socket.Shutdown(SocketShutdown.Both); Log($"Upstream relay transmission failed: {ex.Message}", mailLogPath);
socket.Close();
} }
end_IL_03a8:; finally
}
catch (ArgumentNullException ex2)
{ {
Log($"ArgumentNullException : {ex2.ToString()}", text2); if (relaySocket != null)
}
catch (SocketException ex3)
{ {
Log($"SocketException : {ex3.ToString()}", text2); try
}
catch (Exception ex4)
{ {
Log($"Unexpected exception : {ex4.ToString()}", text2); if (relaySocket.Connected) relaySocket.Shutdown(SocketShutdown.Both);
relaySocket.Close();
}
catch { }
}
} }
} }
} }
private static string CleanseDataBlock(string sData, bool bHideRecipients) private static string CleanseDataBlock(string sData, bool bHideRecipients)
{ {
if (bHideRecipients) if (!bHideRecipients || string.IsNullOrEmpty(sData)) return sData;
StringBuilder sb = new StringBuilder();
using (StringReader reader = new StringReader(sData))
{ {
StringBuilder stringBuilder = new StringBuilder(); string line;
using StringReader stringReader = new StringReader(sData); bool inHeaderSection = true;
string pattern = "^[Tt][Oo]: *$"; bool skippingToHeader = false;
string pattern2 = "^[Dd][Aa][Tt][Ee]:.*$";
bool flag = false; while ((line = reader.ReadLine()) != null)
bool flag2 = false;
for (string text = stringReader.ReadLine(); text != null; text = stringReader.ReadLine())
{ {
if (flag2) // Empty line marks end of email headers and start of MIME/Body
if (inHeaderSection && line.Length == 0)
{ {
stringBuilder.Append(text + "\r\n"); inHeaderSection = false;
} skippingToHeader = false;
else if (!flag)
{
if (Regex.Match(text, pattern).Success)
{
flag = true;
}
stringBuilder.Append(text + "\r\n");
}
else if (Regex.Match(text, pattern2).Success)
{
flag = false;
stringBuilder.Append(text + "\r\n");
flag2 = true;
}
}
return stringBuilder.ToString();
}
return sData;
} }
private bool CheckAndSend(string PrevSuccessCode, string NextCommand, Socket sock, string LogFileName) if (inHeaderSection)
{ {
byte[] array = new byte[2500]; if (Regex.IsMatch(line, @"^[Tt][Oo]:", RegexOptions.IgnoreCase))
string text = "";
int num = sock.Receive(array);
if (num <= 0)
{ {
Log("recv failed", LogFileName); skippingToHeader = true;
sock.Shutdown(SocketShutdown.Both); continue; // Strip To: line
sock.Close();
return false;
} }
array[num] = 0;
text = Encoding.ASCII.GetString(array, 0, num); // Check for header folding (continuation line starting with whitespace)
Log(text, LogFileName); if (skippingToHeader && (line.StartsWith(" ") || line.StartsWith("\t")))
if (text.Substring(0, 3) != PrevSuccessCode)
{ {
sock.Shutdown(SocketShutdown.Both); continue; // Strip folded recipient lines
sock.Close();
return false;
}
if (NextCommand.Length > 500)
{
Log(NextCommand.Substring(0, 500), LogFileName);
} }
else else
{ {
Log(NextCommand, LogFileName); skippingToHeader = false;
} }
byte[] bytes = Encoding.ASCII.GetBytes(NextCommand); }
sock.Send(bytes);
sb.Append(line).Append("\r\n");
}
}
return sb.ToString();
}
private bool CheckAndSend(string prevSuccessCode, string nextCommand, Socket sock, string logFileName)
{
byte[] buffer = new byte[4096];
try
{
int bytesRead = sock.Receive(buffer);
if (bytesRead <= 0)
{
Log("Upstream closed connection unexpectedly.", logFileName);
return false;
}
string response = Encoding.ASCII.GetString(buffer, 0, bytesRead);
Log($"Upstream << {response.TrimEnd()}", logFileName);
if (!response.StartsWith(prevSuccessCode, StringComparison.Ordinal))
{
Log($"Expected response code {prevSuccessCode}, received: {response}", logFileName);
return false;
}
Log($"Upstream >> {(nextCommand.Length > 200 ? nextCommand.Substring(0, 200) + "..." : nextCommand.TrimEnd())}", logFileName);
byte[] sendBytes = Encoding.ASCII.GetBytes(nextCommand);
sock.Send(sendBytes);
return true; return true;
} }
catch (Exception ex)
{
Log($"CheckAndSend failed: {ex.Message}", logFileName);
return false;
}
}
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
if (disposing && components != null) if (disposing)
{ {
components.Dispose(); components?.Dispose();
mre?.Dispose();
} }
base.Dispose(disposing); base.Dispose(disposing);
} }
@@ -559,6 +672,7 @@ public class SMTPProxy : ServiceBase
private void InitializeComponent() private void InitializeComponent()
{ {
components = new Container(); components = new Container();
base.ServiceName = "Service1"; base.ServiceName = "SMTPProxy.net";
}
} }
} }