pipeline support
This commit is contained in:
parent
dfa2fe0de4
commit
e0b732267b
6 changed files with 87 additions and 20 deletions
|
|
@ -55,7 +55,8 @@ internal partial class Program
|
||||||
MaxMessageBytes = host.Configuration.GetSection("Smtp").GetValue<long?>("MaxMessageBytes") ?? 10 * 1024 * 1024,
|
MaxMessageBytes = host.Configuration.GetSection("Smtp").GetValue<long?>("MaxMessageBytes") ?? 10 * 1024 * 1024,
|
||||||
Greetings = host.Configuration.GetSection("Smtp").GetValue<string?>("Greetings") ?? "OAuth Relay",
|
Greetings = host.Configuration.GetSection("Smtp").GetValue<string?>("Greetings") ?? "OAuth Relay",
|
||||||
UseAuthentication = host.Configuration.GetSection("Smtp").GetValue<bool?>("UseAuthentication") ?? false,
|
UseAuthentication = host.Configuration.GetSection("Smtp").GetValue<bool?>("UseAuthentication") ?? false,
|
||||||
UseWhiteList = host.Configuration.GetSection("Smtp").GetValue<bool?>("UseWhitelist") ?? false
|
UseWhiteList = host.Configuration.GetSection("Smtp").GetValue<bool?>("UseWhitelist") ?? false,
|
||||||
|
UsePipelining = host.Configuration.GetSection("Smtp").GetValue<bool?>("UsePipelining") ?? false
|
||||||
};
|
};
|
||||||
|
|
||||||
//// fetch authentication entries
|
//// fetch authentication entries
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
|
||||||
|
<Project>
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Platform>Any CPU</Platform>
|
||||||
|
<PublishDir>bin\Release\net9.0\publish\win-x64\</PublishDir>
|
||||||
|
<PublishProtocol>FileSystem</PublishProtocol>
|
||||||
|
<_TargetId>Folder</_TargetId>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||||
|
<SelfContained>true</SelfContained>
|
||||||
|
<PublishSingleFile>true</PublishSingleFile>
|
||||||
|
<PublishReadyToRun>false</PublishReadyToRun>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
|
||||||
|
<Project>
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Platform>Any CPU</Platform>
|
||||||
|
<PublishDir>bin\Release\net9.0\publish2\</PublishDir>
|
||||||
|
<PublishProtocol>FileSystem</PublishProtocol>
|
||||||
|
<_TargetId>Folder</_TargetId>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||||
|
<SelfContained>true</SelfContained>
|
||||||
|
<PublishSingleFile>false</PublishSingleFile>
|
||||||
|
<PublishReadyToRun>false</PublishReadyToRun>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
|
|
@ -8,6 +8,7 @@ public class SmtpConfig
|
||||||
public string Greetings { get; set; } = "Relay";
|
public string Greetings { get; set; } = "Relay";
|
||||||
public TimeSpan ReadTimeout { get; init; } = TimeSpan.FromMinutes(3);
|
public TimeSpan ReadTimeout { get; init; } = TimeSpan.FromMinutes(3);
|
||||||
public TimeSpan WriteTimeout { get; init; } = TimeSpan.FromMinutes(1);
|
public TimeSpan WriteTimeout { get; init; } = TimeSpan.FromMinutes(1);
|
||||||
|
public bool UsePipelining { get; init; } = false;
|
||||||
public bool UseAuthentication { get; set; } = false;
|
public bool UseAuthentication { get; set; } = false;
|
||||||
public bool UseWhiteList { get; set; } = false;
|
public bool UseWhiteList { get; set; } = false;
|
||||||
public Dictionary<string, string> Authentication { get; } = [];
|
public Dictionary<string, string> Authentication { get; } = [];
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using SmtpRelay.Graph;
|
using SmtpRelay.Graph;
|
||||||
using System.Net;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
@ -52,6 +52,8 @@ public partial class SmtpSession(GraphSender graphSender, ILogger<SmtpSession> l
|
||||||
break;
|
break;
|
||||||
|
|
||||||
var line = Encoding.ASCII.GetString(rawLine);
|
var line = Encoding.ASCII.GetString(rawLine);
|
||||||
|
_logger.LogInformation("[{REP}] > {LINE}", socket.RemoteEndPoint, line);
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(line))
|
if (string.IsNullOrWhiteSpace(line))
|
||||||
{
|
{
|
||||||
await OnEmpty(socket, cancellationToken);
|
await OnEmpty(socket, cancellationToken);
|
||||||
|
|
@ -127,6 +129,12 @@ public partial class SmtpSession(GraphSender graphSender, ILogger<SmtpSession> l
|
||||||
await WriteLineAsync(socket, "250-AUTH PLAIN LOGIN", true, cancellationToken);
|
await WriteLineAsync(socket, "250-AUTH PLAIN LOGIN", true, cancellationToken);
|
||||||
|
|
||||||
// write basic pipelining accept (sequencing enforced in code)
|
// write basic pipelining accept (sequencing enforced in code)
|
||||||
|
if (!config.UsePipelining)
|
||||||
|
{
|
||||||
|
await WriteLineAsync(socket, "250 OK", true, cancellationToken);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await WriteLineAsync(socket, "250 PIPELINING", true, cancellationToken);
|
await WriteLineAsync(socket, "250 PIPELINING", true, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -372,7 +380,7 @@ public partial class SmtpSession(GraphSender graphSender, ILogger<SmtpSession> l
|
||||||
|
|
||||||
private async Task HandleAuthPlainAsync(Socket socket, SmtpConfig config, string? raw, CancellationToken cancellationToken)
|
private async Task HandleAuthPlainAsync(Socket socket, SmtpConfig config, string? raw, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("auth plain");
|
_logger.LogCritical("AUTH PLAIN");
|
||||||
|
|
||||||
// PLAIN == base64
|
// PLAIN == base64
|
||||||
string payloadB64 = raw ?? string.Empty;
|
string payloadB64 = raw ?? string.Empty;
|
||||||
|
|
@ -385,6 +393,8 @@ public partial class SmtpSession(GraphSender graphSender, ILogger<SmtpSession> l
|
||||||
|
|
||||||
var rawLine = await ReadToSpanAsync(socket, CrlnSpan, true, cancellationToken);
|
var rawLine = await ReadToSpanAsync(socket, CrlnSpan, true, cancellationToken);
|
||||||
payloadB64 = Encoding.ASCII.GetString(rawLine ?? throw new InvalidDataException("null")).Trim() ?? "";
|
payloadB64 = Encoding.ASCII.GetString(rawLine ?? throw new InvalidDataException("null")).Trim() ?? "";
|
||||||
|
|
||||||
|
_logger.LogInformation("[{REP}] > {LINE}", socket.RemoteEndPoint, rawLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!SmtpHelper.TryBase64(payloadB64, out var bytes))
|
if (!SmtpHelper.TryBase64(payloadB64, out var bytes))
|
||||||
|
|
@ -406,6 +416,9 @@ public partial class SmtpSession(GraphSender graphSender, ILogger<SmtpSession> l
|
||||||
var user = parts[1];
|
var user = parts[1];
|
||||||
var pass = parts[2];
|
var pass = parts[2];
|
||||||
|
|
||||||
|
_logger.LogCritical(user);
|
||||||
|
_logger.LogCritical(pass);
|
||||||
|
|
||||||
if (ValidateUser(config, user, pass))
|
if (ValidateUser(config, user, pass))
|
||||||
{
|
{
|
||||||
_authenticated = true;
|
_authenticated = true;
|
||||||
|
|
@ -420,7 +433,7 @@ public partial class SmtpSession(GraphSender graphSender, ILogger<SmtpSession> l
|
||||||
|
|
||||||
private async Task HandleAuthLoginAsync(Socket socket, SmtpConfig config, string? raw, CancellationToken cancellationToken)
|
private async Task HandleAuthLoginAsync(Socket socket, SmtpConfig config, string? raw, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("auth login");
|
_logger.LogCritical("AUTH LOGIN");
|
||||||
|
|
||||||
string? user;
|
string? user;
|
||||||
string? pass;
|
string? pass;
|
||||||
|
|
@ -442,6 +455,8 @@ public partial class SmtpSession(GraphSender graphSender, ILogger<SmtpSession> l
|
||||||
var rawLine = await ReadToSpanAsync(socket, CrlnSpan, true, cancellationToken);
|
var rawLine = await ReadToSpanAsync(socket, CrlnSpan, true, cancellationToken);
|
||||||
var response = Encoding.ASCII.GetString(rawLine ?? throw new InvalidDataException("null")).Trim() ?? "";
|
var response = Encoding.ASCII.GetString(rawLine ?? throw new InvalidDataException("null")).Trim() ?? "";
|
||||||
|
|
||||||
|
_logger.LogInformation("[{REP}] > {LINE}", socket.RemoteEndPoint, rawLine);
|
||||||
|
|
||||||
if (!SmtpHelper.TryBase64(response, out var ub))
|
if (!SmtpHelper.TryBase64(response, out var ub))
|
||||||
{
|
{
|
||||||
await WriteLineAsync(socket, $"501 5.5.2 Invalid base64", true, cancellationToken);
|
await WriteLineAsync(socket, $"501 5.5.2 Invalid base64", true, cancellationToken);
|
||||||
|
|
@ -485,29 +500,42 @@ public partial class SmtpSession(GraphSender graphSender, ILogger<SmtpSession> l
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
Memory<byte> readBuffer = new byte[8192];
|
||||||
|
|
||||||
while (!cancellationToken.IsCancellationRequested)
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
Memory<byte> readBuffer = new byte[8192];
|
// alloc memory over buffer
|
||||||
|
|
||||||
var read = await socket.ReceiveAsync(readBuffer, SocketFlags.None, cancellationToken);
|
|
||||||
if (read == 0)
|
|
||||||
throw new Exception("Connection Reset");
|
|
||||||
|
|
||||||
while (_buffer.Length < _bufferOffset + read)
|
|
||||||
Array.Resize(ref _buffer, _buffer.Length * 2);
|
|
||||||
|
|
||||||
Memory<byte> bufferMemory = new(_buffer);
|
Memory<byte> bufferMemory = new(_buffer);
|
||||||
|
|
||||||
readBuffer[..read].CopyTo(bufferMemory.Slice(_bufferOffset, read));
|
|
||||||
_bufferOffset += read;
|
|
||||||
|
|
||||||
// cut out unsed buffer
|
// cut out unsed buffer
|
||||||
var unusedBuffer = bufferMemory.Slice(_dataOffset, _bufferOffset);
|
var unusedBuffer = bufferMemory.Slice(_dataOffset, _bufferOffset - _dataOffset);
|
||||||
|
|
||||||
// find escape sequence
|
// DEBUG ONLY (no aot)
|
||||||
|
//_logger.LogTrace(_dataOffset.ToString());
|
||||||
|
//_logger.LogTrace(_bufferOffset.ToString());
|
||||||
|
|
||||||
|
//var dbgLine = Encoding.ASCII.GetString(unusedBuffer.Span);
|
||||||
|
//_logger.LogTrace(JsonSerializer.Serialize(dbgLine));
|
||||||
|
|
||||||
|
// find escape sequence if theres more left, before fetching new data
|
||||||
var escapeIndex = unusedBuffer.Span.IndexOf(until.Span);
|
var escapeIndex = unusedBuffer.Span.IndexOf(until.Span);
|
||||||
if (escapeIndex == -1)
|
if (escapeIndex == -1)
|
||||||
|
{
|
||||||
|
// try fetch new data
|
||||||
|
var read = await socket.ReceiveAsync(readBuffer, SocketFlags.None, cancellationToken);
|
||||||
|
if (read == 0)
|
||||||
|
throw new Exception("Connection Reset");
|
||||||
|
|
||||||
|
// increase buffer if needed
|
||||||
|
while (_buffer.Length < _bufferOffset + read)
|
||||||
|
Array.Resize(ref _buffer, _buffer.Length * 2);
|
||||||
|
|
||||||
|
bufferMemory = new(_buffer);
|
||||||
|
readBuffer[..read].CopyTo(bufferMemory.Slice(_bufferOffset, read));
|
||||||
|
_bufferOffset += read;
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// slice out bytes from last escape to new escape
|
// slice out bytes from last escape to new escape
|
||||||
var data = unusedBuffer[..(escapeIndex + until.Length)];
|
var data = unusedBuffer[..(escapeIndex + until.Length)];
|
||||||
|
|
@ -567,6 +595,8 @@ public partial class SmtpSession(GraphSender graphSender, ILogger<SmtpSession> l
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_writeLock.Release();
|
_writeLock.Release();
|
||||||
|
|
||||||
|
_logger.LogInformation("[{REP}] < {LINE}", socket.RemoteEndPoint, line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,15 @@
|
||||||
"MaxMessageBytes": 52428800,
|
"MaxMessageBytes": 52428800,
|
||||||
"UseAuthentication": true,
|
"UseAuthentication": true,
|
||||||
"UseWhitelist": true,
|
"UseWhitelist": true,
|
||||||
|
"UsePipelining": true,
|
||||||
"Authentication": {
|
"Authentication": {
|
||||||
"relay@example.local": "supersecret"
|
"relay@example.local": "supersecret",
|
||||||
|
"relay@autohaus-weigl.local": "Secret-Prevent-Whole"
|
||||||
},
|
},
|
||||||
"Whitelist": {
|
"Whitelist": {
|
||||||
"Senders": [
|
"Senders": [
|
||||||
"test@example.local"
|
"test@example.local",
|
||||||
|
"rechnung@autohaus-weigl.de"
|
||||||
],
|
],
|
||||||
"Receivers": [
|
"Receivers": [
|
||||||
"%"
|
"%"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue