using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; using System.Configuration; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.ServiceProcess; using System.Text; using System.Text.RegularExpressions; using System.Threading; using Newtonsoft.Json.Linq; namespace smtpproxy.net; public class SMTPProxy : ServiceBase { private ConcurrentQueue EmailQueue; private ManualResetEvent mre; private bool bRunning; private Thread Sending; private Thread Listening; private long ThreadCount; private bool ConsoleMode; private static readonly object _syncObject = new object(); private IContainer components; public SMTPProxy() { InitializeComponent(); base.ServiceName = "SMTPProxy.net"; ConsoleMode = false; EmailQueue = new ConcurrentQueue(); mre = new ManualResetEvent(initialState: false); bRunning = true; ThreadCount = 0L; } protected override void OnStart(string[] args) { Listening = new Thread(StartListening); Listening.Start(); Sending = new Thread(StartSending); Sending.Start(); } public void Start() { ConsoleMode = true; Listening = new Thread(StartListening); Listening.Start(); Sending = new Thread(StartSending); Sending.Start(); } protected override void OnStop() { bRunning = false; mre.Set(); } public void Reload(string Folder) { foreach (string item in Directory.EnumerateFiles(Folder, "*.eml").ToList()) { Email email = new Email(); 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.sRCPTs.Add(item2 + "\r\n"); } } email.FromIP = "127.0.0.1"; EmailQueue.Enqueue(email); } SendQueuedEmails(); } public void StartSending() { while (!mre.WaitOne(30000) && bRunning) { SendQueuedEmails(); } } public void StartListening() { Dns.GetHostEntry(Dns.GetHostName()); IPAddress iPAddress = IPAddress.Parse(ConfigurationManager.AppSettings["ListeningAddress"]); IPEndPoint localEP = new IPEndPoint(iPAddress, 25); Socket socket = new Socket(iPAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp); Log("Started Listening on port 25 at " + iPAddress.ToString()); socket.Bind(localEP); socket.Listen(100); while (true) { try { Socket socket2 = socket.Accept(); if (socket2.Connected) { Log("New Connection:" + DateTime.Now.ToString("G", CultureInfo.CreateSpecificCulture("en-AU"))); ThreadPool.QueueUserWorkItem(ProcessMailClient, socket2); } } catch (SocketException ex) { Log($"SocketException : {ex.ToString()}"); } catch (Exception ex2) { Log($"Unexpected exception : {ex2.ToString()}"); } } } private void ProcessMailClient(object stateinfo) { Socket socket = (Socket)stateinfo; socket.ReceiveTimeout = 180000; socket.SendTimeout = 180000; byte[] bytes = Encoding.ASCII.GetBytes("220 Connected to SBC SMTPProxy.net\r\n"); socket.Send(bytes); IPEndPoint iPEndPoint = socket.RemoteEndPoint as IPEndPoint; string text = ConfigurationManager.AppSettings["LogFolder"] + "in"; Directory.CreateDirectory(text); string logFileName = $"{text}\\{Guid.NewGuid()}_{iPEndPoint.Address}.log"; Log("New Client Connected from " + iPEndPoint.Address, logFileName); new List(); List list = new List(); byte[] array = new byte[2000]; StringBuilder stringBuilder = new StringBuilder(); string text2 = ""; string text3 = ""; string text4 = ""; string text5 = ""; string text6 = ""; int num = 0; bool flag = false; do { try { num = socket.Receive(array, 1999, SocketFlags.None); if (num > 0) { array[num] = 0; string text7 = Encoding.Default.GetString(array, 0, num); text3 = text2 + text7; text2 = text7; stringBuilder.Append(text7); if (num == 1999 || !text3.Contains("\r\n")) { continue; } string text8 = stringBuilder.ToString(); string text9 = ((text8.Length < 4) ? text8 : text8.Substring(0, 4)); string text10 = "500 Invalid Command\r\n"; bool flag2 = false; if (flag) { text6 += text8; if (text8 == ".\r\n" || text8.Contains("\r\n.\r\n")) { flag = false; text10 = "250 OK\r\n"; Email email = new Email(); email.sHELO = text4; email.sMAIL = text5; email.sRCPTs = new List(list); email.sDATA = text6; email.FromIP = iPEndPoint.Address.ToString(); EmailQueue.Enqueue(email); text5 = (text6 = ""); list.Clear(); } } else { switch (text9) { case "HELO": case "EHLO": text4 = stringBuilder.ToString(); text10 = "250 Hi\r\n"; break; case "MAIL": if (text4.Length == 0) { text10 = "503 Do HELO first\r\n"; break; } if (text5.Length > 0) { text10 = "503 Already have MAIL\r\n"; break; } text5 = stringBuilder.ToString(); text10 = "250 MAIL command recorded\r\n"; break; case "RCPT": if (text4.Length == 0) { text10 = "503 Do HELO first\r\n"; break; } if (text5.Length == 0) { text10 = "503 Do MAIL first\r\n"; break; } list.Add(stringBuilder.ToString()); text10 = "250 RCPT command recorded\r\n"; break; case "DATA": if (text4.Length == 0) { text10 = "503 Do HELO first\r\n"; } else if (text5.Length == 0) { text10 = "503 Do MAIL first\r\n"; } else if (list.Count == 0) { text10 = "503 Do RCPT first\r\n"; } text10 = "354 DATA\r\n"; flag2 = true; break; case "RSET": text10 = "250 OK\r\n"; text5 = (text6 = ""); list.Clear(); break; case "QUIT": socket.Shutdown(SocketShutdown.Both); socket.Close(); num = 0; goto end_IL_00ed; default: Log("Unknown:" + text9, logFileName); break; } } if (!flag) { byte[] bytes2 = Encoding.ASCII.GetBytes(text10); socket.Send(bytes2); Log(text10, logFileName); } if (flag2) { flag = true; } stringBuilder.Clear(); array[0] = 0; continue; } if (num == 0) { Log("Client disconnected.", logFileName); continue; } Log("Client reset the connection.", logFileName); socket.Shutdown(SocketShutdown.Both); 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); if (EmailQueue.Count > 5 && Interlocked.Read(ref ThreadCount) < Convert.ToUInt32(ConfigurationManager.AppSettings["ThreadMax"])) { Interlocked.Increment(ref ThreadCount); SendQueuedEmails(); Interlocked.Decrement(ref ThreadCount); } } private void Log(string info, string LogFileName = null) { if (ConsoleMode) { Console.WriteLine(info.Replace("\r\n", "")); } if (!string.IsNullOrEmpty(LogFileName)) { using (StreamWriter streamWriter = File.AppendText(LogFileName)) { streamWriter.WriteLine("{0}", info); streamWriter.Flush(); return; } } lock (_syncObject) { DateTime now = DateTime.Now; 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); streamWriter2.Flush(); } } private void SendQueuedEmails() { JToken val2 = default(JToken); while (!EmailQueue.IsEmpty) { if (!EmailQueue.TryDequeue(out var result)) { continue; } string sMAIL = result.sMAIL; sMAIL = sMAIL.Replace(" ", ""); 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 { val = JObject.Parse(File.ReadAllText(path)); } catch (Exception ex) { Log(ex.Message); continue; } int num2 = Convert.ToInt32(val["logging"][(object)"enabled"]); bool bHideRecipients = Convert.ToInt32(val["HideRecipients"]) == 1; bool num3 = num2 == 1; string text2 = ""; string text3 = ((object)val["logging"][(object)"path"]).ToString() + "\\log"; Directory.CreateDirectory(text3); Guid guid = Guid.NewGuid(); string recipientEmail = result.GetRecipientEmail(result.sRCPTs[0]); text2 = $"{text3}\\{recipientEmail}.{guid}.log"; string text4 = ((object)val["logging"][(object)"path"]).ToString() + "\\out"; Directory.CreateDirectory(text4); if (num3) { File.WriteAllText($"{text4}\\{recipientEmail}.{guid}.eml", result.sDATA); File.WriteAllText($"{text4}\\{recipientEmail}.{guid}.HELO", result.sHELO); File.WriteAllText($"{text4}\\{recipientEmail}.{guid}.MAIL", result.sMAIL); File.WriteAllLines($"{text4}\\{recipientEmail}.{guid}.RCPTS", result.sRCPTs); } IPAddress iPAddress = Dns.GetHostEntry(((object)val["smtpserver"]).ToString()).AddressList[0]; IPEndPoint remoteEP = new IPEndPoint(iPAddress, Convert.ToInt32(((object)val["smtpserverport"]).ToString())); int num4 = Convert.ToInt32(val["recipientreplacement"][(object)"enabled"]); List list = val["recipientreplacement"][(object)"replacements"].ToObject>(); int num5 = Convert.ToInt32(val["recipientfilter"][(object)"enabled"]); List list2 = val["recipientfilter"][(object)"whitelist"].ToObject>(); bool flag = false; List list3 = null; if (val.TryGetValue("bcc", ref val2)) { flag = Extensions.Value((IEnumerable)val2[(object)"enabled"]) == 1; list3 = val2[(object)"recipients"].ToObject>(); } Socket socket = null; bool flag2 = num4 == 1; bool flag3 = num5 == 1; try { socket = new Socket(iPAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp); socket.Connect(remoteEP); if (!CheckAndSend("220", result.sHELO, socket, text2) || !CheckAndSend("250", result.sMAIL, socket, text2)) { continue; } if (flag2) { foreach (string item in list) { string nextCommand = $"RCPT TO: {item}\r\n"; CheckAndSend("250", nextCommand, socket, text2); } goto IL_053d; } int num6 = 0; foreach (string sRCPT in result.sRCPTs) { if (sRCPT.Length < 8) { continue; } if (flag3) { string r = result.GetRecipientEmail(sRCPT); if (!list2.Exists((string prd) => prd.Equals(r))) { Log("Filtering Recipient " + r, text2); continue; } Log("Recipient " + r + " OK", text2); } if (CheckAndSend("250", sRCPT, socket, text2)) { num6++; } } if (num6 != 0) { goto IL_053d; } Log("No Valid recipients, aborting", text2); socket.Shutdown(SocketShutdown.Both); socket.Close(); goto end_IL_03a8; IL_053d: if (flag) { foreach (string item2 in list3) { string nextCommand2 = $"RCPT TO: {item2}\r\n"; CheckAndSend("250", nextCommand2, socket, text2); } } 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)) { socket.Shutdown(SocketShutdown.Both); socket.Close(); } end_IL_03a8:; } catch (ArgumentNullException ex2) { Log($"ArgumentNullException : {ex2.ToString()}", text2); } catch (SocketException ex3) { Log($"SocketException : {ex3.ToString()}", text2); } catch (Exception ex4) { Log($"Unexpected exception : {ex4.ToString()}", text2); } } } private static string CleanseDataBlock(string sData, bool bHideRecipients) { if (bHideRecipients) { StringBuilder stringBuilder = new StringBuilder(); using StringReader stringReader = new StringReader(sData); string pattern = "^[Tt][Oo]: *$"; string pattern2 = "^[Dd][Aa][Tt][Ee]:.*$"; bool flag = false; bool flag2 = false; for (string text = stringReader.ReadLine(); text != null; text = stringReader.ReadLine()) { if (flag2) { stringBuilder.Append(text + "\r\n"); } 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) { byte[] array = new byte[2500]; string text = ""; int num = sock.Receive(array); if (num <= 0) { Log("recv failed", LogFileName); sock.Shutdown(SocketShutdown.Both); sock.Close(); return false; } array[num] = 0; text = Encoding.ASCII.GetString(array, 0, num); Log(text, LogFileName); if (text.Substring(0, 3) != PrevSuccessCode) { sock.Shutdown(SocketShutdown.Both); sock.Close(); return false; } if (NextCommand.Length > 500) { Log(NextCommand.Substring(0, 500), LogFileName); } else { Log(NextCommand, LogFileName); } byte[] bytes = Encoding.ASCII.GetBytes(NextCommand); sock.Send(bytes); return true; } protected override void Dispose(bool disposing) { if (disposing && components != null) { components.Dispose(); } base.Dispose(disposing); } private void InitializeComponent() { components = new Container(); base.ServiceName = "Service1"; } }