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;
2using System.Collections.Generic;
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 throw new BadRequestException();
104
105 object Obj = await Request.DecodeDataAsync();
106 if (!(Obj is string TabID))
107 throw new BadRequestException();
108
109 Response.StatusCode = 200;
110
111 Task _ = Task.Run(async () => await this.Test(TabID));
112 }
113
114 private async Task Test(params string[] TabIDs)
115 {
116 try
117 {
118 Dictionary<CaseInsensitiveString, bool> Processed = new Dictionary<CaseInsensitiveString, bool>();
119 List<KeyValuePair<string, string>> DomainNames = new List<KeyValuePair<string, string>>() { };
120
121 if (!string.IsNullOrEmpty(DomainConfiguration.Instance?.Domain))
122 DomainNames.Add(new KeyValuePair<string, string>(DomainConfiguration.Instance.Domain, DomainConfiguration.GATEWAY_DOMAIN_NAME));
123
125 {
126 foreach (string AltDomain in DomainConfiguration.Instance.AlternativeDomains)
127 DomainNames.Add(new KeyValuePair<string, string>(AltDomain, DomainConfiguration.GATEWAY_DOMAIN_ALT));
128 }
129
130 foreach (KeyValuePair<string, string> P in DomainNames)
131 {
132 string DomainName = P.Key;
133 if (Processed.ContainsKey(DomainName))
134 continue;
135
136 try
137 {
138 string[] Exchanges = await DnsResolver.LookupMailExchange(DomainName);
139
140 if (Exchanges.Length != 1)
141 throw new Exception("Invalid number of host names.");
142
143 if (Exchanges[0] != DomainName &&
144 Exchanges[0] != await Networking.SMTP.Server.SmtpServer.GetSalutation(DomainName))
145 {
146 throw new Exception("Does not point to correct host.");
147 }
148
149 this.PushResult(TabIDs, DomainName, "MX", true, string.Empty, P.Value);
150 }
151 catch (Exception ex)
152 {
153 this.PushResult(TabIDs, DomainName, "MX", false, ex.Message, P.Value);
154 await this.DiagnoseQuery(TabIDs, DomainName, QTYPE.MX);
155 }
156
157 try
158 {
159 string[] Rows = await DnsResolver.LookupText(DomainName);
160 int Found = 0;
161
162 foreach (string Row in Rows)
163 {
164 string s = Row.Trim();
165
166 if (s.StartsWith("\"") && s.EndsWith("\""))
167 s = s.Substring(1, s.Length - 2);
168
169 if (s.StartsWith("v=spf1"))
170 Found++;
171 }
172
173 if (Found != 1)
174 throw new Exception("Exactly one SPF record must be configured.");
175
176 this.PushResult(TabIDs, DomainName, "SPF", true, string.Empty, P.Value);
177 }
178 catch (Exception ex)
179 {
180 this.PushResult(TabIDs, DomainName, "SPF", false, ex.Message, P.Value);
181 await this.DiagnoseQuery(TabIDs, DomainName, QTYPE.TXT);
182 }
183
184 try
185 {
186 string[] Rows = await DnsResolver.LookupText("_xmppconnect." + DomainName);
187 int BoshFound = 0;
188 int WsFound = 0;
189 bool BoshValid = true;
190 bool WsValid = true;
191 bool Error = false;
192
193 foreach (string Row in Rows)
194 {
195 string s = Row.Trim();
196
197 if (s.StartsWith("_xmpp-client-xbosh="))
198 {
199 BoshFound++;
200 if (s.Substring(19) != WebServices.WebHostMetaDataXml.BoshLink(DomainName))
201 BoshValid = false;
202 }
203 else if (s.StartsWith("_xmpp-client-websocket="))
204 {
205 WsFound++;
206 if (s.Substring(23) != WebServices.WebHostMetaDataXml.WebSocketLink(DomainName))
207 WsValid = false;
208 }
209 }
210
211 if (BoshFound == 1 && BoshValid)
212 this.PushResult(TabIDs, DomainName, "BOSH", true, string.Empty, P.Value);
213 else
214 {
215 Error = true;
216
217 if (BoshFound == 0)
218 this.PushResult(TabIDs, DomainName, "BOSH", false, "TXT Record for BOSH not found.", P.Value);
219 else if (BoshFound > 1)
220 this.PushResult(TabIDs, DomainName, "BOSH", false, "More than one TXT Record for BOSH found.", P.Value);
221 else
222 this.PushResult(TabIDs, DomainName, "BOSH", false, "TXT record for BOSH incorrect.", P.Value);
223 }
224
225 if (WsFound == 1 && WsValid)
226 this.PushResult(TabIDs, DomainName, "WS", true, string.Empty, P.Value);
227 else
228 {
229 Error = true;
230
231 if (WsFound == 0)
232 this.PushResult(TabIDs, DomainName, "WS", false, "TXT Record for WebSockets not found.", P.Value);
233 else if (WsFound > 1)
234 this.PushResult(TabIDs, DomainName, "WS", false, "More than one TXT Record for WebSockets found.", P.Value);
235 else
236 this.PushResult(TabIDs, DomainName, "WS", false, "TXT record for WebSockets incorrect.", P.Value);
237 }
238
239 if (Error)
240 await this.DiagnoseQuery(TabIDs, "_xmppconnect." + DomainName, QTYPE.TXT);
241 }
242 catch (Exception ex)
243 {
244 this.PushResult(TabIDs, DomainName, "BOSH", false, ex.Message, P.Value);
245 this.PushResult(TabIDs, DomainName, "WS", false, ex.Message, P.Value);
246
247 await this.DiagnoseQuery(TabIDs, "_xmppconnect." + DomainName, QTYPE.TXT);
248 }
249
250 try
251 {
252 SRV SRV = await DnsResolver.LookupServiceEndpoint(DomainName, "xmpp-client", "tcp");
253
254 if (SRV.Port != 5222)
255 throw new Exception("Invalid c2s port number.");
256
257 if (SRV.TargetHost != DomainName)
258 throw new Exception("Does not point to correct host.");
259
260 this.PushResult(TabIDs, DomainName, "SRV_XmppTcpClient", true, string.Empty, P.Value);
261 }
262 catch (Exception ex)
263 {
264 this.PushResult(TabIDs, DomainName, "SRV_XmppTcpClient", false, ex.Message, P.Value);
265 await this.DiagnoseQuery(TabIDs, "_xmpp-client._tcp." + DomainName, QTYPE.SRV);
266 }
267
268 try
269 {
270 SRV SRV = await DnsResolver.LookupServiceEndpoint(DomainName, "xmpp-server", "tcp");
271
272 if (SRV.Port != 5269)
273 throw new Exception("Invalid s2s port number.");
274
275 if (SRV.TargetHost != DomainName)
276 throw new Exception("Does not point to correct host.");
277
278 this.PushResult(TabIDs, DomainName, "SRV_XmppTcpServer", true, string.Empty, P.Value);
279 }
280 catch (Exception ex)
281 {
282 this.PushResult(TabIDs, DomainName, "SRV_XmppTcpServer", false, ex.Message, P.Value);
283 await this.DiagnoseQuery(TabIDs, "_xmpp-server._tcp." + DomainName, QTYPE.SRV);
284 }
285
286 foreach (IComponent Component in (XmppServerModule.Server?.Components ?? new IComponent[0]))
287 {
288 string ComponentDomainName = Component.Subdomain.Value + "." + DomainName;
289
290 try
291 {
292 SRV SRV = await DnsResolver.LookupServiceEndpoint(ComponentDomainName, "xmpp-server", "tcp");
293
294 if (SRV.Port != 5269)
295 throw new Exception("Invalid s2s port number.");
296
298 throw new Exception("Does not point to correct host.");
299
300 this.PushResult(TabIDs, ComponentDomainName, "SRV_Component", true, string.Empty, P.Value);
301 }
302 catch (Exception ex)
303 {
304 this.PushResult(TabIDs, ComponentDomainName, "SRV_Component", false, ex.Message, P.Value);
305 await this.DiagnoseQuery(TabIDs, "_xmpp-server._tcp." + ComponentDomainName, QTYPE.SRV);
306 }
307 }
308 }
309 }
310 catch (Exception ex)
311 {
312 Log.Exception(ex);
313 }
314 }
315
316 private async Task DiagnoseQuery(string[] TabIDs, string Name, QTYPE TYPE)
317 {
318 StringBuilder sb = new StringBuilder();
319
320 sb.AppendLine("============ QUERY ============");
321 sb.Append("Name: ");
322 sb.AppendLine(Name);
323 sb.Append("Type: ");
324 sb.AppendLine(TYPE.ToString());
325 sb.AppendLine("Class: IN");
326 sb.AppendLine();
327 sb.AppendLine("========== RESPONSE ===========");
328
329 try
330 {
331 DnsResponse Response = await DnsResolver.Query(Name, TYPE, QCLASS.IN);
332 sb.AppendLine(Response.ToString());
333 }
334 catch (Exception ex)
335 {
336 sb.AppendLine(ex.Message);
337 }
338
339 await ClientEvents.PushEvent(TabIDs, "DnsDiagnosis", sb.ToString(), false);
340 }
341
342 private void PushResult(string[] TabIDs, string DomainName, string Suffix, bool Ok, string Message, string VariableName)
343 {
344 if (TabIDs is null)
345 {
346 if (!Ok)
347 this.LogEnvironmentError(Message, VariableName, DomainName);
348 }
349 else
350 {
351 ClientEvents.PushEvent(TabIDs, "DnsStatus", JSON.Encode(new Dictionary<string, object>()
352 {
353 { "domainName", DomainName },
354 { "suffix", Suffix },
355 { "ok", Ok },
356 { "message", Message }
357 }, false), true);
358 }
359 }
360
365 public override Task<bool> SimplifiedConfiguration()
366 {
367 return Task.FromResult(true);
368 }
369
374 public override async Task<bool> EnvironmentConfiguration()
375 {
376 try
377 {
378 await this.Test(null);
379 return true;
380 }
381 catch (Exception ex)
382 {
383 Log.Exception(ex);
384 return false;
385 }
386 }
387
388 }
389}
Helps with common JSON-related tasks.
Definition: JSON.cs:14
static string Encode(string s)
Encodes a string for inclusion in JSON.
Definition: JSON.cs:507
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 CaseInsensitiveString Domain
Domain name.
Definition: Gateway.cs:2354
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 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 async Task< string[]> LookupText(string Name)
Looks up text (TXT) records for a name.
Definition: DnsResolver.cs:745
static async Task< string[]> LookupMailExchange(string DomainName)
Looks up the Mail Exchanges related to a given domain name.
Definition: DnsResolver.cs:620
static Task< DnsResponse > Query(string Name, QTYPE TYPE, QCLASS CLASS)
Resolves a DNS name.
Definition: DnsResolver.cs:329
static Task< SRV > LookupServiceEndpoint(string DomainName, string ServiceName, string Protocol)
Looks up a service endpoint for a domain. If multiple are available, an appropriate one is selected a...
Definition: DnsResolver.cs:825
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
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
Base class for components.
Definition: Component.cs:16
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 Resource
Resource to be redirected to, to perform the configuration.
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
QTYPE
QTYPE fields appear in the question part of a query.
Definition: QTYPE.cs:11
QCLASS
QCLASS fields appear in the question section of a query.
Definition: QCLASS.cs:11
TYPE
TYPE fields are used in resource records.
Definition: TYPE.cs:11