Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
SmtpServer.cs
1using System;
3using System.Net;
4using System.Net.NetworkInformation;
6using System.Security.Cryptography.X509Certificates;
7using System.Text;
8using System.Text.RegularExpressions;
9using System.Threading.Tasks;
10using Waher.Content;
12using Waher.Events;
21using Waher.Security;
23
25{
44 public class SmtpServer : IDisposable, ITlsCertificateEndpoint
45 {
49 public const int DefaultSmtpPort = 25;
50
54 public const int DefaultSmtpRelayPort = 587;
55
59 public const int DefaultConnectionBacklog = 10;
60
64 public const int DefaultBufferSize = 16384;
65
69 public const string SmtpRelayPrivilegeID = "SMTP.Relay";
70
71 private static string salutation = null;
72 private static DateTime salutationExpires = DateTime.MinValue;
73
74 private LinkedList<TcpListener> listeners = new LinkedList<TcpListener>();
75 private Cache<Guid, SmtpClientConnection> clientConnections;
77 private X509Certificate serverCertificate;
78 private readonly ISaslPersistenceLayer persistenceLayer;
79 private readonly CaseInsensitiveString domain;
80 private readonly bool encryptionRequired;
81 private readonly int maxMessageSize;
82 private readonly string[] ip4DnsBlackLists;
83 private readonly string[] ip6DnsBlackLists;
84 private readonly SpfExpression[] spfExpressions;
85 private string smtpSnifferPath = null;
86 private bool disposed = false;
87 private readonly CommunicationLayer externalSniffers = new CommunicationLayer(false);
88
89 private string[] relayDomains = null;
90 private Regex[] relayDomainsEx = null;
91 private string relayHost = string.Empty;
92 private string relayUserName = string.Empty;
93 private string relayPassword = string.Empty;
94 private int relayPort = 587;
95 private bool useRelayServer = false;
96 private bool relayLocked = false;
97
98 #region Constructors
99
111 public SmtpServer(CaseInsensitiveString Domain, int MaxMessageSize,
112 X509Certificate ServerCertificate, bool EncryptionRequired, ISaslPersistenceLayer PersistenceLayer,
113 string[] Ip4DnsBlackLists, string[] Ip6DnsBlackLists, SpfExpression[] SpfExpressions)
114 : this(Domain, new int[] { DefaultSmtpPort, DefaultSmtpRelayPort }, MaxMessageSize, ServerCertificate,
115 EncryptionRequired, PersistenceLayer, Ip4DnsBlackLists, Ip6DnsBlackLists, SpfExpressions)
116 {
117 }
118
131 public SmtpServer(CaseInsensitiveString Domain, int Port, int MaxMessageSize,
132 X509Certificate ServerCertificate, bool EncryptionRequired, ISaslPersistenceLayer PersistenceLayer,
133 string[] Ip4DnsBlackLists, string[] Ip6DnsBlackLists, SpfExpression[] SpfExpressions)
134 : this(Domain, new int[] { Port }, MaxMessageSize, ServerCertificate, EncryptionRequired, PersistenceLayer,
135 Ip4DnsBlackLists, Ip6DnsBlackLists, SpfExpressions)
136 {
137 }
138
151 public SmtpServer(CaseInsensitiveString Domain, int[] Ports, int MaxMessageSize, X509Certificate ServerCertificate,
152 bool EncryptionRequired, ISaslPersistenceLayer PersistenceLayer, string[] Ip4DnsBlackLists, string[] Ip6DnsBlackLists,
153 SpfExpression[] SpfExpressions)
154 {
155 this.persistenceLayer = PersistenceLayer;
156 this.serverCertificate = ServerCertificate;
157 this.encryptionRequired = EncryptionRequired;
158 this.domain = Domain;
159 this.maxMessageSize = MaxMessageSize;
160 this.ip4DnsBlackLists = Ip4DnsBlackLists;
161 this.ip6DnsBlackLists = Ip6DnsBlackLists;
162 this.spfExpressions = SpfExpressions;
163
164 if (EncryptionRequired && this.serverCertificate is null)
165 throw new ArgumentException("Server Certificate must be provided, if encryption is required.", nameof(ServerCertificate));
166
167 this.clientConnections = new Cache<Guid, SmtpClientConnection>(int.MaxValue, TimeSpan.MaxValue, TimeSpan.FromMinutes(2), true);
168 this.clientConnections.Removed += this.ClientConnections_Removed;
169
170 this.Initialize(Ports);
171 }
172
173 private void Initialize(int[] Ports)
174 {
175 try
176 {
177 TcpListener Listener;
178
179 foreach (NetworkInterface Interface in NetworkInterface.GetAllNetworkInterfaces())
180 {
181 if (Interface.OperationalStatus != OperationalStatus.Up)
182 continue;
183
184 IPInterfaceProperties Properties = Interface.GetIPProperties();
185
186 foreach (UnicastIPAddressInformation UnicastAddress in Properties.UnicastAddresses)
187 {
188 if ((UnicastAddress.Address.AddressFamily == AddressFamily.InterNetwork && Socket.OSSupportsIPv4) ||
189 (UnicastAddress.Address.AddressFamily == AddressFamily.InterNetworkV6 && Socket.OSSupportsIPv6))
190 {
191 if (!(Ports is null))
192 {
193 foreach (int Port in Ports)
194 {
195 try
196 {
197 this.externalSniffers.Information("Opening port " + Port.ToString() + " on " + UnicastAddress.Address.ToString() + ".");
198
199 Listener = new TcpListener(UnicastAddress.Address, Port);
200 Listener.Start(DefaultConnectionBacklog);
201 Listener.BeginAcceptTcpClient(this.AcceptTcpClientCallback, Listener);
202 this.listeners.AddLast(Listener);
203
204 this.externalSniffers.Information("Port " + Port.ToString() + " on " + UnicastAddress.Address.ToString() + " opened.");
205 }
206 catch (Exception ex)
207 {
208 Log.Exception(ex, UnicastAddress.Address.ToString() + ":" + Port);
209 }
210 }
211 }
212 }
213 }
214 }
215 }
216 catch (Exception ex)
217 {
218 Log.Exception(ex);
219 }
220 }
221
225 public CommunicationLayer ExternalSniffers => this.externalSniffers;
226
231 public string SmtpSnifferPath
232 {
233 get => this.smtpSnifferPath;
234 set => this.smtpSnifferPath = value;
235 }
236
237 private async Task ClientConnections_Removed(object Sender, CacheItemEventArgs<Guid, SmtpClientConnection> e)
238 {
239 try
240 {
241 await this.ClientConnectionRemoved.Raise(this, new ClientConnectionEventArgs(e.Value));
242 await e.Value.DisposeAsync();
243 }
244 catch (Exception ex)
245 {
246 Log.Exception(ex);
247 }
248 }
249
254 {
255 get => this.clientConnections.Count;
256 }
257
263 {
264 List<SmtpClientConnection> Connections = new List<SmtpClientConnection>();
265
266 foreach (Guid Id in this.clientConnections.GetKeys())
267 {
268 if (this.clientConnections.TryGetValue(Id, out SmtpClientConnection Connection))
269 Connections.Add(Connection);
270 }
271
272 Connections.Sort((c1, c2) =>
273 {
274 return c1.UserName.CompareTo(c2.UserName);
275 });
276
277 return Connections.ToArray();
278 }
279
286 public bool TryGetClientConnection(Guid ID, out SmtpClientConnection Connection)
287 {
288 return this.clientConnections.TryGetValue(ID, out Connection);
289 }
290
294 public CaseInsensitiveString Domain => this.domain;
295
299 public X509Certificate ServerCertificate => this.serverCertificate;
300
305 public void UpdateCertificate(X509Certificate ServerCertificate)
306 {
307 this.serverCertificate = ServerCertificate;
308 }
309
313 public bool EncryptionRequired => this.encryptionRequired;
314
315 internal ISaslPersistenceLayer PersistenceLayer => this.persistenceLayer;
316 internal string[] Ip4DnsBlackLists => this.ip4DnsBlackLists;
317 internal string[] Ip6DnsBlackLists => this.ip6DnsBlackLists;
318 internal SpfExpression[] SpfExpressions => this.spfExpressions;
319
323 public void Dispose()
324 {
325 this.disposed = true;
326
327 if (!(this.clientConnections is null))
328 {
329 this.clientConnections.Clear();
330 this.clientConnections.Dispose();
331 this.clientConnections = null;
332 }
333
334 if (!(this.listeners is null))
335 {
336 LinkedList<TcpListener> Listeners = this.listeners;
337 this.listeners = null;
338
339 foreach (TcpListener Listener in Listeners)
340 Listener.Stop();
341 }
342
343 if (!(this.sniffers is null))
344 {
345 this.sniffers.Clear();
346 this.sniffers.Dispose();
347 this.sniffers = null;
348 }
349
350 if (!(this.externalSniffers is null))
351 {
352 foreach (ISniffer Sniffer in this.externalSniffers)
353 (Sniffer as IDisposable)?.Dispose();
354 }
355 }
356
360 public int[] OpenPorts
361 {
362 get
363 {
364 return this.GetOpenPorts(this.listeners);
365 }
366 }
367
368 private int[] GetOpenPorts(LinkedList<TcpListener> Listeners)
369 {
370 SortedDictionary<int, bool> Open = new SortedDictionary<int, bool>();
371
372 if (!(Listeners is null))
373 {
374 IPEndPoint IPEndPoint;
375
376 foreach (TcpListener Listener in Listeners)
377 {
378 IPEndPoint = Listener.LocalEndpoint as IPEndPoint;
379 if (!(IPEndPoint is null))
380 Open[IPEndPoint.Port] = true;
381 }
382 }
383
384 int[] Result = new int[Open.Count];
385 Open.Keys.CopyTo(Result, 0);
386
387 return Result;
388 }
389
390 #endregion
391
392 #region Connections
393
394 private async void AcceptTcpClientCallback(IAsyncResult ar)
395 {
396 try
397 {
398 if (this.disposed || NetworkingModule.Stopping)
399 return;
400
401 TcpListener Listener = (TcpListener)ar.AsyncState;
402
403 try
404 {
405 TcpClient Client = Listener.EndAcceptTcpClient(ar);
406 SmtpClientConnection ClientConnection;
407 ISniffer[] Sniffers;
408
409 if (!string.IsNullOrEmpty(this.smtpSnifferPath))
410 Sniffers = new ISniffer[] { new InMemorySniffer("SMTP In-memory Sniffer") };
411 else if (this.externalSniffers.HasSniffers)
412 Sniffers = this.externalSniffers.Sniffers;
413 else
414 Sniffers = Array.Empty<ISniffer>();
415
416 BinaryTcpClient BinaryTcpClient = new BinaryTcpClient(Client, false, false, Sniffers);
417 BinaryTcpClient.Bind(true);
418
419 ClientConnection = new SmtpClientConnection(BinaryTcpClient, this, this.persistenceLayer, this.maxMessageSize, Sniffers);
420 ClientConnection.Information("Connection accepted from " + BinaryTcpClient.RemoteEndPoint + ".");
421
422 this.clientConnections[ClientConnection.ID] = ClientConnection;
423
424 await this.ClientConnectionAdded.Raise(this, new ClientConnectionEventArgs(ClientConnection));
425
427 await ClientConnection.BeginWrite("220 " + this.domain + " ESMTP Sendmail ...\r\n", null, null);
428 }
429 finally
430 {
431 if (!this.disposed)
432 Listener.BeginAcceptTcpClient(this.AcceptTcpClientCallback, Listener);
433 }
434 }
435 catch (SocketException)
436 {
437 // Ignore
438 }
439 catch (ObjectDisposedException)
440 {
441 // Ignore
442 }
443 catch (NullReferenceException)
444 {
445 // Ignore
446 }
447 catch (Exception ex)
448 {
449 if (this.listeners is null)
450 return;
451
452 Log.Exception(ex);
453 }
454 }
455
456 internal string GetTransformPath()
457 {
458 foreach (ISniffer Sniffer in this.externalSniffers.Sniffers)
459 {
460 if (Sniffer is XmlFileSniffer XmlFileSniffer)
462 }
463
464 return null;
465 }
466
467 internal void Closed(SmtpClientConnection Connection)
468 {
469 this.clientConnections?.Remove(Connection.ID);
470 }
471
472 #endregion
473
474 #region Accounts
475
481 internal Task<IAccount> GetAccount(CaseInsensitiveString UserName)
482 {
483 return this.persistenceLayer.GetAccount(UserName);
484 }
485
489 public event EventHandlerAsync<ClientConnectionEventArgs> ClientConnectionAdded = null;
490
494 public event EventHandlerAsync<ClientConnectionEventArgs> ClientConnectionRemoved = null;
495
496 #endregion
497
498 #region Messages
499
503 public event EventHandlerAsync<SmtpMessageEventArgs> MessageReceived = null;
504
505 internal Task ProcessMessage(SmtpMessage Message)
506 {
507 return this.MessageReceived.Raise(this, new SmtpMessageEventArgs(Message));
508 }
509
510 #endregion
511
512 #region Sniffers
513
514 internal XmlFileSniffer GetSniffer(string Key)
515 {
516 string FileName;
517
518 FileName = this.smtpSnifferPath.Replace("%ENDPOINT%", Key);
519
520 if (!(this.sniffers is null) && this.sniffers.TryGetValue(FileName, out XmlFileSniffer XmlFileSniffer))
521 return XmlFileSniffer;
522
523 return new XmlFileSniffer(FileName, this.GetTransformPath(), 7, BinaryPresentationMethod.ByteCount);
524 }
525
526 internal async Task CacheSniffers(IEnumerable<ISniffer> Sniffers)
527 {
528 foreach (ISniffer Sniffer in Sniffers)
529 {
530 if (Sniffer is XmlFileSniffer XmlFileSniffer)
531 this.CacheSniffer(XmlFileSniffer);
532 else if (Sniffer is IDisposableAsync DisposableAsync)
533 await DisposableAsync.DisposeAsync();
534 else if (Sniffer is IDisposable Disposable)
535 Disposable.Dispose();
536 }
537 }
538
539 internal void CacheSniffer(XmlFileSniffer Sniffer)
540 {
541 if (this.sniffers is null)
542 {
543 this.sniffers = new Cache<CaseInsensitiveString, XmlFileSniffer>(int.MaxValue, TimeSpan.MaxValue, new TimeSpan(1, 1, 0), true);
544 this.sniffers.Removed += this.Sniffers_Removed;
545 }
546
547 this.sniffers.Add(Sniffer.FileName, Sniffer);
548 }
549
550 private Task Sniffers_Removed(object Sender, CacheItemEventArgs<CaseInsensitiveString, XmlFileSniffer> e)
551 {
552 if (this.disposed || (DateTime.Now - e.Value.LastEvent).TotalMinutes > 30)
553 return e.Value.DisposeAsync();
554 else
555 return Task.CompletedTask;
556 }
557
558 #endregion
559
560 #region Relay
561
572 public void SetRelaySettings(bool UseRelayServer, string HostName, int PortNumber,
573 string UserName, string Password, string[] RelayDomains, bool LockSettings)
574 {
575 if (this.useRelayServer != UseRelayServer ||
576 this.relayHost != HostName ||
577 this.relayPort != PortNumber ||
578 this.relayUserName != UserName ||
579 this.relayPassword != Password ||
580 !AreSame(this.relayDomains, RelayDomains))
581 {
582 if (this.relayLocked)
583 throw new InvalidOperationException("Relay settings locked.");
584
585 this.useRelayServer = UseRelayServer;
586 this.relayHost = HostName;
587 this.relayPort = PortNumber;
588 this.relayUserName = UserName;
589 this.relayPassword = Password;
590 this.relayDomains = RelayDomains;
591 this.relayDomainsEx = null;
592 }
593
594 if (LockSettings)
595 this.relayLocked = true;
596 }
597
598 private static bool AreSame(string[] A1, string[] A2)
599 {
600 if ((A1 is null) ^ (A2 is null))
601 return false;
602
603 if (A1 is null)
604 return true;
605
606 int c = A1.Length;
607 if (c != A2.Length)
608 return false;
609
610 int i;
611
612 for (i = 0; i < c; i++)
613 {
614 if (A1[i] != A2[i])
615 return false;
616 }
617
618 return true;
619 }
620
626 public bool CanRelayForDomain(string Domain)
627 {
628 if (this.relayDomains is null)
629 return false;
630
631 int i, c = this.relayDomains.Length;
632
633 for (i = 0; i < c; i++)
634 {
635 string s = this.relayDomains[i];
636
637 if (string.Compare(s, Domain, true) == 0)
638 return true;
639
640 if (s.IndexOf('*') < 0)
641 continue;
642
643 this.relayDomainsEx ??= new Regex[this.relayDomains.Length];
644
645 if (this.relayDomainsEx[i] is null)
646 this.relayDomainsEx[i] = new Regex(Database.WildcardToRegex(s, "*"), RegexOptions.Singleline | RegexOptions.IgnoreCase);
647
648 Match M = this.relayDomainsEx[i].Match(Domain);
649 if (M.Success && M.Index == 0 && M.Length == Domain.Length)
650 return true;
651 }
652
653 return false;
654 }
655
664 string Subject, object[] AlternativeBodies)
665 {
666 int i, c = AlternativeBodies.Length;
667 EmbeddedContent[] Alternatives = new EmbeddedContent[c];
668
669 for (i = 0; i < c; i++)
670 {
671 ContentResponse P = await InternetContent.EncodeAsync(AlternativeBodies[i], Encoding.UTF8);
672 if (P.HasError)
673 return false;
674
675 byte[] EncodedBody = P.Encoded;
676 string BodyContentType = P.ContentType;
677 Alternatives[i] = new EmbeddedContent()
678 {
679 ContentType = BodyContentType,
680 TransferDecoded = EncodedBody
681 };
682 }
683
684 return await this.SendMessage(From, To, Subject, Alternatives, null);
685 }
686
695 public Task<bool> SendMessage(CaseInsensitiveString From, CaseInsensitiveString To, string Subject,
696 EmbeddedContent[] Alternatives, EmbeddedContent[] Attachments)
697 {
698 return this.SendMessage(From, To, Subject, string.Empty, Alternatives, Attachments);
699 }
700
711 public async Task<bool> SendMessage(CaseInsensitiveString From, CaseInsensitiveString To, string Subject, string MessageId,
712 EmbeddedContent[] Alternatives, EmbeddedContent[] Attachments)
713 {
714 ContentResponse P = await InternetContent.EncodeAsync(new ContentAlternatives(Alternatives), Encoding.UTF8);
715 if (P.HasError)
716 return false;
717
718 byte[] BodyBin = P.Encoded;
719 string ContentType = P.ContentType;
720
721 if (!(Attachments is null) && Attachments.Length > 0)
722 {
723 EmbeddedContent[] Mixed = new EmbeddedContent[Attachments.Length + 1];
724 Mixed[0] = new EmbeddedContent()
725 {
726 ContentType = ContentType,
727 Raw = BodyBin
728 };
729 Array.Copy(Attachments, 0, Mixed, 1, Attachments.Length);
730
731 P = await InternetContent.EncodeAsync(new MixedContent(Mixed), Encoding.UTF8);
732 if (P.HasError)
733 return false;
734
735 BodyBin = P.Encoded;
736 ContentType = P.ContentType;
737 }
738
739 DateTime Now = DateTime.Now;
740 List<KeyValuePair<string, string>> Headers = new List<KeyValuePair<string, string>>()
741 {
742 new KeyValuePair<string, string>("MIME-VERSION", "1.0"),
743 new KeyValuePair<string, string>("FROM", From),
744 new KeyValuePair<string, string>("TO", To),
745 new KeyValuePair<string, string>("SUBJECT", Subject),
746 new KeyValuePair<string, string>("DATE", CommonTypes.EncodeRfc822(Now)),
747 new KeyValuePair<string, string>("IMPORTANCE", "normal"),
748 new KeyValuePair<string, string>("X-PRIORITY", "3"),
749 new KeyValuePair<string, string>("MESSAGE-ID", string.IsNullOrEmpty(MessageId) ? Guid.NewGuid().ToString() : MessageId),
750 new KeyValuePair<string, string>("CONTENT-TYPE", ContentType)
751 };
752
753 return await this.SendMessage(From, To, Headers.ToArray(), BodyBin, Now);
754 }
755
765 public async Task<bool> SendMessage(CaseInsensitiveString From,
766 CaseInsensitiveString To, KeyValuePair<string, string>[] Headers,
767 byte[] Data, DateTime Start)
768 {
769 int i = To.IndexOf('@');
770 if (i < 0)
771 throw new ArgumentException("Invalid mail address: " + To, nameof(To));
772
773 string Domain = To.Substring(i + 1).Trim();
774 string UserName;
775 string Password;
776 string Host;
777 int Port;
778
779 if (this.useRelayServer)
780 {
781 Host = this.relayHost;
782 Port = this.relayPort;
783 UserName = this.relayUserName;
784 Password = this.relayPassword;
785 }
786 else
787 {
788 string[] Exchanges = await DnsResolver.TryLookupMailExchange(Domain);
789 if (Exchanges is null || Exchanges.Length == 0)
790 throw new ArgumentException("No mail exchange at " + Domain + ".", nameof(To));
791
792 Host = Exchanges[SmtpClientConnection.Next(Exchanges.Length)];
793 Port = DefaultSmtpPort;
794 UserName = null;
795 Password = null;
796 }
797
798 return await this.SendMessage(Domain, Host, Port, UserName, Password, From, To, Headers, Data, Start);
799 }
800
815 public async Task<bool> SendMessage(string Domain, string Host, int Port,
816 string UserName, string Password, CaseInsensitiveString From,
817 CaseInsensitiveString To, KeyValuePair<string, string>[] Headers, byte[] Data,
818 DateTime Start)
819 {
820 Exception Exception = null;
821 bool Retry = false;
822
823 try
824 {
825 using SimpleSmtpClient Client = new SimpleSmtpClient(Domain, Host, Port, UserName, Password,
826 this.GetSniffer(Host + " OUT"));
827
828 await Client.Connect();
829 await Client.EHLO(await GetSalutation(this.domain)); // Also performs Encryption & Authentication handshakes, as required.
830 await Client.MAIL_FROM(From);
831 await Client.RCPT_TO(To);
832 await Client.DATA(Headers, Data);
833 await Client.QUIT();
834
835 return true;
836 }
837 catch (TimeoutException ex)
838 {
839 Exception = ex;
840 Retry = true;
841 }
843 {
844 Exception = ex;
845 Retry = true;
846 }
847 catch (Exception ex)
848 {
849 Exception = ex;
850 Retry = false;
851 }
852
853 if (!Retry ||
855 {
856 Log.Error("Unable to send message.\r\n\r\n" + Exception?.Message,
857 new KeyValuePair<string, object>("From", From),
858 new KeyValuePair<string, object>("To", To));
859
860 // TODO: Mail error message back to sender.
861
862 return false;
863 }
864
865 DateTime TP = DateTime.Now;
866 double Minutes = (TP - Start).TotalMinutes;
867
868 if (Minutes >= 24 * 60)
869 return false;
870
871 if (Minutes < 5)
872 TP = TP.AddMinutes(1);
873 else if (Minutes < 60)
874 TP = TP.AddMinutes(5);
875 else
876 TP = TP.AddMinutes(15);
877
878 // TODO: Mail message (only once) back to sender saying a temporary error occurred, but that new attempts will be made.
879 // (make configurable/controllable by argument)
880
881 Scheduler.Add(TP, this.Resend, new object[] { Domain, Host, Port, UserName, Password, From, To, Headers, Data, Start });
882
883 return true;
884 }
885
886 private async void Resend(object State)
887 {
888 try
889 {
890 object[] P = (object[])State;
891 string Domain = (string)P[0];
892 string Host = (string)P[1];
893 int Port = (int)P[2];
894 string UserName = (string)P[3];
895 string Password = (string)P[4];
898 KeyValuePair<string, string>[] Headers = (KeyValuePair<string, string>[])P[7];
899 byte[] Data = (byte[])P[8];
900 DateTime Start = (DateTime)P[9];
901
902 await this.SendMessage(Domain, Host, Port, UserName, Password, From, To, Headers, Data, Start);
903 }
904 catch (Exception ex)
905 {
906 Log.Exception(ex);
907 }
908 }
909
915 public static async Task<string> GetSalutation(string DefaultDomain)
916 {
917 if (salutationExpires < DateTime.Now)
918 salutation = null;
919
920 if (salutation is null)
921 {
922 try
923 {
924 foreach (NetworkInterface Interface in NetworkInterface.GetAllNetworkInterfaces())
925 {
926 if (Interface.OperationalStatus != OperationalStatus.Up)
927 continue;
928
929 IPInterfaceProperties Properties = Interface.GetIPProperties();
930
931 foreach (UnicastIPAddressInformation UnicastAddress in Properties.UnicastAddresses)
932 {
933 if (UnicastAddress.Address.AddressFamily == AddressFamily.InterNetwork && Socket.OSSupportsIPv4)
934 {
935 if (IsPublicAddress(UnicastAddress.Address))
936 {
937 try
938 {
939 string AddrStr = UnicastAddress.Address.ToString();
940 string[] Names = await DnsResolver.TryLookupDomainName(UnicastAddress.Address);
941
942 if (!(Names is null))
943 {
944 foreach (string Name in Names)
945 {
946 try
947 {
948 IPAddress[] Addresses = await DnsResolver.TryLookupIP4Addresses(Name);
949
950 if (!(Addresses is null))
951 {
952 foreach (IPAddress Addr in Addresses)
953 {
954 if (Addr.ToString() == AddrStr)
955 {
956 salutation = Name;
957 break;
958 }
959 }
960 }
961
962 if (!(salutation is null))
963 break;
964 }
965 catch (Exception)
966 {
967 // Ignore
968 }
969 }
970 }
971
972 if (!(salutation is null))
973 break;
974 }
975 catch (Exception)
976 {
977 // Ignore
978 }
979 }
980 }
981 }
982
983 if (!(salutation is null))
984 break;
985 }
986 }
987 catch (Exception)
988 {
989 // Ignore
990 }
991
992 salutation ??= DefaultDomain;
993
994 salutationExpires = DateTime.Now.AddHours(1);
995 }
996
997 return salutation;
998 }
999
1005 public static bool IsPublicAddress(IPAddress Address)
1006 {
1007 if (Address.AddressFamily == AddressFamily.InterNetwork && Socket.OSSupportsIPv4)
1008 {
1009 byte[] Addr = Address.GetAddressBytes();
1010
1011 if (Addr[0] == 127)
1012 return false; // Loopback address range: 127.0.0.0 - 127.255.255.55
1013
1014 else if (Addr[0] == 10)
1015 return false; // Private address range: 10.0.0.0 - 10.255.255.55
1016
1017 else if (Addr[0] == 172 && Addr[1] >= 16 && Addr[1] <= 31)
1018 return false; // Private address range: 172.16.0.0 - 172.31.255.255
1019
1020 else if (Addr[0] == 192 && Addr[1] == 168)
1021 return false; // Private address range: 192.168.0.0 - 192.168.255.255
1022
1023 else if (Addr[0] == 169 && Addr[1] == 254)
1024 return false; // Link-local address range: 169.254.0.0 - 169.254.255.255
1025
1026 return true;
1027 }
1028 else
1029 return false;
1030 }
1031 #endregion
1032
1033 }
1034}
Helps with parsing of commong data types.
Definition: CommonTypes.cs:15
static string EncodeRfc822(DateTime Timestamp)
Encodes a date and time, according to RFC 822 §5.
Definition: CommonTypes.cs:669
Contains information about a response to a content request.
byte[] Encoded
Encoded object.
string ContentType
Internet Content-Type of encoded object.
bool HasError
If an error occurred.
Static class managing encoding and decoding of internet content.
static Task< ContentResponse > EncodeAsync(object Object, Encoding Encoding, params string[] AcceptedContentTypes)
Encodes an object.
Represents alternative versions of the same content, encoded with multipart/alternative
Represents content embedded in other content.
Represents mixed content, encoded with multipart/mixed
Definition: MixedContent.cs:7
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:14
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:1657
static void Error(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs an error event.
Definition: Log.cs:692
Implements a binary TCP Client, by encapsulating a TcpClient. It also makes the use of TcpClient safe...
void Bind()
Binds to a TcpClient that was already connected when provided to the constructor.
string RemoteEndPoint
Remote End-point of connection. This corresponds to the IP Endpoint of the remote party in normal cas...
void Continue()
Continues reading from the socket, if paused in an event handler.
Simple base class for classes implementing communication protocols.
DNS resolver, as defined in:
Definition: DnsResolver.cs:32
static Task< IPAddress[]> TryLookupIP4Addresses(string DomainName)
Tries to look up the IPv4 addresses related to a given domain name.
Definition: DnsResolver.cs:684
static Task< string[]> TryLookupMailExchange(string DomainName)
Tries to look up the Mail Exchanges related to a given domain name.
Definition: DnsResolver.cs:776
static Task< string[]> TryLookupDomainName(IPAddress Address)
Tries to look up the domain name pointing to a specific IP address.
Definition: DnsResolver.cs:909
Module that controls the life cycle of communication.
static bool Stopping
If the system is stopping.
Event arguments for SMTP Message events.
Represents one message received over SMTP
Definition: SmtpMessage.cs:13
Implements a simple SMTP Server, as defined in:
Definition: SmtpServer.cs:45
const int DefaultSmtpRelayPort
Secondary SMTP Port (587).
Definition: SmtpServer.cs:54
EventHandlerAsync< ClientConnectionEventArgs > ClientConnectionRemoved
Event raised when a client connection has been removed.
Definition: SmtpServer.cs:494
static bool IsPublicAddress(IPAddress Address)
Checks if an IPv4 address is public.
Definition: SmtpServer.cs:1005
SmtpServer(CaseInsensitiveString Domain, int MaxMessageSize, X509Certificate ServerCertificate, bool EncryptionRequired, ISaslPersistenceLayer PersistenceLayer, string[] Ip4DnsBlackLists, string[] Ip6DnsBlackLists, SpfExpression[] SpfExpressions)
Creates an instance of an SMTP server.
Definition: SmtpServer.cs:111
static async Task< string > GetSalutation(string DefaultDomain)
Gets the proper salutation name for the server.
Definition: SmtpServer.cs:915
async Task< bool > SendMessage(CaseInsensitiveString From, CaseInsensitiveString To, string Subject, object[] AlternativeBodies)
Sends a mail message
Definition: SmtpServer.cs:663
async Task< bool > SendMessage(string Domain, string Host, int Port, string UserName, string Password, CaseInsensitiveString From, CaseInsensitiveString To, KeyValuePair< string, string >[] Headers, byte[] Data, DateTime Start)
Sends a mail message
Definition: SmtpServer.cs:815
CommunicationLayer ExternalSniffers
External Sniffers for SMTP communication.
Definition: SmtpServer.cs:225
const int DefaultConnectionBacklog
Default Connection backlog (10).
Definition: SmtpServer.cs:59
int NrClientConnections
Number of client connections.
Definition: SmtpServer.cs:254
async Task< bool > SendMessage(CaseInsensitiveString From, CaseInsensitiveString To, string Subject, string MessageId, EmbeddedContent[] Alternatives, EmbeddedContent[] Attachments)
Sends a mail message
Definition: SmtpServer.cs:711
SmtpServer(CaseInsensitiveString Domain, int[] Ports, int MaxMessageSize, X509Certificate ServerCertificate, bool EncryptionRequired, ISaslPersistenceLayer PersistenceLayer, string[] Ip4DnsBlackLists, string[] Ip6DnsBlackLists, SpfExpression[] SpfExpressions)
Implements an SMTP server.
Definition: SmtpServer.cs:151
EventHandlerAsync< ClientConnectionEventArgs > ClientConnectionAdded
Event raised when a client connection has been added.
Definition: SmtpServer.cs:489
bool TryGetClientConnection(Guid ID, out SmtpClientConnection Connection)
Tries to get a client connection, given its identifier.
Definition: SmtpServer.cs:286
CaseInsensitiveString Domain
Domain name.
Definition: SmtpServer.cs:294
int[] OpenPorts
Ports successfully opened.
Definition: SmtpServer.cs:361
bool EncryptionRequired
If C2S encryption is requried.
Definition: SmtpServer.cs:313
void UpdateCertificate(X509Certificate ServerCertificate)
Updates the server certificate
Definition: SmtpServer.cs:305
SmtpClientConnection[] GetClientConnections()
Gets an array of available client connection.s
Definition: SmtpServer.cs:262
Task< bool > SendMessage(CaseInsensitiveString From, CaseInsensitiveString To, string Subject, EmbeddedContent[] Alternatives, EmbeddedContent[] Attachments)
Sends a mail message
Definition: SmtpServer.cs:695
void SetRelaySettings(bool UseRelayServer, string HostName, int PortNumber, string UserName, string Password, string[] RelayDomains, bool LockSettings)
Sets mail relay settings.
Definition: SmtpServer.cs:572
void Dispose()
IDisposable.Dispose
Definition: SmtpServer.cs:323
async Task< bool > SendMessage(CaseInsensitiveString From, CaseInsensitiveString To, KeyValuePair< string, string >[] Headers, byte[] Data, DateTime Start)
Sends a mail message
Definition: SmtpServer.cs:765
string SmtpSnifferPath
If separate sniffers are to be created for each connected client, set this property to the file path ...
Definition: SmtpServer.cs:232
SmtpServer(CaseInsensitiveString Domain, int Port, int MaxMessageSize, X509Certificate ServerCertificate, bool EncryptionRequired, ISaslPersistenceLayer PersistenceLayer, string[] Ip4DnsBlackLists, string[] Ip6DnsBlackLists, SpfExpression[] SpfExpressions)
Creates an instance of an SMTP server.
Definition: SmtpServer.cs:131
const string SmtpRelayPrivilegeID
SmtpRelay
Definition: SmtpServer.cs:69
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
const int DefaultSmtpPort
Default SMTP Port (25).
Definition: SmtpServer.cs:49
const int DefaultBufferSize
Default buffer size (16384).
Definition: SmtpServer.cs:64
Sniffer that stores events in memory.
Outputs sniffed data to an XML file.
Represents a case-insensitive string.
int IndexOf(CaseInsensitiveString value, StringComparison comparisonType)
Reports the zero-based index of the first occurrence of the specified string in the current System....
CaseInsensitiveString Trim()
Removes all leading and trailing white-space characters from the current CaseInsensitiveString object...
CaseInsensitiveString Substring(int startIndex, int length)
Retrieves a substring from this instance. The substring starts at a specified character position and ...
Static interface for database persistence. In order to work, a database provider has to be assigned t...
Definition: Database.cs:21
static string WildcardToRegex(string s, string Wildcard)
Converts a wildcard string to a regular expression string.
Definition: Database.cs:2426
Implements an in-memory cache.
Definition: Cache.cs:17
void Dispose()
IDisposable.Dispose
Definition: Cache.cs:99
int Count
Number of items in cache
Definition: Cache.cs:337
bool Remove(KeyType Key)
Removes an item from the cache.
Definition: Cache.cs:616
bool TryGetValue(KeyType Key, out ValueType Value)
Tries to get a value from the cache.
Definition: Cache.cs:311
KeyType[] GetKeys()
Gets all available keys in the cache.
Definition: Cache.cs:366
void Add(KeyType Key, ValueType Value)
Adds an item to the cache.
Definition: Cache.cs:446
void Clear()
Clears the cache.
Definition: Cache.cs:679
Event arguments for cache item removal events.
ValueType Value
Value of item that was removed.
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
Class that can be used to schedule events in time. It uses a timer to execute tasks at the appointed ...
Definition: Scheduler.cs:14
DateTime Add(DateTime When, Action< object > Callback, object State)
Adds an event.
Definition: Scheduler.cs:54
Contains information about a SPF string.
Interface for asynchronously disposable objects.
Interface for XMPP Server persistence layers. The persistence layer should implement caching.
Interface for sniffers. Sniffers can be added to ICommunicationLayer classes to eavesdrop on communic...
Definition: ISniffer.cs:10
Interface for Mutual TLS (mTLS) Clients or TLS servers.
BinaryPresentationMethod
How binary data is to be presented.