Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
DnsConfiguration.cs
1using System;
3using System.Text;
4using System.Threading.Tasks;
5using Waher.Content;
6using Waher.Events;
17
19{
24 {
25 private static DnsConfiguration instance = null;
26
27 private HttpResource testDns = null;
28
32 public static DnsConfiguration Instance => instance;
33
37 public override string Resource => "/Settings/DNS.md";
38
42 public override int Priority => 250;
43
49 public override Task<string> Title(Language Language)
50 {
51 return Language.GetStringAsync(typeof(XmppServerModule), 29, "DNS");
52 }
53
57 public override Task ConfigureSystem()
58 {
59 return Task.CompletedTask;
60 }
61
66 public override void SetStaticInstance(ISystemConfiguration Configuration)
67 {
68 instance = Configuration as DnsConfiguration;
69 }
70
75 public override Task InitSetup(HttpServer WebServer)
76 {
77 this.testDns = WebServer.Register("/Settings/TestDns", null, this.TestDns, true, false, true);
78
79 return base.InitSetup(WebServer);
80 }
81
86 public override Task UnregisterSetup(HttpServer WebServer)
87 {
88 WebServer.Unregister(this.testDns);
89
90 return base.UnregisterSetup(WebServer);
91 }
92
96 protected override string ConfigPrivilege => "Admin.Communication.DNS";
97
98 private async Task TestDns(HttpRequest Request, HttpResponse Response)
99 {
100 Gateway.AssertUserAuthenticated(Request, this.ConfigPrivilege);
101
102 if (!Request.HasData)
103 {
104 await Response.SendResponse(new BadRequestException());
105 return;
106 }
107
108 ContentResponse Content = await Request.DecodeDataAsync();
109 if (Content.HasError || !(Content.Decoded is string TabID))
110 {
111 await Response.SendResponse(new BadRequestException());
112 return;
113 }
114
115 Response.StatusCode = 200;
116 Response.StatusMessage = "OK";
117
118 Task _ = Task.Run(async () => await this.Test(TabID));
119 }
120
121 private async Task Test(params string[] TabIDs)
122 {
123 try
124 {
125 Dictionary<CaseInsensitiveString, bool> Processed = new Dictionary<CaseInsensitiveString, bool>();
126 List<KeyValuePair<string, string>> DomainNames = new List<KeyValuePair<string, string>>() { };
127
128 if (!string.IsNullOrEmpty(DomainConfiguration.Instance?.Domain))
129 DomainNames.Add(new KeyValuePair<string, string>(DomainConfiguration.Instance.Domain, DomainConfiguration.GATEWAY_DOMAIN_NAME));
130
132 {
133 foreach (string AltDomain in DomainConfiguration.Instance.AlternativeDomains)
134 DomainNames.Add(new KeyValuePair<string, string>(AltDomain, DomainConfiguration.GATEWAY_DOMAIN_ALT));
135 }
136
137 foreach (KeyValuePair<string, string> P in DomainNames)
138 {
139 string DomainName = P.Key;
140 if (Processed.ContainsKey(DomainName))
141 continue;
142
143 try
144 {
145 string[] Exchanges = await DnsResolver.TryLookupMailExchange(DomainName);
146
147 if (Exchanges is null || Exchanges.Length != 1)
148 throw new Exception("Invalid number of host names.");
149
150 if (Exchanges[0] != DomainName &&
151 Exchanges[0] != await Networking.SMTP.Server.SmtpServer.GetSalutation(DomainName))
152 {
153 throw new Exception("Does not point to correct host.");
154 }
155
156 this.PushResult(TabIDs, DomainName, "MX", true, string.Empty, P.Value);
157 }
158 catch (Exception ex)
159 {
160 this.PushResult(TabIDs, DomainName, "MX", false, ex.Message, P.Value);
161 await this.DiagnoseQuery(TabIDs, DomainName, QTYPE.MX);
162 }
163
164 try
165 {
166 string[] Rows = await DnsResolver.LookupText(DomainName);
167 int Found = 0;
168
169 if (!(Rows is null))
170 {
171 foreach (string Row in Rows)
172 {
173 string s = Row.Trim();
174
175 if (s.StartsWith("\"") && s.EndsWith("\""))
176 s = s[1..^1];
177
178 if (s.StartsWith("v=spf1"))
179 Found++;
180 }
181 }
182
183 if (Found != 1)
184 throw new Exception("Exactly one SPF record must be configured.");
185
186 this.PushResult(TabIDs, DomainName, "SPF", true, string.Empty, P.Value);
187 }
188 catch (Exception ex)
189 {
190 this.PushResult(TabIDs, DomainName, "SPF", false, ex.Message, P.Value);
191 await this.DiagnoseQuery(TabIDs, DomainName, QTYPE.TXT);
192 }
193
194 try
195 {
196 string[] Rows = await DnsResolver.TryLookupText("_xmppconnect." + DomainName);
197 int BoshFound = 0;
198 int WsFound = 0;
199 bool BoshValid = true;
200 bool WsValid = true;
201 bool Error = false;
202
203 if (!(Rows is null))
204 {
205 foreach (string Row in Rows)
206 {
207 string s = Row.Trim();
208
209 if (s.StartsWith("_xmpp-client-xbosh="))
210 {
211 BoshFound++;
212 if (s[19..] != WebServices.WebHostMetaDataXml.BoshLink(DomainName))
213 BoshValid = false;
214 }
215 else if (s.StartsWith("_xmpp-client-websocket="))
216 {
217 WsFound++;
218 if (s[23..] != WebServices.WebHostMetaDataXml.WebSocketLink(DomainName))
219 WsValid = false;
220 }
221 }
222 }
223
224 if (BoshFound == 1 && BoshValid)
225 this.PushResult(TabIDs, DomainName, "BOSH", true, string.Empty, P.Value);
226 else
227 {
228 Error = true;
229
230 if (BoshFound == 0)
231 this.PushResult(TabIDs, DomainName, "BOSH", false, "TXT Record for BOSH not found.", P.Value);
232 else if (BoshFound > 1)
233 this.PushResult(TabIDs, DomainName, "BOSH", false, "More than one TXT Record for BOSH found.", P.Value);
234 else
235 this.PushResult(TabIDs, DomainName, "BOSH", false, "TXT record for BOSH incorrect.", P.Value);
236 }
237
238 if (WsFound == 1 && WsValid)
239 this.PushResult(TabIDs, DomainName, "WS", true, string.Empty, P.Value);
240 else
241 {
242 Error = true;
243
244 if (WsFound == 0)
245 this.PushResult(TabIDs, DomainName, "WS", false, "TXT Record for WebSockets not found.", P.Value);
246 else if (WsFound > 1)
247 this.PushResult(TabIDs, DomainName, "WS", false, "More than one TXT Record for WebSockets found.", P.Value);
248 else
249 this.PushResult(TabIDs, DomainName, "WS", false, "TXT record for WebSockets incorrect.", P.Value);
250 }
251
252 if (Error)
253 await this.DiagnoseQuery(TabIDs, "_xmppconnect." + DomainName, QTYPE.TXT);
254 }
255 catch (Exception ex)
256 {
257 this.PushResult(TabIDs, DomainName, "BOSH", false, ex.Message, P.Value);
258 this.PushResult(TabIDs, DomainName, "WS", false, ex.Message, P.Value);
259
260 await this.DiagnoseQuery(TabIDs, "_xmppconnect." + DomainName, QTYPE.TXT);
261 }
262
263 try
264 {
265 SRV SRV = await DnsResolver.TryLookupServiceEndpoint(DomainName, "xmpp-client", "tcp");
266 if (SRV is null)
267 {
268 this.PushResult(TabIDs, DomainName, "SRV_XmppTcpClient", false, "XMPP Client TCP service not defined in DNS.", P.Value);
269 await this.DiagnoseQuery(TabIDs, "_xmpp-client._tcp." + DomainName, QTYPE.SRV);
270 }
271 else
272 {
273 if (SRV.Port != 5222)
274 throw new Exception("Invalid c2s port number.");
275
276 if (SRV.TargetHost != DomainName)
277 throw new Exception("Does not point to correct host.");
278
279 this.PushResult(TabIDs, DomainName, "SRV_XmppTcpClient", true, string.Empty, P.Value);
280 }
281 }
282 catch (Exception ex)
283 {
284 this.PushResult(TabIDs, DomainName, "SRV_XmppTcpClient", false, ex.Message, P.Value);
285 await this.DiagnoseQuery(TabIDs, "_xmpp-client._tcp." + DomainName, QTYPE.SRV);
286 }
287
288 try
289 {
290 SRV SRV = await DnsResolver.TryLookupServiceEndpoint(DomainName, "xmpp-server", "tcp");
291
292 if (SRV is null)
293 {
294 this.PushResult(TabIDs, DomainName, "SRV_XmppTcpServer", false, "XMPP Server TCP service not defined in DNS.", P.Value);
295 await this.DiagnoseQuery(TabIDs, "_xmpp-server._tcp." + DomainName, QTYPE.SRV);
296 }
297 else
298 {
299 if (SRV.Port != 5269)
300 throw new Exception("Invalid s2s port number.");
301
302 if (SRV.TargetHost != DomainName)
303 throw new Exception("Does not point to correct host.");
304
305 this.PushResult(TabIDs, DomainName, "SRV_XmppTcpServer", true, string.Empty, P.Value);
306 }
307 }
308 catch (Exception ex)
309 {
310 this.PushResult(TabIDs, DomainName, "SRV_XmppTcpServer", false, ex.Message, P.Value);
311 await this.DiagnoseQuery(TabIDs, "_xmpp-server._tcp." + DomainName, QTYPE.SRV);
312 }
313
314 foreach (IComponent Component in (XmppServerModule.Server?.Components ?? Array.Empty<IComponent>()))
315 {
316 string ComponentDomainName = Component.Subdomain.Value + "." + DomainName;
317
318 try
319 {
320 SRV SRV = await DnsResolver.TryLookupServiceEndpoint(ComponentDomainName, "xmpp-server", "tcp");
321
322 if (SRV is null)
323 {
324 this.PushResult(TabIDs, ComponentDomainName, "SRV_Component", false, "XMPP Server Component TCP service not defined in DNS.", P.Value);
325 await this.DiagnoseQuery(TabIDs, "_xmpp-server._tcp." + ComponentDomainName, QTYPE.SRV);
326 }
327 else
328 {
329 if (SRV.Port != 5269)
330 throw new Exception("Invalid s2s port number.");
331
333 throw new Exception("Does not point to correct host.");
334
335 this.PushResult(TabIDs, ComponentDomainName, "SRV_Component", true, string.Empty, P.Value);
336 }
337 }
338 catch (Exception ex)
339 {
340 this.PushResult(TabIDs, ComponentDomainName, "SRV_Component", false, ex.Message, P.Value);
341 await this.DiagnoseQuery(TabIDs, "_xmpp-server._tcp." + ComponentDomainName, QTYPE.SRV);
342 }
343 }
344 }
345 }
346 catch (Exception ex)
347 {
348 Log.Exception(ex);
349 }
350 }
351
352 private async Task DiagnoseQuery(string[] TabIDs, string Name, QTYPE TYPE)
353 {
354 StringBuilder sb = new StringBuilder();
355
356 sb.AppendLine("============ QUERY ============");
357 sb.Append("Name: ");
358 sb.AppendLine(Name);
359 sb.Append("Type: ");
360 sb.AppendLine(TYPE.ToString());
361 sb.AppendLine("Class: IN");
362 sb.AppendLine();
363 sb.AppendLine("========== RESPONSE ===========");
364
365 DnsResponse Response = await DnsResolver.TryQuery(Name, TYPE, QCLASS.IN);
366 if (Response is null)
367 sb.AppendLine("Unable to resolve DNS query.");
368 else
369 sb.AppendLine(Response.ToString());
370
371 await ClientEvents.PushEvent(TabIDs, "DnsDiagnosis", sb.ToString(), false);
372 }
373
374 private void PushResult(string[] TabIDs, string DomainName, string Suffix, bool Ok, string Message, string VariableName)
375 {
376 if (TabIDs is null)
377 {
378 if (!Ok)
379 this.LogEnvironmentError(Message, VariableName, DomainName);
380 }
381 else
382 {
383 ClientEvents.PushEvent(TabIDs, "DnsStatus", JSON.Encode(new Dictionary<string, object>()
384 {
385 { "domainName", DomainName },
386 { "suffix", Suffix },
387 { "ok", Ok },
388 { "message", Message }
389 }, false), true);
390 }
391 }
392
397 public override Task<bool> SimplifiedConfiguration()
398 {
399 return Task.FromResult(true);
400 }
401
406 public override async Task<bool> EnvironmentConfiguration()
407 {
408 try
409 {
410 await this.Test(null);
411 return true;
412 }
413 catch (Exception ex)
414 {
415 Log.Exception(ex);
416 return false;
417 }
418 }
419
420 }
421}
Contains information about a response to a content request.
bool HasError
If an error occurred.
object Decoded
Decoded object.
Helps with common JSON-related tasks.
Definition: JSON.cs:16
static string Encode(string s)
Encodes a string for inclusion in JSON.
Definition: JSON.cs:537
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 CaseInsensitiveString Domain
Domain name.
Definition: Gateway.cs:3087
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 DomainConfiguration Instance
Current instance of configuration.
const string GATEWAY_DOMAIN_NAME
Main Domain Name of the gateway, if defined. If not provided, the gateway will not use a domain name.
string[] AlternativeDomains
Alternative domain names
const string GATEWAY_DOMAIN_ALT
Comma-separated list of alternative domain names for the gateway, if defined.
Abstract base class for system configurations.
void LogEnvironmentError(string EnvironmentVariable, object Value)
Logs an error to the event log, telling the operator an environment variable value contains an error.
DNS resolver, as defined in:
Definition: DnsResolver.cs:32
static Task< SRV > TryLookupServiceEndpoint(string DomainName, string ServiceName, string Protocol)
Tries to look up a service endpoint for a domain. If multiple are available, an appropriate one is se...
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[]> LookupText(string Name)
Looks up text (TXT) records for a name.
Definition: DnsResolver.cs:944
static Task< DnsResponse > TryQuery(string Name, QTYPE TYPE, QCLASS CLASS)
Tries to query a DNS name.
Definition: DnsResolver.cs:424
static Task< string[]> TryLookupText(string Name)
Tries to look up text (TXT) records for a name.
Definition: DnsResolver.cs:954
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
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
Base class for components.
Definition: Component.cs:17
string Value
String-representation of the case-insensitive string. (Representation is case sensitive....
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 with information about what DNS configurations must be performed for the broker to ...
override Task InitSetup(HttpServer WebServer)
Initializes the setup object.
override void SetStaticInstance(ISystemConfiguration Configuration)
Sets the static instance of the configuration.
override Task UnregisterSetup(HttpServer WebServer)
Unregisters the setup object.
override async Task< bool > EnvironmentConfiguration()
Environment configuration by configuring values available in environment variables.
override string ConfigPrivilege
Minimum required privilege for a user to be allowed to change the configuration defined by the class.
override Task< string > Title(Language Language)
Gets a title for the system configuration.
override Task< bool > SimplifiedConfiguration()
Simplified configuration by configuring simple default values.
static DnsConfiguration Instance
Current instance of configuration.
override Task ConfigureSystem()
Is called during startup to configure the system.
override int Priority
Priority of the setting. Configurations are sorted in ascending order.
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 components.
Definition: IComponent.cs:10
Definition: ImplTypes.g.cs:58
QTYPE
QTYPE fields appear in the question part of a query.
Definition: QTYPE.cs:7
QCLASS
QCLASS fields appear in the question section of a query.
Definition: QCLASS.cs:7
TYPE
TYPE fields are used in resource records.
Definition: TYPE.cs:7