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;
2using System.Collections.Generic;
3using System.Text;
4using System.Threading.Tasks;
5using Waher.Content;
7using Waher.Events;
18
20{
25 {
26 private static RelayConfiguration instance = null;
27
28 private string host = string.Empty;
29 private string userName = string.Empty;
30 private string password = string.Empty;
31 private string testMailRecipient = string.Empty;
32 private string sender = string.Empty;
33 private string[] relayDomains = null;
34 private int port = 587;
35 private bool useRelayServer = false;
36
37 private HttpResource testMail = null;
38
39 [DefaultValueStringEmpty]
40 public string Host
41 {
42 get => this.host;
43 set => this.host = value;
44 }
45
46 [DefaultValue(587)]
47 public int Port
48 {
49 get => this.port;
50 set => this.port = value;
51 }
52
53 [DefaultValueStringEmpty]
54 public string UserName
55 {
56 get => this.userName;
57 set => this.userName = value;
58 }
59
60 [DefaultValueStringEmpty]
61 public string Password
62 {
63 get => this.password;
64 set => this.password = value;
65 }
66
67 [DefaultValue(false)]
68 public bool UseRelayServer
69 {
70 get => this.useRelayServer;
71 set => this.useRelayServer = value;
72 }
73
74 [DefaultValueStringEmpty]
75 public string TestMailRecipient
76 {
77 get => this.testMailRecipient;
78 set => this.testMailRecipient = value;
79 }
80
81 [DefaultValueStringEmpty]
82 public string Sender
83 {
84 get => this.sender;
85 set => this.sender = value;
86 }
87
88 [DefaultValueNull]
89 public string[] RelayDomains
90 {
91 get => this.relayDomains;
92 set => this.relayDomains = value;
93 }
94
95 public string RelayDomainsText
96 {
97 get
98 {
99 if (this.relayDomains is null)
100 return string.Empty;
101
102 StringBuilder Result = new StringBuilder();
103
104 foreach (string Domain in this.relayDomains)
105 Result.AppendLine(Domain);
106
107 return Result.ToString();
108 }
109 }
110
114 public static RelayConfiguration Instance => instance;
115
119 public override string Resource => "/Settings/Relay.md";
120
124 public override int Priority => 450;
125
131 public override Task<string> Title(Language Language)
132 {
133 return Language.GetStringAsync(typeof(XmppServerModule), 31, "Mail Relay");
134 }
135
139 public override Task ConfigureSystem()
140 {
141 XmppServerModule.MailServer?.SetRelaySettings(this.useRelayServer, this.host, this.port,
142 this.userName, this.password, this.relayDomains, true);
143 return Task.CompletedTask;
144 }
145
150 public override void SetStaticInstance(ISystemConfiguration Configuration)
151 {
152 instance = Configuration as RelayConfiguration;
153 }
154
159 public override Task InitSetup(HttpServer WebServer)
160 {
161 this.testMail = WebServer.Register("/Settings/TestMail", null, this.TestMail, true, false, true);
162
163 return base.InitSetup(WebServer);
164 }
165
170 public override Task UnregisterSetup(HttpServer WebServer)
171 {
172 WebServer.Unregister(this.testMail);
173
174 return base.UnregisterSetup(WebServer);
175 }
176
180 protected override string ConfigPrivilege => "Admin.Communication.MailRelay";
181
182 private async Task TestMail(HttpRequest Request, HttpResponse Response)
183 {
184 Gateway.AssertUserAuthenticated(Request, this.ConfigPrivilege);
185
186 if (!Request.HasData)
187 throw new BadRequestException();
188
189 object Obj = await Request.DecodeDataAsync();
190 if (!(Obj is Dictionary<string, object> Form) ||
191 !Form.TryGetValue("useRelayServer", out Obj) || !(Obj is bool UseRelayServer) ||
192 !Form.TryGetValue("testMailRecipient", out Obj) || !(Obj is string TestMailRecipient) ||
193 !Form.TryGetValue("sender", out Obj) || !(Obj is string Sender) ||
194 !Form.TryGetValue("relayDomains", out Obj) || !(Obj is string RelayDomains) ||
195 !Form.TryGetValue("sendMail", out Obj) || !(Obj is bool SendMail))
196 {
197 throw new BadRequestException();
198 }
199
200 if (!Form.TryGetValue("hostName", out Obj) || !(Obj is string HostName) ||
201 !Form.TryGetValue("portNumber", out Obj) || !(Obj is int PortNumber) || PortNumber < 1 || PortNumber > 65535 ||
202 !Form.TryGetValue("userName", out Obj) || !(Obj is string UserName) ||
203 !Form.TryGetValue("password", out Obj) || !(Obj is string Password))
204 {
205 if (UseRelayServer)
206 throw new BadRequestException();
207 else
208 {
209 HostName = string.Empty;
210 PortNumber = 0;
211 UserName = string.Empty;
212 Password = string.Empty;
213 }
214 }
215
216 string TabID = Request.Header["X-TabID"];
217 if (string.IsNullOrEmpty(TabID))
218 throw new BadRequestException();
219
220 Response.StatusCode = 200;
221
222 this.useRelayServer = UseRelayServer;
223 this.host = HostName;
224 this.port = PortNumber;
225 this.userName = UserName;
226 this.password = Password;
227 this.testMailRecipient = TestMailRecipient;
228 this.sender = Sender;
229 this.relayDomains = RelayDomains.Trim().Split(CommonTypes.CRLF, StringSplitOptions.RemoveEmptyEntries);
230
231 await Database.Update(this);
232
233 Task _ = Task.Run(async () =>
234 {
235 if (SendMail)
236 {
237 try
238 {
239 string[] TabIDs = new string[] { TabID };
240 string Message = await this.Test(TabIDs);
241
242 if (string.IsNullOrEmpty(Message))
243 await ClientEvents.PushEvent(TabIDs, "MailSent", string.Empty, false, "User");
244 else
245 {
246 await ClientEvents.PushEvent(TabIDs, "ShowStatus", Message, false, "User");
247 await ClientEvents.PushEvent(TabIDs, "MailError", string.Empty, false, "User");
248 }
249 }
250 catch (Exception ex)
251 {
252 Log.Exception(ex);
253 }
254 }
255 });
256 }
257
258 private async Task<string> Test(string[] TabIDs)
259 {
260 try
261 {
262 string DefaultDomain = DomainConfiguration.Instance.Domain;
263 if (string.IsNullOrEmpty(DefaultDomain))
264 DefaultDomain = "localhost";
265
266 string Sender = this.sender;
267 if (string.IsNullOrEmpty(Sender))
269
270 await this.SendMessage(TabIDs, Sender, this.testMailRecipient,
271 "Test message",
272 "Test message\r\n" +
273 "------------------\r\n\r\n" +
274 "This is a test message sent from `" + await SmtpServer.GetSalutation(DefaultDomain) + "`");
275
276 return null;
277 }
278 catch (Exception ex)
279 {
280 Log.Exception(ex);
281 return ex.Message;
282 }
283 }
284
285 private async Task SendMessage(string[] TabIDs, CaseInsensitiveString From, CaseInsensitiveString To, string Subject, string Markdown)
286 {
287 int i = To.IndexOf('@');
288 if (i < 0)
289 throw new ArgumentException("Invalid mail address: " + To, nameof(To));
290
291 string Domain = To.Substring(i + 1).Trim();
292 string UserName;
293 string Password;
294 string Host;
295 int Port;
296
297 if (this.useRelayServer)
298 {
299 Host = this.host;
300 Port = this.port;
301 UserName = this.userName;
302 Password = this.password;
303 }
304 else
305 {
306 string[] Exchanges = await DnsResolver.LookupMailExchange(Domain);
307 if (Exchanges is null || Exchanges.Length == 0)
308 throw new ArgumentException("No mail exchange at " + Domain + ".", nameof(To));
309
310 Host = Exchanges[Gateway.NextInteger(Exchanges.Length)];
312 UserName = null;
313 Password = null;
314 }
315
316 using (SimpleSmtpClient Client = new SimpleSmtpClient(Domain, Host, Port, UserName, Password))
317 {
318 if (!(TabIDs is null))
319 {
320 Client.Add(new SnifferProxy()
321 {
322 Config = this,
323 TabIDs = TabIDs
324 });
325 }
326
327 await Client.Connect();
328 await Client.EHLO(await SmtpServer.GetSalutation(Domain)); // Also performs Encryption & Authentication handshakes, as required.
329 await Client.SendFormattedEMail(From, To, Subject, Markdown);
330 await Client.QUIT();
331 }
332 }
333
334 private class SnifferProxy : SnifferBase
335 {
336 public RelayConfiguration Config;
337 public string[] TabIDs;
338
339 public override Task ReceiveBinary(DateTime _, byte[] Data)
340 {
341 return ClientEvents.PushEvent(this.TabIDs, "ShowStatus", "Rx: <" + Data.Length.ToString() + " bytes>", false, "User");
342 }
343
344 public override Task TransmitBinary(DateTime _, byte[] Data)
345 {
346 return ClientEvents.PushEvent(this.TabIDs, "ShowStatus", "Tx: <" + Data.Length.ToString() + " bytes>", false, "User");
347 }
348
349 public override Task ReceiveText(DateTime _, string Text)
350 {
351 return ClientEvents.PushEvent(this.TabIDs, "ShowStatus", "Rx: " + XML.HtmlValueEncode(Text), false, "User");
352 }
353
354 public override Task TransmitText(DateTime _, string Text)
355 {
356 return ClientEvents.PushEvent(this.TabIDs, "ShowStatus", "Tx: " + XML.HtmlValueEncode(Text), false, "User");
357 }
358
359 public override Task Information(DateTime _, string Comment)
360 {
361 return ClientEvents.PushEvent(this.TabIDs, "ShowStatus", XML.HtmlValueEncode(Comment), false, "User");
362 }
363
364 public override Task Warning(DateTime _, string Warning)
365 {
366 return ClientEvents.PushEvent(this.TabIDs, "ShowStatus", "Warning: " + XML.HtmlValueEncode(Warning), false, "User");
367 }
368
369 public override Task Error(DateTime _, string Error)
370 {
371 return ClientEvents.PushEvent(this.TabIDs, "ShowStatus", "ERROR: " + XML.HtmlValueEncode(Error), false, "User");
372 }
373
374 public override Task Exception(DateTime _, string Exception)
375 {
376 return ClientEvents.PushEvent(this.TabIDs, "ShowStatus", "EXCEPTION: " + XML.HtmlValueEncode(Exception), false, "User");
377 }
378 }
379
384 public override Task<bool> SimplifiedConfiguration()
385 {
386 return Task.FromResult(true);
387 }
388
393 public const string BROKER_RELAY_USE = nameof(BROKER_RELAY_USE);
394
398 public const string BROKER_RELAY_DOMAINS = nameof(BROKER_RELAY_DOMAINS);
399
403 public const string BROKER_RELAY_SENDER = nameof(BROKER_RELAY_SENDER);
404
408 public const string BROKER_RELAY_HOST = nameof(BROKER_RELAY_HOST);
409
413 public const string BROKER_RELAY_PORT = nameof(BROKER_RELAY_PORT);
414
418 public const string BROKER_RELAY_USER = nameof(BROKER_RELAY_USER);
419
423 public const string BROKER_RELAY_PASSWORD = nameof(BROKER_RELAY_PASSWORD);
424
429 public override async Task<bool> EnvironmentConfiguration()
430 {
431 if (!this.TryGetEnvironmentVariable(BROKER_RELAY_USE, false, out this.useRelayServer))
432 return false;
433
434 string Value = Environment.GetEnvironmentVariable(BROKER_RELAY_DOMAINS);
435 if (!string.IsNullOrEmpty(Value))
436 this.relayDomains = Value.Split(',');
437
438 Value = Environment.GetEnvironmentVariable(BROKER_RELAY_SENDER);
439 if (!string.IsNullOrEmpty(Value))
440 this.sender = Value;
441
442 if (this.useRelayServer)
443 {
444 if (!this.TryGetEnvironmentVariable(BROKER_RELAY_HOST, true, out this.host))
445 return false;
446
447 if (!this.TryGetEnvironmentVariable(BROKER_RELAY_PORT, 1, 65535, true, ref this.port))
448 return false;
449
450 if (!this.TryGetEnvironmentVariable(BROKER_RELAY_USER, true, out this.userName))
451 return false;
452
453 if (!this.TryGetEnvironmentVariable(BROKER_RELAY_PASSWORD, true, out this.password))
454 return false;
455
456 Value = await this.Test(null);
457 if (!string.IsNullOrEmpty(Value))
458 {
459 this.LogEnvironmentError("Unable to send e-mail with Relay settings: " + Value, BROKER_RELAY_HOST, this.host);
460 return false;
461 }
462 }
463
464 return true;
465 }
466
467 }
468}
Helps with parsing of commong data types.
Definition: CommonTypes.cs:13
static readonly char[] CRLF
Contains the CR LF character sequence.
Definition: CommonTypes.cs:17
Helps with common XML-related tasks.
Definition: XML.cs:19
static string HtmlValueEncode(string s)
Differs from Encode(String), in that it does not encode the aposotrophe or the quote.
Definition: XML.cs:209
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
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:126
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:3041
static int NextInteger(int Max)
Returns a non-negative random integer that is less than the specified maximum.
Definition: Gateway.cs:3510
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.
virtual void Add(ISniffer Sniffer)
ICommunicationLayer.Add
DNS resolver, as defined in:
Definition: DnsResolver.cs:32
static async Task< string[]> LookupMailExchange(string DomainName)
Looks up the Mail Exchanges related to a given domain name.
Definition: DnsResolver.cs:620
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:18
HttpRequestHeader Header
Request header.
Definition: HttpRequest.cs:134
bool HasData
If the request has data.
Definition: HttpRequest.cs:74
async Task< object > DecodeDataAsync()
Decodes data sent in request.
Definition: HttpRequest.cs:95
Base class for all HTTP resources.
Definition: HttpResource.cs:23
Represets a response of an HTTP client request.
Definition: HttpResponse.cs:21
Implements an HTTP server.
Definition: HttpServer.cs:36
HttpResource Register(HttpResource Resource)
Registers a resource with the server.
Definition: HttpServer.cs:1287
bool Unregister(HttpResource Resource)
Unregisters a resource from the server.
Definition: HttpServer.cs:1438
Implements a simple SMTP Server, as defined in:
Definition: SmtpServer.cs:44
static async Task< string > GetSalutation(string DefaultDomain)
Gets the proper salutation name for the server.
Definition: SmtpServer.cs:904
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:48
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 SendFormattedEMail(string Sender, string Recipient, string Subject, string MarkdownContent, params object[] Attachments)
Sends a formatted e-mail message.
Abstract base class for sniffers. Implements default method overloads.
Definition: SnifferBase.cs:12
virtual Task TransmitBinary(byte[] Data)
Called when binary data has been transmitted.
Definition: SnifferBase.cs:40
virtual Task Information(string Comment)
Called to inform the viewer of something.
Definition: SnifferBase.cs:88
virtual Task Error(string Error)
Called to inform the viewer of an error state.
Definition: SnifferBase.cs:120
virtual Task Exception(string Exception)
Called to inform the viewer of an exception state.
Definition: SnifferBase.cs:136
virtual Task ReceiveText(string Text)
Called when text has been received.
Definition: SnifferBase.cs:56
virtual Task Warning(string Warning)
Called to inform the viewer of a warning state.
Definition: SnifferBase.cs:104
virtual Task ReceiveBinary(byte[] Data)
Called when binary data has been received.
Definition: SnifferBase.cs:24
virtual Task TransmitText(string Text)
Called when text has been transmitted.
Definition: SnifferBase.cs:72
A sniffer that redirects incoming events to another sniffable object.
Definition: SnifferProxy.cs:10
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 async Task Update(object Object)
Updates an object in the database.
Definition: Database.cs:626
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.
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.
override string Resource
Resource to be redirected to, to perform the configuration.
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...