Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
SmtpClientConnection.cs
1using System;
3using System.ComponentModel;
4using System.IO;
5using System.Net;
6using System.Net.Mail;
9using System.Runtime.ExceptionServices;
10using System.Security.Authentication;
11using System.Security.Cryptography.X509Certificates;
12using System.Text;
13using System.Threading.Tasks;
14using Waher.Content;
17using Waher.Events;
24using Waher.Security;
27
29{
30 internal enum ReceptionMode
31 {
32 Command,
33 MailHeader,
34 MailBody,
35 MailBodyCR,
36 MailBodyCRLF,
37 MailBodyCRLFPeriod,
38 MailBodyCRLFPeriodCR,
39 ChallengeResponse
40 };
41
45 public enum Priority
46 {
50 High = 1,
51
55 Normal = 3,
56
60 Low = 5
61 }
62
67 {
68 private static readonly Random rnd = new Random();
69
70 private readonly Guid id = Guid.NewGuid();
71 private readonly UTF8Encoding encoding = new UTF8Encoding(false, false);
72 private SmtpServer server;
73 private BinaryTcpClient client;
74 private readonly ISaslPersistenceLayer persistence;
75 private IAccount account = null;
76 private bool disposed = false;
77 private SmtpConnectionState state;
79 private object tag = null;
80 private string clientName = null;
81 private string authId = null;
82 private MailAddress mailFrom = null;
83 private CaseInsensitiveString mailFromDomain = 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;
89 private IAuthenticationMechanism mechanism = null;
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;
96
106 ISaslPersistenceLayer Persistence, int MaxMessageSize, params ISniffer[] Sniffers)
107 : base(false, Sniffers)
108 {
109 this.state = SmtpConnectionState.Initiating;
110 this.client = Client;
111 this.server = Server;
112 this.persistence = Persistence;
113 this.maxMessageSize = MaxMessageSize;
114
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;
119 }
120
124 public Guid ID => this.id;
125
129 public string ClientName => this.clientName;
130
134 public string AuthId => this.authId;
135
139 public SmtpConnectionState State => this.state;
140
144 internal async Task SetState(SmtpConnectionState NewState)
145 {
146 if (this.state != NewState && !this.disposed)
147 {
148 this.state = NewState;
149
150 this.Information("State changed to " + NewState.ToString());
151
152 await this.OnStateChanged.Raise(this, NewState);
153 }
154 }
155
159 public event EventHandlerAsync<SmtpConnectionState> OnStateChanged = null;
160
164 public object Tag
165 {
166 get => this.tag;
167 set => this.tag = value;
168 }
169
173 public SmtpServer Server => this.server;
174
178 public CaseInsensitiveString UserName => this.userName;
179
183 public string RemoteEndPoint => this.client.RemoteEndPoint;
184
188 public string Protocol => "SMTP";
189
190 internal X509Certificate ClientCertificate => this.client.RemoteCertificate;
191 internal bool ClientCertificateValid => this.client.RemoteCertificateValid;
192
196 [Obsolete("Use the DisposeAsync() method.")]
197 public void Dispose()
198 {
199 this.DisposeAsync().Wait();
200 }
201
205 public async Task DisposeAsync()
206 {
207 if (!this.disposed)
208 {
209 if (this.state != SmtpConnectionState.Error)
210 await this.SetState(SmtpConnectionState.Offline);
211
212 ISniffer[] Sniffers = this.Sniffers;
213 if (!(Sniffers is null) && !(this.server is null))
214 await this.server.CacheSniffers(Sniffers);
215
216 this.disposed = true;
217 this.server = null;
218
219 this.client?.DisposeWhenDone();
220 this.client = null;
221 }
222 }
223
224 private async Task<bool> Client_OnReceived(object Sender, bool ConstantBuffer, byte[] Buffer, int Offset, int Count)
225 {
226 try
227 {
228 return await this.ParseIncoming(ConstantBuffer, Buffer, Offset, Count);
229 }
230 catch (Exception ex)
231 {
232 if (!this.disposed)
233 {
234 this.Exception(ex);
235 await this.DisposeAsync();
236 }
237
238 return false;
239 }
240 }
241
242 private async Task Client_OnError(object Sender, Exception Exception)
243 {
244 await this.SetState(SmtpConnectionState.Error);
245 await this.DisposeAsync();
246 }
247
248 private Task Client_OnDisconnected(object Sender, EventArgs e)
249 {
250 return this.DisposeAsync();
251 }
252
253 private async Task<bool> ParseIncoming(bool ConstantBuffer, byte[] Data, int Offset, int NrRead)
254 {
255 int End = Offset + NrRead;
256 byte b;
257
258 while (Offset < End)
259 {
260 b = Data[Offset++];
261
262 switch (this.mode)
263 {
264 case ReceptionMode.MailBody:
265 if (b == 13)
266 this.mode = ReceptionMode.MailBodyCR;
267 else
268 {
269 this.messageSize++;
270 if (this.overflow || this.messageSize > this.maxMessageSize)
271 {
272 this.overflow = true;
273 break;
274 }
275
276 this.body.WriteByte(b);
277 }
278 break;
279
280 case ReceptionMode.MailBodyCR:
281 if (b == 10)
282 this.mode = ReceptionMode.MailBodyCRLF;
283 else if (b == 13)
284 {
285 this.messageSize++;
286 if (this.overflow || this.messageSize > this.maxMessageSize)
287 {
288 this.overflow = true;
289 break;
290 }
291
292 this.body.WriteByte(13);
293 }
294 else
295 {
296 this.messageSize += 2;
297 if (this.overflow || this.messageSize > this.maxMessageSize)
298 {
299 this.overflow = true;
300 break;
301 }
302
303 this.body.WriteByte(13);
304 this.body.WriteByte(b);
305 this.mode = ReceptionMode.MailBody;
306 }
307 break;
308
309 case ReceptionMode.MailBodyCRLF:
310 if (b == (byte)'.')
311 this.mode = ReceptionMode.MailBodyCRLFPeriod;
312 else if (b == 13)
313 {
314 this.messageSize += 2;
315 if (this.overflow || this.messageSize > this.maxMessageSize)
316 {
317 this.overflow = true;
318 break;
319 }
320
321 this.body.WriteByte(13);
322 this.body.WriteByte(10);
323 this.mode = ReceptionMode.MailBodyCR;
324 }
325 else
326 {
327 this.messageSize += 3;
328 if (this.overflow || this.messageSize > this.maxMessageSize)
329 {
330 this.overflow = true;
331 break;
332 }
333
334 this.body.WriteByte(13);
335 this.body.WriteByte(10);
336 this.body.WriteByte(b);
337 this.mode = ReceptionMode.MailBody;
338 }
339 break;
340
341 case ReceptionMode.MailBodyCRLFPeriod:
342 if (b == 13)
343 this.mode = ReceptionMode.MailBodyCRLFPeriodCR;
344 else
345 {
346 this.messageSize += 3;
347 if (this.overflow || this.messageSize > this.maxMessageSize)
348 {
349 this.overflow = true;
350 break;
351 }
352
353 this.body.WriteByte(13);
354 this.body.WriteByte(10);
355 this.body.WriteByte(b);
356 this.mode = ReceptionMode.MailBody;
357 }
358 break;
359
360 case ReceptionMode.MailBodyCRLFPeriodCR:
361 if (b == 10)
362 {
363 if (this.overflow)
364 {
365 if (!await this.BeginWrite("552 Requested mail action aborted: exceeded storage allocation.\r\n", null, null))
366 return false;
367 }
368 else
369 {
370 try
371 {
372 byte[] Body = this.body.ToArray();
373 this.ReceiveBinary(true, Body);
374
375 string Id = await this.ProcessIncomingMail(this.mailFrom, this.recipients.ToArray(),
376 this.headers.ToArray(), Body, this.clientName, this.RemoteEndPoint);
377
378 if (!await this.BeginWrite("250 2.6.0 " + Id.ToString() + " Message accepted for delivery.\r\n", null, null))
379 return false;
380 }
381 catch (Exception ex)
382 {
383 this.Error(ex.Message + "\r\n\r\n" + Log.CleanStackTrace(ex.StackTrace));
384 if (!await this.BeginWrite("554 Unable to parse incoming message: " + FirstRow(ex.Message) + "\r\n", null, null))
385 return false;
386 }
387 }
388
389 this.ResetState();
390 }
391 else if (b == 13)
392 {
393 this.messageSize += 3;
394 if (this.overflow || this.messageSize > this.maxMessageSize)
395 {
396 this.overflow = true;
397 break;
398 }
399
400 this.body.WriteByte(13);
401 this.body.WriteByte(10);
402 this.body.WriteByte(13);
403 this.mode = ReceptionMode.MailBodyCR;
404 }
405 else
406 {
407 this.messageSize += 4;
408 if (this.overflow || this.messageSize > this.maxMessageSize)
409 {
410 this.overflow = true;
411 break;
412 }
413
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;
419 }
420 break;
421
422 default:
423 if (b == 13) // CR
424 break;
425 else if (b == 10) // LF
426 {
427 string Row = Encoding.UTF8.GetString(this.incoming.ToArray());
428 this.incoming.Position = 0;
429 this.incoming.SetLength(0);
430 this.incomingSize = 0;
431
432 this.ReceiveText(Row);
433 if (!await this.ParseIncomingRow(Row))
434 return false;
435
436 if (this.mode == ReceptionMode.MailBody)
437 break;
438 }
439 else
440 {
441 this.incoming.WriteByte(b);
442 this.incomingSize++;
443
444 if (this.incomingSize > this.maxMessageSize)
445 {
446 await this.BeginWrite("552 Requested mail action aborted: exceeded storage allocation.\r\n", async (Sender, e) =>
447 {
448 await this.SetState(SmtpConnectionState.Offline);
449 this.server.Closed(this);
450 }, null);
451 return false;
452 }
453 }
454 break;
455 }
456 }
457
458 return true;
459 }
460
461 private async Task<bool> ParseIncomingRow(string s)
462 {
463 switch (this.mode)
464 {
465 case ReceptionMode.Command:
466 int i = s.IndexOf(' ');
467 string Cmd, Value;
468
469 if (i < 0)
470 {
471 Cmd = s;
472 Value = string.Empty;
473 }
474 else
475 {
476 Cmd = s[..i];
477 Value = s[(i + 1)..];
478 }
479
480 switch (Cmd.ToUpper())
481 {
482 case "HELO":
483 case "EHLO":
484 if (this.clientName is null)
485 {
486 this.clientName = Value.Trim();
487 await this.SetState(SmtpConnectionState.Greeting);
488 this.SetUserIdentity(this.clientName);
489 }
490
491 StringBuilder Response = new StringBuilder();
492
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());
501
502 if (!(this.server.ServerCertificate is null) && !(this.client.Stream is SslStream))
503 Response.Append("\r\n250-STARTTLS");
504
505 SslStream SslStream;
506
507 if (this.account is null)
508 {
509 Response.Append("\r\n250-AUTH");
510
511 if (!(this.ClientCertificate is null) && this.ClientCertificateValid)
512 Response.Append(" EXTERNAL");
513
514 // TODO: EXTERNAL authentication mechanism
515
516 SslStream = this.client.Stream as SslStream;
518 {
519 if (M.Allowed(SslStream))
520 {
521 Response.Append(' ');
522 Response.Append(M.Name);
523 }
524 }
525 }
526
527 Response.Append("\r\n250-SMTPUTF8\r\n250-8BITMIME\r\n250-ENHANCEDSTATUSCODES\r\n250 HELP\r\n");
528
529 if (!await this.BeginWrite(Response.ToString(), null, null))
530 return false;
531
532 break;
533
534 case "MAIL":
535 s = Value.Trim();
536 i = s.IndexOf(':');
537 if (i < 0)
538 {
539 if (!await this.BeginWrite("554 : expected.\r\n", null, null))
540 return false;
541 break;
542 }
543
544 Cmd = s[..i].ToUpper();
545 s = s[(i + 1)..].TrimStart();
546
547 switch (Cmd)
548 {
549 case "FROM":
550 MailAddress From = this.ParseMailAddress(ref s);
551
552 int j = From.Address.IndexOf('@');
553 if (j < 0)
554 {
555 if (!await this.BeginWrite("554 @ expected\r\n", null, null))
556 return false;
557 break;
558 }
559
560 CaseInsensitiveString Domain = From.Address[(j + 1)..];
561 CaseInsensitiveString AccountName = From.Address[..j];
562
563 if (Domain == this.server.Domain)
564 {
565 if (this.account is null)
566 {
567 if (!await this.BeginWrite("530 5.7.0 Authentication required.\r\n", null, null))
568 return false;
569 break;
570 }
571
572 if (AccountName != this.account.UserName)
573 {
574 if (!await this.BeginWrite("555 Sender must be the same as the authenticated user.\r\n", null, null))
575 return false;
576 break;
577 }
578 }
579 else
580 {
581 if (!(this.client.Client.Client.RemoteEndPoint is IPEndPoint RemoteIPEndPoint))
582 {
583 if (!await this.BeginWrite("550 Invalid remote address.\r\n", null, null))
584 return false;
585 break;
586 }
587
588 this.Information("Checking SPF records.");
589
590 KeyValuePair<SpfResult, string> SpfStatus = await SpfResolver.CheckHost(RemoteIPEndPoint.Address,
591 Domain, From.Address, this.clientName, this.server.Domain, this.server.SpfExpressions);
592
593 bool Accept = false;
594
595 switch (SpfStatus.Key)
596 {
597 case SpfResult.Pass:
598 this.Information("SPF check passed.");
599 Accept = true;
600 break;
601
602 case SpfResult.Fail:
603 this.Warning("SPF check failed.");
604 if (!await this.BeginWrite("550 " + (SpfStatus.Value ?? "SPF check failed.") + "\r\n", null, null))
605 return false;
606 break;
607
608 case SpfResult.SoftFail:
609 this.Warning("SPF check soft-failed.");
610 if (!await this.BeginWrite("550 " + (SpfStatus.Value ?? "SPF check soft-failed.") + "\r\n", null, null))
611 return false;
612 break;
613
614 case SpfResult.Neutral:
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))
617 return false;
618 break;
619
620 case SpfResult.None:
621 this.Warning("No SPF records.");
622 if (!await this.BeginWrite("550 " + (SpfStatus.Value ?? "No SPF records found.") + "\r\n", null, null))
623 return false;
624 break;
625
626 case SpfResult.PermanentError:
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))
629 return false;
630 break;
631
632 case SpfResult.TemporaryError:
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))
635 return false;
636 break;
637
638 default:
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))
641 return false;
642 break;
643 }
644
645 if (!Accept)
646 {
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));
650 break;
651 }
652
653 string[] BlackLists;
654
655 switch (RemoteIPEndPoint.Address.AddressFamily)
656 {
657 case AddressFamily.InterNetwork:
658 BlackLists = this.server.Ip4DnsBlackLists;
659 break;
660
661 case AddressFamily.InterNetworkV6:
662 BlackLists = this.server.Ip6DnsBlackLists;
663
664 if ((BlackLists is null || BlackLists.Length == 0) &&
665 !(this.server.Ip4DnsBlackLists is null) && this.server.Ip4DnsBlackLists.Length > 0)
666 {
667 BlackLists = null;
668 }
669 break;
670
671 default:
672 BlackLists = null;
673 break;
674 }
675
676 if (!(BlackLists is null))
677 {
678 List<string> A = new List<string>();
679 int c = BlackLists.Length;
680
681 A.AddRange(BlackLists);
682 BlackLists = new string[c];
683
684 lock (rnd)
685 {
686 j = 0;
687
688 while (c > 0)
689 {
690 i = rnd.Next(c--);
691 BlackLists[j++] = A[i];
692 A.RemoveAt(i);
693 }
694 }
695
696 BlackLists = A.ToArray();
697
698 foreach (string BlackList in BlackLists)
699 {
700 this.Information("Checking DNSBL " + BlackList);
701
702 string[] Reason = await DnsResolver.LookupBlackList(RemoteIPEndPoint.Address, BlackList);
703
704 if (Reason is null)
705 this.Information(RemoteIPEndPoint.Address.ToString() + " not in DNSBL " + BlackList);
706 else
707 {
708 this.Warning(RemoteIPEndPoint.Address.ToString() + " exists in DNSBL " + BlackList);
709
710 StringBuilder sb = new StringBuilder();
711
712 sb.Append("550 Blocked by " + BlackList + ".");
713
714 foreach (string s2 in Reason)
715 {
716 sb.Append(' ');
717 sb.Append(s2);
718 }
719
720 sb.Append("\r\n");
721
722 if (!await this.BeginWrite(sb.ToString(), null, null))
723 return false;
724 Accept = false;
725
726 Log.Warning("Remote IP blocked by DNS Black List.", RemoteIPEndPoint.Address.ToString(), BlackList);
727 break;
728 }
729 }
730
731 if (!Accept)
732 break;
733 }
734 }
735
736 this.mailFrom = From;
737 this.mailFromDomain = Domain;
738 this.mailFromMe = !(this.account is null) && (AccountName == this.account.UserName) &&
739 Domain == this.server.Domain;
740
741 if (!await this.BeginWrite("250 2.1.0 Originator <" + this.mailFrom +
742 (this.mailFromMe ? "> ok (you).\r\n" : "> ok.\r\n"), null, null))
743 {
744 return false;
745 }
746 break;
747
748 default:
749 if (!await this.BeginWrite("500 Syntax error\r\n", null, null))
750 return false;
751 break;
752 }
753 break;
754
755 case "RCPT":
756 s = Value.Trim();
757 i = s.IndexOf(':');
758 if (i < 0)
759 {
760 if (!await this.BeginWrite("554 : expected.\r\n", null, null))
761 return false;
762 break;
763 }
764
765 Cmd = s[..i].ToUpper();
766 s = s[(i + 1)..].TrimStart();
767
768 switch (Cmd)
769 {
770 case "TO":
771 if (this.mailFrom is null)
772 {
773 if (!await this.BeginWrite("503 Bad sequence of commands.\r\n", null, null))
774 return false;
775 break;
776 }
777
778 MailAddress Recipient = this.ParseMailAddress(ref s);
779
780 int j = Recipient.Address.IndexOf('@');
781 if (j < 0)
782 {
783 if (!await this.BeginWrite("554 @ expected.\r\n", null, null))
784 return false;
785 break;
786 }
787
788 CaseInsensitiveString Domain = Recipient.Address[(j + 1)..];
789 CaseInsensitiveString AccountName = Recipient.Address[..j];
790
791 if (Domain == this.server.Domain)
792 {
793 IAccount Account = await this.persistence.GetAccount(AccountName);
794 if (Account is null)
795 {
796 if (!await this.BeginWrite("550 5.1.1 Mailbox does not exist.\r\n", null, null))
797 return false;
798 break;
799 }
800 }
801 else
802 {
803 if (this.account is null)
804 {
805 if (!await this.BeginWrite("530 5.7.0 Authentication required\r\n", null, null))
806 return false;
807 break;
808 }
809
810 if (!this.account.HasPrivilege(SmtpServer.SmtpRelayPrivilegeID))
811 {
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));
816
817 if (!await this.BeginWrite("550 5.7.1 Relaying messages using this account not permitted.\r\n", null, null))
818 return false;
819 break;
820 }
821
822 if (!(this.mailFromMe || this.server.CanRelayForDomain(this.mailFromDomain)))
823 {
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));
829
830 if (!await this.BeginWrite("550 5.7.1 Relaying messages from " + this.mailFromDomain + " not allowed.\r\n", null, null))
831 return false;
832 break;
833 }
834 }
835
836 this.messageSize += Recipient.Address.Length + Recipient.DisplayName.Length;
837 if (this.overflow || this.messageSize > this.maxMessageSize)
838 {
839 this.overflow = true;
840 if (!await this.BeginWrite("554 Message too large.\r\n", null, null))
841 return false;
842 break;
843 }
844
845 this.recipients ??= new List<MailAddress>();
846 this.recipients.Add(Recipient);
847
848 if (!await this.BeginWrite("250 2.1.5 Recipient <" + Recipient + "> ok.\r\n", null, null))
849 return false;
850 break;
851
852 default:
853 if (!await this.BeginWrite("500 Syntax error\r\n", null, null))
854 return false;
855 break;
856 }
857 break;
858
859 case "DATA":
860 if (this.overflow)
861 {
862 if (!await this.BeginWrite("552 Requested mail action aborted: exceeded storage allocation.\r\n", null, null))
863 return false;
864 break;
865 }
866
867 if (this.mailFrom is null || this.recipients is null || this.recipients.Count == 0)
868 {
869 if (!await this.BeginWrite("503 Bad sequence of commands.\r\n", null, null))
870 return false;
871 break;
872 }
873
874 this.headers = new List<KeyValuePair<string, string>>();
875 this.body = new MemoryStream();
876 this.messageSize = 0;
877 this.mode = ReceptionMode.MailHeader;
878
879 if (!await this.BeginWrite("354 Enter message body, end with \".\" on a line by itself.\r\n", null, null))
880 return false;
881 break;
882
883 case "QUIT":
884 await this.SetState(SmtpConnectionState.Closing);
885
886 await this.BeginWrite("221 2.0.0 " + this.server.Domain + " closing connection.\r\n", async (Sender, e) =>
887 {
888 await this.SetState(SmtpConnectionState.Offline);
889 this.server.Closed(this);
890
891 }, null);
892 break;
893
894 case "VRFY":
895 case "EXPN":
896 if (!await this.BeginWrite("252 Command disabled.\r\n", null, null))
897 return false;
898 break;
899
900 case "RSET":
901 this.ResetState();
902 if (!await this.BeginWrite("250 OK\r\n", null, null))
903 return false;
904 break;
905
906 case "HELP":
907 if (!await this.BeginWrite("214 " + typeof(SmtpServer).Namespace + " mail-server.\r\n", null, null))
908 return false;
909 break;
910
911 case "NOOP":
912 if (!await this.BeginWrite("250 OK\r\n", null, null))
913 return false;
914 break;
915
916 case "AUTH":
917 if (!(this.account is null))
918 {
919 if (!await this.BeginWrite("503 5.5.4 Already authenticated.\r\n", null, null))
920 return false;
921 break;
922 }
923
924 LoginAuditor Auditor = this.persistence.Auditor;
925 DateTime? Next;
926
927 if (Auditor is null)
928 Next = null;
929 else
930 Next = await Auditor.GetEarliestLoginOpportunity(this.RemoteEndPoint, "SMTP");
931
932 if (Next.HasValue)
933 {
934 StringBuilder sb = new StringBuilder();
935 DateTime TP = Next.Value;
936 DateTime Today = DateTime.Today;
937
938 sb.Append("550 5.7.26 ");
939
940 if (Next.Value == DateTime.MaxValue)
941 {
942 sb.Append("This endpoint (");
943 sb.Append(this.RemoteEndPoint);
944 sb.Append(") has been blocked from the system");
945 }
946 else
947 {
948 sb.Append("Too many failed login attempts in a row registered. Try again after ");
949 sb.Append(TP.ToLongTimeString());
950
951 if (TP.Date != Today)
952 {
953 if (TP.Date == Today.AddDays(1))
954 sb.Append(" tomorrow");
955 else
956 {
957 sb.Append(", ");
958 sb.Append(TP.ToShortDateString());
959 }
960 }
961 }
962
963 sb.Append(". Remote Endpoint: ");
964 sb.Append(this.RemoteEndPoint);
965 sb.Append("\r\n");
966
967 if (!await this.BeginWrite(sb.ToString(), null, null))
968 return false;
969 break;
970 }
971
972 await this.SetState(SmtpConnectionState.Authenticating);
973 s = Value.Trim();
974
976
977 i = s.IndexOf(' ');
978 if (i < 0)
979 {
980 Name = s;
981 s = string.Empty;
982 }
983 else
984 {
985 Name = s[..i];
986 s = s[(i + 1)..].TrimStart();
987 }
988
989 this.mechanism = null;
990
992 {
993 if (M.Name == Name)
994 {
995 this.mechanism = M;
996 break;
997 }
998 }
999
1000 if (this.mechanism is null)
1001 {
1002 if (!await this.BeginWrite("503 5.5.4 Invalid authentication mechanism.\r\n", null, null))
1003 return false;
1004 break;
1005 }
1006
1007 SslStream = this.client.Stream as SslStream;
1008 if (!this.mechanism.Allowed(SslStream))
1009 {
1010 this.mechanism = null;
1011
1012 if (SslStream is null)
1013 {
1014 if (!await this.BeginWrite("538 5.7.11 Encryption required for requested authentication mechanism\r\n", null, null))
1015 return false;
1016 }
1017 else
1018 {
1019 if (!await this.BeginWrite("534 5.7.9 Authentication mechanism is too weak\r\n", null, null))
1020 return false;
1021 }
1022
1023 break;
1024 }
1025
1026 this.mode = ReceptionMode.ChallengeResponse;
1027 await this.mechanism.AuthenticationRequest(s, this, this.persistence);
1028 break;
1029
1030 case "STARTTLS":
1031 if (await this.BeginWrite("220 Go ahead\r\n", null, null))
1032 this.upgradeToTls = true;
1033 return false;
1034
1035 default:
1036 if (!await this.BeginWrite("502 Command not implemented\r\n", null, null))
1037 return false;
1038 break;
1039 }
1040 break;
1041
1042 case ReceptionMode.ChallengeResponse:
1043 bool? AuthResult = await this.mechanism.ResponseRequest(s, this, this.persistence);
1044 if (AuthResult.HasValue)
1045 {
1046 this.ResetState(true);
1047
1048 if (AuthResult.Value)
1049 {
1050 if (!await this.SaslSuccess(null))
1051 return false;
1052 }
1053 else
1054 {
1055 if (!await this.SaslErrorNotAuthorized())
1056 return false;
1057 }
1058
1059 this.mode = ReceptionMode.Command;
1060 }
1061 break;
1062
1063 case ReceptionMode.MailHeader:
1064 if (string.IsNullOrEmpty(s))
1065 this.mode = ReceptionMode.MailBody;
1066 else
1067 {
1068 this.messageSize += s.Length;
1069 if (this.overflow || this.messageSize > this.maxMessageSize)
1070 {
1071 this.overflow = true;
1072 break;
1073 }
1074
1075 int c;
1076
1077 if (char.IsWhiteSpace(s[0]) && (c = this.headers.Count) > 0)
1078 {
1079 KeyValuePair<string, string> P = this.headers[c - 1];
1080 this.headers[c - 1] = new KeyValuePair<string, string>(P.Key, P.Value + s);
1081 }
1082 else
1083 {
1084 string Key;
1085
1086 i = s.IndexOf(':');
1087 if (i < 0)
1088 {
1089 Key = s;
1090 Value = string.Empty;
1091 }
1092 else
1093 {
1094 Key = s[..i];
1095 Value = s[(i + 1)..].Trim();
1096 }
1097
1098 i = s.Length;
1099
1100 Key = Key.ToUpper();
1101
1102 this.headers.Add(new KeyValuePair<string, string>(Key, Value));
1103 }
1104 }
1105 break;
1106 }
1107
1108 return true;
1109 }
1110
1111 private async Task Client_OnPaused(object Sender, EventArgs e)
1112 {
1113 if (this.upgradeToTls)
1114 {
1115 this.upgradeToTls = false;
1116
1117 string RemoteEndPoint = this.client.RemoteEndPoint.RemovePortNumber();
1118
1120 {
1121 try
1122 {
1123 SmtpConnectionState Bak = this.state;
1124 await this.SetState(SmtpConnectionState.StartingEncryption);
1125
1126 await this.client.UpgradeToTlsAsServer(this.server.ServerCertificate, Crypto.SecureTls, ClientCertificates.Optional);
1127
1128 await this.SetState(Bak);
1129
1130 this.client.Continue();
1131 }
1132 catch (AuthenticationException ex)
1133 {
1134 await this.LoginFailure(ex, RemoteEndPoint);
1135 }
1136 catch (Win32Exception ex)
1137 {
1138 await this.LoginFailure(ex, RemoteEndPoint);
1139 }
1140 catch (Exception ex)
1141 {
1142 this.Exception(ex);
1143 await this.ToError(null);
1144 }
1145 }
1146 else
1147 await this.ToError(null);
1148 }
1149 }
1150
1151 private async Task LoginFailure(Exception ex, string RemoteIpEndpoint)
1152 {
1153 Exception ex2 = Log.UnnestException(ex);
1154 LoginAuditor.ReportTlsHackAttempt(RemoteIpEndpoint, "TLS handshake failed: " + ex2.Message, "SMTP");
1155
1156 await this.ToError(null);
1157 }
1158
1159 private static string FirstRow(string s)
1160 {
1161 int i = s.IndexOfAny(CRLF);
1162 if (i < 0)
1163 return s;
1164 else
1165 return s[..i];
1166 }
1167
1168 private static readonly char[] CRLF = new char[] { '\r', '\n' };
1169
1170 private MailAddress[] ParseMailAddresses(string s)
1171 {
1172 List<MailAddress> Result = null;
1173 MailAddress Addr;
1174
1175 foreach (string Part in s.Split(','))
1176 {
1177 s = Part;
1178 Addr = this.ParseMailAddress(ref s);
1179 if (Addr is null)
1180 continue;
1181
1182 Result ??= new List<MailAddress>();
1183 Result.Add(Addr);
1184 }
1185
1186 return Result?.ToArray();
1187 }
1188
1189 private MailAddress ParseMailAddress(ref string s)
1190 {
1191 int i = s.IndexOf('<');
1192 if (i < 0)
1193 {
1194 if (s.IndexOf('@') >= 0)
1195 return new MailAddress(s.Trim());
1196 else
1197 return null;
1198 }
1199
1200 int j = s.IndexOf('>', i + 1);
1201 if (j < 0)
1202 return null;
1203
1204 string Name = s[..i].Trim();
1205 string Address = s.Substring(i + 1, j - i - 1).Trim();
1206
1207 s = s[(j + 1)..].TrimStart();
1208
1209 if (string.IsNullOrEmpty(Name))
1210 return new MailAddress(Address);
1211 else
1212 return new MailAddress(Address, Name);
1213 }
1214
1215 private async Task ToError(string ClosingCommand)
1216 {
1217 if (string.IsNullOrEmpty(ClosingCommand))
1218 {
1219 await this.SetState(SmtpConnectionState.Error);
1220 await this.DisposeAsync();
1221 }
1222 else
1223 {
1224 await this.BeginWrite(ClosingCommand, async (Sender, e) =>
1225 {
1226 await this.SetState(SmtpConnectionState.Error);
1227 await this.DisposeAsync();
1228 }, null);
1229 }
1230 }
1231
1238 public Task<bool> BeginWrite(string Text, EventHandlerAsync<DeliveryEventArgs> Callback, object State)
1239 {
1240 if (this.disposed)
1241 return Task.FromResult(false);
1242
1243 return this.client.SendAsync(true, this.encoding.GetBytes(Text), async (Sender, e) =>
1244 {
1245 this.TransmitText(Text);
1246
1247 if (!(Callback is null))
1248 await Callback.Raise(this, e);
1249 }, State);
1250 }
1251
1257 {
1259
1260 foreach (ISniffer Sniffer in this.Sniffers)
1261 {
1262 InMemorySniffer = Sniffer as InMemorySniffer;
1263 if (!(InMemorySniffer is null))
1264 break;
1265 }
1266
1267 if (!(InMemorySniffer is null))
1268 {
1269 this.Remove(InMemorySniffer);
1270 this.Add(this.server.GetSniffer(UserName + " IN"));
1271 InMemorySniffer.Replay(this);
1272 }
1273
1274 this.userName = UserName;
1275 this.authId = UserName + "@" + this.server.Domain;
1276 }
1277
1282 public async Task SetAccount(IAccount Account)
1283 {
1284 this.account = Account;
1285 this.userName = Account.UserName;
1286
1287 await this.SetState(SmtpConnectionState.Authenticated);
1288 this.server.PersistenceLayer.AccountLogin(this.userName, this.client.RemoteEndPoint);
1289 }
1290
1295 public bool CheckLive()
1296 {
1297 try
1298 {
1299 if (this.disposed || this.state == SmtpConnectionState.Error || this.state == SmtpConnectionState.Offline)
1300 return false;
1301
1302 if (!this.client.Connected)
1303 return false;
1304
1305 // https://msdn.microsoft.com/en-us/library/system.net.sockets.socket.connected.aspx
1306
1307 bool BlockingBak = this.client.Client.Client.Blocking;
1308 try
1309 {
1310 byte[] Temp = new byte[1];
1311
1312 this.client.Client.Client.Blocking = false;
1313 this.client.Client.Client.Send(Temp, 0, 0);
1314
1315 return true;
1316 }
1317 catch (SocketException e)
1318 {
1319 if (e.NativeErrorCode.Equals(10035)) // WSAEWOULDBLOCK
1320 return true;
1321 else
1322 return false;
1323 }
1324 finally
1325 {
1326 this.client.Client.Client.Blocking = BlockingBak;
1327 }
1328 }
1329 catch (Exception)
1330 {
1331 return false;
1332 }
1333 }
1334
1338 public void ResetState()
1339 {
1340 this.ResetState(!(this.account is null));
1341 }
1342
1347 public void ResetState(bool Authenticated)
1348 {
1349 this.mode = ReceptionMode.Command;
1350 this.mailFrom = null;
1351 this.mailFromDomain = null;
1352 this.recipients = null;
1353 this.headers = null;
1354 this.body = null;
1355 this.messageSize = 0;
1356 this.overflow = false;
1357 }
1358
1363 public Task<bool> SaslErrorNotAuthorized()
1364 {
1365 this.mode = ReceptionMode.Command;
1366 return this.BeginWrite("535 5.7.8 Authentication credentials invalid\r\n", null, null);
1367 }
1368
1373 public Task<bool> SaslErrorAccountDisabled()
1374 {
1375 this.mode = ReceptionMode.Command;
1376 return this.BeginWrite("454 4.7.0 Accound disabled.\r\n", null, null);
1377 }
1378
1383 public Task<bool> SaslErrorMalformedRequest()
1384 {
1385 this.mode = ReceptionMode.Command;
1386 return this.BeginWrite("503 Malformed request.\r\n", null, null);
1387 }
1388
1393 public Task<bool> SaslChallenge(string ChallengeBase64)
1394 {
1395 if (string.IsNullOrEmpty(ChallengeBase64))
1396 return this.BeginWrite("334\r\n", null, null);
1397 else
1398 return this.BeginWrite("334 " + ChallengeBase64 + "\r\n", null, null);
1399 }
1400
1405 public Task<bool> SaslSuccess(string ProofBase64)
1406 {
1407 this.mode = ReceptionMode.Command;
1408 return this.BeginWrite("235 2.7.0 Authentication successful\r\n", null, null);
1409 }
1410
1411 private async Task<string> ProcessIncomingMail(MailAddress From, MailAddress[] Recipients, KeyValuePair<string, string>[] Header,
1412 byte[] Body, string ClientName, string RemoteEndPoint)
1413 {
1414 SmtpMessage Message = new SmtpMessage()
1415 {
1416 FromMail = From,
1417 Recipients = Recipients,
1418 AllHeaders = Header,
1419 UntransformedBody = Body,
1422
1423 };
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>>();
1429 string ContentType = PlainTextCodec.DefaultContentType;
1430 Uri BaseUri = null;
1431
1432 foreach (KeyValuePair<string, string> P in Header)
1433 {
1434 string s = P.Key.ToUpper();
1435 int i;
1436
1437 switch (s)
1438 {
1439 case "CC":
1440 if (!this.AddRecipients(ref Cc, P.Value))
1441 OtherHeaders.Add(P);
1442 break;
1443
1444 case "CONTENT-ID":
1445 Message.ContentId = P.Value.Trim();
1446 break;
1447
1448 case "CONTENT-LOCATION":
1449 Message.ContentLocation = P.Value.Trim();
1450 BaseUri = new Uri(Message.ContentLocation);
1451 break;
1452
1453 case "CONTENT-TRANSFER-ENCODING":
1454 Message.ContentTransferEncoding = P.Value.Trim();
1455 break;
1456
1457 case "CONTENT-TYPE":
1458 Message.ContentType = ContentType = P.Value.Trim();
1459 break;
1460
1461 case "DATE":
1462 if (CommonTypes.TryParseRfc822(P.Value, out DateTimeOffset DTO))
1463 Message.Date = DTO;
1464 else
1465 OtherHeaders.Add(P);
1466 break;
1467
1468 case "FROM":
1469 s = P.Value;
1470 Message.FromHeader = this.ParseMailAddress(ref s);
1471 if (Message.FromHeader is null)
1472 OtherHeaders.Add(P);
1473 break;
1474
1475 case "IMPORTANCE":
1476 switch (P.Value.ToLower())
1477 {
1478 case "high":
1479 Message.Priority = Priority.High;
1480 break;
1481
1482 case "normal":
1483 Message.Priority = Priority.Normal;
1484 break;
1485
1486 case "low":
1487 Message.Priority = Priority.Low;
1488 break;
1489
1490 default:
1491 OtherHeaders.Add(P);
1492 break;
1493 }
1494 break;
1495
1496 case "MESSAGE-ID":
1497 Message.MessageID = P.Value;
1498 break;
1499
1500 case "MIME-VERSION":
1501 if (CommonTypes.TryParse(P.Value, out double d))
1502 Message.MimeVersion = d;
1503 else
1504 OtherHeaders.Add(P);
1505 break;
1506
1507 case "PRIORITY":
1508 switch (P.Value.ToLower())
1509 {
1510 case "urgent":
1511 Message.Priority = Priority.High;
1512 break;
1513
1514 case "normal":
1515 Message.Priority = Priority.Normal;
1516 break;
1517
1518 case "non-urgent":
1519 Message.Priority = Priority.Low;
1520 break;
1521
1522 default:
1523 OtherHeaders.Add(P);
1524 break;
1525 }
1526 break;
1527
1528 case "REPLY-TO":
1529 if (!this.AddRecipients(ref ReplyTo, P.Value))
1530 OtherHeaders.Add(P);
1531 break;
1532
1533 case "SENDER":
1534 s = P.Value;
1535 Message.Sender = this.ParseMailAddress(ref s);
1536 if (Message.Sender is null)
1537 OtherHeaders.Add(P);
1538 break;
1539
1540 case "SUBJECT":
1541 Message.Subject = P.Value;
1542 break;
1543
1544 case "TO":
1545 if (!this.AddRecipients(ref To, P.Value))
1546 OtherHeaders.Add(P);
1547 break;
1548
1549 case "X-PRIORITY":
1550 s = P.Value;
1551 i = s.IndexOf(' ');
1552 if (i > 0)
1553 s = s[..i];
1554
1555 if (int.TryParse(s.Trim(), out i))
1556 Message.Priority = (Priority)i;
1557 else
1558 OtherHeaders.Add(P);
1559 break;
1560
1561 default:
1562 OtherHeaders.Add(P);
1563 break;
1564 }
1565 }
1566
1567 if (Message.ContentTransferEncoding is null)
1568 Message.TransformedBody = null;
1569 else
1570 {
1571 if (!FormDataDecoder.TryTransferDecode(Body, Message.ContentTransferEncoding, out byte[] ToDecode))
1572 throw new NotSupportedException("Content-Transfer-Encoding not supported: " + Message.ContentTransferEncoding);
1573
1574 Message.TransformedBody = ToDecode;
1575 }
1576
1577 KeyValuePair<string, string>[] ContentTypeFields = null;
1578 string ContentType0 = ContentType;
1579
1580 if (!string.IsNullOrEmpty(ContentType))
1581 {
1582 if (InternetContent.ParseContentType(ref ContentType, out Encoding Encoding,
1583 out KeyValuePair<string, string>[] Fields))
1584 {
1585 Message.ContentTypeEncoding = Encoding ?? Message.ContentTypeEncoding;
1586 ContentTypeFields = Fields;
1587 }
1588 }
1589
1590 if (!InternetContent.Decodes(ContentType, out Grade _, out IContentDecoder Decoder))
1591 Message.DecodedBody = Message.TransformedBody ?? Message.UntransformedBody;
1592 else
1593 {
1594 try
1595 {
1596 ContentResponse Content = await Decoder.DecodeAsync(ContentType, Message.TransformedBody ?? Message.UntransformedBody,
1597 Message.ContentTypeEncoding, ContentTypeFields ?? Array.Empty<KeyValuePair<string, string>>(), BaseUri, null);
1598 Content.AssertOk();
1599
1600 Message.DecodedBody = Content.Decoded;
1601 }
1602 catch (Exception ex)
1603 {
1604 if (Types.TryGetModuleParameter("AppData", out string AppDataFolder))
1605 {
1606 string Path = System.IO.Path.Combine(AppDataFolder, "SMTP");
1607 if (!Directory.Exists(Path))
1608 Directory.CreateDirectory(Path);
1609
1610 string LocalId = Guid.NewGuid().ToString();
1611 Path = System.IO.Path.Combine(Path, LocalId);
1612
1613 await Files.WriteAllBytesAsync(Path + ".bin", Message.TransformedBody ?? Message.UntransformedBody);
1614 await Files.WriteAllTextAsync(Path + ".txt", ContentType0);
1615 await Files.WriteAllTextAsync(Path + ".err", ex.Message + "\r\n\r\n" + Log.CleanStackTrace(ex.StackTrace));
1616
1617 throw new Exception("Error when decoding message. Content stored under " + LocalId + "[.bin][.txt][.err].");
1618 }
1619 else
1620 ExceptionDispatchInfo.Capture(ex).Throw();
1621 }
1622
1623 if (Message.DecodedBody is MixedContent MixedContent)
1624 {
1625 List<EmbeddedContent> InlineObjects = null;
1626 List<EmbeddedContent> Attachments = null;
1627 EmbeddedContent First = null;
1628
1629 foreach (EmbeddedContent Object in MixedContent.Content)
1630 {
1631 if (First is null)
1632 First = Object;
1633 else
1634 {
1635 switch (Object.Disposition)
1636 {
1637 case ContentDisposition.Inline:
1638 InlineObjects ??= new List<EmbeddedContent>();
1639 InlineObjects.Add(Object);
1640 break;
1641
1642 case ContentDisposition.Attachment:
1643 Attachments ??= new List<EmbeddedContent>();
1644 Attachments.Add(Object);
1645 break;
1646
1647 default:
1648 switch (First.Disposition)
1649 {
1650 case ContentDisposition.Inline:
1651 InlineObjects ??= new List<EmbeddedContent>();
1652 InlineObjects.Insert(0, First);
1653 First = Object;
1654 break;
1655
1656 case ContentDisposition.Attachment:
1657 Attachments ??= new List<EmbeddedContent>();
1658 Attachments.Insert(0, First);
1659 First = Object;
1660 break;
1661
1662 default:
1663 Attachments ??= new List<EmbeddedContent>();
1664 Attachments.Add(Object);
1665 break;
1666 }
1667 break;
1668 }
1669 }
1670 }
1671
1672 Message.InlineObjects = InlineObjects?.ToArray();
1673 Message.Attachments = Attachments?.ToArray();
1674 Message.DecodedBody = First.Decoded;
1675 }
1676 }
1677
1678 foreach (MailAddress Addr in Message.Recipients)
1679 {
1680 bool Found = false;
1681
1682 if (!(To is null))
1683 {
1684 foreach (MailAddress A in To)
1685 {
1686 if (Addr.Address == A.Address)
1687 {
1688 Found = true;
1689 break;
1690 }
1691 }
1692
1693 if (Found)
1694 continue;
1695 }
1696
1697 if (!(Cc is null))
1698 {
1699 foreach (MailAddress A in Cc)
1700 {
1701 if (Addr.Address == A.Address)
1702 {
1703 Found = true;
1704 break;
1705 }
1706 }
1707
1708 if (Found)
1709 continue;
1710 }
1711
1712 Bcc ??= new List<MailAddress>();
1713 Bcc.Add(Addr);
1714 }
1715
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();
1721
1722 Message.Sender ??= Message.FromMail;
1723
1724 if (!Message.Date.HasValue)
1725 Message.Date = DateTimeOffset.Now;
1726
1727 Message.MessageID ??= Guid.NewGuid().ToString();
1728
1729 await this.server.ProcessMessage(Message);
1730
1731 return Message.MessageID;
1732 }
1733
1734 private bool AddRecipients(ref List<MailAddress> AddressList, string s)
1735 {
1736 AddressList ??= new List<MailAddress>();
1737
1738 MailAddress[] Addresses = this.ParseMailAddresses(s);
1739 if (Addresses is null)
1740 return false;
1741 else
1742 {
1743 AddressList.AddRange(Addresses);
1744 return true;
1745 }
1746 }
1747
1748 internal static int Next(int MaxValue)
1749 {
1750 lock (rnd)
1751 {
1752 return rnd.Next(MaxValue);
1753 }
1754 }
1755
1756 }
1757}
Helps with parsing of commong data types.
Definition: CommonTypes.cs:15
static bool TryParseRfc822(string s, out DateTimeOffset Value)
Parses a date and time value encoded according to RFC 822, §5.
Definition: CommonTypes.cs:172
static bool TryParse(string s, out double Value)
Tries to decode a string encoded double.
Definition: CommonTypes.cs:48
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
Definition: MixedContent.cs:7
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 ...
Definition: Log.cs:14
static string CleanStackTrace(string StackTrace)
Cleans a Stack Trace string, removing entries from the asynchronous execution model,...
Definition: Log.cs:194
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.
Definition: Log.cs:576
static Exception UnnestException(Exception Exception)
Unnests an exception, to extract the relevant inner exception.
Definition: Log.cs:828
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:
Definition: DnsResolver.cs:32
static async Task< string[]> LookupBlackList(IPAddress Address, string BlackListDomainName)
Looks up an IP Address in a DNS Block List.
Definition: DnsResolver.cs:989
Module maintaining available SASL mechanisms.
Definition: SaslModule.cs:14
static IAuthenticationMechanism[] Mechanisms
Available SASL mechanisms.
Definition: SaslModule.cs:39
SmtpClientConnection(BinaryTcpClient Client, SmtpServer Server, ISaslPersistenceLayer Persistence, int MaxMessageSize, params ISniffer[] Sniffers)
Class managing a connection.
void ResetState(bool Authenticated)
Resets the state machine.
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.
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.
Represents one message received over SMTP
Definition: SmtpMessage.cs:13
object DecodedBody
Decoded body. ContentType defines how TransformedBody is transformed into DecodedBody.
Definition: SmtpMessage.cs:198
byte[] TransformedBody
Transformed body. ContentTransferEncoding defines how UntransformedBody is transformed into Transform...
Definition: SmtpMessage.cs:187
DateTimeOffset? Date
Date of message, if defined
Definition: SmtpMessage.cs:64
MailAddress FromMail
From address, as specified by the client to initiate mail transfer.
Definition: SmtpMessage.cs:122
MailAddress FromHeader
From address, as specified in the mail headers.
Definition: SmtpMessage.cs:131
MailAddress[] Recipients
Recipients of message, as defined during initiation of transfer.
Definition: SmtpMessage.cs:149
string ContentTransferEncoding
Content Transfer Encoding of message, if defined. Affects how UntransformedBody is transformed into T...
Definition: SmtpMessage.cs:93
MailAddress Sender
Sender, as specified in the mail headers.
Definition: SmtpMessage.cs:140
Encoding ContentTypeEncoding
Content-Type encoding, if specified in the Content-Type header field.
Definition: SmtpMessage.cs:252
string ContentLocation
Content Location of message, if defined
Definition: SmtpMessage.cs:82
byte[] UntransformedBody
Raw, untrasnformed body of message.
Definition: SmtpMessage.cs:176
Implements a simple SMTP Server, as defined in:
Definition: SmtpServer.cs:45
CaseInsensitiveString Domain
Domain name.
Definition: SmtpServer.cs:294
bool CanRelayForDomain(string Domain)
If the server is permitted to relay messages from a particular domain.
Definition: SmtpServer.cs:626
X509Certificate ServerCertificate
Server domain certificate.
Definition: SmtpServer.cs:299
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 ...
Contains static methods
Definition: Files.cs:14
static Task WriteAllBytesAsync(string FileName, byte[] Data)
Creates a binary file asynchronously.
Definition: Files.cs:33
static Task WriteAllTextAsync(string FileName, string Text)
Creates a text file asynchronously.
Definition: Files.cs:95
Static class that dynamically manages types and interfaces available in the runtime environment.
Definition: Types.cs:15
static bool TryGetModuleParameter(string Name, out object Value)
Tries to get a module parameter value.
Definition: Types.cs:607
Helper methods for encrypting and decrypting streams of data.
Definition: Crypto.cs:14
const SslProtocols SecureTls
TLS 1.2 & 1.3
Definition: Crypto.cs:18
Class that monitors login events, and help applications determine malicious intent....
Definition: LoginAuditor.cs:26
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:
Definition: SpfResolver.cs:16
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 ...
Definition: SpfResolver.cs:33
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.
Definition: IAccount.cs:11
CaseInsensitiveString UserName
User Name
Definition: IAccount.cs:24
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.
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...
Definition: ISniffer.cs:10
Definition: ImplTypes.g.cs:58
ContentDisposition
Content disposition
SmtpConnectionState
State of SMTP connection.
ClientCertificates
Client Certificate Options
Grade
Grade enumeration
Definition: Grade.cs:7
ContentType
DTLS Record content type.
Definition: Enumerations.cs:11
Reason
Reason a token is not valid.
Definition: JwtFactory.cs:15
SpfResult
Result of a SPF (Sender Policy Framework) evaluation.
Definition: SpfResult.cs:11