Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
RelayConfiguration.cs
1using System;
3using System.Text;
4using System.Threading;
5using System.Threading.Tasks;
6using Waher.Content;
8using Waher.Events;
20
22{
27 {
28 private static RelayConfiguration instance = null;
29
30 private string host = string.Empty;
31 private string userName = string.Empty;
32 private string password = string.Empty;
33 private string testMailRecipient = string.Empty;
34 private string sender = string.Empty;
35 private string[] relayDomains = null;
36 private int port = 587;
37 private bool useRelayServer = false;
38
39 private HttpResource testMail = null;
40
41 [DefaultValueStringEmpty]
42 public string Host
43 {
44 get => this.host;
45 set => this.host = value;
46 }
47
48 [DefaultValue(587)]
49 public int Port
50 {
51 get => this.port;
52 set => this.port = value;
53 }
54
55 [DefaultValueStringEmpty]
56 public string UserName
57 {
58 get => this.userName;
59 set => this.userName = value;
60 }
61
62 [Encrypted(32)]
63 public string Password
64 {
65 get => this.password;
66 set => this.password = value;
67 }
68
72 public string[] EncryptedProperties => new string[] { nameof(this.Password) };
73
74 [DefaultValue(false)]
75 public bool UseRelayServer
76 {
77 get => this.useRelayServer;
78 set => this.useRelayServer = value;
79 }
80
81 [DefaultValueStringEmpty]
82 public string TestMailRecipient
83 {
84 get => this.testMailRecipient;
85 set => this.testMailRecipient = value;
86 }
87
88 [DefaultValueStringEmpty]
89 public string Sender
90 {
91 get => this.sender;
92 set => this.sender = value;
93 }
94
95 [DefaultValueNull]
96 public string[] RelayDomains
97 {
98 get => this.relayDomains;
99 set => this.relayDomains = value;
100 }
101
102 public string RelayDomainsText
103 {
104 get
105 {
106 if (this.relayDomains is null)
107 return string.Empty;
108
109 StringBuilder Result = new StringBuilder();
110
111 foreach (string Domain in this.relayDomains)
112 Result.AppendLine(Domain);
113
114 return Result.ToString();
115 }
116 }
117
121 public static RelayConfiguration Instance => instance;
122
126 public override string Resource => "/Settings/Relay.md";
127
131 public override int Priority => 450;
132
138 public override Task<string> Title(Language Language)
139 {
140 return Language.GetStringAsync(typeof(XmppServerModule), 31, "Mail Relay");
141 }
142
146 public override Task ConfigureSystem()
147 {
148 XmppServerModule.MailServer?.SetRelaySettings(this.useRelayServer, this.host, this.port,
149 this.userName, this.password, this.relayDomains, true);
150 return Task.CompletedTask;
151 }
152
157 public override void SetStaticInstance(ISystemConfiguration Configuration)
158 {
159 instance = Configuration as RelayConfiguration;
160 }
161
166 public override Task InitSetup(HttpServer WebServer)
167 {
168 this.testMail = WebServer.Register("/Settings/TestMail", null, this.TestMail, true, false, true);
169
170 return base.InitSetup(WebServer);
171 }
172
177 public override Task UnregisterSetup(HttpServer WebServer)
178 {
179 WebServer.Unregister(this.testMail);
180
181 return base.UnregisterSetup(WebServer);
182 }
183
187 protected override string ConfigPrivilege => "Admin.Communication.MailRelay";
188
189 private async Task TestMail(HttpRequest Request, HttpResponse Response)
190 {
191 Gateway.AssertUserAuthenticated(Request, this.ConfigPrivilege);
192
193 if (!Request.HasData)
194 {
195 await Response.SendResponse(new BadRequestException());
196 return;
197 }
198
199 ContentResponse Content = await Request.DecodeDataAsync();
200
201 if (Content.HasError ||
202 !(Content.Decoded is Dictionary<string, object> Form) ||
203 !Form.TryGetValue("useRelayServer", out object Obj) || !(Obj is bool UseRelayServer) ||
204 !Form.TryGetValue("testMailRecipient", out Obj) || !(Obj is string TestMailRecipient) ||
205 !Form.TryGetValue("sender", out Obj) || !(Obj is string Sender) ||
206 !Form.TryGetValue("relayDomains", out Obj) || !(Obj is string RelayDomains) ||
207 !Form.TryGetValue("sendMail", out Obj) || !(Obj is bool SendMail))
208 {
209 await Response.SendResponse(new BadRequestException());
210 return;
211 }
212
213 if (!Form.TryGetValue("hostName", out Obj) || !(Obj is string HostName) ||
214 !Form.TryGetValue("portNumber", out Obj) || !(Obj is int PortNumber) || PortNumber < 1 || PortNumber > 65535 ||
215 !Form.TryGetValue("userName", out Obj) || !(Obj is string UserName) ||
216 !Form.TryGetValue("password", out Obj) || !(Obj is string Password))
217 {
218 if (UseRelayServer)
219 {
220 await Response.SendResponse(new BadRequestException());
221 return;
222 }
223 else
224 {
225 HostName = string.Empty;
226 PortNumber = 0;
227 UserName = string.Empty;
228 Password = string.Empty;
229 }
230 }
231
232 string TabID = Request.Header["X-TabID"];
233 if (string.IsNullOrEmpty(TabID))
234 {
235 await Response.SendResponse(new BadRequestException());
236 return;
237 }
238
239 Response.StatusCode = 200;
240 Response.StatusMessage = "OK";
241
242 this.useRelayServer = UseRelayServer;
243 this.host = HostName;
244 this.port = PortNumber;
245 this.userName = UserName;
246 this.password = Password;
247 this.testMailRecipient = TestMailRecipient;
248 this.sender = Sender;
249 this.relayDomains = RelayDomains.Trim().Split(CommonTypes.CRLF, StringSplitOptions.RemoveEmptyEntries);
250
251 await Database.Update(this);
252
253 Task _ = Task.Run(async () =>
254 {
255 if (SendMail)
256 {
257 try
258 {
259 string[] TabIDs = new string[] { TabID };
260 string Message = await this.Test(TabIDs);
261
262 if (string.IsNullOrEmpty(Message))
263 await ClientEvents.PushEvent(TabIDs, "MailSent", string.Empty, false, "User");
264 else
265 {
266 await ClientEvents.PushEvent(TabIDs, "ShowStatus", Message, false, "User");
267 await ClientEvents.PushEvent(TabIDs, "MailError", string.Empty, false, "User");
268 }
269 }
270 catch (Exception ex)
271 {
272 Log.Exception(ex);
273 }
274 }
275 });
276 }
277
278 private async Task<string> Test(string[] TabIDs)
279 {
280 try
281 {
282 string DefaultDomain = DomainConfiguration.Instance.Domain;
283 if (string.IsNullOrEmpty(DefaultDomain))
284 DefaultDomain = "localhost";
285
286 string Sender = this.sender;
287 if (string.IsNullOrEmpty(Sender))
289
290 await this.SendMessage(TabIDs, Sender, this.testMailRecipient,
291 "Test message",
292 "Test message\r\n" +
293 "------------------\r\n\r\n" +
294 "This is a test message sent from `" + await SmtpServer.GetSalutation(DefaultDomain) + "`");
295
296 return null;
297 }
298 catch (Exception ex)
299 {
300 Log.Exception(ex);
301 return ex.Message;
302 }
303 }
304
305 private async Task SendMessage(string[] TabIDs, CaseInsensitiveString From, CaseInsensitiveString To, string Subject, string Markdown)
306 {
307 int i = To.IndexOf('@');
308 if (i < 0)
309 throw new ArgumentException("Invalid mail address: " + To, nameof(To));
310
311 string Domain = To.Substring(i + 1).Trim();
312 string UserName;
313 string Password;
314 string Host;
315 int Port;
316
317 if (this.useRelayServer)
318 {
319 Host = this.host;
320 Port = this.port;
321 UserName = this.userName;
322 Password = this.password;
323 }
324 else
325 {
326 string[] Exchanges = await DnsResolver.TryLookupMailExchange(Domain);
327 if (Exchanges is null || Exchanges.Length == 0)
328 throw new ArgumentException("No mail exchange at " + Domain + ".", nameof(To));
329
330 Host = Exchanges[Gateway.NextInteger(Exchanges.Length)];
332 UserName = null;
333 Password = null;
334 }
335
336 using SimpleSmtpClient Client = new SimpleSmtpClient(Domain, Host, Port, UserName, Password);
337
338 if (!(TabIDs is null))
339 {
340 Client.Add(new SnifferProxy()
341 {
342 Config = this,
343 TabIDs = TabIDs
344 });
345 }
346
347 await Client.Connect();
348 await Client.EHLO(await SmtpServer.GetSalutation(Domain)); // Also performs Encryption & Authentication handshakes, as required.
349 await Client.SendFormattedEMail(From, To, Subject, Markdown);
350 await Client.QUIT();
351 }
352
353 private class SnifferProxy : SnifferBase
354 {
355 public RelayConfiguration Config;
356 public string[] TabIDs;
357
358 public SnifferProxy()
359 : base("Relay Sniffer Proxy")
360 {
361 }
362
364
365 public override Task Process(SnifferRxBinary Event, CancellationToken Cancel)
366 {
367 return ClientEvents.PushEvent(this.TabIDs, "ShowStatus", "Rx: <" +
368 Event.Count.ToString() + " bytes>", false, "User");
369 }
370
371 public override Task Process(SnifferTxBinary Event, CancellationToken Cancel)
372 {
373 return ClientEvents.PushEvent(this.TabIDs, "ShowStatus", "Tx: <" +
374 Event.Count.ToString() + " bytes>", false, "User");
375 }
376
377 public override Task Process(SnifferRxText Event, CancellationToken Cancel)
378 {
379 return ClientEvents.PushEvent(this.TabIDs, "ShowStatus", "Rx: " +
380 XML.HtmlValueEncode(Event.Text), false, "User");
381 }
382
383 public override Task Process(SnifferTxText Event, CancellationToken Cancel)
384 {
385 return ClientEvents.PushEvent(this.TabIDs, "ShowStatus", "Tx: " +
386 XML.HtmlValueEncode(Event.Text), false, "User");
387 }
388
389 public override Task Process(SnifferInformation Event, CancellationToken Cancel)
390 {
391 return ClientEvents.PushEvent(this.TabIDs, "ShowStatus",
392 XML.HtmlValueEncode(Event.Text), false, "User");
393 }
394
395 public override Task Process(SnifferWarning Event, CancellationToken Cancel)
396 {
397 return ClientEvents.PushEvent(this.TabIDs, "ShowStatus", "Warning: " +
398 XML.HtmlValueEncode(Event.Text), false, "User");
399 }
400
401 public override Task Process(SnifferError Event, CancellationToken Cancel)
402 {
403 return ClientEvents.PushEvent(this.TabIDs, "ShowStatus", "ERROR: " +
404 XML.HtmlValueEncode(Event.Text), false, "User");
405 }
406
407 public override Task Process(SnifferException Event, CancellationToken Cancel)
408 {
409 return ClientEvents.PushEvent(this.TabIDs, "ShowStatus", "EXCEPTION: " +
410 XML.HtmlValueEncode(Event.Text), false, "User");
411 }
412 }
413
418 public override Task<bool> SimplifiedConfiguration()
419 {
420 return Task.FromResult(true);
421 }
422
427 public const string BROKER_RELAY_USE = nameof(BROKER_RELAY_USE);
428
432 public const string BROKER_RELAY_DOMAINS = nameof(BROKER_RELAY_DOMAINS);
433
437 public const string BROKER_RELAY_SENDER = nameof(BROKER_RELAY_SENDER);
438
442 public const string BROKER_RELAY_HOST = nameof(BROKER_RELAY_HOST);
443
447 public const string BROKER_RELAY_PORT = nameof(BROKER_RELAY_PORT);
448
452 public const string BROKER_RELAY_USER = nameof(BROKER_RELAY_USER);
453
457 public const string BROKER_RELAY_PASSWORD = nameof(BROKER_RELAY_PASSWORD);
458
463 public override async Task<bool> EnvironmentConfiguration()
464 {
465 if (!this.TryGetEnvironmentVariable(BROKER_RELAY_USE, false, out this.useRelayServer))
466 return false;
467
468 string Value = Environment.GetEnvironmentVariable(BROKER_RELAY_DOMAINS);
469 if (!string.IsNullOrEmpty(Value))
470 this.relayDomains = Value.Split(',');
471
472 Value = Environment.GetEnvironmentVariable(BROKER_RELAY_SENDER);
473 if (!string.IsNullOrEmpty(Value))
474 this.sender = Value;
475
476 if (this.useRelayServer)
477 {
478 if (!this.TryGetEnvironmentVariable(BROKER_RELAY_HOST, true, out this.host))
479 return false;
480
481 if (!this.TryGetEnvironmentVariable(BROKER_RELAY_PORT, 1, 65535, true, ref this.port))
482 return false;
483
484 if (!this.TryGetEnvironmentVariable(BROKER_RELAY_USER, true, out this.userName))
485 return false;
486
487 if (!this.TryGetEnvironmentVariable(BROKER_RELAY_PASSWORD, true, out this.password))
488 return false;
489
490 Value = await this.Test(null);
491 if (!string.IsNullOrEmpty(Value))
492 {
493 this.LogEnvironmentError("Unable to send e-mail with Relay settings: " + Value, BROKER_RELAY_HOST, this.host);
494 return false;
495 }
496 }
497
498 return true;
499 }
500
501 }
502}
Helps with parsing of commong data types.
Definition: CommonTypes.cs:15
static readonly char[] CRLF
Contains the CR LF character sequence.
Definition: CommonTypes.cs:19
Contains information about a response to a content request.
bool HasError
If an error occurred.
object Decoded
Decoded object.
Helps with common XML-related tasks.
Definition: XML.cs:21
static string HtmlValueEncode(string s)
Differs from Encode(String), in that it does not encode the aposotrophe or the quote.
Definition: XML.cs:211
Class representing an event.
Definition: Event.cs:11
override string ToString()
Definition: Event.cs:170
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
The ClientEvents class allows applications to push information asynchronously to web clients connecte...
Definition: ClientEvents.cs:51
static Task< int > PushEvent(string[] TabIDs, string Type, object Data)
Puses an event to a set of Tabs, given their Tab IDs.
Static class managing the runtime environment of the IoT Gateway.
Definition: Gateway.cs:147
static IUser AssertUserAuthenticated(HttpRequest Request, string Privilege)
Makes sure a request is being made from a session with a successful user login.
Definition: Gateway.cs:3868
static int NextInteger(int Max)
Returns a non-negative random integer that is less than the specified maximum.
Definition: Gateway.cs:4311
static DomainConfiguration Instance
Current instance of configuration.
void LogEnvironmentError(string EnvironmentVariable, object Value)
Logs an error to the event log, telling the operator an environment variable value contains an error.
bool TryGetEnvironmentVariable(string VariableName, bool Required, out string Value)
Tries to get a string-valued environment variable.
Abstract base class for multi-step system configurations.
static XmppConfiguration Instance
Current instance of configuration.
DNS resolver, as defined in:
Definition: DnsResolver.cs:32
static Task< string[]> TryLookupMailExchange(string DomainName)
Tries to look up the Mail Exchanges related to a given domain name.
Definition: DnsResolver.cs:776
The request could not be understood by the server due to malformed syntax. The client SHOULD NOT repe...
Represents an HTTP request.
Definition: HttpRequest.cs:22
HttpRequestHeader Header
Request header.
Definition: HttpRequest.cs:182
bool HasData
If the request has data.
Definition: HttpRequest.cs:113
async Task< ContentResponse > DecodeDataAsync()
Decodes data sent in request.
Definition: HttpRequest.cs:139
Base class for all HTTP resources.
Definition: HttpResource.cs:23
Represets a response of an HTTP client request.
Definition: HttpResponse.cs:23
async Task SendResponse()
Sends the response back to the client. If the resource is synchronous, there's no need to call this m...
Implements an HTTP server.
Definition: HttpServer.cs:41
HttpResource Register(HttpResource Resource)
Registers a resource with the server.
Definition: HttpServer.cs:1568
bool Unregister(HttpResource Resource)
Unregisters a resource from the server.
Definition: HttpServer.cs:1743
Implements a simple SMTP Server, as defined in:
Definition: SmtpServer.cs:45
static async Task< string > GetSalutation(string DefaultDomain)
Gets the proper salutation name for the server.
Definition: SmtpServer.cs:915
void SetRelaySettings(bool UseRelayServer, string HostName, int PortNumber, string UserName, string Password, string[] RelayDomains, bool LockSettings)
Sets mail relay settings.
Definition: SmtpServer.cs:572
const int DefaultSmtpPort
Default SMTP Port (25).
Definition: SmtpServer.cs:49
Represents a sniffer error event.
Definition: SnifferError.cs:11
Represents a sniffer exception event.
Represents a sniffer information event.
Represents a sniffer binary reception event.
Represents a sniffer text reception event.
Represents a sniffer binary transmission event.
Represents a sniffer text transmission event.
Represents a sniffer warning event.
Abstract base class for sniffers. Implements default method overloads.
Definition: SnifferBase.cs:15
A sniffer that redirects incoming events to another sniffable object.
Definition: SnifferProxy.cs:9
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 async Task Update(object Object)
Updates an object in the database.
Definition: Database.cs:1211
Contains information about a language.
Definition: Language.cs:17
Task< string > GetStringAsync(Type Type, int Id, string Default)
Gets the string value of a string ID. If no such string exists, a string is created with the default ...
Definition: Language.cs:209
Provides the user configuration options regarding use of SMTP Relay server to send mail.
override void SetStaticInstance(ISystemConfiguration Configuration)
Sets the static instance of the configuration.
override Task< bool > SimplifiedConfiguration()
Simplified configuration by configuring simple default values.
const string BROKER_RELAY_PORT
Port number to use when connecting relay server.
override async Task< bool > EnvironmentConfiguration()
Environment configuration by configuring values available in environment variables.
string[] EncryptedProperties
Array of properties that are encrypted.
const string BROKER_RELAY_USE
If an SMTP relay server is to be used (true or 1), or if the broker should connect to the recipient m...
static RelayConfiguration Instance
Current instance of configuration.
override Task InitSetup(HttpServer WebServer)
Initializes the setup object.
override Task ConfigureSystem()
Is called during startup to configure the system.
const string BROKER_RELAY_PASSWORD
Password of account when authenticating access to the relay server.
const string BROKER_RELAY_HOST
Host(or domain) or the SMTP Relay server.
override Task UnregisterSetup(HttpServer WebServer)
Unregisters the setup object.
const string BROKER_RELAY_DOMAINS
Optional Comma-separated list of domain names for which the broker can act as an SMTP relay.
const string BROKER_RELAY_USER
User account in the relay server.
override string ConfigPrivilege
Minimum required privilege for a user to be allowed to change the configuration defined by the class.
const string BROKER_RELAY_SENDER
Default sender of mail messages from broker.
override Task< string > Title(Language Language)
Gets a title for the system configuration.
Service Module hosting the XMPP broker and its components.
Interface for system configurations. The gateway will scan all module for system configuration classe...
Interface for objects containing encrypted properties. Mark the properties that are encrypted with th...
Definition: ImplTypes.g.cs:58
BinaryPresentationMethod
How binary data is to be presented.