2using System.Collections.Generic;
3using System.ComponentModel;
7using System.Net.Security;
8using System.Net.Sockets;
9using System.Runtime.ExceptionServices;
10using System.Security.Authentication;
11using System.Security.Cryptography.X509Certificates;
13using System.Threading.Tasks;
28 internal enum ReceptionMode
52 private static readonly Random rnd =
new Random();
54 private readonly Guid
id = Guid.NewGuid();
55 private readonly UTF8Encoding encoding =
new UTF8Encoding(
false,
false);
60 private bool disposed =
false;
63 private object tag =
null;
64 private string clientName =
null;
65 private string authId =
null;
66 private MailAddress mailFrom =
null;
68 private bool mailFromMe =
false;
69 private List<MailAddress> recipients =
null;
70 private List<KeyValuePair<string, string>> headers =
null;
71 private MemoryStream body =
null;
72 private ReceptionMode mode = ReceptionMode.Command;
74 private readonly MemoryStream incoming =
new MemoryStream();
75 private readonly
int maxMessageSize;
76 private int incomingSize = 0;
77 private int messageSize = 0;
78 private bool overflow =
false;
79 private bool upgradeToTls =
false;
95 this.persistence = Persistence;
96 this.maxMessageSize = MaxMessageSize;
98 this.client.OnDisconnected += this.Client_OnDisconnected;
99 this.client.OnError += this.Client_OnError;
100 this.client.OnReceived += this.Client_OnReceived;
101 this.client.OnPaused += this.Client_OnPaused;
107 public Guid
ID => this.id;
135 if (this.state != NewState && !this.disposed)
137 this.state = NewState;
139 await this.
Information(
"State changed to " + NewState.ToString());
156 set => this.tag = value;
174 get {
return this.client?.
Client?.Client?.RemoteEndPoint?.ToString(); }
188 [Obsolete(
"Use the DisposeAsync() method.")]
212 if (!(Sniffers is
null))
213 this.server?.CacheSniffers(
Sniffers);
215 this.disposed =
true;
223 private async Task<bool> Client_OnReceived(
object Sender,
byte[] Buffer,
int Offset,
int Count)
227 return await this.ParseIncoming(Buffer, Offset, Count);
233 await this.Exception(ex);
241 private async Task Client_OnError(
object Sender, Exception Exception)
247 private Task Client_OnDisconnected(
object Sender, EventArgs e)
252 private async Task<bool> ParseIncoming(
byte[] Data,
int Offset,
int NrRead)
256 while (Offset < NrRead)
262 case ReceptionMode.MailBody:
264 this.mode = ReceptionMode.MailBodyCR;
268 if (this.overflow || this.messageSize > this.maxMessageSize)
270 this.overflow =
true;
274 this.body.WriteByte(b);
278 case ReceptionMode.MailBodyCR:
280 this.mode = ReceptionMode.MailBodyCRLF;
284 if (this.overflow || this.messageSize > this.maxMessageSize)
286 this.overflow =
true;
290 this.body.WriteByte(13);
294 this.messageSize += 2;
295 if (this.overflow || this.messageSize > this.maxMessageSize)
297 this.overflow =
true;
301 this.body.WriteByte(13);
302 this.body.WriteByte(b);
303 this.mode = ReceptionMode.MailBody;
307 case ReceptionMode.MailBodyCRLF:
309 this.mode = ReceptionMode.MailBodyCRLFPeriod;
312 this.messageSize += 2;
313 if (this.overflow || this.messageSize > this.maxMessageSize)
315 this.overflow =
true;
319 this.body.WriteByte(13);
320 this.body.WriteByte(10);
321 this.mode = ReceptionMode.MailBodyCR;
325 this.messageSize += 3;
326 if (this.overflow || this.messageSize > this.maxMessageSize)
328 this.overflow =
true;
332 this.body.WriteByte(13);
333 this.body.WriteByte(10);
334 this.body.WriteByte(b);
335 this.mode = ReceptionMode.MailBody;
339 case ReceptionMode.MailBodyCRLFPeriod:
341 this.mode = ReceptionMode.MailBodyCRLFPeriodCR;
344 this.messageSize += 3;
345 if (this.overflow || this.messageSize > this.maxMessageSize)
347 this.overflow =
true;
351 this.body.WriteByte(13);
352 this.body.WriteByte(10);
353 this.body.WriteByte(b);
354 this.mode = ReceptionMode.MailBody;
358 case ReceptionMode.MailBodyCRLFPeriodCR:
363 if (!await this.
BeginWrite(
"552 Requested mail action aborted: exceeded storage allocation.\r\n",
null,
null))
370 byte[] Body = this.body.ToArray();
373 string Id = await this.ProcessIncomingMail(this.mailFrom, this.recipients.ToArray(),
374 this.headers.ToArray(), Body,
this.clientName,
this.RemoteEndpoint);
376 if (!await this.
BeginWrite(
"250 2.6.0 " + Id.ToString() +
" Message accepted for delivery.\r\n",
null,
null))
382 if (!await this.
BeginWrite(
"554 Unable to parse incoming message: " + FirstRow(ex.Message) +
"\r\n",
null,
null))
391 this.messageSize += 3;
392 if (this.overflow || this.messageSize > this.maxMessageSize)
394 this.overflow =
true;
398 this.body.WriteByte(13);
399 this.body.WriteByte(10);
400 this.body.WriteByte(13);
401 this.mode = ReceptionMode.MailBodyCR;
405 this.messageSize += 4;
406 if (this.overflow || this.messageSize > this.maxMessageSize)
408 this.overflow =
true;
412 this.body.WriteByte(13);
413 this.body.WriteByte(10);
414 this.body.WriteByte(13);
415 this.body.WriteByte(b);
416 this.mode = ReceptionMode.MailBody;
425 string Row = Encoding.UTF8.GetString(this.incoming.ToArray());
426 this.incoming.Position = 0;
427 this.incoming.SetLength(0);
428 this.incomingSize = 0;
431 if (!await this.ParseIncomingRow(Row))
434 if (this.mode == ReceptionMode.MailBody)
439 this.incoming.WriteByte(b);
442 if (this.incomingSize > this.maxMessageSize)
444 await this.
BeginWrite(
"552 Requested mail action aborted: exceeded storage allocation.\r\n", async (Sender, e) =>
447 this.server.Closed(
this);
459 private async Task<bool> ParseIncomingRow(
string s)
463 case ReceptionMode.Command:
464 int i = s.IndexOf(
' ');
470 Value =
string.Empty;
474 Cmd = s.Substring(0, i);
475 Value = s.Substring(i + 1);
478 switch (Cmd.ToUpper())
482 if (this.clientName is
null)
484 this.clientName = Value.Trim();
489 StringBuilder Response =
new StringBuilder();
491 Response.Append(
"250-");
492 Response.Append(this.server.
Domain);
493 Response.Append(
" Hello ");
494 Response.Append(this.clientName);
495 Response.Append(
" [");
496 Response.Append(this.client.
Client.Client.RemoteEndPoint.ToString());
497 Response.Append(
"]. Pleased to meet you.\r\n250-SIZE ");
498 Response.Append(this.maxMessageSize.ToString());
500 if (!(this.server.
ServerCertificate is
null) && !(
this.client.Stream is SslStream))
501 Response.Append(
"\r\n250-STARTTLS");
505 if (this.account is
null)
507 Response.Append(
"\r\n250-AUTH");
509 if (!(this.ClientCertificate is
null) && this.ClientCertificateValid)
510 Response.Append(
" EXTERNAL");
514 SslStream = this.client.Stream as SslStream;
519 Response.Append(
' ');
520 Response.Append(M.
Name);
525 Response.Append(
"\r\n250-SMTPUTF8\r\n250-8BITMIME\r\n250-ENHANCEDSTATUSCODES\r\n250 HELP\r\n");
527 if (!await this.
BeginWrite(Response.ToString(),
null,
null))
537 if (!await this.
BeginWrite(
"554 : expected.\r\n",
null,
null))
542 Cmd = s.Substring(0, i).ToUpper();
543 s = s.Substring(i + 1).TrimStart();
548 MailAddress From = this.ParseMailAddress(ref s);
550 int j = From.Address.IndexOf(
'@');
553 if (!await this.
BeginWrite(
"554 @ expected\r\n",
null,
null))
561 if (Domain == this.server.
Domain)
563 if (this.account is
null)
565 if (!await this.
BeginWrite(
"530 5.7.0 Authentication required.\r\n",
null,
null))
570 if (AccountName != this.account.
UserName)
572 if (!await this.
BeginWrite(
"555 Sender must be the same as the authenticated user.\r\n",
null,
null))
579 if (!(this.client.
Client.Client.RemoteEndPoint is IPEndPoint RemoteIPEndPoint))
581 if (!await this.
BeginWrite(
"550 Invalid remote address.\r\n",
null,
null))
588 KeyValuePair<SpfResult, string> SpfStatus = await
SpfResolver.
CheckHost(RemoteIPEndPoint.Address,
589 Domain, From.Address,
this.clientName,
this.server.Domain,
this.server.SpfExpressions);
593 switch (SpfStatus.Key)
601 await this.
Warning(
"SPF check failed.");
602 if (!await this.
BeginWrite(
"550 " + (SpfStatus.Value ??
"SPF check failed.") +
"\r\n",
null,
null))
607 await this.
Warning(
"SPF check soft-failed.");
608 if (!await this.
BeginWrite(
"550 " + (SpfStatus.Value ??
"SPF check soft-failed.") +
"\r\n",
null,
null))
613 await this.
Warning(
"SPF check neutral.");
614 if (!await this.
BeginWrite(
"550 " + (SpfStatus.Value ??
"SPF check neutral. Only senders successfully passing a SPF check will be accepted.") +
"\r\n",
null,
null))
619 await this.
Warning(
"No SPF records.");
620 if (!await this.
BeginWrite(
"550 " + (SpfStatus.Value ??
"No SPF records found.") +
"\r\n",
null,
null))
625 await this.
Error(
"SPF records contain a permanent error.");
626 if (!await this.
BeginWrite(
"550 " + (SpfStatus.Value ??
"SPF records contain a permanent error.") +
"\r\n",
null,
null))
631 await this.
Error(
"SPF records contain a temporary error.");
632 if (!await this.
BeginWrite(
"550 " + (SpfStatus.Value ??
"SPF records contain a temporary error.") +
"\r\n",
null,
null))
637 await this.
Error(
"Unable to evaluate SPF records.");
638 if (!await this.
BeginWrite(
"550 " + (SpfStatus.Value ??
"Unable to evaluate SPF records.") +
"\r\n",
null,
null))
645 Log.
Warning(
"Remote IP blocked by SPF records.", RemoteIPEndPoint.Address.ToString(),
646 new KeyValuePair<string, object>(
"Status", SpfStatus.Key),
647 new KeyValuePair<string, object>(
"Reason", SpfStatus.Value));
653 switch (RemoteIPEndPoint.Address.AddressFamily)
655 case AddressFamily.InterNetwork:
656 BlackLists = this.server.Ip4DnsBlackLists;
659 case AddressFamily.InterNetworkV6:
660 BlackLists = this.server.Ip6DnsBlackLists;
662 if ((BlackLists is
null || BlackLists.Length == 0) &&
663 !(
this.server.Ip4DnsBlackLists is
null) &&
this.server.Ip4DnsBlackLists.Length > 0)
674 if (!(BlackLists is
null))
676 List<string> A =
new List<string>();
677 int c = BlackLists.Length;
679 A.AddRange(BlackLists);
680 BlackLists =
new string[c];
689 BlackLists[j++] = A[i];
694 BlackLists = A.ToArray();
696 foreach (
string BlackList
in BlackLists)
698 await this.
Information(
"Checking DNSBL " + BlackList);
703 await this.
Information(RemoteIPEndPoint.Address.ToString() +
" not in DNSBL " + BlackList);
706 await this.
Warning(RemoteIPEndPoint.Address.ToString() +
" exists in DNSBL " + BlackList);
708 StringBuilder sb =
new StringBuilder();
710 sb.Append(
"550 Blocked by " + BlackList +
".");
712 foreach (
string s2
in Reason)
720 if (!await this.
BeginWrite(sb.ToString(),
null,
null))
724 Log.
Warning(
"Remote IP blocked by DNS Black List.", RemoteIPEndPoint.Address.ToString(), BlackList);
734 this.mailFrom = From;
735 this.mailFromDomain = Domain;
736 this.mailFromMe = !(this.account is
null) && (AccountName == this.account.
UserName) &&
737 Domain == this.server.
Domain;
739 if (!await this.
BeginWrite(
"250 2.1.0 Originator <" + this.mailFrom +
740 (this.mailFromMe ?
"> ok (you).\r\n" :
"> ok.\r\n"),
null,
null))
747 if (!await this.
BeginWrite(
"500 Syntax error\r\n",
null,
null))
758 if (!await this.
BeginWrite(
"554 : expected.\r\n",
null,
null))
763 Cmd = s.Substring(0, i).ToUpper();
764 s = s.Substring(i + 1).TrimStart();
769 if (this.mailFrom is
null)
771 if (!await this.
BeginWrite(
"503 Bad sequence of commands.\r\n",
null,
null))
776 MailAddress Recipient = this.ParseMailAddress(ref s);
778 int j = Recipient.Address.IndexOf(
'@');
781 if (!await this.
BeginWrite(
"554 @ expected.\r\n",
null,
null))
789 if (Domain == this.server.
Domain)
791 IAccount Account = await this.persistence.GetAccount(AccountName);
794 if (!await this.
BeginWrite(
"550 5.1.1 Mailbox does not exist.\r\n",
null,
null))
801 if (this.account is
null)
803 if (!await this.
BeginWrite(
"530 5.7.0 Authentication required\r\n",
null,
null))
808 if (!this.account.
HasPrivilege(SmtpServer.SmtpRelayPrivilegeID))
810 Log.
Warning(
"Failed attempt to relay SMTP message. Account does not have relay permissions.", Recipient.ToString(),
this.account.UserName,
811 new KeyValuePair<string, object>(
"From",
this.mailFrom.Address),
812 new KeyValuePair<string, object>(
"RemoteEndpoint",
this.RemoteEndpoint),
813 new KeyValuePair<string, object>(
"ClientName",
this.clientName));
815 if (!await this.
BeginWrite(
"550 5.7.1 Relaying messages using this account not permitted.\r\n",
null,
null))
822 Log.
Warning(
"Failed attempt to relay SMTP message. From Domain not white-listed.", Recipient.ToString(),
this.account.UserName,
823 new KeyValuePair<string, object>(
"From",
this.mailFrom.Address),
824 new KeyValuePair<string, object>(
"FromDomain",
this.mailFromDomain),
825 new KeyValuePair<string, object>(
"RemoteEndpoint",
this.RemoteEndpoint),
826 new KeyValuePair<string, object>(
"ClientName",
this.clientName));
828 if (!await this.
BeginWrite(
"550 5.7.1 Relaying messages from " + this.mailFromDomain +
" not allowed.\r\n",
null,
null))
834 this.messageSize += Recipient.Address.Length + Recipient.DisplayName.Length;
835 if (this.overflow || this.messageSize > this.maxMessageSize)
837 this.overflow =
true;
838 if (!await this.
BeginWrite(
"554 Message too large.\r\n",
null,
null))
843 if (this.recipients is
null)
844 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 (");
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: ");
967 if (!await this.
BeginWrite(sb.ToString(),
null,
null))
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)
1046 this.ResetState(
true);
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;
1094 Key = s.Substring(0, i);
1095 Value = s.Substring(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;
1117 string RemoteIpEndpoint;
1118 EndPoint EP = this.client.
Client.Client.RemoteEndPoint;
1120 if (EP is IPEndPoint IpEP)
1121 RemoteIpEndpoint = IpEP.Address.ToString();
1123 RemoteIpEndpoint = EP.ToString();
1134 await this.SetState(Bak);
1138 catch (AuthenticationException ex)
1140 await this.LoginFailure(ex, RemoteIpEndpoint);
1142 catch (Win32Exception ex)
1144 await this.LoginFailure(ex, RemoteIpEndpoint);
1146 catch (Exception ex)
1149 await this.ToError(
null);
1153 await this.ToError(
null);
1157 private async Task LoginFailure(Exception ex,
string RemoteIpEndpoint)
1162 await this.ToError(
null);
1165 private static string FirstRow(
string s)
1167 int i = s.IndexOfAny(CRLF);
1171 return s.Substring(0, i);
1174 private static readonly
char[] CRLF =
new char[] {
'\r',
'\n' };
1176 private MailAddress[] ParseMailAddresses(
string s)
1178 List<MailAddress> Result =
null;
1181 foreach (
string Part
in s.Split(
','))
1184 Addr = this.ParseMailAddress(ref s);
1189 Result =
new List<MailAddress>();
1194 return Result?.ToArray();
1197 private MailAddress ParseMailAddress(ref
string s)
1199 int i = s.IndexOf(
'<');
1202 if (s.IndexOf(
'@') >= 0)
1203 return new MailAddress(s.Trim());
1208 int j = s.IndexOf(
'>', i + 1);
1217 if (
string.IsNullOrEmpty(Name))
1218 return new MailAddress(Address);
1220 return new MailAddress(Address, Name);
1223 private async Task ToError(
string ClosingCommand)
1225 if (
string.IsNullOrEmpty(ClosingCommand))
1232 await this.
BeginWrite(ClosingCommand, async (Sender, e) =>
1246 public Task<bool>
BeginWrite(
string Text, EventHandlerAsync<DeliveryEventArgs> Callback,
object State)
1249 return Task.FromResult(
false);
1251 return this.client.
SendAsync(this.encoding.GetBytes(Text), async (Sender, e) =>
1254 await Callback.Raise(
this, e);
1272 this.
Add(this.server.GetSniffer(
UserName +
" IN"));
1286 this.account = Account;
1290 this.server.PersistenceLayer.
AccountLogin(this.userName, this.client.
Client.Client.RemoteEndPoint.ToString());
1309 bool BlockingBak = this.client.
Client.Client.Blocking;
1312 byte[] Temp =
new byte[1];
1314 this.client.Client.Client.Blocking =
false;
1315 this.client.
Client.Client.Send(Temp, 0, 0);
1319 catch (SocketException e)
1321 if (e.NativeErrorCode.Equals(10035))
1328 this.client.Client.Client.Blocking = BlockingBak;
1337 public void ResetState()
1339 this.ResetState(!(this.account is
null));
1344 this.mode = ReceptionMode.Command;
1345 this.mailFrom =
null;
1346 this.mailFromDomain =
null;
1347 this.recipients =
null;
1348 this.headers =
null;
1350 this.messageSize = 0;
1351 this.overflow =
false;
1356 this.mode = ReceptionMode.Command;
1357 return this.
BeginWrite(
"535 5.7.8 Authentication credentials invalid\r\n",
null,
null);
1362 this.mode = ReceptionMode.Command;
1363 return this.
BeginWrite(
"454 4.7.0 Accound disabled.\r\n",
null,
null);
1368 this.mode = ReceptionMode.Command;
1369 return this.
BeginWrite(
"503 Malformed request.\r\n",
null,
null);
1374 if (
string.IsNullOrEmpty(ChallengeBase64))
1375 return this.
BeginWrite(
"334\r\n",
null,
null);
1377 return this.
BeginWrite(
"334 " + ChallengeBase64 +
"\r\n",
null,
null);
1382 this.mode = ReceptionMode.Command;
1383 return this.
BeginWrite(
"235 2.7.0 Authentication successful\r\n",
null,
null);
1386 private async Task<string> ProcessIncomingMail(MailAddress From, MailAddress[] Recipients, KeyValuePair<string, string>[] Header,
1392 Recipients = Recipients,
1393 AllHeaders = Header,
1394 UntransformedBody = Body,
1399 List<MailAddress> To =
null;
1400 List<MailAddress> Cc =
null;
1401 List<MailAddress> Bcc =
null;
1402 List<MailAddress> ReplyTo =
null;
1403 List<KeyValuePair<string, string>> OtherHeaders =
new List<KeyValuePair<string, string>>();
1407 foreach (KeyValuePair<string, string> P
in Header)
1409 string s = P.Key.ToUpper();
1415 if (!this.AddRecipients(ref Cc, P.Value))
1416 OtherHeaders.Add(P);
1420 Message.ContentId = P.Value.Trim();
1423 case "CONTENT-LOCATION":
1424 Message.ContentLocation = P.Value.Trim();
1428 case "CONTENT-TRANSFER-ENCODING":
1429 Message.ContentTransferEncoding = P.Value.Trim();
1432 case "CONTENT-TYPE":
1433 Message.ContentType = ContentType = P.Value.Trim();
1440 OtherHeaders.Add(P);
1445 Message.FromHeader = this.ParseMailAddress(ref s);
1447 OtherHeaders.Add(P);
1451 switch (P.Value.ToLower())
1454 Message.Priority = Priority.High;
1458 Message.Priority = Priority.Normal;
1462 Message.Priority = Priority.Low;
1466 OtherHeaders.Add(P);
1472 Message.MessageID = P.Value;
1475 case "MIME-VERSION":
1477 Message.MimeVersion = d;
1479 OtherHeaders.Add(P);
1483 switch (P.Value.ToLower())
1486 Message.Priority = Priority.High;
1490 Message.Priority = Priority.Normal;
1494 Message.Priority = Priority.Low;
1498 OtherHeaders.Add(P);
1504 if (!this.AddRecipients(ref ReplyTo, P.Value))
1505 OtherHeaders.Add(P);
1510 Message.Sender = this.ParseMailAddress(ref s);
1511 if (Message.
Sender is
null)
1512 OtherHeaders.Add(P);
1516 Message.Subject = P.Value;
1520 if (!this.AddRecipients(ref To, P.Value))
1521 OtherHeaders.Add(P);
1528 s = s.Substring(0, i);
1530 if (
int.TryParse(s.Trim(), out i))
1531 Message.Priority = (Priority)i;
1533 OtherHeaders.Add(P);
1537 OtherHeaders.Add(P);
1543 Message.TransformedBody =
null;
1547 throw new NotSupportedException(
"Content-Transfer-Encoding not supported: " + Message.
ContentTransferEncoding);
1549 Message.TransformedBody = ToDecode;
1552 KeyValuePair<string, string>[] ContentTypeFields =
null;
1555 if (!
string.IsNullOrEmpty(ContentType))
1565 foreach (KeyValuePair<string, string> P
in ContentTypeFields)
1567 if (
string.Compare(P.Key,
"charset",
true) == 0)
1569 Message.ContentTypeEncoding = Encoding.GetEncoding(P.Value);
1583 Message.
ContentTypeEncoding, ContentTypeFields ??
new KeyValuePair<string, string>[0], BaseUri);
1585 catch (Exception ex)
1589 string Path = System.IO.Path.Combine(AppDataFolder,
"SMTP");
1590 if (!Directory.Exists(Path))
1591 Directory.CreateDirectory(Path);
1593 string LocalId = Guid.NewGuid().ToString();
1594 Path = System.IO.Path.Combine(Path, LocalId);
1600 throw new Exception(
"Error when decoding message. Content stored under " + LocalId +
"[.bin][.txt][.err].");
1603 ExceptionDispatchInfo.Capture(ex).Throw();
1608 List<EmbeddedContent> InlineObjects =
null;
1609 List<EmbeddedContent> Attachments =
null;
1621 if (InlineObjects is
null)
1622 InlineObjects =
new List<EmbeddedContent>();
1624 InlineObjects.Add(Object);
1628 if (Attachments is
null)
1629 Attachments =
new List<EmbeddedContent>();
1631 Attachments.Add(Object);
1638 if (InlineObjects is
null)
1639 InlineObjects =
new List<EmbeddedContent>();
1641 InlineObjects.Insert(0, First);
1646 if (Attachments is
null)
1647 Attachments =
new List<EmbeddedContent>();
1649 Attachments.Insert(0, First);
1654 if (Attachments is
null)
1655 Attachments =
new List<EmbeddedContent>();
1657 Attachments.Add(Object);
1665 Message.InlineObjects = InlineObjects?.ToArray();
1666 Message.Attachments = Attachments?.ToArray();
1667 Message.DecodedBody = First.
Decoded;
1671 foreach (MailAddress Addr
in Message.
Recipients)
1677 foreach (MailAddress A
in To)
1679 if (Addr.Address == A.Address)
1692 foreach (MailAddress A
in Cc)
1694 if (Addr.Address == A.Address)
1706 Bcc =
new List<MailAddress>();
1711 Message.UnparsedHeaders = OtherHeaders.ToArray();
1712 Message.To = To?.ToArray();
1713 Message.Cc = Cc?.ToArray();
1714 Message.Bcc = Bcc?.ToArray();
1715 Message.ReplyTo = ReplyTo?.ToArray();
1717 if (Message.
Sender is
null)
1720 if (!Message.
Date.HasValue)
1721 Message.Date = DateTimeOffset.Now;
1724 Message.MessageID = Guid.NewGuid().ToString();
1726 await this.server.ProcessMessage(Message);
1731 private bool AddRecipients(ref List<MailAddress> AddressList,
string s)
1733 if (AddressList is
null)
1734 AddressList =
new List<MailAddress>();
1736 MailAddress[] Addresses = this.ParseMailAddresses(s);
1737 if (Addresses is
null)
1741 AddressList.AddRange(Addresses);
1746 internal static int Next(
int MaxValue)
1750 return rnd.Next(MaxValue);
Helps with parsing of commong data types.
static KeyValuePair< string, string >[] ParseFieldValues(string Value)
Parses a set of comma or semicolon-separated field values, optionaly delimited by ' or " characters.
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.
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.
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.
Static class managing loading of resources stored as embedded resources or in content files.
static Task WriteAllBytesAsync(string FileName, byte[] Data)
Creates a binary file asynchronously.
static Task WriteAllTextAsync(string FileName, string Text)
Creates a text file asynchronously.
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 Exception(Exception Exception, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, params KeyValuePair< string, object >[] Tags)
Logs an exception. Event type will be determined by the severity of the exception.
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.
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.
Task Error(string Error)
Called to inform the viewer of an error state.
Task Exception(Exception Exception)
Called to inform the viewer of an exception state.
virtual bool Remove(ISniffer Sniffer)
ICommunicationLayer.Remove
ISniffer[] Sniffers
Registered sniffers.
Task Information(string Comment)
Called to inform the viewer of something.
Task TransmitText(string Text)
Called when text has been transmitted.
Task ReceiveText(string Text)
Called when text has been received.
Task ReceiveBinary(byte[] Data)
Called when binary data has been received.
Task Warning(string Warning)
Called to inform the viewer of a warning state.
virtual void Add(ISniffer Sniffer)
ICommunicationLayer.Add
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.
string ClientName
Name of client.
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.
SmtpServer Server
SMTP Server serving the client.
string AuthId
ID client claims to have
async void Dispose()
IDisposable.Dispose
Task< bool > SaslErrorNotAuthorized()
Reports the SASL error: Not Authorized
async Task SetUserIdentity(CaseInsensitiveString UserName)
Sets the identity of the user.
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.
async Task ReplayAsync(CommunicationLayer ComLayer)
Replays sniffer events.
Represents a case-insensitive string.
static readonly CaseInsensitiveString Empty
Empty case-insensitive string
CaseInsensitiveString TrimStart(params char[] trimChars)
Removes all leading occurrences of a set of characters specified in an array from the current System....
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 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.
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 async Task ReportTlsHackAttempt(string RemoteEndpoint, string Message, string Protocol)
Reports a TLS hacking attempt from an endpoint. Can be used to deny TLS negotiation to proceed,...
static bool CanStartTls(string RemoteEndpoint)
Checks if TLS negotiation can start, for a given endpoint. If the endpoint has tries a TLS hack attem...
Login state information relating to a remote endpoint
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 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.
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.