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;
2using System.Collections.Generic;
3using System.Net;
4using System.Net.NetworkInformation;
5using System.Net.Sockets;
6using System.Security.Cryptography.X509Certificates;
7using System.Text;
8using System.Text.RegularExpressions;
9using System.Threading.Tasks;
10using Waher.Content;
12using Waher.Events;
22
24{
43 public class SmtpServer : IDisposable
44 {
48 public const int DefaultSmtpPort = 25;
49
53 public const int DefaultSmtpRelayPort = 587;
54
58 public const int DefaultConnectionBacklog = 10;
59
63 public const int DefaultBufferSize = 16384;
64
68 public const string SmtpRelayPrivilegeID = "SMTP.Relay";
69
70 private static string salutation = null;
71 private static DateTime salutationExpires = DateTime.MinValue;
72
73 private LinkedList<TcpListener> listeners = new LinkedList<TcpListener>();
74 private Cache<Guid, SmtpClientConnection> clientConnections;
76 private X509Certificate serverCertificate;
77 private readonly ISaslPersistenceLayer persistenceLayer;
78 private readonly CaseInsensitiveString domain;
79 private readonly bool encryptionRequired;
80 private readonly int maxMessageSize;
81 private readonly string[] ip4DnsBlackLists;
82 private readonly string[] ip6DnsBlackLists;
83 private readonly SpfExpression[] spfExpressions;
84 private string smtpSnifferPath = null;
85 private bool disposed = false;
86 private readonly CommunicationLayer externalSniffers = new CommunicationLayer(false);
87
88 private string[] relayDomains = null;
89 private Regex[] relayDomainsEx = null;
90 private string relayHost = string.Empty;
91 private string relayUserName = string.Empty;
92 private string relayPassword = string.Empty;
93 private int relayPort = 587;
94 private bool useRelayServer = false;
95 private bool relayLocked = false;
96
97 #region Constructors
98
110 public SmtpServer(CaseInsensitiveString Domain, int MaxMessageSize,
111 X509Certificate ServerCertificate, bool EncryptionRequired, ISaslPersistenceLayer PersistenceLayer,
112 string[] Ip4DnsBlackLists, string[] Ip6DnsBlackLists, SpfExpression[] SpfExpressions)
113 : this(Domain, new int[] { DefaultSmtpPort, DefaultSmtpRelayPort }, MaxMessageSize, ServerCertificate,
114 EncryptionRequired, PersistenceLayer, Ip4DnsBlackLists, Ip6DnsBlackLists, SpfExpressions)
115 {
116 }
117
130 public SmtpServer(CaseInsensitiveString Domain, int Port, int MaxMessageSize,
131 X509Certificate ServerCertificate, bool EncryptionRequired, ISaslPersistenceLayer PersistenceLayer,
132 string[] Ip4DnsBlackLists, string[] Ip6DnsBlackLists, SpfExpression[] SpfExpressions)
133 : this(Domain, new int[] { Port }, MaxMessageSize, ServerCertificate, EncryptionRequired, PersistenceLayer,
134 Ip4DnsBlackLists, Ip6DnsBlackLists, SpfExpressions)
135 {
136 }
137
150 public SmtpServer(CaseInsensitiveString Domain, int[] Ports, int MaxMessageSize, X509Certificate ServerCertificate,
151 bool EncryptionRequired, ISaslPersistenceLayer PersistenceLayer, string[] Ip4DnsBlackLists, string[] Ip6DnsBlackLists,
152 SpfExpression[] SpfExpressions)
153 {
154 this.persistenceLayer = PersistenceLayer;
155 this.serverCertificate = ServerCertificate;
156 this.encryptionRequired = EncryptionRequired;
157 this.domain = Domain;
158 this.maxMessageSize = MaxMessageSize;
159 this.ip4DnsBlackLists = Ip4DnsBlackLists;
160 this.ip6DnsBlackLists = Ip6DnsBlackLists;
161 this.spfExpressions = SpfExpressions;
162
163 if (EncryptionRequired && this.serverCertificate is null)
164 throw new ArgumentException("Server Certificate must be provided, if encryption is required.", nameof(ServerCertificate));
165
166 this.clientConnections = new Cache<Guid, SmtpClientConnection>(int.MaxValue, TimeSpan.MaxValue, TimeSpan.FromMinutes(2), true);
167 this.clientConnections.Removed += this.ClientConnections_Removed;
168
169 this.Initialize(Ports);
170 }
171
172 private async void Initialize(int[] Ports)
173 {
174 try
175 {
176 TcpListener Listener;
177
178 foreach (NetworkInterface Interface in NetworkInterface.GetAllNetworkInterfaces())
179 {
180 if (Interface.OperationalStatus != OperationalStatus.Up)
181 continue;
182
183 IPInterfaceProperties Properties = Interface.GetIPProperties();
184
185 foreach (UnicastIPAddressInformation UnicastAddress in Properties.UnicastAddresses)
186 {
187 if ((UnicastAddress.Address.AddressFamily == AddressFamily.InterNetwork && Socket.OSSupportsIPv4) ||
188 (UnicastAddress.Address.AddressFamily == AddressFamily.InterNetworkV6 && Socket.OSSupportsIPv6))
189 {
190 if (!(Ports is null))
191 {
192 foreach (int Port in Ports)
193 {
194 try
195 {
196 await this.externalSniffers.Information("Opening port " + Port.ToString() + " on " + UnicastAddress.Address.ToString() + ".");
197
198 Listener = new TcpListener(UnicastAddress.Address, Port);
199 Listener.Start(DefaultConnectionBacklog);
200 Listener.BeginAcceptTcpClient(this.AcceptTcpClientCallback, Listener);
201 this.listeners.AddLast(Listener);
202
203 await this.externalSniffers.Information("Port " + Port.ToString() + " on " + UnicastAddress.Address.ToString() + " opened.");
204 }
205 catch (Exception ex)
206 {
207 Log.Exception(ex, UnicastAddress.Address.ToString() + ":" + Port);
208 }
209 }
210 }
211 }
212 }
213 }
214 }
215 catch (Exception ex)
216 {
217 Log.Exception(ex);
218 }
219 }
220
224 public CommunicationLayer ExternalSniffers => this.externalSniffers;
225
230 public string SmtpSnifferPath
231 {
232 get => this.smtpSnifferPath;
233 set => this.smtpSnifferPath = value;
234 }
235
236 private async Task ClientConnections_Removed(object Sender, CacheItemEventArgs<Guid, SmtpClientConnection> e)
237 {
238 try
239 {
240 await this.ClientConnectionRemoved.Raise(this, new ClientConnectionEventArgs(e.Value));
241 await e.Value.DisposeAsync();
242 }
243 catch (Exception ex)
244 {
245 Log.Exception(ex);
246 }
247 }
248
253 {
254 get => this.clientConnections.Count;
255 }
256
257 public SmtpClientConnection[] GetClientConnections()
258 {
259 List<SmtpClientConnection> Connections = new List<SmtpClientConnection>();
260
261 foreach (Guid Id in this.clientConnections.GetKeys())
262 {
263 if (this.clientConnections.TryGetValue(Id, out SmtpClientConnection Connection))
264 Connections.Add(Connection);
265 }
266
267 Connections.Sort((c1, c2) =>
268 {
269 return c1.UserName.CompareTo(c2.UserName);
270 });
271
272 return Connections.ToArray();
273 }
274
275 public bool TryGetClientConnection(Guid ID, out SmtpClientConnection Connection)
276 {
277 return this.clientConnections.TryGetValue(ID, out Connection);
278 }
279
284 {
285 get => this.domain;
286 }
287
291 public X509Certificate ServerCertificate
292 {
293 get => this.serverCertificate;
294 }
295
300 public void UpdateCertificate(X509Certificate ServerCertificate)
301 {
302 this.serverCertificate = ServerCertificate;
303 }
304
309 {
310 get => this.encryptionRequired;
311 }
312
313 internal ISaslPersistenceLayer PersistenceLayer
314 {
315 get => this.persistenceLayer;
316 }
317
318 internal string[] Ip4DnsBlackLists => this.ip4DnsBlackLists;
319 internal string[] Ip6DnsBlackLists => this.ip6DnsBlackLists;
320 internal SpfExpression[] SpfExpressions => this.spfExpressions;
321
325 public void Dispose()
326 {
327 this.disposed = true;
328
329 if (!(this.clientConnections is null))
330 {
331 this.clientConnections.Clear();
332 this.clientConnections.Dispose();
333 this.clientConnections = null;
334 }
335
336 if (!(this.listeners is null))
337 {
338 LinkedList<TcpListener> Listeners = this.listeners;
339 this.listeners = null;
340
341 foreach (TcpListener Listener in Listeners)
342 Listener.Stop();
343 }
344
345 if (!(this.sniffers is null))
346 {
347 this.sniffers.Clear();
348 this.sniffers.Dispose();
349 this.sniffers = null;
350 }
351
352 if (!(this.externalSniffers is null))
353 {
354 foreach (ISniffer Sniffer in this.externalSniffers)
355 (Sniffer as IDisposable)?.Dispose();
356 }
357 }
358
362 public int[] OpenPorts
363 {
364 get
365 {
366 return this.GetOpenPorts(this.listeners);
367 }
368 }
369
370 private int[] GetOpenPorts(LinkedList<TcpListener> Listeners)
371 {
372 SortedDictionary<int, bool> Open = new SortedDictionary<int, bool>();
373
374 if (!(Listeners is null))
375 {
376 IPEndPoint IPEndPoint;
377
378 foreach (TcpListener Listener in Listeners)
379 {
380 IPEndPoint = Listener.LocalEndpoint as IPEndPoint;
381 if (!(IPEndPoint is null))
382 Open[IPEndPoint.Port] = true;
383 }
384 }
385
386 int[] Result = new int[Open.Count];
387 Open.Keys.CopyTo(Result, 0);
388
389 return Result;
390 }
391
392 #endregion
393
394 #region Connections
395
396 private async void AcceptTcpClientCallback(IAsyncResult ar)
397 {
398 try
399 {
400 if (this.disposed || NetworkingModule.Stopping)
401 return;
402
403 TcpListener Listener = (TcpListener)ar.AsyncState;
404
405 try
406 {
407 TcpClient Client = Listener.EndAcceptTcpClient(ar);
408 SmtpClientConnection ClientConnection;
409 ISniffer[] Sniffers;
410
411 if (!string.IsNullOrEmpty(this.smtpSnifferPath))
412 Sniffers = new ISniffer[] { new InMemorySniffer() };
413 else if (this.externalSniffers.HasSniffers)
414 Sniffers = this.externalSniffers.Sniffers;
415 else
416 Sniffers = new ISniffer[0];
417
418 BinaryTcpClient BinaryTcpClient = new BinaryTcpClient(Client, false, false);
419 BinaryTcpClient.Bind(true);
420
421 ClientConnection = new SmtpClientConnection(BinaryTcpClient, this, this.persistenceLayer, this.maxMessageSize, Sniffers);
422 await ClientConnection.Information("Connection accepted from " + Client.Client.RemoteEndPoint.ToString() + ".");
423
424 this.clientConnections[ClientConnection.ID] = ClientConnection;
425
426 await this.ClientConnectionAdded.Raise(this, new ClientConnectionEventArgs(ClientConnection));
427
429 await ClientConnection.BeginWrite("220 " + this.domain + " ESMTP Sendmail ...\r\n", null, null);
430 }
431 finally
432 {
433 if (!this.disposed)
434 Listener.BeginAcceptTcpClient(this.AcceptTcpClientCallback, Listener);
435 }
436 }
437 catch (SocketException)
438 {
439 // Ignore
440 }
441 catch (ObjectDisposedException)
442 {
443 // Ignore
444 }
445 catch (NullReferenceException)
446 {
447 // Ignore
448 }
449 catch (Exception ex)
450 {
451 if (this.listeners is null)
452 return;
453
454 Log.Exception(ex);
455 }
456 }
457
458 internal string GetTransformPath()
459 {
460 foreach (ISniffer Sniffer in this.externalSniffers.Sniffers)
461 {
462 if (Sniffer is XmlFileSniffer XmlFileSniffer)
464 }
465
466 return null;
467 }
468
469 internal void Closed(SmtpClientConnection Connection)
470 {
471 this.clientConnections?.Remove(Connection.ID);
472 }
473
474 #endregion
475
476 #region Accounts
477
483 internal Task<IAccount> GetAccount(CaseInsensitiveString UserName)
484 {
485 return this.persistenceLayer.GetAccount(UserName);
486 }
487
491 public event EventHandlerAsync<ClientConnectionEventArgs> ClientConnectionAdded = null;
492
496 public event EventHandlerAsync<ClientConnectionEventArgs> ClientConnectionRemoved = null;
497
498 #endregion
499
500 #region Messages
501
505 public event EventHandlerAsync<SmtpMessageEventArgs> MessageReceived = null;
506
507 internal Task ProcessMessage(SmtpMessage Message)
508 {
509 return this.MessageReceived.Raise(this, new SmtpMessageEventArgs(Message));
510 }
511
512 #endregion
513
514 #region Sniffers
515
516 internal XmlFileSniffer GetSniffer(string Key)
517 {
518 string FileName;
519
520 FileName = this.smtpSnifferPath.Replace("%ENDPOINT%", Key);
521
522 if (!(this.sniffers is null) && this.sniffers.TryGetValue(FileName, out XmlFileSniffer XmlFileSniffer))
523 return XmlFileSniffer;
524
525 return new XmlFileSniffer(FileName, this.GetTransformPath(), 7, BinaryPresentationMethod.ByteCount);
526 }
527
528 internal void CacheSniffers(IEnumerable<ISniffer> Sniffers)
529 {
530 foreach (ISniffer Sniffer in Sniffers)
531 {
532 if (Sniffer is XmlFileSniffer XmlFileSniffer)
533 this.CacheSniffer(XmlFileSniffer);
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 e.Value.Dispose();
554
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 if (this.relayDomainsEx is null)
644 this.relayDomainsEx = new Regex[this.relayDomains.Length];
645
646 if (this.relayDomainsEx[i] is null)
647 this.relayDomainsEx[i] = new Regex(Database.WildcardToRegex(s, "*"), RegexOptions.Singleline | RegexOptions.IgnoreCase);
648
649 Match M = this.relayDomainsEx[i].Match(Domain);
650 if (M.Success && M.Index == 0 && M.Length == Domain.Length)
651 return true;
652 }
653
654 return false;
655 }
656
665 string Subject, object[] AlternativeBodies)
666 {
667 int i, c = AlternativeBodies.Length;
668 EmbeddedContent[] Alternatives = new EmbeddedContent[c];
669
670 for (i = 0; i < c; i++)
671 {
672 KeyValuePair<byte[], string> P = await InternetContent.EncodeAsync(AlternativeBodies[i], Encoding.UTF8);
673 byte[] EncodedBody = P.Key;
674 string BodyContentType = P.Value;
675 Alternatives[i] = new EmbeddedContent()
676 {
677 ContentType = BodyContentType,
678 TransferDecoded = EncodedBody
679 };
680 }
681
682 return await this.SendMessage(From, To, Subject, Alternatives, null);
683 }
684
692 public Task<bool> SendMessage(CaseInsensitiveString From, CaseInsensitiveString To, string Subject,
693 EmbeddedContent[] Alternatives, EmbeddedContent[] Attachments)
694 {
695 return this.SendMessage(From, To, Subject, string.Empty, Alternatives, Attachments);
696 }
697
708 public async Task<bool> SendMessage(CaseInsensitiveString From, CaseInsensitiveString To, string Subject, string MessageId,
709 EmbeddedContent[] Alternatives, EmbeddedContent[] Attachments)
710 {
711 KeyValuePair<byte[], string> P = await InternetContent.EncodeAsync(new ContentAlternatives(Alternatives), Encoding.UTF8);
712 byte[] BodyBin = P.Key;
713 string ContentType = P.Value;
714
715 if (!(Attachments is null) && Attachments.Length > 0)
716 {
717 EmbeddedContent[] Mixed = new EmbeddedContent[Attachments.Length + 1];
718 Mixed[0] = new EmbeddedContent()
719 {
720 ContentType = ContentType,
721 Raw = BodyBin
722 };
723 Array.Copy(Attachments, 0, Mixed, 1, Attachments.Length);
724
725 P = await InternetContent.EncodeAsync(new MixedContent(Mixed), Encoding.UTF8);
726 BodyBin = P.Key;
727 ContentType = P.Value;
728 }
729
730 DateTime Now = DateTime.Now;
731 List<KeyValuePair<string, string>> Headers = new List<KeyValuePair<string, string>>()
732 {
733 new KeyValuePair<string, string>("MIME-VERSION", "1.0"),
734 new KeyValuePair<string, string>("FROM", From),
735 new KeyValuePair<string, string>("TO", To),
736 new KeyValuePair<string, string>("SUBJECT", Subject),
737 new KeyValuePair<string, string>("DATE", CommonTypes.EncodeRfc822(Now)),
738 new KeyValuePair<string, string>("IMPORTANCE", "normal"),
739 new KeyValuePair<string, string>("X-PRIORITY", "3"),
740 new KeyValuePair<string, string>("MESSAGE-ID", string.IsNullOrEmpty(MessageId) ? Guid.NewGuid().ToString() : MessageId),
741 new KeyValuePair<string, string>("CONTENT-TYPE", ContentType)
742 };
743
744 return await this.SendMessage(From, To, Headers.ToArray(), BodyBin, Now);
745 }
746
758 public async Task<bool> SendMessage(CaseInsensitiveString From, CaseInsensitiveString To, KeyValuePair<string, string>[] Headers,
759 byte[] Data, DateTime Start)
760 {
761 int i = To.IndexOf('@');
762 if (i < 0)
763 throw new ArgumentException("Invalid mail address: " + To, nameof(To));
764
765 string Domain = To.Substring(i + 1).Trim();
766 string UserName;
767 string Password;
768 string Host;
769 int Port;
770
771 if (this.useRelayServer)
772 {
773 Host = this.relayHost;
774 Port = this.relayPort;
775 UserName = this.relayUserName;
776 Password = this.relayPassword;
777 }
778 else
779 {
780 string[] Exchanges = await DnsResolver.LookupMailExchange(Domain);
781 if (Exchanges is null || Exchanges.Length == 0)
782 throw new ArgumentException("No mail exchange at " + Domain + ".", nameof(To));
783
784 Host = Exchanges[SmtpClientConnection.Next(Exchanges.Length)];
785 Port = DefaultSmtpPort;
786 UserName = null;
787 Password = null;
788 }
789
790 return await this.SendMessage(Domain, Host, Port, UserName, Password, From, To, Headers, Data, Start);
791 }
792
804 public async Task<bool> SendMessage(string Domain, string Host, int Port, string UserName, string Password,
805 CaseInsensitiveString From, CaseInsensitiveString To, KeyValuePair<string, string>[] Headers, byte[] Data, DateTime Start)
806 {
807 Exception Exception = null;
808 bool Retry = false;
809
810 try
811 {
812 using (SimpleSmtpClient Client = new SimpleSmtpClient(Domain, Host, Port, UserName, Password,
813 this.GetSniffer(Host + " OUT")))
814 {
815 await Client.Connect();
816 await Client.EHLO(await GetSalutation(this.domain)); // Also performs Encryption & Authentication handshakes, as required.
817 await Client.MAIL_FROM(From);
818 await Client.RCPT_TO(To);
819 await Client.DATA(Headers, Data);
820 await Client.QUIT();
821 }
822
823 return true;
824 }
825 catch (TimeoutException ex)
826 {
827 Exception = ex;
828 Retry = true;
829 }
831 {
832 Exception = ex;
833 Retry = true;
834 }
835 catch (Exception ex)
836 {
837 Exception = ex;
838 Retry = false;
839 }
840
841 if (!Retry ||
842 !Types.TryGetModuleParameter("Scheduler", out object Obj) ||
843 !(Obj is Scheduler Scheduler))
844 {
845 Log.Error("Unable to send message.\r\n\r\n" + Exception?.Message,
846 new KeyValuePair<string, object>("From", From),
847 new KeyValuePair<string, object>("To", To));
848
849 // TODO: Mail error message back to sender.
850
851 return false;
852 }
853
854 DateTime TP = DateTime.Now;
855 double Minutes = (TP - Start).TotalMinutes;
856
857 if (Minutes >= 24 * 60)
858 return false;
859
860 if (Minutes < 5)
861 TP = TP.AddMinutes(1);
862 else if (Minutes < 60)
863 TP = TP.AddMinutes(5);
864 else
865 TP = TP.AddMinutes(15);
866
867 // TODO: Mail message (only once) back to sender saying a temporary error occurred, but that new attempts will be made.
868 // (make configurable/controllable by argument)
869
870 Scheduler.Add(TP, this.Resend, new object[] { Domain, Host, Port, UserName, Password, From, To, Headers, Data, Start });
871
872 return true;
873 }
874
875 private async void Resend(object State)
876 {
877 try
878 {
879 object[] P = (object[])State;
880 string Domain = (string)P[0];
881 string Host = (string)P[1];
882 int Port = (int)P[2];
883 string UserName = (string)P[3];
884 string Password = (string)P[4];
887 KeyValuePair<string, string>[] Headers = (KeyValuePair<string, string>[])P[7];
888 byte[] Data = (byte[])P[8];
889 DateTime Start = (DateTime)P[9];
890
891 await this.SendMessage(Domain, Host, Port, UserName, Password, From, To, Headers, Data, Start);
892 }
893 catch (Exception ex)
894 {
895 Log.Exception(ex);
896 }
897 }
898
904 public static async Task<string> GetSalutation(string DefaultDomain)
905 {
906 if (salutationExpires < DateTime.Now)
907 salutation = null;
908
909 if (salutation is null)
910 {
911 try
912 {
913 foreach (NetworkInterface Interface in NetworkInterface.GetAllNetworkInterfaces())
914 {
915 if (Interface.OperationalStatus != OperationalStatus.Up)
916 continue;
917
918 IPInterfaceProperties Properties = Interface.GetIPProperties();
919
920 foreach (UnicastIPAddressInformation UnicastAddress in Properties.UnicastAddresses)
921 {
922 if (UnicastAddress.Address.AddressFamily == AddressFamily.InterNetwork && Socket.OSSupportsIPv4)
923 {
924 if (IsPublicAddress(UnicastAddress.Address))
925 {
926 try
927 {
928 string AddrStr = UnicastAddress.Address.ToString();
929 string[] Names = await DnsResolver.LookupDomainName(UnicastAddress.Address);
930
931 foreach (string Name in Names)
932 {
933 try
934 {
935 IPAddress[] Addresses = await DnsResolver.LookupIP4Addresses(Name);
936
937 foreach (IPAddress Addr in Addresses)
938 {
939 if (Addr.ToString() == AddrStr)
940 {
941 salutation = Name;
942 break;
943 }
944 }
945
946 if (!(salutation is null))
947 break;
948 }
949 catch (Exception)
950 {
951 // Ignore
952 }
953 }
954
955 if (!(salutation is null))
956 break;
957 }
958 catch (Exception)
959 {
960 // Ignore
961 }
962 }
963 }
964 }
965
966 if (!(salutation is null))
967 break;
968 }
969 }
970 catch (Exception)
971 {
972 // Ignore
973 }
974
975 if (salutation is null)
976 salutation = DefaultDomain;
977
978 salutationExpires = DateTime.Now.AddHours(1);
979 }
980
981 return salutation;
982 }
983
989 public static bool IsPublicAddress(IPAddress Address)
990 {
991 if (Address.AddressFamily == AddressFamily.InterNetwork && Socket.OSSupportsIPv4)
992 {
993 byte[] Addr = Address.GetAddressBytes();
994
995 if (Addr[0] == 127)
996 return false; // Loopback address range: 127.0.0.0 - 127.255.255.55
997
998 else if (Addr[0] == 10)
999 return false; // Private address range: 10.0.0.0 - 10.255.255.55
1000
1001 else if (Addr[0] == 172 && Addr[1] >= 16 && Addr[1] <= 31)
1002 return false; // Private address range: 172.16.0.0 - 172.31.255.255
1003
1004 else if (Addr[0] == 192 && Addr[1] == 168)
1005 return false; // Private address range: 192.168.0.0 - 192.168.255.255
1006
1007 else if (Addr[0] == 169 && Addr[1] == 254)
1008 return false; // Link-local address range: 169.254.0.0 - 169.254.255.255
1009
1010 return true;
1011 }
1012 else
1013 return false;
1014 }
1015 #endregion
1016
1017 }
1018}
Helps with parsing of commong data types.
Definition: CommonTypes.cs:13
static string EncodeRfc822(DateTime Timestamp)
Encodes a date and time, according to RFC 822 §5.
Definition: CommonTypes.cs:667
Static class managing encoding and decoding of internet content.
static Task< KeyValuePair< byte[], string > > 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:13
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 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:682
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.
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 async Task< string[]> LookupDomainName(IPAddress Address)
Looks up the domain name pointing to a specific IP address.
Definition: DnsResolver.cs:726
static async Task< string[]> LookupMailExchange(string DomainName)
Looks up the Mail Exchanges related to a given domain name.
Definition: DnsResolver.cs:620
static async Task< IPAddress[]> LookupIP4Addresses(string DomainName)
Looks up the IPv4 addresses related to a given domain name.
Definition: DnsResolver.cs:580
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:44
const int DefaultSmtpRelayPort
Secondary SMTP Port (587).
Definition: SmtpServer.cs:53
EventHandlerAsync< ClientConnectionEventArgs > ClientConnectionRemoved
Event raised when a client connection has been removed.
Definition: SmtpServer.cs:496
static bool IsPublicAddress(IPAddress Address)
Checks if an IPv4 address is public.
Definition: SmtpServer.cs:989
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:110
static async Task< string > GetSalutation(string DefaultDomain)
Gets the proper salutation name for the server.
Definition: SmtpServer.cs:904
async Task< bool > SendMessage(CaseInsensitiveString From, CaseInsensitiveString To, string Subject, object[] AlternativeBodies)
Sends a mail message
Definition: SmtpServer.cs:664
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:804
CommunicationLayer ExternalSniffers
External Sniffers for SMTP communication.
Definition: SmtpServer.cs:224
const int DefaultConnectionBacklog
Default Connection backlog (10).
Definition: SmtpServer.cs:58
int NrClientConnections
Number of client connections.
Definition: SmtpServer.cs:253
async Task< bool > SendMessage(CaseInsensitiveString From, CaseInsensitiveString To, string Subject, string MessageId, EmbeddedContent[] Alternatives, EmbeddedContent[] Attachments)
Sends a mail message
Definition: SmtpServer.cs:708
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:150
EventHandlerAsync< ClientConnectionEventArgs > ClientConnectionAdded
Event raised when a client connection has been added.
Definition: SmtpServer.cs:491
CaseInsensitiveString Domain
Domain name.
Definition: SmtpServer.cs:284
int[] OpenPorts
Ports successfully opened.
Definition: SmtpServer.cs:363
bool EncryptionRequired
If C2S encryption is requried.
Definition: SmtpServer.cs:309
void UpdateCertificate(X509Certificate ServerCertificate)
Updates the server certificate
Definition: SmtpServer.cs:300
Task< bool > SendMessage(CaseInsensitiveString From, CaseInsensitiveString To, string Subject, EmbeddedContent[] Alternatives, EmbeddedContent[] Attachments)
Sends a mail message
Definition: SmtpServer.cs:692
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:325
async Task< bool > SendMessage(CaseInsensitiveString From, CaseInsensitiveString To, KeyValuePair< string, string >[] Headers, byte[] Data, DateTime Start)
Sends a mail message
Definition: SmtpServer.cs:758
string SmtpSnifferPath
If separate sniffers are to be created for each connected client, set this property to the file path ...
Definition: SmtpServer.cs:231
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:130
const string SmtpRelayPrivilegeID
SmtpRelay
Definition: SmtpServer.cs:68
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
const int DefaultSmtpPort
Default SMTP Port (25).
Definition: SmtpServer.cs:48
const int DefaultBufferSize
Default buffer size (16384).
Definition: SmtpServer.cs:63
async Task Connect()
Connects to the server.
async Task< string > EHLO(string Domain)
Sends the EHLO command.
async Task QUIT()
Executes the QUIT command.
async Task MAIL_FROM(string Sender)
Executes the MAIL FROM command.
async Task DATA(KeyValuePair< string, string >[] Headers, byte[] Body)
Executes the DATA command.
async Task RCPT_TO(string Receiver)
Executes the RCPT TO command.
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:19
static string WildcardToRegex(string s, string Wildcard)
Converts a wildcard string to a regular expression string.
Definition: Database.cs:1631
Implements an in-memory cache.
Definition: Cache.cs:15
void Dispose()
IDisposable.Dispose
Definition: Cache.cs:74
int Count
Number of items in cache
Definition: Cache.cs:229
bool Remove(KeyType Key)
Removes an item from the cache.
Definition: Cache.cs:451
bool TryGetValue(KeyType Key, out ValueType Value)
Tries to get a value from the cache.
Definition: Cache.cs:203
KeyType[] GetKeys()
Gets all available keys in the cache.
Definition: Cache.cs:258
void Add(KeyType Key, ValueType Value)
Adds an item to the cache.
Definition: Cache.cs:338
void Clear()
Clears the cache.
Definition: Cache.cs:484
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:14
static bool TryGetModuleParameter(string Name, out object Value)
Tries to get a module parameter value.
Definition: Types.cs:583
Class that can be used to schedule events in time. It uses a timer to execute tasks at the appointed ...
Definition: Scheduler.cs:26
DateTime Add(DateTime When, ScheduledEventCallback Callback, object State)
Adds an event.
Definition: Scheduler.cs:66
Contains information about a SPF string.
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:11
BinaryPresentationMethod
How binary data is to be presented.