3using System.ComponentModel;
9using System.Runtime.ExceptionServices;
10using System.Security.Authentication;
11using System.Security.Cryptography.X509Certificates;
13using System.Threading.Tasks;
30 internal enum ReceptionMode
68 private static readonly Random rnd =
new Random();
70 private readonly Guid
id = Guid.NewGuid();
71 private readonly UTF8Encoding encoding =
new UTF8Encoding(
false,
false);
76 private bool disposed =
false;
79 private object tag =
null;
80 private string clientName =
null;
81 private string authId =
null;
82 private MailAddress mailFrom =
null;
84 private bool mailFromMe =
false;
85 private List<MailAddress> recipients =
null;
86 private List<KeyValuePair<string, string>> headers =
null;
87 private MemoryStream body =
null;
88 private ReceptionMode mode = ReceptionMode.Command;
90 private readonly MemoryStream incoming =
new MemoryStream();
91 private readonly
int maxMessageSize;
92 private int incomingSize = 0;
93 private int messageSize = 0;
94 private bool overflow =
false;
95 private bool upgradeToTls =
false;
110 this.client = Client;
112 this.persistence = Persistence;
113 this.maxMessageSize = MaxMessageSize;
115 this.client.OnDisconnected += this.Client_OnDisconnected;
116 this.client.OnError += this.Client_OnError;
117 this.client.OnReceived += this.Client_OnReceived;
118 this.client.OnPaused += this.Client_OnPaused;
124 public Guid
ID => this.id;
146 if (this.state != NewState && !this.disposed)
148 this.state = NewState;
150 this.
Information(
"State changed to " + NewState.ToString());
167 set => this.tag = value;
196 [Obsolete(
"Use the DisposeAsync() method.")]
213 if (!(Sniffers is
null) && !(this.server is
null))
214 await this.server.CacheSniffers(
Sniffers);
216 this.disposed =
true;
224 private async Task<bool> Client_OnReceived(
object Sender,
bool ConstantBuffer,
byte[] Buffer,
int Offset,
int Count)
228 return await this.ParseIncoming(ConstantBuffer, Buffer, Offset, Count);
242 private async Task Client_OnError(
object Sender, Exception Exception)
248 private Task Client_OnDisconnected(
object Sender, EventArgs e)
253 private async Task<bool> ParseIncoming(
bool ConstantBuffer,
byte[] Data,
int Offset,
int NrRead)
255 int End = Offset + NrRead;
264 case ReceptionMode.MailBody:
266 this.mode = ReceptionMode.MailBodyCR;
270 if (this.overflow || this.messageSize > this.maxMessageSize)
272 this.overflow =
true;
276 this.body.WriteByte(b);
280 case ReceptionMode.MailBodyCR:
282 this.mode = ReceptionMode.MailBodyCRLF;
286 if (this.overflow || this.messageSize > this.maxMessageSize)
288 this.overflow =
true;
292 this.body.WriteByte(13);
296 this.messageSize += 2;
297 if (this.overflow || this.messageSize > this.maxMessageSize)
299 this.overflow =
true;
303 this.body.WriteByte(13);
304 this.body.WriteByte(b);
305 this.mode = ReceptionMode.MailBody;
309 case ReceptionMode.MailBodyCRLF:
311 this.mode = ReceptionMode.MailBodyCRLFPeriod;
314 this.messageSize += 2;
315 if (this.overflow || this.messageSize > this.maxMessageSize)
317 this.overflow =
true;
321 this.body.WriteByte(13);
322 this.body.WriteByte(10);
323 this.mode = ReceptionMode.MailBodyCR;
327 this.messageSize += 3;
328 if (this.overflow || this.messageSize > this.maxMessageSize)
330 this.overflow =
true;
334 this.body.WriteByte(13);
335 this.body.WriteByte(10);
336 this.body.WriteByte(b);
337 this.mode = ReceptionMode.MailBody;
341 case ReceptionMode.MailBodyCRLFPeriod:
343 this.mode = ReceptionMode.MailBodyCRLFPeriodCR;
346 this.messageSize += 3;
347 if (this.overflow || this.messageSize > this.maxMessageSize)
349 this.overflow =
true;
353 this.body.WriteByte(13);
354 this.body.WriteByte(10);
355 this.body.WriteByte(b);
356 this.mode = ReceptionMode.MailBody;
360 case ReceptionMode.MailBodyCRLFPeriodCR:
365 if (!await this.
BeginWrite(
"552 Requested mail action aborted: exceeded storage allocation.\r\n",
null,
null))
372 byte[] Body = this.body.ToArray();
375 string Id = await this.ProcessIncomingMail(this.mailFrom, this.recipients.ToArray(),
376 this.headers.ToArray(), Body,
this.clientName,
this.RemoteEndPoint);
378 if (!await this.
BeginWrite(
"250 2.6.0 " + Id.ToString() +
" Message accepted for delivery.\r\n",
null,
null))
384 if (!await this.
BeginWrite(
"554 Unable to parse incoming message: " + FirstRow(ex.Message) +
"\r\n",
null,
null))
393 this.messageSize += 3;
394 if (this.overflow || this.messageSize > this.maxMessageSize)
396 this.overflow =
true;
400 this.body.WriteByte(13);
401 this.body.WriteByte(10);
402 this.body.WriteByte(13);
403 this.mode = ReceptionMode.MailBodyCR;
407 this.messageSize += 4;
408 if (this.overflow || this.messageSize > this.maxMessageSize)
410 this.overflow =
true;
414 this.body.WriteByte(13);
415 this.body.WriteByte(10);
416 this.body.WriteByte(13);
417 this.body.WriteByte(b);
418 this.mode = ReceptionMode.MailBody;
427 string Row = Encoding.UTF8.GetString(this.incoming.ToArray());
428 this.incoming.Position = 0;
429 this.incoming.SetLength(0);
430 this.incomingSize = 0;
433 if (!await this.ParseIncomingRow(Row))
436 if (this.mode == ReceptionMode.MailBody)
441 this.incoming.WriteByte(b);
444 if (this.incomingSize > this.maxMessageSize)
446 await this.
BeginWrite(
"552 Requested mail action aborted: exceeded storage allocation.\r\n", async (Sender, e) =>
449 this.server.Closed(
this);
461 private async Task<bool> ParseIncomingRow(
string s)
465 case ReceptionMode.Command:
466 int i = s.IndexOf(
' ');
472 Value =
string.Empty;
477 Value = s[(i + 1)..];
480 switch (Cmd.ToUpper())
484 if (this.clientName is
null)
486 this.clientName = Value.Trim();
491 StringBuilder Response =
new StringBuilder();
493 Response.Append(
"250-");
494 Response.Append(this.server.
Domain);
495 Response.Append(
" Hello ");
496 Response.Append(this.clientName);
497 Response.Append(
" [");
498 Response.Append(this.client.
Client.Client.RemoteEndPoint.ToString());
499 Response.Append(
"]. Pleased to meet you.\r\n250-SIZE ");
500 Response.Append(this.maxMessageSize.ToString());
502 if (!(this.server.
ServerCertificate is
null) && !(
this.client.Stream is SslStream))
503 Response.Append(
"\r\n250-STARTTLS");
507 if (this.account is
null)
509 Response.Append(
"\r\n250-AUTH");
511 if (!(this.ClientCertificate is
null) && this.ClientCertificateValid)
512 Response.Append(
" EXTERNAL");
516 SslStream = this.client.Stream as SslStream;
521 Response.Append(
' ');
522 Response.Append(M.
Name);
527 Response.Append(
"\r\n250-SMTPUTF8\r\n250-8BITMIME\r\n250-ENHANCEDSTATUSCODES\r\n250 HELP\r\n");
529 if (!await this.
BeginWrite(Response.ToString(),
null,
null))
539 if (!await this.
BeginWrite(
"554 : expected.\r\n",
null,
null))
544 Cmd = s[..i].ToUpper();
545 s = s[(i + 1)..].TrimStart();
550 MailAddress From = this.ParseMailAddress(ref s);
552 int j = From.Address.IndexOf(
'@');
555 if (!await this.
BeginWrite(
"554 @ expected\r\n",
null,
null))
563 if (Domain == this.server.
Domain)
565 if (this.account is
null)
567 if (!await this.
BeginWrite(
"530 5.7.0 Authentication required.\r\n",
null,
null))
572 if (AccountName != this.account.
UserName)
574 if (!await this.
BeginWrite(
"555 Sender must be the same as the authenticated user.\r\n",
null,
null))
581 if (!(this.client.
Client.Client.RemoteEndPoint is IPEndPoint RemoteIPEndPoint))
583 if (!await this.
BeginWrite(
"550 Invalid remote address.\r\n",
null,
null))
590 KeyValuePair<SpfResult, string> SpfStatus = await
SpfResolver.
CheckHost(RemoteIPEndPoint.Address,
591 Domain, From.Address,
this.clientName,
this.server.Domain,
this.server.SpfExpressions);
595 switch (SpfStatus.Key)
603 this.
Warning(
"SPF check failed.");
604 if (!await this.
BeginWrite(
"550 " + (SpfStatus.Value ??
"SPF check failed.") +
"\r\n",
null,
null))
609 this.
Warning(
"SPF check soft-failed.");
610 if (!await this.
BeginWrite(
"550 " + (SpfStatus.Value ??
"SPF check soft-failed.") +
"\r\n",
null,
null))
615 this.
Warning(
"SPF check neutral.");
616 if (!await this.
BeginWrite(
"550 " + (SpfStatus.Value ??
"SPF check neutral. Only senders successfully passing a SPF check will be accepted.") +
"\r\n",
null,
null))
621 this.
Warning(
"No SPF records.");
622 if (!await this.
BeginWrite(
"550 " + (SpfStatus.Value ??
"No SPF records found.") +
"\r\n",
null,
null))
627 this.
Error(
"SPF records contain a permanent error.");
628 if (!await this.
BeginWrite(
"550 " + (SpfStatus.Value ??
"SPF records contain a permanent error.") +
"\r\n",
null,
null))
633 this.
Error(
"SPF records contain a temporary error.");
634 if (!await this.
BeginWrite(
"550 " + (SpfStatus.Value ??
"SPF records contain a temporary error.") +
"\r\n",
null,
null))
639 this.
Error(
"Unable to evaluate SPF records.");
640 if (!await this.
BeginWrite(
"550 " + (SpfStatus.Value ??
"Unable to evaluate SPF records.") +
"\r\n",
null,
null))
647 Log.
Warning(
"Remote IP blocked by SPF records.", RemoteIPEndPoint.Address.ToString(),
648 new KeyValuePair<string, object>(
"Status", SpfStatus.Key),
649 new KeyValuePair<string, object>(
"Reason", SpfStatus.Value));
655 switch (RemoteIPEndPoint.Address.AddressFamily)
657 case AddressFamily.InterNetwork:
658 BlackLists = this.server.Ip4DnsBlackLists;
661 case AddressFamily.InterNetworkV6:
662 BlackLists = this.server.Ip6DnsBlackLists;
664 if ((BlackLists is
null || BlackLists.Length == 0) &&
665 !(
this.server.Ip4DnsBlackLists is
null) &&
this.server.Ip4DnsBlackLists.Length > 0)
676 if (!(BlackLists is
null))
678 List<string> A =
new List<string>();
679 int c = BlackLists.Length;
681 A.AddRange(BlackLists);
682 BlackLists =
new string[c];
691 BlackLists[j++] = A[i];
696 BlackLists = A.ToArray();
698 foreach (
string BlackList
in BlackLists)
705 this.
Information(RemoteIPEndPoint.Address.ToString() +
" not in DNSBL " + BlackList);
708 this.
Warning(RemoteIPEndPoint.Address.ToString() +
" exists in DNSBL " + BlackList);
710 StringBuilder sb =
new StringBuilder();
712 sb.Append(
"550 Blocked by " + BlackList +
".");
714 foreach (
string s2
in Reason)
722 if (!await this.
BeginWrite(sb.ToString(),
null,
null))
726 Log.
Warning(
"Remote IP blocked by DNS Black List.", RemoteIPEndPoint.Address.ToString(), BlackList);
736 this.mailFrom = From;
737 this.mailFromDomain = Domain;
738 this.mailFromMe = !(this.account is
null) && (AccountName == this.account.
UserName) &&
739 Domain == this.server.
Domain;
741 if (!await this.
BeginWrite(
"250 2.1.0 Originator <" + this.mailFrom +
742 (this.mailFromMe ?
"> ok (you).\r\n" :
"> ok.\r\n"),
null,
null))
749 if (!await this.
BeginWrite(
"500 Syntax error\r\n",
null,
null))
760 if (!await this.
BeginWrite(
"554 : expected.\r\n",
null,
null))
765 Cmd = s[..i].ToUpper();
766 s = s[(i + 1)..].TrimStart();
771 if (this.mailFrom is
null)
773 if (!await this.
BeginWrite(
"503 Bad sequence of commands.\r\n",
null,
null))
778 MailAddress Recipient = this.ParseMailAddress(ref s);
780 int j = Recipient.Address.IndexOf(
'@');
783 if (!await this.
BeginWrite(
"554 @ expected.\r\n",
null,
null))
791 if (Domain == this.server.
Domain)
793 IAccount Account = await this.persistence.GetAccount(AccountName);
796 if (!await this.
BeginWrite(
"550 5.1.1 Mailbox does not exist.\r\n",
null,
null))
803 if (this.account is
null)
805 if (!await this.
BeginWrite(
"530 5.7.0 Authentication required\r\n",
null,
null))
810 if (!this.account.
HasPrivilege(SmtpServer.SmtpRelayPrivilegeID))
812 Log.
Warning(
"Failed attempt to relay SMTP message. Account does not have relay permissions.", Recipient.ToString(), this.account.
UserName,
813 new KeyValuePair<string, object>(
"From", this.mailFrom.Address),
814 new KeyValuePair<string, object>(
"RemoteEndpoint", this.RemoteEndPoint),
815 new KeyValuePair<string, object>(
"ClientName", this.clientName));
817 if (!await this.
BeginWrite(
"550 5.7.1 Relaying messages using this account not permitted.\r\n",
null,
null))
824 Log.
Warning(
"Failed attempt to relay SMTP message. From Domain not white-listed.", Recipient.ToString(), this.account.
UserName,
825 new KeyValuePair<string, object>(
"From", this.mailFrom.Address),
826 new KeyValuePair<string, object>(
"FromDomain", this.mailFromDomain),
827 new KeyValuePair<string, object>(
"RemoteEndpoint", this.RemoteEndPoint),
828 new KeyValuePair<string, object>(
"ClientName", this.clientName));
830 if (!await this.
BeginWrite(
"550 5.7.1 Relaying messages from " + this.mailFromDomain +
" not allowed.\r\n",
null,
null))
836 this.messageSize += Recipient.Address.Length + Recipient.DisplayName.Length;
837 if (this.overflow || this.messageSize > this.maxMessageSize)
839 this.overflow =
true;
840 if (!await this.
BeginWrite(
"554 Message too large.\r\n",
null,
null))
845 this.recipients ??=
new List<MailAddress>();
846 this.recipients.Add(Recipient);
848 if (!await this.
BeginWrite(
"250 2.1.5 Recipient <" + Recipient +
"> ok.\r\n",
null,
null))
853 if (!await this.
BeginWrite(
"500 Syntax error\r\n",
null,
null))
862 if (!await this.
BeginWrite(
"552 Requested mail action aborted: exceeded storage allocation.\r\n",
null,
null))
867 if (this.mailFrom is
null || this.recipients is
null || this.recipients.Count == 0)
869 if (!await this.
BeginWrite(
"503 Bad sequence of commands.\r\n",
null,
null))
874 this.headers =
new List<KeyValuePair<string, string>>();
875 this.body =
new MemoryStream();
876 this.messageSize = 0;
877 this.mode = ReceptionMode.MailHeader;
879 if (!await this.
BeginWrite(
"354 Enter message body, end with \".\" on a line by itself.\r\n",
null,
null))
886 await this.
BeginWrite(
"221 2.0.0 " + this.server.
Domain +
" closing connection.\r\n", async (Sender, e) =>
889 this.server.Closed(
this);
896 if (!await this.
BeginWrite(
"252 Command disabled.\r\n",
null,
null))
902 if (!await this.
BeginWrite(
"250 OK\r\n",
null,
null))
907 if (!await this.
BeginWrite(
"214 " + typeof(SmtpServer).Namespace +
" mail-server.\r\n",
null,
null))
912 if (!await this.
BeginWrite(
"250 OK\r\n",
null,
null))
917 if (!(this.account is
null))
919 if (!await this.
BeginWrite(
"503 5.5.4 Already authenticated.\r\n",
null,
null))
934 StringBuilder sb =
new StringBuilder();
935 DateTime TP = Next.Value;
936 DateTime Today = DateTime.Today;
938 sb.Append(
"550 5.7.26 ");
940 if (Next.Value == DateTime.MaxValue)
942 sb.Append(
"This endpoint (");
943 sb.Append(this.RemoteEndPoint);
944 sb.Append(
") has been blocked from the system");
948 sb.Append(
"Too many failed login attempts in a row registered. Try again after ");
949 sb.Append(TP.ToLongTimeString());
951 if (TP.Date != Today)
953 if (TP.Date == Today.AddDays(1))
954 sb.Append(
" tomorrow");
958 sb.Append(TP.ToShortDateString());
963 sb.Append(
". Remote Endpoint: ");
964 sb.Append(this.RemoteEndPoint);
967 if (!await this.
BeginWrite(sb.ToString(),
null,
null))
986 s = s[(i + 1)..].TrimStart();
989 this.mechanism =
null;
1000 if (this.mechanism is
null)
1002 if (!await this.
BeginWrite(
"503 5.5.4 Invalid authentication mechanism.\r\n",
null,
null))
1007 SslStream = this.client.Stream as SslStream;
1008 if (!this.mechanism.
Allowed(SslStream))
1010 this.mechanism =
null;
1012 if (SslStream is
null)
1014 if (!await this.
BeginWrite(
"538 5.7.11 Encryption required for requested authentication mechanism\r\n",
null,
null))
1019 if (!await this.
BeginWrite(
"534 5.7.9 Authentication mechanism is too weak\r\n",
null,
null))
1026 this.mode = ReceptionMode.ChallengeResponse;
1031 if (await this.
BeginWrite(
"220 Go ahead\r\n",
null,
null))
1032 this.upgradeToTls =
true;
1036 if (!await this.
BeginWrite(
"502 Command not implemented\r\n",
null,
null))
1042 case ReceptionMode.ChallengeResponse:
1043 bool? AuthResult = await this.mechanism.
ResponseRequest(s,
this, this.persistence);
1044 if (AuthResult.HasValue)
1048 if (AuthResult.Value)
1059 this.mode = ReceptionMode.Command;
1063 case ReceptionMode.MailHeader:
1064 if (
string.IsNullOrEmpty(s))
1065 this.mode = ReceptionMode.MailBody;
1068 this.messageSize += s.Length;
1069 if (this.overflow || this.messageSize > this.maxMessageSize)
1071 this.overflow =
true;
1077 if (
char.IsWhiteSpace(s[0]) && (c = this.headers.Count) > 0)
1079 KeyValuePair<string, string> P = this.headers[c - 1];
1080 this.headers[c - 1] =
new KeyValuePair<string, string>(P.Key, P.Value + s);
1090 Value =
string.Empty;
1095 Value = s[(i + 1)..].Trim();
1100 Key = Key.ToUpper();
1102 this.headers.Add(
new KeyValuePair<string, string>(Key, Value));
1111 private async Task Client_OnPaused(
object Sender, EventArgs e)
1113 if (this.upgradeToTls)
1115 this.upgradeToTls =
false;
1128 await this.SetState(Bak);
1132 catch (AuthenticationException ex)
1136 catch (Win32Exception ex)
1140 catch (Exception ex)
1143 await this.ToError(
null);
1147 await this.ToError(
null);
1151 private async Task LoginFailure(Exception ex,
string RemoteIpEndpoint)
1156 await this.ToError(
null);
1159 private static string FirstRow(
string s)
1161 int i = s.IndexOfAny(CRLF);
1168 private static readonly
char[] CRLF =
new char[] {
'\r',
'\n' };
1170 private MailAddress[] ParseMailAddresses(
string s)
1172 List<MailAddress> Result =
null;
1175 foreach (
string Part
in s.Split(
','))
1178 Addr = this.ParseMailAddress(ref s);
1182 Result ??=
new List<MailAddress>();
1186 return Result?.ToArray();
1189 private MailAddress ParseMailAddress(ref
string s)
1191 int i = s.IndexOf(
'<');
1194 if (s.IndexOf(
'@') >= 0)
1195 return new MailAddress(s.Trim());
1200 int j = s.IndexOf(
'>', i + 1);
1204 string Name = s[..i].
Trim();
1207 s = s[(j + 1)..].TrimStart();
1209 if (
string.IsNullOrEmpty(Name))
1210 return new MailAddress(Address);
1212 return new MailAddress(Address, Name);
1215 private async Task ToError(
string ClosingCommand)
1217 if (
string.IsNullOrEmpty(ClosingCommand))
1224 await this.
BeginWrite(ClosingCommand, async (Sender, e) =>
1238 public Task<bool>
BeginWrite(
string Text, EventHandlerAsync<DeliveryEventArgs> Callback,
object State)
1241 return Task.FromResult(
false);
1243 return this.client.
SendAsync(
true, this.encoding.GetBytes(Text), async (Sender, e) =>
1247 if (!(Callback is
null))
1248 await Callback.Raise(
this, e);
1270 this.
Add(this.server.GetSniffer(
UserName +
" IN"));
1284 this.account = Account;
1307 bool BlockingBak = this.client.
Client.Client.Blocking;
1310 byte[] Temp =
new byte[1];
1312 this.client.Client.Client.Blocking =
false;
1313 this.client.
Client.Client.Send(Temp, 0, 0);
1317 catch (SocketException e)
1319 if (e.NativeErrorCode.Equals(10035))
1326 this.client.Client.Client.Blocking = BlockingBak;
1349 this.mode = ReceptionMode.Command;
1350 this.mailFrom =
null;
1351 this.mailFromDomain =
null;
1352 this.recipients =
null;
1353 this.headers =
null;
1355 this.messageSize = 0;
1356 this.overflow =
false;
1365 this.mode = ReceptionMode.Command;
1366 return this.
BeginWrite(
"535 5.7.8 Authentication credentials invalid\r\n",
null,
null);
1375 this.mode = ReceptionMode.Command;
1376 return this.
BeginWrite(
"454 4.7.0 Accound disabled.\r\n",
null,
null);
1385 this.mode = ReceptionMode.Command;
1386 return this.
BeginWrite(
"503 Malformed request.\r\n",
null,
null);
1395 if (
string.IsNullOrEmpty(ChallengeBase64))
1396 return this.
BeginWrite(
"334\r\n",
null,
null);
1398 return this.
BeginWrite(
"334 " + ChallengeBase64 +
"\r\n",
null,
null);
1407 this.mode = ReceptionMode.Command;
1408 return this.
BeginWrite(
"235 2.7.0 Authentication successful\r\n",
null,
null);
1411 private async Task<string> ProcessIncomingMail(MailAddress From, MailAddress[] Recipients, KeyValuePair<string, string>[] Header,
1417 Recipients = Recipients,
1418 AllHeaders = Header,
1419 UntransformedBody = Body,
1424 List<MailAddress> To =
null;
1425 List<MailAddress> Cc =
null;
1426 List<MailAddress> Bcc =
null;
1427 List<MailAddress> ReplyTo =
null;
1428 List<KeyValuePair<string, string>> OtherHeaders =
new List<KeyValuePair<string, string>>();
1432 foreach (KeyValuePair<string, string> P
in Header)
1434 string s = P.Key.ToUpper();
1440 if (!this.AddRecipients(ref Cc, P.Value))
1441 OtherHeaders.Add(P);
1445 Message.ContentId = P.Value.Trim();
1448 case "CONTENT-LOCATION":
1449 Message.ContentLocation = P.Value.Trim();
1453 case "CONTENT-TRANSFER-ENCODING":
1454 Message.ContentTransferEncoding = P.Value.Trim();
1457 case "CONTENT-TYPE":
1458 Message.ContentType = ContentType = P.Value.Trim();
1465 OtherHeaders.Add(P);
1470 Message.FromHeader = this.ParseMailAddress(ref s);
1472 OtherHeaders.Add(P);
1476 switch (P.Value.ToLower())
1483 Message.Priority =
Priority.Normal;
1491 OtherHeaders.Add(P);
1497 Message.MessageID = P.Value;
1500 case "MIME-VERSION":
1502 Message.MimeVersion = d;
1504 OtherHeaders.Add(P);
1508 switch (P.Value.ToLower())
1515 Message.Priority =
Priority.Normal;
1523 OtherHeaders.Add(P);
1529 if (!this.AddRecipients(ref ReplyTo, P.Value))
1530 OtherHeaders.Add(P);
1535 Message.Sender = this.ParseMailAddress(ref s);
1536 if (Message.
Sender is
null)
1537 OtherHeaders.Add(P);
1541 Message.Subject = P.Value;
1545 if (!this.AddRecipients(ref To, P.Value))
1546 OtherHeaders.Add(P);
1555 if (
int.TryParse(s.Trim(), out i))
1558 OtherHeaders.Add(P);
1562 OtherHeaders.Add(P);
1568 Message.TransformedBody =
null;
1572 throw new NotSupportedException(
"Content-Transfer-Encoding not supported: " + Message.
ContentTransferEncoding);
1574 Message.TransformedBody = ToDecode;
1577 KeyValuePair<string, string>[] ContentTypeFields =
null;
1580 if (!
string.IsNullOrEmpty(ContentType))
1583 out KeyValuePair<string, string>[] Fields))
1586 ContentTypeFields = Fields;
1597 Message.
ContentTypeEncoding, ContentTypeFields ?? Array.Empty<KeyValuePair<string, string>>(), BaseUri,
null);
1600 Message.DecodedBody = Content.Decoded;
1602 catch (Exception ex)
1606 string Path =
System.IO.Path.Combine(AppDataFolder,
"SMTP");
1607 if (!Directory.Exists(Path))
1608 Directory.CreateDirectory(Path);
1610 string LocalId = Guid.NewGuid().ToString();
1611 Path =
System.IO.Path.Combine(Path, LocalId);
1617 throw new Exception(
"Error when decoding message. Content stored under " + LocalId +
"[.bin][.txt][.err].");
1620 ExceptionDispatchInfo.Capture(ex).Throw();
1625 List<EmbeddedContent> InlineObjects =
null;
1626 List<EmbeddedContent> Attachments =
null;
1638 InlineObjects ??=
new List<EmbeddedContent>();
1639 InlineObjects.Add(Object);
1643 Attachments ??=
new List<EmbeddedContent>();
1644 Attachments.Add(Object);
1651 InlineObjects ??=
new List<EmbeddedContent>();
1652 InlineObjects.Insert(0, First);
1657 Attachments ??=
new List<EmbeddedContent>();
1658 Attachments.Insert(0, First);
1663 Attachments ??=
new List<EmbeddedContent>();
1664 Attachments.Add(Object);
1672 Message.InlineObjects = InlineObjects?.ToArray();
1673 Message.Attachments = Attachments?.ToArray();
1674 Message.DecodedBody = First.
Decoded;
1678 foreach (MailAddress Addr
in Message.
Recipients)
1684 foreach (MailAddress A
in To)
1686 if (Addr.Address == A.Address)
1699 foreach (MailAddress A
in Cc)
1701 if (Addr.Address == A.Address)
1712 Bcc ??=
new List<MailAddress>();
1716 Message.UnparsedHeaders = OtherHeaders.ToArray();
1717 Message.To = To?.ToArray();
1718 Message.Cc = Cc?.ToArray();
1719 Message.Bcc = Bcc?.ToArray();
1720 Message.ReplyTo = ReplyTo?.ToArray();
1722 Message.Sender ??= Message.
FromMail;
1724 if (!Message.
Date.HasValue)
1725 Message.Date = DateTimeOffset.Now;
1727 Message.MessageID ??= Guid.NewGuid().ToString();
1729 await this.server.ProcessMessage(Message);
1734 private bool AddRecipients(ref List<MailAddress> AddressList,
string s)
1736 AddressList ??=
new List<MailAddress>();
1738 MailAddress[] Addresses = this.ParseMailAddresses(s);
1739 if (Addresses is
null)
1743 AddressList.AddRange(Addresses);
1748 internal static int Next(
int MaxValue)
1752 return rnd.Next(MaxValue);
Helps with parsing of commong data types.
static bool TryParseRfc822(string s, out DateTimeOffset Value)
Parses a date and time value encoded according to RFC 822, §5.
static bool TryParse(string s, out double Value)
Tries to decode a string encoded double.
Contains information about a response to a content request.
void AssertOk()
Asserts response is OK.
Static class managing encoding and decoding of internet content.
static bool Decodes(string ContentType, out Grade Grade, out IContentDecoder Decoder)
If an object with a given content type can be decoded.
static bool ParseContentType(ref string ContentType, out Encoding Encoding, out KeyValuePair< string, string >[] Fields)
Parses a Content-Type, providing the base Content-Type, character encoding, if any,...
Represents content embedded in other content.
ContentDisposition Disposition
Disposition of embedded object.
object Decoded
Decoded body of embedded object. ContentType defines how TransferDecoded is transformed into Decoded.
static bool TryTransferDecode(byte[] Encoded, string TransferEncoding, out byte[] Decoded)
Tries to decode transfer-encoded binary data.
Represents mixed content, encoded with multipart/mixed
EmbeddedContent[] Content
Embedded content.
Plain text encoder/decoder.
const string DefaultContentType
text/plain
Static class managing the application event log. Applications and services log events on this static ...
static string CleanStackTrace(string StackTrace)
Cleans a Stack Trace string, removing entries from the asynchronous execution model,...
static void Warning(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs a warning event.
static Exception UnnestException(Exception Exception)
Unnests an exception, to extract the relevant inner exception.
Implements a binary TCP Client, by encapsulating a TcpClient. It also makes the use of TcpClient safe...
bool Connected
If the connection is open.
bool RemoteCertificateValid
If the remote certificate is valid.
TcpClient Client
Underlying TcpClient object.
Task< bool > SendAsync(byte[] Packet)
Sends a binary packet.
string RemoteEndPoint
Remote End-point of connection. This corresponds to the IP Endpoint of the remote party in normal cas...
void DisposeWhenDone()
Disposes the client when done sending all data.
void Continue()
Continues reading from the socket, if paused in an event handler.
X509Certificate RemoteCertificate
Certificate used by the remote endpoint.
Task UpgradeToTlsAsServer(X509Certificate ServerCertificate)
Upgrades a server connection to TLS.
Simple base class for classes implementing communication protocols.
void TransmitText(string Text)
Called when text has been transmitted.
virtual bool Remove(ISniffer Sniffer)
ICommunicationLayer.Remove
void Exception(Exception Exception)
Called to inform the viewer of an exception state.
void ReceiveText(string Text)
Called when text has been received.
ISniffer[] Sniffers
Registered sniffers.
void Error(string Error)
Called to inform the viewer of an error state.
void Warning(string Warning)
Called to inform the viewer of a warning state.
virtual void Add(ISniffer Sniffer)
ICommunicationLayer.Add
void ReceiveBinary(int Count)
Called when binary data has been received.
void Information(string Comment)
Called to inform the viewer of something.
DNS resolver, as defined in:
static async Task< string[]> LookupBlackList(IPAddress Address, string BlackListDomainName)
Looks up an IP Address in a DNS Block List.
Module maintaining available SASL mechanisms.
static IAuthenticationMechanism[] Mechanisms
Available SASL mechanisms.
Class managing a connection.
SmtpClientConnection(BinaryTcpClient Client, SmtpServer Server, ISaslPersistenceLayer Persistence, int MaxMessageSize, params ISniffer[] Sniffers)
Class managing a connection.
void ResetState(bool Authenticated)
Resets the state machine.
void Dispose()
IDisposable.Dispose
string ClientName
Name of client.
string RemoteEndPoint
Remote endpoint.
object Tag
Tag object. Can be used to maintain states between calls, for instance during authentication.
string Protocol
String representing protocol being used.
Task< bool > SaslErrorMalformedRequest()
Reports the SASL error: Malformed request
Task< bool > SaslErrorAccountDisabled()
Reports the SASL error: Account disabled
Task< bool > SaslSuccess(string ProofBase64)
Returns a sucess response to the client.
async Task SetAccount(IAccount Account)
Sets the account for the connection.
bool CheckLive()
Checks if the connection is live.
Task< bool > BeginWrite(string Text, EventHandlerAsync< DeliveryEventArgs > Callback, object State)
Starts sending a text command to the client.
Task< bool > SaslChallenge(string ChallengeBase64)
Returns a challenge to the client.
async Task DisposeAsync()
Closes the connection and disposes of all resources.
void ResetState()
Resets the state machine.
SmtpServer Server
SMTP Server serving the client.
string AuthId
ID client claims to have
void SetUserIdentity(CaseInsensitiveString UserName)
Sets the identity of the user.
Task< bool > SaslErrorNotAuthorized()
Reports the SASL error: Not Authorized
SmtpConnectionState State
Current state of connection.
EventHandlerAsync< SmtpConnectionState > OnStateChanged
Event raised whenever the internal state of the connection changes.
CaseInsensitiveString UserName
User name
Represents one message received over SMTP
string MessageID
Message-ID.
object DecodedBody
Decoded body. ContentType defines how TransformedBody is transformed into DecodedBody.
byte[] TransformedBody
Transformed body. ContentTransferEncoding defines how UntransformedBody is transformed into Transform...
DateTimeOffset? Date
Date of message, if defined
MailAddress FromMail
From address, as specified by the client to initiate mail transfer.
MailAddress FromHeader
From address, as specified in the mail headers.
MailAddress[] Recipients
Recipients of message, as defined during initiation of transfer.
string ContentTransferEncoding
Content Transfer Encoding of message, if defined. Affects how UntransformedBody is transformed into T...
MailAddress Sender
Sender, as specified in the mail headers.
Encoding ContentTypeEncoding
Content-Type encoding, if specified in the Content-Type header field.
string ContentLocation
Content Location of message, if defined
byte[] UntransformedBody
Raw, untrasnformed body of message.
Implements a simple SMTP Server, as defined in:
CaseInsensitiveString Domain
Domain name.
bool CanRelayForDomain(string Domain)
If the server is permitted to relay messages from a particular domain.
X509Certificate ServerCertificate
Server domain certificate.
Sniffer that stores events in memory.
void Replay(CommunicationLayer ComLayer)
Replays sniffer events.
Represents a case-insensitive string.
static readonly CaseInsensitiveString Empty
Empty case-insensitive string
int IndexOf(CaseInsensitiveString value, StringComparison comparisonType)
Reports the zero-based index of the first occurrence of the specified string in the current System....
CaseInsensitiveString Trim()
Removes all leading and trailing white-space characters from the current CaseInsensitiveString object...
CaseInsensitiveString Substring(int startIndex, int length)
Retrieves a substring from this instance. The substring starts at a specified character position and ...
static Task WriteAllBytesAsync(string FileName, byte[] Data)
Creates a binary file asynchronously.
static Task WriteAllTextAsync(string FileName, string Text)
Creates a text file asynchronously.
Static class that dynamically manages types and interfaces available in the runtime environment.
static bool TryGetModuleParameter(string Name, out object Value)
Tries to get a module parameter value.
Helper methods for encrypting and decrypting streams of data.
const SslProtocols SecureTls
TLS 1.2 & 1.3
Class that monitors login events, and help applications determine malicious intent....
async Task< DateTime?> GetEarliestLoginOpportunity(string RemoteEndPoint, string Protocol)
Checks when a remote endpoint can login.
static bool CanStartTls(string RemoteEndPoint)
Checks if TLS negotiation can start, for a given endpoint. If the endpoint has tries a TLS hack attem...
static void ReportTlsHackAttempt(string RemoteEndPoint, string Message, string Protocol)
Reports a TLS hacking attempt from an endpoint. Can be used to deny TLS negotiation to proceed,...
Resolves a SPF string, as defined in:
static Task< KeyValuePair< SpfResult, string > > CheckHost(IPAddress Address, string DomainName, string Sender, string HelloDomain, string HostDomain, params SpfExpression[] SpfExpressions)
Fetches SPF records, parses them, and evaluates them to determine whether a particular host is or is ...
Basic interface for Internet Content decoders. A class implementing this interface and having a defau...
Interface for asynchronously disposable objects.
Interface for SMTP user accounts.
CaseInsensitiveString UserName
User Name
bool HasPrivilege(string PrivilegeID)
If the account has a given privilege.
Interface for authentication mechanisms.
Task< bool?> AuthenticationRequest(string Data, ISaslServerSide Connection, ISaslPersistenceLayer PersistenceLayer)
Authentication request has been made.
bool Allowed(SslStream SslStream)
Checks if a mechanism is allowed during the current conditions.
Task< bool?> ResponseRequest(string Data, ISaslServerSide Connection, ISaslPersistenceLayer PersistenceLayer)
Response request has been made.
string Name
Name of the mechanism.
Interface for XMPP Server persistence layers. The persistence layer should implement caching.
void AccountLogin(CaseInsensitiveString UserName, string RemoteEndPoint)
Successful login to account registered.
Interface for server-side client connections.
Interface for sniffers. Sniffers can be added to ICommunicationLayer classes to eavesdrop on communic...
ContentDisposition
Content disposition
SmtpConnectionState
State of SMTP connection.
Priority
Priority of message
ClientCertificates
Client Certificate Options
ContentType
DTLS Record content type.
Reason
Reason a token is not valid.
SpfResult
Result of a SPF (Sender Policy Framework) evaluation.