.net update, implemented base session, added test client
This commit is contained in:
parent
e0b732267b
commit
7b147569e0
11 changed files with 371 additions and 41 deletions
|
|
@ -1,12 +1,16 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.9.34902.65
|
||||
# Visual Studio Version 18
|
||||
VisualStudioVersion = 18.0.11222.15 d18.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{7162BB38-DF40-4438-827C-05A55268EB23}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "smtprelay", "src\smtprelay\smtprelay.csproj", "{F1834A4A-8E76-61A2-D639-D02937B8885C}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{8B4B0C4C-3F23-4418-91B4-C143295838F5}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "smtpclient", "tests\smtpclient\smtpclient.csproj", "{19058057-D6D9-980C-0359-AC3A630D5EAD}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -17,12 +21,17 @@ Global
|
|||
{F1834A4A-8E76-61A2-D639-D02937B8885C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F1834A4A-8E76-61A2-D639-D02937B8885C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F1834A4A-8E76-61A2-D639-D02937B8885C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{19058057-D6D9-980C-0359-AC3A630D5EAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{19058057-D6D9-980C-0359-AC3A630D5EAD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{19058057-D6D9-980C-0359-AC3A630D5EAD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{19058057-D6D9-980C-0359-AC3A630D5EAD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{F1834A4A-8E76-61A2-D639-D02937B8885C} = {7162BB38-DF40-4438-827C-05A55268EB23}
|
||||
{19058057-D6D9-980C-0359-AC3A630D5EAD} = {8B4B0C4C-3F23-4418-91B4-C143295838F5}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {91A1FDC3-5235-4A64-AFB8-BF772349C4E7}
|
||||
|
|
|
|||
31
src/smtprelay/Graph/GraphSmtpSession.cs
Normal file
31
src/smtprelay/Graph/GraphSmtpSession.cs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace SmtpRelay.Graph;
|
||||
|
||||
public partial class GraphSmtpSession(GraphSender graphSender, ILogger<GraphSmtpSession> logger) : SmtpSessionBase(logger)
|
||||
{
|
||||
private readonly GraphSender _graphSender = graphSender;
|
||||
|
||||
protected override async Task OnProcessDataAsync(ReadOnlyMemory<byte> dataBytes, CancellationToken cancellationToken)
|
||||
{
|
||||
// save to spool dir (optional)
|
||||
var spoolFile = await SpoolToFileAsync(dataBytes, cancellationToken);
|
||||
|
||||
// sent via graph api
|
||||
await _graphSender.SendAsync(spoolFile, dataBytes, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task<FileInfo> SpoolToFileAsync(ReadOnlyMemory<byte> data, CancellationToken cancellationToken)
|
||||
{
|
||||
Directory.CreateDirectory(Common.SpoolDir.FullName);
|
||||
|
||||
var path = Path.Combine(Common.SpoolDir.FullName, $"{Guid.NewGuid()}.eml");
|
||||
var spoolFile = new FileInfo(path);
|
||||
|
||||
await File.WriteAllBytesAsync(spoolFile.FullName, data, cancellationToken);
|
||||
|
||||
_logger.LogInformation("Spooled: {Path}", spoolFile.FullName);
|
||||
|
||||
return spoolFile;
|
||||
}
|
||||
}
|
||||
|
|
@ -136,8 +136,8 @@ internal partial class Program
|
|||
services.AddSingleton(graphConfig);
|
||||
|
||||
// register smtp instances
|
||||
services.AddSingleton<SmtpServer>();
|
||||
services.AddScoped<SmtpSession>();
|
||||
services.AddSingleton<SmtpServer<GraphSmtpSession>>();
|
||||
services.AddScoped<GraphSmtpSession>();
|
||||
|
||||
// register smtp service
|
||||
services.AddHostedService<Service>();
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SmtpRelay.Graph;
|
||||
|
||||
namespace SmtpRelay;
|
||||
|
||||
internal class Service(SmtpServer server, SmtpConfig config, ILogger<Service> logger) : BackgroundService
|
||||
internal class Service(SmtpServer<GraphSmtpSession> server, SmtpConfig config, ILogger<Service> logger) : BackgroundService
|
||||
{
|
||||
private readonly SmtpServer _server = server;
|
||||
private readonly SmtpServer<GraphSmtpSession> _server = server;
|
||||
private readonly SmtpConfig _config = config;
|
||||
private readonly ILogger<Service> _logger = logger;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,16 +5,17 @@ using System.Net.Sockets;
|
|||
|
||||
namespace SmtpRelay;
|
||||
|
||||
public class SmtpServer(IServiceScopeFactory scopeFactory, ILogger<SmtpServer> logger)
|
||||
public class SmtpServer<TSession>(IServiceScopeFactory scopeFactory, ILogger<SmtpServer<TSession>> logger)
|
||||
where TSession : SmtpSessionBase
|
||||
{
|
||||
private readonly ILogger<SmtpServer> _logger = logger;
|
||||
private readonly IServiceScopeFactory _scopeFactory = scopeFactory;
|
||||
protected readonly IServiceScopeFactory _scopeFactory = scopeFactory;
|
||||
protected readonly ILogger<SmtpServer<TSession>> _logger = logger;
|
||||
|
||||
public bool IsRunning { get; private set; } = false;
|
||||
|
||||
public async Task RunAsync(SmtpConfig config, CancellationToken cancellationToken)
|
||||
{
|
||||
if (IsRunning)
|
||||
if (IsRunning)
|
||||
return;
|
||||
|
||||
var ep = new IPEndPoint(IPAddress.Parse(config.Host), config.Port);
|
||||
|
|
@ -48,7 +49,7 @@ public class SmtpServer(IServiceScopeFactory scopeFactory, ILogger<SmtpServer> l
|
|||
_logger.LogError(ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// set listener state
|
||||
IsRunning = false;
|
||||
}
|
||||
|
|
@ -57,7 +58,7 @@ public class SmtpServer(IServiceScopeFactory scopeFactory, ILogger<SmtpServer> l
|
|||
{
|
||||
// create session
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var session = scope.ServiceProvider.GetRequiredService<SmtpSession>();
|
||||
var session = scope.ServiceProvider.GetRequiredService<TSession>();
|
||||
|
||||
try
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,21 +1,18 @@
|
|||
using Microsoft.Extensions.Logging;
|
||||
using SmtpRelay.Graph;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SmtpRelay;
|
||||
|
||||
public partial class SmtpSession(GraphSender graphSender, ILogger<SmtpSession> logger)
|
||||
public abstract partial class SmtpSessionBase(ILogger<SmtpSessionBase> logger)
|
||||
{
|
||||
private static byte[] CrlnSpan { get; } = Encoding.ASCII.GetBytes("\r\n");
|
||||
private static byte[] DataDelimiterSpan { get; } = Encoding.ASCII.GetBytes("\r\n.\r\n");
|
||||
|
||||
private readonly GraphSender _graphSender = graphSender;
|
||||
private readonly ILogger<SmtpSession> _logger = logger;
|
||||
private readonly List<string> _rcptTo = [];
|
||||
protected readonly ILogger<SmtpSessionBase> _logger = logger;
|
||||
protected readonly List<string> _rcptTo = [];
|
||||
|
||||
private bool _ehlo = false;
|
||||
private bool _authenticated = false;
|
||||
|
|
@ -86,6 +83,8 @@ public partial class SmtpSession(GraphSender graphSender, ILogger<SmtpSession> l
|
|||
_logger.LogInformation("[{REP}] Disconnected", socket.RemoteEndPoint);
|
||||
}
|
||||
|
||||
protected abstract Task OnProcessDataAsync(ReadOnlyMemory<byte> dataBytes, CancellationToken cancellationToken);
|
||||
|
||||
private async Task OnGreeting(Socket socket, SmtpConfig config, CancellationToken cancellationToken)
|
||||
{
|
||||
// write default reply message
|
||||
|
|
@ -354,7 +353,7 @@ public partial class SmtpSession(GraphSender graphSender, ILogger<SmtpSession> l
|
|||
{
|
||||
var rawData = await ReadToSpanAsync(socket, DataDelimiterSpan, true, cancellationToken);
|
||||
|
||||
await ProcessCapturedData(rawData, cancellationToken);
|
||||
await OnProcessDataAsync(rawData, cancellationToken);
|
||||
|
||||
// dot unstuff
|
||||
//if (response.StartsWith(".."))
|
||||
|
|
@ -600,15 +599,6 @@ public partial class SmtpSession(GraphSender graphSender, ILogger<SmtpSession> l
|
|||
}
|
||||
}
|
||||
|
||||
private async Task ProcessCapturedData(ReadOnlyMemory<byte> dataBytes, CancellationToken cancellationToken)
|
||||
{
|
||||
// save to spool dir
|
||||
var spoolFile = await SpoolToFileAsync(dataBytes, cancellationToken);
|
||||
|
||||
// sent via graph api
|
||||
await _graphSender.SendAsync(spoolFile, dataBytes, cancellationToken);
|
||||
}
|
||||
|
||||
private static bool ValidateUser(SmtpConfig config, string user, string password)
|
||||
{
|
||||
if (!config.Authentication.Any(p => p.Key == user))
|
||||
|
|
@ -636,7 +626,6 @@ public partial class SmtpSession(GraphSender graphSender, ILogger<SmtpSession> l
|
|||
return spoolFile;
|
||||
}
|
||||
|
||||
|
||||
[GeneratedRegex(@"^MAIL\s+FROM:\s*<([^>]+)>(?:\s+(.*))?$", RegexOptions.IgnoreCase)]
|
||||
public static partial Regex MailFromRegex();
|
||||
|
||||
|
|
|
|||
|
|
@ -8,24 +8,23 @@
|
|||
"UseWhitelist": true,
|
||||
"UsePipelining": true,
|
||||
"Authentication": {
|
||||
"relay@example.local": "supersecret",
|
||||
"relay@autohaus-weigl.local": "Secret-Prevent-Whole"
|
||||
"relay@example.local": "supersecret"
|
||||
},
|
||||
"Whitelist": {
|
||||
"Senders": [
|
||||
"test@example.local",
|
||||
"rechnung@autohaus-weigl.de"
|
||||
"me2@example.local"
|
||||
],
|
||||
"Receivers": [
|
||||
"noreply@example.local",
|
||||
"%"
|
||||
]
|
||||
}
|
||||
},
|
||||
"Graph": {
|
||||
"Bypass": true,
|
||||
"TenantId": "",
|
||||
"ClientId": "",
|
||||
"ClientSecret": "",
|
||||
"SenderUpn": "",
|
||||
"Bypass": true
|
||||
"SenderUpn": ""
|
||||
}
|
||||
}
|
||||
|
|
@ -3,9 +3,9 @@
|
|||
<PropertyGroup>
|
||||
<Product>SmtpRelay</Product>
|
||||
<AssemblyName>smtp_relay</AssemblyName>
|
||||
<AssemblyVersion>2025.10.27.0</AssemblyVersion>
|
||||
<AssemblyVersion>2025.12.3.0</AssemblyVersion>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<RootNamespace>SmtpRelay</RootNamespace>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
|
|
@ -17,11 +17,11 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="9.0.10" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.10" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="10.0.0" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging.File" Version="3.0.0" />
|
||||
<PackageReference Include="Azure.Identity" Version="1.17.0" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.14.0" />
|
||||
<PackageReference Include="Azure.Identity" Version="1.17.1" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.15.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
276
tests/smtpclient/Program.cs
Normal file
276
tests/smtpclient/Program.cs
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
using System.Data;
|
||||
using System.Net;
|
||||
using System.Net.Mail;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
|
||||
namespace SmtpClient;
|
||||
|
||||
internal class Program
|
||||
{
|
||||
private static string? _host = null;
|
||||
private static string? _username = null;
|
||||
private static string? _password = null;
|
||||
private static string? _from = null;
|
||||
private static List<string>? _rcpts = [];
|
||||
private static List<string>? _rcptsCc = [];
|
||||
private static string? _subject = null;
|
||||
private static string? _body = null;
|
||||
private static bool _secure = false;
|
||||
|
||||
static async Task Main()
|
||||
{
|
||||
Console.WriteLine("Relay Test Client");
|
||||
|
||||
Console.WriteLine("\nSelect Input:");
|
||||
Console.WriteLine("\t1-Static");
|
||||
Console.WriteLine("\t2-Interactive");
|
||||
|
||||
switch (Console.ReadKey().Key)
|
||||
{
|
||||
case ConsoleKey.D1:
|
||||
case ConsoleKey.NumPad1:
|
||||
StaticInput();
|
||||
break;
|
||||
case ConsoleKey.D2:
|
||||
case ConsoleKey.NumPad2:
|
||||
InteractiveInput();
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
Console.WriteLine("\nSelect Method:");
|
||||
Console.WriteLine("\t1-Single Mail");
|
||||
Console.WriteLine("\t2-Single Mail (Raw Pipelined)");
|
||||
Console.WriteLine("\t3-Mutli Mail");
|
||||
|
||||
try
|
||||
{
|
||||
switch (Console.ReadKey(true).Key)
|
||||
{
|
||||
case ConsoleKey.D1:
|
||||
case ConsoleKey.NumPad1:
|
||||
await SendAsync();
|
||||
break;
|
||||
case ConsoleKey.D2:
|
||||
case ConsoleKey.NumPad2:
|
||||
await SendPipelinedAsync();
|
||||
break;
|
||||
case ConsoleKey.D3:
|
||||
case ConsoleKey.NumPad3:
|
||||
await SendMultiAsync();
|
||||
break;
|
||||
default:
|
||||
Console.WriteLine("\nUndefined Method");
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
static void StaticInput()
|
||||
{
|
||||
_host = "localhost:2525"?.Trim();
|
||||
_secure = false;
|
||||
_username = "relay@example.local";
|
||||
_password = "supersecret";
|
||||
_from = "me@example.local"?.Trim();
|
||||
_rcpts = "noreply@example.local"?.Trim()?.Split(';').ToList();
|
||||
_rcptsCc = [];
|
||||
_subject = "Testnachricht";
|
||||
_body = "Das ist eine Testnachricht.";
|
||||
}
|
||||
|
||||
static void InteractiveInput()
|
||||
{
|
||||
Console.WriteLine("\nEnter Relay Host: ");
|
||||
_host = Console.ReadLine()?.Trim();
|
||||
|
||||
Console.WriteLine("\nEnter Username (Optional): ");
|
||||
_username = Console.ReadLine()?.Trim();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_username))
|
||||
{
|
||||
Console.WriteLine("\nEnter Password: ");
|
||||
_password = Console.ReadLine()?.Trim();
|
||||
}
|
||||
|
||||
Console.WriteLine("\nEnter Sender:");
|
||||
_from = Console.ReadLine()?.Trim();
|
||||
|
||||
Console.WriteLine("\nEnter Receiver (delimit with ';'):");
|
||||
_rcpts = Console.ReadLine()?.Trim()?.Split(';').ToList();
|
||||
|
||||
Console.WriteLine("\nEnter Cc Receiver (delimit with ';'):");
|
||||
_rcptsCc = Console.ReadLine()?.Trim()?.Split(';').ToList();
|
||||
|
||||
Console.WriteLine("\nEnter Subject:");
|
||||
_subject = Console.ReadLine()?.Trim();
|
||||
|
||||
Console.WriteLine("\nEnter Body:");
|
||||
_body = Console.ReadLine()?.Trim();
|
||||
}
|
||||
|
||||
static async Task SendAsync()
|
||||
{
|
||||
var from = new MailAddress(_from!);
|
||||
var to = _rcpts!.Select(p => new MailAddress(p)).ToList();
|
||||
var cc = _rcptsCc!.Select(p => new MailAddress(p)).ToList();
|
||||
|
||||
var firstTo = to.First();
|
||||
to.Remove(firstTo);
|
||||
|
||||
using var message = new MailMessage(from, firstTo)
|
||||
{
|
||||
Priority = MailPriority.Normal,
|
||||
Subject = _subject ?? "Undefined",
|
||||
IsBodyHtml = false,
|
||||
Body = _body ?? string.Empty,
|
||||
};
|
||||
|
||||
foreach (var x in to)
|
||||
message.To.Add(x);
|
||||
|
||||
foreach (var x in cc)
|
||||
message.CC.Add(x);
|
||||
|
||||
message.Attachments.Add(new Attachment("attachment.txt"));
|
||||
|
||||
var credential = new NetworkCredential();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_username))
|
||||
{
|
||||
credential.UserName = _username;
|
||||
credential.Password = _password;
|
||||
}
|
||||
|
||||
var host = _host;
|
||||
var port = 25;
|
||||
|
||||
if (_host!.Contains(':'))
|
||||
{
|
||||
host = _host.Split(':')[0];
|
||||
port = int.Parse(_host.Split(":")[1]);
|
||||
}
|
||||
|
||||
using var smtp = new System.Net.Mail.SmtpClient(host, port)
|
||||
{
|
||||
UseDefaultCredentials = false,
|
||||
Credentials = credential,
|
||||
EnableSsl = _secure,
|
||||
DeliveryMethod = SmtpDeliveryMethod.Network
|
||||
};
|
||||
|
||||
await smtp.SendMailAsync(message);
|
||||
}
|
||||
|
||||
static async Task SendMultiAsync()
|
||||
{
|
||||
var from = new MailAddress(_from!);
|
||||
var to = _rcpts!.Select(p => new MailAddress(p)).ToList();
|
||||
var cc = _rcptsCc!.Select(p => new MailAddress(p)).ToList();
|
||||
|
||||
var firstTo = to.First();
|
||||
to.Remove(firstTo);
|
||||
|
||||
using var message = new MailMessage(from, firstTo)
|
||||
{
|
||||
Priority = MailPriority.Normal,
|
||||
Subject = _subject ?? "Undefined",
|
||||
IsBodyHtml = false,
|
||||
Body = _body ?? string.Empty
|
||||
};
|
||||
|
||||
foreach (var x in to)
|
||||
message.To.Add(x);
|
||||
|
||||
foreach (var x in cc)
|
||||
message.CC.Add(x);
|
||||
|
||||
message.Attachments.Add(new Attachment("attachment.txt"));
|
||||
|
||||
var credential = new NetworkCredential();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_username))
|
||||
{
|
||||
credential.UserName = _username;
|
||||
credential.Password = _password;
|
||||
}
|
||||
|
||||
var host = _host;
|
||||
var port = 25;
|
||||
|
||||
if (_host!.Contains(':'))
|
||||
{
|
||||
host = _host.Split(':')[0];
|
||||
port = int.Parse(_host.Split(":")[1]);
|
||||
}
|
||||
|
||||
using var smtp = new System.Net.Mail.SmtpClient(host, port)
|
||||
{
|
||||
UseDefaultCredentials = false,
|
||||
Credentials = credential,
|
||||
EnableSsl = _secure,
|
||||
DeliveryMethod = SmtpDeliveryMethod.Network
|
||||
};
|
||||
|
||||
await smtp.SendMailAsync(message);
|
||||
await smtp.SendMailAsync(message);
|
||||
}
|
||||
|
||||
static async Task SendPipelinedAsync()
|
||||
{
|
||||
var raw = "EHLO SmtpClient\r\n";
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_username))
|
||||
{
|
||||
raw += $"AUTH PLAIN {Convert.ToBase64String(Encoding.UTF8.GetBytes($"\0{_username}\0{_password}"))}\r\n";
|
||||
}
|
||||
|
||||
raw += $"MAIL FROM:<{_from}> SIZE=512\r\n";
|
||||
|
||||
foreach (var rcpt in _rcpts!)
|
||||
{
|
||||
raw += $"RCPT TO:<{rcpt}>\r\n";
|
||||
}
|
||||
|
||||
raw += "DATA\r\n";
|
||||
raw += "MIME-Version: 1.0\r\n";
|
||||
raw += $"From: {_from}\r\n";
|
||||
raw += $"To: {string.Join(", ", _rcpts!)}\r\n";
|
||||
|
||||
if (_rcptsCc != null && _rcptsCc.Count > 0)
|
||||
{
|
||||
raw += $"Cc: {string.Join(", ", _rcptsCc!)}\r\n";
|
||||
}
|
||||
|
||||
raw += "Date: 29 Oct 2025 21:10:56 +0100\r\n";
|
||||
raw += $"Subject:{_subject}\r\n";
|
||||
raw += "\r\n" ;
|
||||
raw += $"{_body}";
|
||||
raw += "\r\n.\r\n";
|
||||
raw += "QUIT\r\n";
|
||||
|
||||
var bytes = Encoding.ASCII.GetBytes(raw);
|
||||
var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
||||
|
||||
var host = _host;
|
||||
var port = 25;
|
||||
|
||||
if (_host!.Contains(':'))
|
||||
{
|
||||
host = _host.Split(':')[0];
|
||||
port = int.Parse(_host.Split(":")[1]);
|
||||
}
|
||||
|
||||
await sock.ConnectAsync(host!, port);
|
||||
await sock.SendAsync(bytes);
|
||||
|
||||
Console.WriteLine("wait 3 seconds...");
|
||||
await Task.Delay(3000);
|
||||
}
|
||||
}
|
||||
1
tests/smtpclient/attachment.txt
Normal file
1
tests/smtpclient/attachment.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
test
|
||||
23
tests/smtpclient/smtpclient.csproj
Normal file
23
tests/smtpclient/smtpclient.csproj
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Product>SmtpClient</Product>
|
||||
<AssemblyName>smtp_client</AssemblyName>
|
||||
<AssemblyVersion>2025.12.3.0</AssemblyVersion>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<RootNamespace>SmtpClient</RootNamespace>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<SatelliteResourceLanguages>none</SatelliteResourceLanguages>
|
||||
<InvariantGlobalization>true</InvariantGlobalization>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="attachment.txt">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Loading…
Add table
Add a link
Reference in a new issue