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;
2using System.Collections.Generic;
3using System.ComponentModel;
4using System.IO;
5using System.Net;
6using System.Net.Mail;
7using System.Net.Security;
8using System.Net.Sockets;
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;
25
27{
28 internal enum ReceptionMode
29 {
30 Command,
31 MailHeader,
32 MailBody,
33 MailBodyCR,
34 MailBodyCRLF,
35 MailBodyCRLFPeriod,
36 MailBodyCRLFPeriodCR,
37 ChallengeResponse
38 };
39
40 public enum Priority
41 {
42 High = 1,
43 Normal = 3,
44 Low = 5
45 }
46
51 {
52 private static readonly Random rnd = new Random();
53
54 private readonly Guid id = Guid.NewGuid();
55 private readonly UTF8Encoding encoding = new UTF8Encoding(false, false);
56 private SmtpServer server;
57 private BinaryTcpClient client;
58 private readonly ISaslPersistenceLayer persistence;
59 private IAccount account = null;
60 private bool disposed = false;
61 private SmtpConnectionState state;
63 private object tag = null;
64 private string clientName = null;
65 private string authId = null;
66 private MailAddress mailFrom = null;
67 private CaseInsensitiveString mailFromDomain = 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;
73 private IAuthenticationMechanism mechanism = null;
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;
80
89 int MaxMessageSize, params ISniffer[] Sniffers)
90 : base(false, Sniffers)
91 {
92 this.state = SmtpConnectionState.Initiating;
93 this.client = Client;
94 this.server = Server;
95 this.persistence = Persistence;
96 this.maxMessageSize = MaxMessageSize;
97
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;
102 }
103
107 public Guid ID => this.id;
108
112 public string ClientName => this.clientName;
113
117 public string AuthId
118 {
119 get => this.authId;
120 }
121
126 {
127 get => this.state;
128 }
129
133 internal async Task SetState(SmtpConnectionState NewState)
134 {
135 if (this.state != NewState && !this.disposed)
136 {
137 this.state = NewState;
138
139 await this.Information("State changed to " + NewState.ToString());
140
141 await this.OnStateChanged.Raise(this, NewState);
142 }
143 }
144
148 public event EventHandlerAsync<SmtpConnectionState> OnStateChanged = null;
149
153 public object Tag
154 {
155 get => this.tag;
156 set => this.tag = value;
157 }
158
162 public SmtpServer Server => this.server;
163
167 public CaseInsensitiveString UserName => this.userName;
168
172 public string RemoteEndpoint
173 {
174 get { return this.client?.Client?.Client?.RemoteEndPoint?.ToString(); }
175 }
176
180 public string Protocol => "SMTP";
181
182 internal X509Certificate ClientCertificate => this.client.RemoteCertificate;
183 internal bool ClientCertificateValid => this.client.RemoteCertificateValid;
184
188 [Obsolete("Use the DisposeAsync() method.")]
189 public async void Dispose()
190 {
191 try
192 {
193 await this.DisposeAsync();
194 }
195 catch (Exception ex)
196 {
197 Log.Exception(ex);
198 }
199 }
200
204 public async Task DisposeAsync()
205 {
206 if (!this.disposed)
207 {
208 if (this.state != SmtpConnectionState.Error)
209 await this.SetState(SmtpConnectionState.Offline);
210
211 ISniffer[] Sniffers = this.Sniffers;
212 if (!(Sniffers is null))
213 this.server?.CacheSniffers(Sniffers);
214
215 this.disposed = true;
216 this.server = null;
217
218 this.client?.DisposeWhenDone();
219 this.client = null;
220 }
221 }
222
223 private async Task<bool> Client_OnReceived(object Sender, byte[] Buffer, int Offset, int Count)
224 {
225 try
226 {
227 return await this.ParseIncoming(Buffer, Offset, Count);
228 }
229 catch (Exception ex)
230 {
231 if (!this.disposed)
232 {
233 await this.Exception(ex);
234 await this.DisposeAsync();
235 }
236
237 return false;
238 }
239 }
240
241 private async Task Client_OnError(object Sender, Exception Exception)
242 {
243 await this.SetState(SmtpConnectionState.Error);
244 await this.DisposeAsync();
245 }
246
247 private Task Client_OnDisconnected(object Sender, EventArgs e)
248 {
249 return this.DisposeAsync();
250 }
251
252 private async Task<bool> ParseIncoming(byte[] Data, int Offset, int NrRead)
253 {
254 byte b;
255
256 while (Offset < NrRead)
257 {
258 b = Data[Offset++];
259
260 switch (this.mode)
261 {
262 case ReceptionMode.MailBody:
263 if (b == 13)
264 this.mode = ReceptionMode.MailBodyCR;
265 else
266 {
267 this.messageSize++;
268 if (this.overflow || this.messageSize > this.maxMessageSize)
269 {
270 this.overflow = true;
271 break;
272 }
273
274 this.body.WriteByte(b);
275 }
276 break;
277
278 case ReceptionMode.MailBodyCR:
279 if (b == 10)
280 this.mode = ReceptionMode.MailBodyCRLF;
281 else if (b == 13)
282 {
283 this.messageSize++;
284 if (this.overflow || this.messageSize > this.maxMessageSize)
285 {
286 this.overflow = true;
287 break;
288 }
289
290 this.body.WriteByte(13);
291 }
292 else
293 {
294 this.messageSize += 2;
295 if (this.overflow || this.messageSize > this.maxMessageSize)
296 {
297 this.overflow = true;
298 break;
299 }
300
301 this.body.WriteByte(13);
302 this.body.WriteByte(b);
303 this.mode = ReceptionMode.MailBody;
304 }
305 break;
306
307 case ReceptionMode.MailBodyCRLF:
308 if (b == (byte)'.')
309 this.mode = ReceptionMode.MailBodyCRLFPeriod;
310 else if (b == 13)
311 {
312 this.messageSize += 2;
313 if (this.overflow || this.messageSize > this.maxMessageSize)
314 {
315 this.overflow = true;
316 break;
317 }
318
319 this.body.WriteByte(13);
320 this.body.WriteByte(10);
321 this.mode = ReceptionMode.MailBodyCR;
322 }
323 else
324 {
325 this.messageSize += 3;
326 if (this.overflow || this.messageSize > this.maxMessageSize)
327 {
328 this.overflow = true;
329 break;
330 }
331
332 this.body.WriteByte(13);
333 this.body.WriteByte(10);
334 this.body.WriteByte(b);
335 this.mode = ReceptionMode.MailBody;
336 }
337 break;
338
339 case ReceptionMode.MailBodyCRLFPeriod:
340 if (b == 13)
341 this.mode = ReceptionMode.MailBodyCRLFPeriodCR;
342 else
343 {
344 this.messageSize += 3;
345 if (this.overflow || this.messageSize > this.maxMessageSize)
346 {
347 this.overflow = true;
348 break;
349 }
350
351 this.body.WriteByte(13);
352 this.body.WriteByte(10);
353 this.body.WriteByte(b);
354 this.mode = ReceptionMode.MailBody;
355 }
356 break;
357
358 case ReceptionMode.MailBodyCRLFPeriodCR:
359 if (b == 10)
360 {
361 if (this.overflow)
362 {
363 if (!await this.BeginWrite("552 Requested mail action aborted: exceeded storage allocation.\r\n", null, null))
364 return false;
365 }
366 else
367 {
368 try
369 {
370 byte[] Body = this.body.ToArray();
371 await this.ReceiveBinary(Body);
372
373 string Id = await this.ProcessIncomingMail(this.mailFrom, this.recipients.ToArray(),
374 this.headers.ToArray(), Body, this.clientName, this.RemoteEndpoint);
375
376 if (!await this.BeginWrite("250 2.6.0 " + Id.ToString() + " Message accepted for delivery.\r\n", null, null))
377 return false;
378 }
379 catch (Exception ex)
380 {
381 await this.Error(ex.Message + "\r\n\r\n" + Log.CleanStackTrace(ex.StackTrace));
382 if (!await this.BeginWrite("554 Unable to parse incoming message: " + FirstRow(ex.Message) + "\r\n", null, null))
383 return false;
384 }
385 }
386
387 this.ResetState();
388 }
389 else if (b == 13)
390 {
391 this.messageSize += 3;
392 if (this.overflow || this.messageSize > this.maxMessageSize)
393 {
394 this.overflow = true;
395 break;
396 }
397
398 this.body.WriteByte(13);
399 this.body.WriteByte(10);
400 this.body.WriteByte(13);
401 this.mode = ReceptionMode.MailBodyCR;
402 }
403 else
404 {
405 this.messageSize += 4;
406 if (this.overflow || this.messageSize > this.maxMessageSize)
407 {
408 this.overflow = true;
409 break;
410 }
411
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;
417 }
418 break;
419
420 default:
421 if (b == 13) // CR
422 break;
423 else if (b == 10) // LF
424 {
425 string Row = Encoding.UTF8.GetString(this.incoming.ToArray());
426 this.incoming.Position = 0;
427 this.incoming.SetLength(0);
428 this.incomingSize = 0;
429
430 await this.ReceiveText(Row);
431 if (!await this.ParseIncomingRow(Row))
432 return false;
433
434 if (this.mode == ReceptionMode.MailBody)
435 break;
436 }
437 else
438 {
439 this.incoming.WriteByte(b);
440 this.incomingSize++;
441
442 if (this.incomingSize > this.maxMessageSize)
443 {
444 await this.BeginWrite("552 Requested mail action aborted: exceeded storage allocation.\r\n", async (Sender, e) =>
445 {
446 await this.SetState(SmtpConnectionState.Offline);
447 this.server.Closed(this);
448 }, null);
449 return false;
450 }
451 }
452 break;
453 }
454 }
455
456 return true;
457 }
458
459 private async Task<bool> ParseIncomingRow(string s)
460 {
461 switch (this.mode)
462 {
463 case ReceptionMode.Command:
464 int i = s.IndexOf(' ');
465 string Cmd, Value;
466
467 if (i < 0)
468 {
469 Cmd = s;
470 Value = string.Empty;
471 }
472 else
473 {
474 Cmd = s.Substring(0, i);
475 Value = s.Substring(i + 1);
476 }
477
478 switch (Cmd.ToUpper())
479 {
480 case "HELO":
481 case "EHLO":
482 if (this.clientName is null)
483 {
484 this.clientName = Value.Trim();
485 await this.SetState(SmtpConnectionState.Greeting);
486 await this.SetUserIdentity(this.clientName);
487 }
488
489 StringBuilder Response = new StringBuilder();
490
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());
499
500 if (!(this.server.ServerCertificate is null) && !(this.client.Stream is SslStream))
501 Response.Append("\r\n250-STARTTLS");
502
503 SslStream SslStream;
504
505 if (this.account is null)
506 {
507 Response.Append("\r\n250-AUTH");
508
509 if (!(this.ClientCertificate is null) && this.ClientCertificateValid)
510 Response.Append(" EXTERNAL");
511
512 // TODO: EXTERNAL authentication mechanism
513
514 SslStream = this.client.Stream as SslStream;
516 {
517 if (M.Allowed(SslStream))
518 {
519 Response.Append(' ');
520 Response.Append(M.Name);
521 }
522 }
523 }
524
525 Response.Append("\r\n250-SMTPUTF8\r\n250-8BITMIME\r\n250-ENHANCEDSTATUSCODES\r\n250 HELP\r\n");
526
527 if (!await this.BeginWrite(Response.ToString(), null, null))
528 return false;
529
530 break;
531
532 case "MAIL":
533 s = Value.Trim();
534 i = s.IndexOf(':');
535 if (i < 0)
536 {
537 if (!await this.BeginWrite("554 : expected.\r\n", null, null))
538 return false;
539 break;
540 }
541
542 Cmd = s.Substring(0, i).ToUpper();
543 s = s.Substring(i + 1).TrimStart();
544
545 switch (Cmd)
546 {
547 case "FROM":
548 MailAddress From = this.ParseMailAddress(ref s);
549
550 int j = From.Address.IndexOf('@');
551 if (j < 0)
552 {
553 if (!await this.BeginWrite("554 @ expected\r\n", null, null))
554 return false;
555 break;
556 }
557
558 CaseInsensitiveString Domain = From.Address.Substring(j + 1);
559 CaseInsensitiveString AccountName = From.Address.Substring(0, j);
560
561 if (Domain == this.server.Domain)
562 {
563 if (this.account is null)
564 {
565 if (!await this.BeginWrite("530 5.7.0 Authentication required.\r\n", null, null))
566 return false;
567 break;
568 }
569
570 if (AccountName != this.account.UserName)
571 {
572 if (!await this.BeginWrite("555 Sender must be the same as the authenticated user.\r\n", null, null))
573 return false;
574 break;
575 }
576 }
577 else
578 {
579 if (!(this.client.Client.Client.RemoteEndPoint is IPEndPoint RemoteIPEndPoint))
580 {
581 if (!await this.BeginWrite("550 Invalid remote address.\r\n", null, null))
582 return false;
583 break;
584 }
585
586 await this.Information("Checking SPF records.");
587
588 KeyValuePair<SpfResult, string> SpfStatus = await SpfResolver.CheckHost(RemoteIPEndPoint.Address,
589 Domain, From.Address, this.clientName, this.server.Domain, this.server.SpfExpressions);
590
591 bool Accept = false;
592
593 switch (SpfStatus.Key)
594 {
595 case SpfResult.Pass:
596 await this.Information("SPF check passed.");
597 Accept = true;
598 break;
599
600 case SpfResult.Fail:
601 await this.Warning("SPF check failed.");
602 if (!await this.BeginWrite("550 " + (SpfStatus.Value ?? "SPF check failed.") + "\r\n", null, null))
603 return false;
604 break;
605
606 case SpfResult.SoftFail:
607 await this.Warning("SPF check soft-failed.");
608 if (!await this.BeginWrite("550 " + (SpfStatus.Value ?? "SPF check soft-failed.") + "\r\n", null, null))
609 return false;
610 break;
611
612 case SpfResult.Neutral:
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))
615 return false;
616 break;
617
618 case SpfResult.None:
619 await this.Warning("No SPF records.");
620 if (!await this.BeginWrite("550 " + (SpfStatus.Value ?? "No SPF records found.") + "\r\n", null, null))
621 return false;
622 break;
623
624 case SpfResult.PermanentError:
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))
627 return false;
628 break;
629
630 case SpfResult.TemporaryError:
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))
633 return false;
634 break;
635
636 default:
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))
639 return false;
640 break;
641 }
642
643 if (!Accept)
644 {
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));
648 break;
649 }
650
651 string[] BlackLists;
652
653 switch (RemoteIPEndPoint.Address.AddressFamily)
654 {
655 case AddressFamily.InterNetwork:
656 BlackLists = this.server.Ip4DnsBlackLists;
657 break;
658
659 case AddressFamily.InterNetworkV6:
660 BlackLists = this.server.Ip6DnsBlackLists;
661
662 if ((BlackLists is null || BlackLists.Length == 0) &&
663 !(this.server.Ip4DnsBlackLists is null) && this.server.Ip4DnsBlackLists.Length > 0)
664 {
665 BlackLists = null;
666 }
667 break;
668
669 default:
670 BlackLists = null;
671 break;
672 }
673
674 if (!(BlackLists is null))
675 {
676 List<string> A = new List<string>();
677 int c = BlackLists.Length;
678
679 A.AddRange(BlackLists);
680 BlackLists = new string[c];
681
682 lock (rnd)
683 {
684 j = 0;
685
686 while (c > 0)
687 {
688 i = rnd.Next(c--);
689 BlackLists[j++] = A[i];
690 A.RemoveAt(i);
691 }
692 }
693
694 BlackLists = A.ToArray();
695
696 foreach (string BlackList in BlackLists)
697 {
698 await this.Information("Checking DNSBL " + BlackList);
699
700 string[] Reason = await DnsResolver.LookupBlackList(RemoteIPEndPoint.Address, BlackList);
701
702 if (Reason is null)
703 await this.Information(RemoteIPEndPoint.Address.ToString() + " not in DNSBL " + BlackList);
704 else
705 {
706 await this.Warning(RemoteIPEndPoint.Address.ToString() + " exists in DNSBL " + BlackList);
707
708 StringBuilder sb = new StringBuilder();
709
710 sb.Append("550 Blocked by " + BlackList + ".");
711
712 foreach (string s2 in Reason)
713 {
714 sb.Append(' ');
715 sb.Append(s2);
716 }
717
718 sb.Append("\r\n");
719
720 if (!await this.BeginWrite(sb.ToString(), null, null))
721 return false;
722 Accept = false;
723
724 Log.Warning("Remote IP blocked by DNS Black List.", RemoteIPEndPoint.Address.ToString(), BlackList);
725 break;
726 }
727 }
728
729 if (!Accept)
730 break;
731 }
732 }
733
734 this.mailFrom = From;
735 this.mailFromDomain = Domain;
736 this.mailFromMe = !(this.account is null) && (AccountName == this.account.UserName) &&
737 Domain == this.server.Domain;
738
739 if (!await this.BeginWrite("250 2.1.0 Originator <" + this.mailFrom +
740 (this.mailFromMe ? "> ok (you).\r\n" : "> ok.\r\n"), null, null))
741 {
742 return false;
743 }
744 break;
745
746 default:
747 if (!await this.BeginWrite("500 Syntax error\r\n", null, null))
748 return false;
749 break;
750 }
751 break;
752
753 case "RCPT":
754 s = Value.Trim();
755 i = s.IndexOf(':');
756 if (i < 0)
757 {
758 if (!await this.BeginWrite("554 : expected.\r\n", null, null))
759 return false;
760 break;
761 }
762
763 Cmd = s.Substring(0, i).ToUpper();
764 s = s.Substring(i + 1).TrimStart();
765
766 switch (Cmd)
767 {
768 case "TO":
769 if (this.mailFrom is null)
770 {
771 if (!await this.BeginWrite("503 Bad sequence of commands.\r\n", null, null))
772 return false;
773 break;
774 }
775
776 MailAddress Recipient = this.ParseMailAddress(ref s);
777
778 int j = Recipient.Address.IndexOf('@');
779 if (j < 0)
780 {
781 if (!await this.BeginWrite("554 @ expected.\r\n", null, null))
782 return false;
783 break;
784 }
785
786 CaseInsensitiveString Domain = Recipient.Address.Substring(j + 1);
787 CaseInsensitiveString AccountName = Recipient.Address.Substring(0, j);
788
789 if (Domain == this.server.Domain)
790 {
791 IAccount Account = await this.persistence.GetAccount(AccountName);
792 if (Account is null)
793 {
794 if (!await this.BeginWrite("550 5.1.1 Mailbox does not exist.\r\n", null, null))
795 return false;
796 break;
797 }
798 }
799 else
800 {
801 if (this.account is null)
802 {
803 if (!await this.BeginWrite("530 5.7.0 Authentication required\r\n", null, null))
804 return false;
805 break;
806 }
807
808 if (!this.account.HasPrivilege(SmtpServer.SmtpRelayPrivilegeID))
809 {
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));
814
815 if (!await this.BeginWrite("550 5.7.1 Relaying messages using this account not permitted.\r\n", null, null))
816 return false;
817 break;
818 }
819
820 if (!(this.mailFromMe || this.server.CanRelayForDomain(this.mailFromDomain)))
821 {
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));
827
828 if (!await this.BeginWrite("550 5.7.1 Relaying messages from " + this.mailFromDomain + " not allowed.\r\n", null, null))
829 return false;
830 break;
831 }
832 }
833
834 this.messageSize += Recipient.Address.Length + Recipient.DisplayName.Length;
835 if (this.overflow || this.messageSize > this.maxMessageSize)
836 {
837 this.overflow = true;
838 if (!await this.BeginWrite("554 Message too large.\r\n", null, null))
839 return false;
840 break;
841 }
842
843 if (this.recipients is null)
844 this.recipients = new List<MailAddress>();
845
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.Substring(0, i);
986 s = s.Substring(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.Substring(0, i);
1095 Value = s.Substring(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 RemoteIpEndpoint;
1118 EndPoint EP = this.client.Client.Client.RemoteEndPoint;
1119
1120 if (EP is IPEndPoint IpEP)
1121 RemoteIpEndpoint = IpEP.Address.ToString();
1122 else
1123 RemoteIpEndpoint = EP.ToString();
1124
1125 if (LoginAuditor.CanStartTls(RemoteIpEndpoint))
1126 {
1127 try
1128 {
1129 SmtpConnectionState Bak = this.state;
1130 await this.SetState(SmtpConnectionState.StartingEncryption);
1131
1132 await this.client.UpgradeToTlsAsServer(this.server.ServerCertificate, SslProtocols.Tls12, ClientCertificates.Optional);
1133
1134 await this.SetState(Bak);
1135
1136 this.client.Continue();
1137 }
1138 catch (AuthenticationException ex)
1139 {
1140 await this.LoginFailure(ex, RemoteIpEndpoint);
1141 }
1142 catch (Win32Exception ex)
1143 {
1144 await this.LoginFailure(ex, RemoteIpEndpoint);
1145 }
1146 catch (Exception ex)
1147 {
1148 await this.Exception(ex);
1149 await this.ToError(null);
1150 }
1151 }
1152 else
1153 await this.ToError(null);
1154 }
1155 }
1156
1157 private async Task LoginFailure(Exception ex, string RemoteIpEndpoint)
1158 {
1159 Exception ex2 = Log.UnnestException(ex);
1160 await LoginAuditor.ReportTlsHackAttempt(RemoteIpEndpoint, "TLS handshake failed: " + ex2.Message, "SMTP");
1161
1162 await this.ToError(null);
1163 }
1164
1165 private static string FirstRow(string s)
1166 {
1167 int i = s.IndexOfAny(CRLF);
1168 if (i < 0)
1169 return s;
1170 else
1171 return s.Substring(0, i);
1172 }
1173
1174 private static readonly char[] CRLF = new char[] { '\r', '\n' };
1175
1176 private MailAddress[] ParseMailAddresses(string s)
1177 {
1178 List<MailAddress> Result = null;
1179 MailAddress Addr;
1180
1181 foreach (string Part in s.Split(','))
1182 {
1183 s = Part;
1184 Addr = this.ParseMailAddress(ref s);
1185 if (Addr is null)
1186 continue;
1187
1188 if (Result is null)
1189 Result = new List<MailAddress>();
1190
1191 Result.Add(Addr);
1192 }
1193
1194 return Result?.ToArray();
1195 }
1196
1197 private MailAddress ParseMailAddress(ref string s)
1198 {
1199 int i = s.IndexOf('<');
1200 if (i < 0)
1201 {
1202 if (s.IndexOf('@') >= 0)
1203 return new MailAddress(s.Trim());
1204 else
1205 return null;
1206 }
1207
1208 int j = s.IndexOf('>', i + 1);
1209 if (j < 0)
1210 return null;
1211
1212 string Name = s.Substring(0, i).Trim();
1213 string Address = s.Substring(i + 1, j - i - 1).Trim();
1214
1215 s = s.Substring(j + 1).TrimStart();
1216
1217 if (string.IsNullOrEmpty(Name))
1218 return new MailAddress(Address);
1219 else
1220 return new MailAddress(Address, Name);
1221 }
1222
1223 private async Task ToError(string ClosingCommand)
1224 {
1225 if (string.IsNullOrEmpty(ClosingCommand))
1226 {
1227 await this.SetState(SmtpConnectionState.Error);
1228 await this.DisposeAsync();
1229 }
1230 else
1231 {
1232 await this.BeginWrite(ClosingCommand, async (Sender, e) =>
1233 {
1234 await this.SetState(SmtpConnectionState.Error);
1235 await this.DisposeAsync();
1236 }, null);
1237 }
1238 }
1239
1246 public Task<bool> BeginWrite(string Text, EventHandlerAsync<DeliveryEventArgs> Callback, object State)
1247 {
1248 if (this.disposed)
1249 return Task.FromResult(false);
1250
1251 return this.client.SendAsync(this.encoding.GetBytes(Text), async (Sender, e) =>
1252 {
1253 await this.TransmitText(Text);
1254 await Callback.Raise(this, e);
1255 }, State);
1256 }
1257
1259 {
1261
1262 foreach (ISniffer Sniffer in this.Sniffers)
1263 {
1264 InMemorySniffer = Sniffer as InMemorySniffer;
1265 if (!(InMemorySniffer is null))
1266 break;
1267 }
1268
1269 if (!(InMemorySniffer is null))
1270 {
1271 this.Remove(InMemorySniffer);
1272 this.Add(this.server.GetSniffer(UserName + " IN"));
1273 await InMemorySniffer.ReplayAsync(this);
1274 }
1275
1276 this.userName = UserName;
1277 this.authId = UserName + "@" + this.server.Domain;
1278 }
1279
1284 public async Task SetAccount(IAccount Account)
1285 {
1286 this.account = Account;
1287 this.userName = Account.UserName;
1288
1289 await this.SetState(SmtpConnectionState.Authenticated);
1290 this.server.PersistenceLayer.AccountLogin(this.userName, this.client.Client.Client.RemoteEndPoint.ToString());
1291 }
1292
1297 public bool CheckLive()
1298 {
1299 try
1300 {
1301 if (this.disposed || this.state == SmtpConnectionState.Error || this.state == SmtpConnectionState.Offline)
1302 return false;
1303
1304 if (!this.client.Connected)
1305 return false;
1306
1307 // https://msdn.microsoft.com/en-us/library/system.net.sockets.socket.connected.aspx
1308
1309 bool BlockingBak = this.client.Client.Client.Blocking;
1310 try
1311 {
1312 byte[] Temp = new byte[1];
1313
1314 this.client.Client.Client.Blocking = false;
1315 this.client.Client.Client.Send(Temp, 0, 0);
1316
1317 return true;
1318 }
1319 catch (SocketException e)
1320 {
1321 if (e.NativeErrorCode.Equals(10035)) // WSAEWOULDBLOCK
1322 return true;
1323 else
1324 return false;
1325 }
1326 finally
1327 {
1328 this.client.Client.Client.Blocking = BlockingBak;
1329 }
1330 }
1331 catch (Exception)
1332 {
1333 return false;
1334 }
1335 }
1336
1337 public void ResetState()
1338 {
1339 this.ResetState(!(this.account is null));
1340 }
1341
1342 public void ResetState(bool Authenticated)
1343 {
1344 this.mode = ReceptionMode.Command;
1345 this.mailFrom = null;
1346 this.mailFromDomain = null;
1347 this.recipients = null;
1348 this.headers = null;
1349 this.body = null;
1350 this.messageSize = 0;
1351 this.overflow = false;
1352 }
1353
1354 public Task<bool> SaslErrorNotAuthorized()
1355 {
1356 this.mode = ReceptionMode.Command;
1357 return this.BeginWrite("535 5.7.8 Authentication credentials invalid\r\n", null, null);
1358 }
1359
1360 public Task<bool> SaslErrorAccountDisabled()
1361 {
1362 this.mode = ReceptionMode.Command;
1363 return this.BeginWrite("454 4.7.0 Accound disabled.\r\n", null, null);
1364 }
1365
1366 public Task<bool> SaslErrorMalformedRequest()
1367 {
1368 this.mode = ReceptionMode.Command;
1369 return this.BeginWrite("503 Malformed request.\r\n", null, null);
1370 }
1371
1372 public Task<bool> SaslChallenge(string ChallengeBase64)
1373 {
1374 if (string.IsNullOrEmpty(ChallengeBase64))
1375 return this.BeginWrite("334\r\n", null, null);
1376 else
1377 return this.BeginWrite("334 " + ChallengeBase64 + "\r\n", null, null);
1378 }
1379
1380 public Task<bool> SaslSuccess(string ProofBase64)
1381 {
1382 this.mode = ReceptionMode.Command;
1383 return this.BeginWrite("235 2.7.0 Authentication successful\r\n", null, null);
1384 }
1385
1386 private async Task<string> ProcessIncomingMail(MailAddress From, MailAddress[] Recipients, KeyValuePair<string, string>[] Header,
1387 byte[] Body, string ClientName, string RemoteEndpoint)
1388 {
1389 SmtpMessage Message = new SmtpMessage()
1390 {
1391 FromMail = From,
1392 Recipients = Recipients,
1393 AllHeaders = Header,
1394 UntransformedBody = Body,
1397
1398 };
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>>();
1404 string ContentType = PlainTextCodec.DefaultContentType;
1405 Uri BaseUri = null;
1406
1407 foreach (KeyValuePair<string, string> P in Header)
1408 {
1409 string s = P.Key.ToUpper();
1410 int i;
1411
1412 switch (s)
1413 {
1414 case "CC":
1415 if (!this.AddRecipients(ref Cc, P.Value))
1416 OtherHeaders.Add(P);
1417 break;
1418
1419 case "CONTENT-ID":
1420 Message.ContentId = P.Value.Trim();
1421 break;
1422
1423 case "CONTENT-LOCATION":
1424 Message.ContentLocation = P.Value.Trim();
1425 BaseUri = new Uri(Message.ContentLocation);
1426 break;
1427
1428 case "CONTENT-TRANSFER-ENCODING":
1429 Message.ContentTransferEncoding = P.Value.Trim();
1430 break;
1431
1432 case "CONTENT-TYPE":
1433 Message.ContentType = ContentType = P.Value.Trim();
1434 break;
1435
1436 case "DATE":
1437 if (CommonTypes.TryParseRfc822(P.Value, out DateTimeOffset DTO))
1438 Message.Date = DTO;
1439 else
1440 OtherHeaders.Add(P);
1441 break;
1442
1443 case "FROM":
1444 s = P.Value;
1445 Message.FromHeader = this.ParseMailAddress(ref s);
1446 if (Message.FromHeader is null)
1447 OtherHeaders.Add(P);
1448 break;
1449
1450 case "IMPORTANCE":
1451 switch (P.Value.ToLower())
1452 {
1453 case "high":
1454 Message.Priority = Priority.High;
1455 break;
1456
1457 case "normal":
1458 Message.Priority = Priority.Normal;
1459 break;
1460
1461 case "low":
1462 Message.Priority = Priority.Low;
1463 break;
1464
1465 default:
1466 OtherHeaders.Add(P);
1467 break;
1468 }
1469 break;
1470
1471 case "MESSAGE-ID":
1472 Message.MessageID = P.Value;
1473 break;
1474
1475 case "MIME-VERSION":
1476 if (CommonTypes.TryParse(P.Value, out double d))
1477 Message.MimeVersion = d;
1478 else
1479 OtherHeaders.Add(P);
1480 break;
1481
1482 case "PRIORITY":
1483 switch (P.Value.ToLower())
1484 {
1485 case "urgent":
1486 Message.Priority = Priority.High;
1487 break;
1488
1489 case "normal":
1490 Message.Priority = Priority.Normal;
1491 break;
1492
1493 case "non-urgent":
1494 Message.Priority = Priority.Low;
1495 break;
1496
1497 default:
1498 OtherHeaders.Add(P);
1499 break;
1500 }
1501 break;
1502
1503 case "REPLY-TO":
1504 if (!this.AddRecipients(ref ReplyTo, P.Value))
1505 OtherHeaders.Add(P);
1506 break;
1507
1508 case "SENDER":
1509 s = P.Value;
1510 Message.Sender = this.ParseMailAddress(ref s);
1511 if (Message.Sender is null)
1512 OtherHeaders.Add(P);
1513 break;
1514
1515 case "SUBJECT":
1516 Message.Subject = P.Value;
1517 break;
1518
1519 case "TO":
1520 if (!this.AddRecipients(ref To, P.Value))
1521 OtherHeaders.Add(P);
1522 break;
1523
1524 case "X-PRIORITY":
1525 s = P.Value;
1526 i = s.IndexOf(' ');
1527 if (i > 0)
1528 s = s.Substring(0, i);
1529
1530 if (int.TryParse(s.Trim(), out i))
1531 Message.Priority = (Priority)i;
1532 else
1533 OtherHeaders.Add(P);
1534 break;
1535
1536 default:
1537 OtherHeaders.Add(P);
1538 break;
1539 }
1540 }
1541
1542 if (Message.ContentTransferEncoding is null)
1543 Message.TransformedBody = null;
1544 else
1545 {
1546 if (!FormDataDecoder.TryTransferDecode(Body, Message.ContentTransferEncoding, out byte[] ToDecode))
1547 throw new NotSupportedException("Content-Transfer-Encoding not supported: " + Message.ContentTransferEncoding);
1548
1549 Message.TransformedBody = ToDecode;
1550 }
1551
1552 KeyValuePair<string, string>[] ContentTypeFields = null;
1553 string ContentType0 = ContentType;
1554
1555 if (!string.IsNullOrEmpty(ContentType))
1556 {
1557 int i = ContentType.IndexOf(';');
1558 if (i >= 0)
1559 {
1560 ContentTypeFields = CommonTypes.ParseFieldValues(
1561 ContentType.Substring(i + 1).Trim());
1562
1563 ContentType = ContentType.Substring(0, i).Trim();
1564
1565 foreach (KeyValuePair<string, string> P in ContentTypeFields)
1566 {
1567 if (string.Compare(P.Key, "charset", true) == 0)
1568 {
1569 Message.ContentTypeEncoding = Encoding.GetEncoding(P.Value);
1570 break;
1571 }
1572 }
1573 }
1574 }
1575
1576 if (!InternetContent.Decodes(ContentType, out Grade _, out IContentDecoder Decoder))
1577 Message.DecodedBody = Message.TransformedBody ?? Message.UntransformedBody;
1578 else
1579 {
1580 try
1581 {
1582 Message.DecodedBody = await Decoder.DecodeAsync(ContentType, Message.TransformedBody ?? Message.UntransformedBody,
1583 Message.ContentTypeEncoding, ContentTypeFields ?? new KeyValuePair<string, string>[0], BaseUri);
1584 }
1585 catch (Exception ex)
1586 {
1587 if (Types.TryGetModuleParameter("AppData", out object Obj) && Obj is string AppDataFolder)
1588 {
1589 string Path = System.IO.Path.Combine(AppDataFolder, "SMTP");
1590 if (!Directory.Exists(Path))
1591 Directory.CreateDirectory(Path);
1592
1593 string LocalId = Guid.NewGuid().ToString();
1594 Path = System.IO.Path.Combine(Path, LocalId);
1595
1596 await Resources.WriteAllBytesAsync(Path + ".bin", Message.TransformedBody ?? Message.UntransformedBody);
1597 await Resources.WriteAllTextAsync(Path + ".txt", ContentType0);
1598 await Resources.WriteAllTextAsync(Path + ".err", ex.Message + "\r\n\r\n" + Log.CleanStackTrace(ex.StackTrace));
1599
1600 throw new Exception("Error when decoding message. Content stored under " + LocalId + "[.bin][.txt][.err].");
1601 }
1602 else
1603 ExceptionDispatchInfo.Capture(ex).Throw();
1604 }
1605
1606 if (Message.DecodedBody is MixedContent MixedContent)
1607 {
1608 List<EmbeddedContent> InlineObjects = null;
1609 List<EmbeddedContent> Attachments = null;
1610 EmbeddedContent First = null;
1611
1612 foreach (EmbeddedContent Object in MixedContent.Content)
1613 {
1614 if (First is null)
1615 First = Object;
1616 else
1617 {
1618 switch (Object.Disposition)
1619 {
1620 case ContentDisposition.Inline:
1621 if (InlineObjects is null)
1622 InlineObjects = new List<EmbeddedContent>();
1623
1624 InlineObjects.Add(Object);
1625 break;
1626
1627 case ContentDisposition.Attachment:
1628 if (Attachments is null)
1629 Attachments = new List<EmbeddedContent>();
1630
1631 Attachments.Add(Object);
1632 break;
1633
1634 default:
1635 switch (First.Disposition)
1636 {
1637 case ContentDisposition.Inline:
1638 if (InlineObjects is null)
1639 InlineObjects = new List<EmbeddedContent>();
1640
1641 InlineObjects.Insert(0, First);
1642 First = Object;
1643 break;
1644
1645 case ContentDisposition.Attachment:
1646 if (Attachments is null)
1647 Attachments = new List<EmbeddedContent>();
1648
1649 Attachments.Insert(0, First);
1650 First = Object;
1651 break;
1652
1653 default:
1654 if (Attachments is null)
1655 Attachments = new List<EmbeddedContent>();
1656
1657 Attachments.Add(Object);
1658 break;
1659 }
1660 break;
1661 }
1662 }
1663 }
1664
1665 Message.InlineObjects = InlineObjects?.ToArray();
1666 Message.Attachments = Attachments?.ToArray();
1667 Message.DecodedBody = First.Decoded;
1668 }
1669 }
1670
1671 foreach (MailAddress Addr in Message.Recipients)
1672 {
1673 bool Found = false;
1674
1675 if (!(To is null))
1676 {
1677 foreach (MailAddress A in To)
1678 {
1679 if (Addr.Address == A.Address)
1680 {
1681 Found = true;
1682 break;
1683 }
1684 }
1685
1686 if (Found)
1687 continue;
1688 }
1689
1690 if (!(Cc is null))
1691 {
1692 foreach (MailAddress A in Cc)
1693 {
1694 if (Addr.Address == A.Address)
1695 {
1696 Found = true;
1697 break;
1698 }
1699 }
1700
1701 if (Found)
1702 continue;
1703 }
1704
1705 if (Bcc is null)
1706 Bcc = new List<MailAddress>();
1707
1708 Bcc.Add(Addr);
1709 }
1710
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();
1716
1717 if (Message.Sender is null)
1718 Message.Sender = Message.FromMail;
1719
1720 if (!Message.Date.HasValue)
1721 Message.Date = DateTimeOffset.Now;
1722
1723 if (Message.MessageID is null)
1724 Message.MessageID = Guid.NewGuid().ToString();
1725
1726 await this.server.ProcessMessage(Message);
1727
1728 return Message.MessageID;
1729 }
1730
1731 private bool AddRecipients(ref List<MailAddress> AddressList, string s)
1732 {
1733 if (AddressList is null)
1734 AddressList = new List<MailAddress>();
1735
1736 MailAddress[] Addresses = this.ParseMailAddresses(s);
1737 if (Addresses is null)
1738 return false;
1739 else
1740 {
1741 AddressList.AddRange(Addresses);
1742 return true;
1743 }
1744 }
1745
1746 internal static int Next(int MaxValue)
1747 {
1748 lock (rnd)
1749 {
1750 return rnd.Next(MaxValue);
1751 }
1752 }
1753
1754 }
1755}
Helps with parsing of commong data types.
Definition: CommonTypes.cs:13
static KeyValuePair< string, string >[] ParseFieldValues(string Value)
Parses a set of comma or semicolon-separated field values, optionaly delimited by ' or " characters.
Definition: CommonTypes.cs:472
static bool TryParseRfc822(string s, out DateTimeOffset Value)
Parses a date and time value encoded according to RFC 822, §5.
Definition: CommonTypes.cs:170
static bool TryParse(string s, out double Value)
Tries to decode a string encoded double.
Definition: CommonTypes.cs:46
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
Definition: MixedContent.cs:7
EmbeddedContent[] Content
Embedded content.
Static class managing loading of resources stored as embedded resources or in content files.
Definition: Resources.cs:15
static Task WriteAllBytesAsync(string FileName, byte[] Data)
Creates a binary file asynchronously.
Definition: Resources.cs:216
static Task WriteAllTextAsync(string FileName, string Text)
Creates a text file asynchronously.
Definition: Resources.cs:267
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:13
static string CleanStackTrace(string StackTrace)
Cleans a Stack Trace string, removing entries from the asynchronous execution model,...
Definition: Log.cs:184
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.
Definition: Log.cs:1647
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:566
static Exception UnnestException(Exception Exception)
Unnests an exception, to extract the relevant inner exception.
Definition: Log.cs:818
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:
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:765
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.
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.
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:44
CaseInsensitiveString Domain
Domain name.
Definition: SmtpServer.cs:284
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:292
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.
Definition: Types.cs:14
static bool TryGetModuleParameter(string Name, out object Value)
Tries to get a module parameter value.
Definition: Types.cs:583
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 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:
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 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:11
ContentDisposition
Content disposition
SmtpConnectionState
State of SMTP connection.
Priority
Mail priority
Definition: Priority.cs:7
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:12
SpfResult
Result of a SPF (Sender Policy Framework) evaluation.
Definition: SpfResult.cs:11