.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
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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue