678 lines
26 KiB
C#
678 lines
26 KiB
C#
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<Email> 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<Email>();
|
|
mre = new ManualResetEvent(false);
|
|
bRunning = true;
|
|
ThreadCount = 0L;
|
|
}
|
|
|
|
protected override void OnStart(string[] args)
|
|
{
|
|
Listening = new Thread(StartListening) { IsBackground = true };
|
|
Listening.Start();
|
|
Sending = new Thread(StartSending) { IsBackground = true };
|
|
Sending.Start();
|
|
}
|
|
|
|
public void Start()
|
|
{
|
|
ConsoleMode = true;
|
|
Listening = new Thread(StartListening) { IsBackground = true };
|
|
Listening.Start();
|
|
Sending = new Thread(StartSending) { IsBackground = true };
|
|
Sending.Start();
|
|
}
|
|
|
|
protected override void OnStop()
|
|
{
|
|
bRunning = false;
|
|
mre.Set();
|
|
}
|
|
|
|
public void Reload(string folder)
|
|
{
|
|
if (!Directory.Exists(folder)) return;
|
|
|
|
foreach (string item in Directory.EnumerateFiles(folder, "*.eml"))
|
|
{
|
|
try
|
|
{
|
|
Email email = new Email
|
|
{
|
|
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");
|
|
}
|
|
}
|
|
}
|
|
|
|
EmailQueue.Enqueue(email);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log($"Failed to reload email file {item}: {ex.Message}");
|
|
}
|
|
}
|
|
SendQueuedEmails();
|
|
}
|
|
|
|
public void StartSending()
|
|
{
|
|
while (!mre.WaitOne(30000) && bRunning)
|
|
{
|
|
SendQueuedEmails();
|
|
}
|
|
}
|
|
|
|
public void StartListening()
|
|
{
|
|
try
|
|
{
|
|
string listenSetting = ConfigurationManager.AppSettings["ListeningAddress"] ?? "0.0.0.0";
|
|
IPAddress ipAddress = IPAddress.Parse(listenSetting);
|
|
IPEndPoint localEP = new IPEndPoint(ipAddress, 25);
|
|
|
|
Socket socket = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
|
|
socket.Bind(localEP);
|
|
socket.Listen(100);
|
|
|
|
Log($"Started Listening on port 25 at {ipAddress}");
|
|
|
|
while (bRunning)
|
|
{
|
|
try
|
|
{
|
|
Socket clientSocket = socket.Accept();
|
|
if (clientSocket.Connected)
|
|
{
|
|
Log($"New Connection: {DateTime.Now.ToString("G", CultureInfo.CreateSpecificCulture("en-AU"))}");
|
|
ThreadPool.QueueUserWorkItem(ProcessMailClient, clientSocket);
|
|
}
|
|
}
|
|
catch (SocketException ex)
|
|
{
|
|
if (!bRunning) break;
|
|
Log($"SocketException accepting client: {ex.Message}");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log($"Unexpected exception accepting client: {ex.Message}");
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log($"Fatal listening error: {ex}");
|
|
}
|
|
}
|
|
|
|
private void ProcessMailClient(object stateInfo)
|
|
{
|
|
Socket socket = (Socket)stateInfo;
|
|
socket.ReceiveTimeout = 180000;
|
|
socket.SendTimeout = 180000;
|
|
|
|
IPEndPoint remoteEndPoint = socket.RemoteEndPoint as IPEndPoint;
|
|
string clientIp = remoteEndPoint?.Address.ToString() ?? "0.0.0.0";
|
|
|
|
string logDir = Path.Combine(ConfigurationManager.AppSettings["LogFolder"] ?? "C:\\Logs\\", "in");
|
|
Directory.CreateDirectory(logDir);
|
|
string logFileName = Path.Combine(logDir, $"{Guid.NewGuid()}_{clientIp}.log");
|
|
|
|
Log($"New Client Connected from {clientIp}", logFileName);
|
|
|
|
byte[] welcome = Encoding.ASCII.GetBytes("220 Connected to SBC SMTPProxy.net\r\n");
|
|
socket.Send(welcome);
|
|
|
|
List<string> rcptList = new List<string>();
|
|
byte[] buffer = new byte[4096];
|
|
StringBuilder commandBuffer = new StringBuilder();
|
|
|
|
string heloHeader = "";
|
|
string mailFrom = "";
|
|
string dataBody = "";
|
|
bool receivingData = false;
|
|
|
|
try
|
|
{
|
|
while (socket.Connected)
|
|
{
|
|
int bytesRead = socket.Receive(buffer, SocketFlags.None);
|
|
if (bytesRead == 0)
|
|
{
|
|
Log("Client disconnected gracefully.", logFileName);
|
|
break;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
string[] lines = fullBuffer.Split(new[] { "\r\n" }, StringSplitOptions.None);
|
|
// Keep uncompleted line in buffer
|
|
commandBuffer.Clear();
|
|
commandBuffer.Append(lines[lines.Length - 1]);
|
|
|
|
for (int i = 0; i < lines.Length - 1; i++)
|
|
{
|
|
string line = lines[i].Trim();
|
|
if (string.IsNullOrEmpty(line)) continue;
|
|
|
|
string verb = line.Length >= 4 ? line.Substring(0, 4).ToUpperInvariant() : line.ToUpperInvariant();
|
|
string response = "500 Invalid Command\r\n";
|
|
|
|
switch (verb)
|
|
{
|
|
case "HELO":
|
|
case "EHLO":
|
|
heloHeader = line + "\r\n";
|
|
response = "250 Hi\r\n";
|
|
break;
|
|
|
|
case "MAIL":
|
|
if (string.IsNullOrEmpty(heloHeader))
|
|
{
|
|
response = "503 Do HELO first\r\n";
|
|
}
|
|
else if (!string.IsNullOrEmpty(mailFrom))
|
|
{
|
|
response = "503 Already have MAIL\r\n";
|
|
}
|
|
else
|
|
{
|
|
mailFrom = line + "\r\n";
|
|
response = "250 MAIL command recorded\r\n";
|
|
}
|
|
break;
|
|
|
|
case "RCPT":
|
|
if (string.IsNullOrEmpty(heloHeader))
|
|
{
|
|
response = "503 Do HELO first\r\n";
|
|
}
|
|
else if (string.IsNullOrEmpty(mailFrom))
|
|
{
|
|
response = "503 Do MAIL first\r\n";
|
|
}
|
|
else
|
|
{
|
|
rcptList.Add(line + "\r\n");
|
|
response = "250 RCPT command recorded\r\n";
|
|
}
|
|
break;
|
|
|
|
case "DATA":
|
|
if (string.IsNullOrEmpty(heloHeader))
|
|
{
|
|
response = "503 Do HELO first\r\n";
|
|
}
|
|
else if (string.IsNullOrEmpty(mailFrom))
|
|
{
|
|
response = "503 Do MAIL first\r\n";
|
|
}
|
|
else if (rcptList.Count == 0)
|
|
{
|
|
response = "503 Do RCPT first\r\n";
|
|
}
|
|
else
|
|
{
|
|
response = "354 Start mail input; end with <CRLF>.<CRLF>\r\n";
|
|
receivingData = true;
|
|
dataBody = "";
|
|
}
|
|
break;
|
|
|
|
case "RSET":
|
|
response = "250 OK\r\n";
|
|
mailFrom = "";
|
|
dataBody = "";
|
|
rcptList.Clear();
|
|
break;
|
|
|
|
case "QUIT":
|
|
byte[] quitBytes = Encoding.ASCII.GetBytes("221 Bye\r\n");
|
|
socket.Send(quitBytes);
|
|
socket.Shutdown(SocketShutdown.Both);
|
|
socket.Close();
|
|
return;
|
|
|
|
default:
|
|
Log($"Unknown command: {line}", logFileName);
|
|
break;
|
|
}
|
|
|
|
byte[] respBytes = Encoding.ASCII.GetBytes(response);
|
|
socket.Send(respBytes);
|
|
Log(response.TrimEnd(), logFileName);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log($"Session exception: {ex.Message}", logFileName);
|
|
}
|
|
finally
|
|
{
|
|
try
|
|
{
|
|
if (socket.Connected)
|
|
{
|
|
socket.Shutdown(SocketShutdown.Both);
|
|
socket.Close();
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
// 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);
|
|
ThreadPool.QueueUserWorkItem(state =>
|
|
{
|
|
try { SendQueuedEmails(); }
|
|
finally { Interlocked.Decrement(ref ThreadCount); }
|
|
});
|
|
}
|
|
}
|
|
|
|
private void Log(string info, string logFileName = null)
|
|
{
|
|
if (ConsoleMode)
|
|
{
|
|
Console.WriteLine(info?.Replace("\r\n", ""));
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(logFileName))
|
|
{
|
|
try
|
|
{
|
|
using (StreamWriter sw = File.AppendText(logFileName))
|
|
{
|
|
sw.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} - {info}");
|
|
}
|
|
return;
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
lock (_syncObject)
|
|
{
|
|
try
|
|
{
|
|
string logDir = Path.Combine(ConfigurationManager.AppSettings["LogFolder"] ?? "C:\\Logs\\", "in");
|
|
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()
|
|
{
|
|
string profilesBase = ConfigurationManager.AppSettings["Profiles"] ?? "C:\\Profiles\\";
|
|
|
|
while (EmailQueue.TryDequeue(out Email email))
|
|
{
|
|
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;
|
|
}
|
|
|
|
JObject config;
|
|
try
|
|
{
|
|
config = JObject.Parse(File.ReadAllText(profilePath));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log($"Error parsing JSON profile at {profilePath}: {ex.Message}");
|
|
continue;
|
|
}
|
|
|
|
bool loggingEnabled = config["logging"]?["enabled"]?.Value<int>() == 1;
|
|
string logBasePath = config["logging"]?["path"]?.ToString() ?? "C:\\Logs\\";
|
|
bool hideRecipients = config["HideRecipients"]?.Value<int>() == 1;
|
|
|
|
string logDir = Path.Combine(logBasePath, "log");
|
|
string outDir = Path.Combine(logBasePath, "out");
|
|
Directory.CreateDirectory(logDir);
|
|
Directory.CreateDirectory(outDir);
|
|
|
|
Guid guid = Guid.NewGuid();
|
|
string primaryRcpt = email.sRCPTs.Count > 0 ? email.GetRecipientEmail(email.sRCPTs[0]) : "unknown";
|
|
string mailLogPath = Path.Combine(logDir, $"{primaryRcpt}.{guid}.log");
|
|
|
|
if (loggingEnabled)
|
|
{
|
|
File.WriteAllText(Path.Combine(outDir, $"{primaryRcpt}.{guid}.eml"), email.sDATA);
|
|
File.WriteAllText(Path.Combine(outDir, $"{primaryRcpt}.{guid}.HELO"), email.sHELO);
|
|
File.WriteAllText(Path.Combine(outDir, $"{primaryRcpt}.{guid}.MAIL"), email.sMAIL);
|
|
File.WriteAllLines(Path.Combine(outDir, $"{primaryRcpt}.{guid}.RCPTS"), email.sRCPTs);
|
|
}
|
|
|
|
string smtpHost = config["smtpserver"]?.ToString();
|
|
int smtpPort = config["smtpserverport"]?.Value<int>() ?? 25;
|
|
|
|
if (string.IsNullOrEmpty(smtpHost))
|
|
{
|
|
Log("Missing smtpserver target in configuration profile.", mailLogPath);
|
|
continue;
|
|
}
|
|
|
|
bool replaceRecipients = config["recipientreplacement"]?["enabled"]?.Value<int>() == 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
|
|
{
|
|
IPAddress targetIp;
|
|
if (!IPAddress.TryParse(smtpHost, out targetIp))
|
|
{
|
|
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;
|
|
}
|
|
|
|
bool hasValidRecipients = false;
|
|
|
|
if (replaceRecipients)
|
|
{
|
|
foreach (string rep in replacementList)
|
|
{
|
|
if (CheckAndSend("250", $"RCPT TO: <{rep}>\r\n", relaySocket, mailLogPath))
|
|
{
|
|
hasValidRecipients = true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
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;
|
|
}
|
|
Log($"Recipient approved by whitelist: {extractedEmail}", mailLogPath);
|
|
}
|
|
|
|
if (CheckAndSend("250", rcptLine, relaySocket, mailLogPath))
|
|
{
|
|
hasValidRecipients = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!hasValidRecipients)
|
|
{
|
|
Log("No valid recipients accepted by upstream relay. Aborting.", mailLogPath);
|
|
continue;
|
|
}
|
|
|
|
if (bccEnabled)
|
|
{
|
|
foreach (string bcc in bccRecipients)
|
|
{
|
|
CheckAndSend("250", $"RCPT TO: <{bcc}>\r\n", relaySocket, mailLogPath);
|
|
}
|
|
}
|
|
|
|
if (CheckAndSend("250", "DATA\r\n", relaySocket, mailLogPath))
|
|
{
|
|
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))
|
|
{
|
|
CheckAndSend("250", "RSET\r\n", relaySocket, mailLogPath);
|
|
CheckAndSend("250", "QUIT\r\n", relaySocket, mailLogPath);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log($"Upstream relay transmission failed: {ex.Message}", mailLogPath);
|
|
}
|
|
finally
|
|
{
|
|
if (relaySocket != null)
|
|
{
|
|
try
|
|
{
|
|
if (relaySocket.Connected) relaySocket.Shutdown(SocketShutdown.Both);
|
|
relaySocket.Close();
|
|
}
|
|
catch { }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static string CleanseDataBlock(string sData, bool bHideRecipients)
|
|
{
|
|
if (!bHideRecipients || string.IsNullOrEmpty(sData)) return sData;
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
using (StringReader reader = new StringReader(sData))
|
|
{
|
|
string line;
|
|
bool inHeaderSection = true;
|
|
bool skippingToHeader = false;
|
|
|
|
while ((line = reader.ReadLine()) != null)
|
|
{
|
|
// Empty line marks end of email headers and start of MIME/Body
|
|
if (inHeaderSection && line.Length == 0)
|
|
{
|
|
inHeaderSection = false;
|
|
skippingToHeader = false;
|
|
}
|
|
|
|
if (inHeaderSection)
|
|
{
|
|
if (Regex.IsMatch(line, @"^[Tt][Oo]:", RegexOptions.IgnoreCase))
|
|
{
|
|
skippingToHeader = true;
|
|
continue; // Strip To: line
|
|
}
|
|
|
|
// Check for header folding (continuation line starting with whitespace)
|
|
if (skippingToHeader && (line.StartsWith(" ") || line.StartsWith("\t")))
|
|
{
|
|
continue; // Strip folded recipient lines
|
|
}
|
|
else
|
|
{
|
|
skippingToHeader = false;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log($"CheckAndSend failed: {ex.Message}", logFileName);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
if (disposing)
|
|
{
|
|
components?.Dispose();
|
|
mre?.Dispose();
|
|
}
|
|
base.Dispose(disposing);
|
|
}
|
|
|
|
private void InitializeComponent()
|
|
{
|
|
components = new Container();
|
|
base.ServiceName = "SMTPProxy.net";
|
|
}
|
|
}
|
|
} |