Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
XmppConfiguration.cs
1using System;
3using System.IO;
4using System.Net;
5using System.Net.Http;
7using System.Security.Cryptography.X509Certificates;
8using System.Text;
9using System.Xml;
10using System.Threading.Tasks;
11using Waher.Content;
14using Waher.Events;
24using Waher.Security;
27
29{
34 {
38 C2S = 0,
39
43 BOSH = 1,
44
48 WS = 2
49 }
50
54 public partial class XmppConfiguration : SystemMultiStepConfiguration, IHostReference, IEncryptedProperties
55 {
56 private static XmppConfiguration instance = null;
57 private static string defaultFacility = string.Empty;
58 private static string defaultFacilityKey = string.Empty;
59
60 private HttpResource connectToHost = null;
61 private HttpResource randomizePassword = null;
62
63 private XmppTransportMethod transportMethod = XmppTransportMethod.C2S;
64 private string host = string.Empty;
65 private int port = XmppCredentials.DefaultPort;
66 private string boshUrl = string.Empty;
67 private string wsUrl = string.Empty;
68 private string account = string.Empty;
69 private string accountHumanReadableName = string.Empty;
70 private string password = string.Empty;
71 private string password0 = string.Empty;
72 private string passwordType = string.Empty;
73 private string thingRegistry = string.Empty;
74 private string provisioning = string.Empty;
75 private string events = string.Empty;
76 private string muc = string.Empty;
77 private string pubSub = string.Empty;
78 private string legal = string.Empty;
79 private string software = string.Empty;
80 private string geo = string.Empty;
81 private string bareJid = string.Empty;
82 private bool sniffer = false;
83 private bool trustServer = false;
84 private bool allowInsecureMechanisms = false;
85 private bool storePasswordInsteadOfHash = false;
86 private bool customBinding = false;
87 private bool offlineMessages = false;
88 private bool blocking = false;
89 private bool reporting = false;
90 private bool abuse = false;
91 private bool spam = false;
92 private bool pep = false;
93 private bool mail = false;
94
95 private TaskCompletionSource<bool> testConnection = null;
96 private XmppClient client = null;
97 private bool createAccount = false;
98
102 public static XmppConfiguration Instance => instance;
103
107 [DefaultValue(XmppTransportMethod.C2S)]
109 {
110 get => this.transportMethod;
111 set => this.transportMethod = value;
112 }
113
117 [DefaultValueStringEmpty]
118 public string Host
119 {
120 get => this.host;
121 set => this.host = value;
122 }
123
127 [DefaultValue(XmppCredentials.DefaultPort)]
128 public int Port
129 {
130 get => this.port;
131 set => this.port = value;
132 }
133
137 [DefaultValueStringEmpty]
138 public string BoshUrl
139 {
140 get => this.boshUrl;
141 set => this.boshUrl = value;
142 }
143
147 [DefaultValueStringEmpty]
148 public string WebSocketUrl
149 {
150 get => this.wsUrl;
151 set => this.wsUrl = value;
152 }
153
157 [DefaultValueStringEmpty]
158 public string Account
159 {
160 get => this.account;
161 set => this.account = value;
162 }
163
167 [Encrypted(32)]
168 public string Password
169 {
170 get => this.password;
171 set => this.password = value;
172 }
173
177 public string[] EncryptedProperties => new string[] { nameof(this.Password) };
178
182 [DefaultValueStringEmpty]
183 public string PasswordType
184 {
185 get => this.passwordType;
186 set => this.passwordType = value;
187 }
188
193 {
194 get => this.accountHumanReadableName;
195 set => this.accountHumanReadableName = value;
196 }
197
201 [DefaultValueStringEmpty]
202 public string ThingRegistry
203 {
204 get => this.thingRegistry;
205 set => this.thingRegistry = value;
206 }
207
211 [DefaultValueStringEmpty]
212 public string Provisioning
213 {
214 get => this.provisioning;
215 set => this.provisioning = value;
216 }
217
221 [DefaultValueStringEmpty]
222 public string Events
223 {
224 get => this.events;
225 set => this.events = value;
226 }
227
231 [DefaultValueStringEmpty]
232 public string MultiUserChat
233 {
234 get => this.muc;
235 set => this.muc = value;
236 }
237
241 [DefaultValueStringEmpty]
242 public string PubSub
243 {
244 get => this.pubSub;
245 set => this.pubSub = value;
246 }
247
251 [DefaultValueStringEmpty]
252 public string LegalIdentities
253 {
254 get => this.legal;
255 set => this.legal = value;
256 }
257
261 [DefaultValueStringEmpty]
262 public string SoftwareUpdates
263 {
264 get => this.software;
265 set => this.software = value;
266 }
267
271 [DefaultValueStringEmpty]
272 public string Geo
273 {
274 get => this.geo;
275 set => this.geo = value;
276 }
277
281 [DefaultValueStringEmpty]
282 public string BareJid
283 {
284 get => this.bareJid;
285 set => this.bareJid = value;
286 }
287
291 [DefaultValue(false)]
292 public bool Sniffer
293 {
294 get => this.sniffer;
295 set => this.sniffer = value;
296 }
297
301 [DefaultValue(false)]
302 public bool TrustServer
303 {
304 get => this.trustServer;
305 set => this.trustServer = value;
306 }
307
311 [DefaultValue(false)]
313 {
314 get => this.allowInsecureMechanisms;
315 set => this.allowInsecureMechanisms = value;
316 }
317
321 [DefaultValue(false)]
323 {
324 get => this.storePasswordInsteadOfHash;
325 set => this.storePasswordInsteadOfHash = value;
326 }
327
331 [DefaultValue(false)]
332 public bool CustomBinding
333 {
334 get => this.customBinding;
335 set => this.customBinding = value;
336 }
337
341 [DefaultValue(false)]
342 public bool OfflineMessages
343 {
344 get => this.offlineMessages;
345 set => this.offlineMessages = value;
346 }
347
351 [DefaultValue(false)]
352 public bool Blocking
353 {
354 get => this.blocking;
355 set => this.blocking = value;
356 }
357
361 [DefaultValue(false)]
362 public bool Reporting
363 {
364 get => this.reporting;
365 set => this.reporting = value;
366 }
367
371 [DefaultValue(false)]
372 public bool Abuse
373 {
374 get => this.abuse;
375 set => this.abuse = value;
376 }
377
381 [DefaultValue(false)]
382 public bool Spam
383 {
384 get => this.spam;
385 set => this.spam = value;
386 }
387
391 [DefaultValue(false)]
393 {
394 get => this.pep;
395 set => this.pep = value;
396 }
397
401 [DefaultValue(false)]
402 public bool Mail
403 {
404 get => this.mail;
405 set => this.mail = value;
406 }
407
411 [IgnoreMember]
412 public bool CreateAccount => this.createAccount;
413
417 public override string Resource => "/Settings/XMPP.md";
418
422 public override int Priority => 300;
423
429 public override Task<string> Title(Language Language)
430 {
431 return Language.GetStringAsync(typeof(Gateway), 6, "XMPP");
432 }
433
437 public override async Task ConfigureSystem()
438 {
439 await Gateway.ConfigureXmpp(this);
440 await this.CheckAdminAccount();
441 }
442
447 public override void SetStaticInstance(ISystemConfiguration Configuration)
448 {
449 instance = Configuration as XmppConfiguration;
450 }
451
456 public override Task InitSetup(HttpServer WebServer)
457 {
458 this.connectToHost = WebServer.Register("/Settings/ConnectToHost", null, this.ConnectToHost, true, false, true);
459 this.randomizePassword = WebServer.Register("/Settings/RandomizePassword", null, this.RandomizePassword, true, false, true);
460
461 return base.InitSetup(WebServer);
462 }
463
468 public override Task UnregisterSetup(HttpServer WebServer)
469 {
470 WebServer.Unregister(this.connectToHost);
471 WebServer.Unregister(this.randomizePassword);
472
473 return base.UnregisterSetup(WebServer);
474 }
475
479 protected override string ConfigPrivilege => "Admin.Communication.XMPP";
480
481 private async Task ConnectToHost(HttpRequest Request, HttpResponse Response)
482 {
483 Gateway.AssertUserAuthenticated(Request, this.ConfigPrivilege);
484
485 if (!Request.HasData)
486 {
487 await Response.SendResponse(new BadRequestException());
488 return;
489 }
490
491 ContentResponse Content = await Request.DecodeDataAsync();
492 if (Content.HasError || !(Content.Decoded is Dictionary<string, object> Parameters))
493 {
494 await Response.SendResponse(new BadRequestException());
495 return;
496 }
497
498 if (!Parameters.TryGetValue("host", out object Obj) || !(Obj is string HostName))
499 {
500 await Response.SendResponse(new BadRequestException());
501 return;
502 }
503
504 string TabID = Request.Header["X-TabID"];
505 if (string.IsNullOrEmpty(TabID))
506 {
507 await Response.SendResponse(new BadRequestException());
508 return;
509 }
510
511 if (!Parameters.TryGetValue("port", out Obj) || !(Obj is int Port) || Port < 1 || Port > 65535)
512 {
513 await Response.SendResponse(new BadRequestException());
514 return;
515 }
516
517 if (!Parameters.TryGetValue("boshUrl", out Obj) || !(Obj is string BoshUrl))
518 {
519 await Response.SendResponse(new BadRequestException());
520 return;
521 }
522
523 if (!Parameters.TryGetValue("wsUrl", out Obj) || !(Obj is string WsUrl))
524 {
525 await Response.SendResponse(new BadRequestException());
526 return;
527 }
528
529 if (!Parameters.TryGetValue("customBinding", out Obj) || !(Obj is bool CustomBinding))
530 {
531 await Response.SendResponse(new BadRequestException());
532 return;
533 }
534
535 if (!Parameters.TryGetValue("trustServer", out Obj) || !(Obj is bool TrustServer))
536 {
537 await Response.SendResponse(new BadRequestException());
538 return;
539 }
540
541 if (!Parameters.TryGetValue("insecureMechanisms", out Obj) || !(Obj is bool InsecureMechanisms))
542 {
543 await Response.SendResponse(new BadRequestException());
544 return;
545 }
546
547 if (!Parameters.TryGetValue("storePassword", out Obj) || !(Obj is bool StorePasswordInsteadOfHash))
548 {
549 await Response.SendResponse(new BadRequestException());
550 return;
551 }
552
553 if (!Parameters.TryGetValue("sniffer", out Obj) || !(Obj is bool Sniffer))
554 {
555 await Response.SendResponse(new BadRequestException());
556 return;
557 }
558
559 if (!Parameters.TryGetValue("transport", out Obj) || !(Obj is string s2) || !Enum.TryParse(s2, out XmppTransportMethod Method))
560 {
561 await Response.SendResponse(new BadRequestException());
562 return;
563 }
564
565 if (!Parameters.TryGetValue("account", out Obj) || !(Obj is string Account))
566 {
567 await Response.SendResponse(new BadRequestException());
568 return;
569 }
570
571 if (!Parameters.TryGetValue("password", out Obj) || !(Obj is string Password))
572 {
573 await Response.SendResponse(new BadRequestException());
574 return;
575 }
576
577 if (!Parameters.TryGetValue("createAccount", out Obj) || !(Obj is bool CreateAccount))
578 {
579 await Response.SendResponse(new BadRequestException());
580 return;
581 }
582
583 if (!Parameters.TryGetValue("accountName", out Obj) || !(Obj is string AccountName))
584 {
585 await Response.SendResponse(new BadRequestException());
586 return;
587 }
588
589 this.host = HostName;
590 this.port = Port;
591 this.boshUrl = BoshUrl.Trim();
592 this.wsUrl = WsUrl.Trim();
593 this.customBinding = CustomBinding;
594 this.trustServer = TrustServer;
595 this.allowInsecureMechanisms = InsecureMechanisms;
596 this.storePasswordInsteadOfHash = StorePasswordInsteadOfHash;
597 this.sniffer = Sniffer;
598 this.transportMethod = Method;
599 this.account = Account;
600 this.createAccount = CreateAccount;
601 this.accountHumanReadableName = AccountName;
602
603 if (this.password != Password)
604 {
605 this.password = this.password0 = Password;
606 this.passwordType = string.Empty;
607 }
608
609 if (!(this.client is null))
610 {
611 await this.client.OfflineAndDisposeAsync();
612 this.client = null;
613 }
614
615 this.Connect(TabID);
616
617 Response.StatusCode = 200;
618 Response.StatusMessage = "OK";
619 }
620
621 private Task RandomizePassword(HttpRequest Request, HttpResponse Response)
622 {
623 Response.StatusCode = 200;
624 Response.StatusMessage = "OK";
625 Response.ContentType = PlainTextCodec.DefaultContentType;
626
627 return Response.Write(RandomPassword.CreateRandomPassword());
628 }
629
635 {
636 XmppCredentials Credentials = new XmppCredentials()
637 {
638 Host = this.host,
639 Port = this.port,
640 Account = this.account,
641 Password = this.password,
642 PasswordType = this.passwordType,
643 ThingRegistry = this.thingRegistry,
644 Provisioning = this.provisioning,
645 Events = this.events,
646 TrustServer = this.trustServer,
647 Sniffer = this.sniffer,
648 AllowEncryption = true,
649 AllowCramMD5 = this.allowInsecureMechanisms,
650 AllowDigestMD5 = this.allowInsecureMechanisms,
651 AllowPlain = this.allowInsecureMechanisms,
652 AllowRegistration = this.createAccount,
653 AllowScramSHA1 = true,
654 AllowScramSHA256 = true,
655 RequestRosterOnStartup = true
656 };
657
658 if (this.createAccount && clp.TryGetValue(this.host, out KeyValuePair<string, string> P))
659 {
660 Credentials.FormSignatureKey = P.Key;
661 Credentials.FormSignatureSecret = P.Value;
662 }
663
664 switch (this.transportMethod)
665 {
666 case XmppTransportMethod.BOSH:
667 Credentials.UriEndpoint = this.boshUrl;
668 break;
669
670 case XmppTransportMethod.WS:
671 Credentials.UriEndpoint = this.wsUrl;
672 break;
673 }
674
675 return Credentials;
676 }
677
678 private void Connect(string TabID)
679 {
680 this.client = new XmppClient(this.GetCredentials(), "en", typeof(Gateway).Assembly);
681 this.client.SetTag("TabID", TabID);
682 this.client.SetTag("StartedEncryption", false);
683 this.client.SetTag("EncyptionSuccessful", false);
684 this.client.SetTag("StartedAuthentication", false);
685
686 if (this.sniffer)
687 {
689
690 if (Gateway.ConsoleOutput)
691 {
692 Sniffer = new ConsoleOutSniffer(BinaryPresentationMethod.ByteCount, LineEnding.PadWithSpaces);
693 this.client.Add(Sniffer);
694 }
695
696 Sniffer = new XmlFileSniffer(Gateway.AppDataFolder + "XMPP" + Path.DirectorySeparatorChar +
697 "XMPP Log %YEAR%-%MONTH%-%DAY%T%HOUR%.xml",
698 Gateway.AppDataFolder + "Transforms" + Path.DirectorySeparatorChar + "SnifferXmlToHtml.xslt",
699 7, BinaryPresentationMethod.ByteCount);
700 this.client.Add(Sniffer);
701 }
702
703 this.client.OnStateChanged += this.Client_OnStateChanged;
704 this.client.Connect();
705 }
706
707 private async Task Client_OnStateChanged(object Sender, XmppState NewState)
708 {
709 if (!(Sender is XmppClient Client))
710 return;
711
712 if (!Client.TryGetTag("TabID", out object Obj) || !(Obj is string TabID))
713 TabID = null;
714
715 try
716 {
717 string Msg;
718
719 switch (NewState)
720 {
721 case XmppState.Authenticating:
722 Client.SetTag("StartedAuthentication", true);
723 Client.SetTag("EncyptionSuccessful", true);
724 if (this.Step == 0)
725 {
726 if (!string.IsNullOrEmpty(TabID))
727 {
728 await ClientEvents.PushEvent(new string[] { TabID }, "ConnectionOK0", "Connection established.", false, "User");
729
730 if (!(this.client is null))
731 {
732 await this.client.OfflineAndDisposeAsync();
733 this.client = null;
734 }
735
736 this.Step = 1;
737 this.Updated = DateTime.Now;
738 await Database.Update(this);
739
740 return;
741 }
742 }
743
744 Msg = "Authenticating user.";
745 break;
746
747 case XmppState.Binding:
748 Msg = "Binding to resource.";
749 break;
750
751 case XmppState.Connected:
752 this.bareJid = Client.BareJID;
753 this.password = Client.PasswordHash;
754 this.passwordType = Client.PasswordHashMethod;
755
756 if (this.bareJid != defaultFacility)
757 {
758 defaultFacility = this.bareJid;
759
760 if (string.IsNullOrEmpty(defaultFacilityKey))
761 defaultFacilityKey = Hashes.BinaryToString(Gateway.NextBytes(32));
762
763 foreach (IEventSink Sink in Log.Sinks)
764 {
766 {
767 try
768 {
769 PersistedEventLog.SetDefaultFacility(defaultFacility, defaultFacilityKey);
770 }
771 catch (Exception ex)
772 {
773 Log.Exception(ex);
774 }
775
776 break;
777 }
778 }
779 }
780
781 if (this.createAccount && !string.IsNullOrEmpty(this.accountHumanReadableName))
782 {
783 if (!string.IsNullOrEmpty(TabID))
784 await ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Setting vCard.", false, "User");
785
786 StringBuilder Xml = new StringBuilder();
787
788 Xml.Append("<vCard xmlns='vcard-temp'>");
789 Xml.Append("<FN>");
790 Xml.Append(XML.Encode(this.accountHumanReadableName));
791 Xml.Append("</FN>");
792 Xml.Append("<JABBERID>");
793 Xml.Append(XML.Encode(this.client.BareJID));
794 Xml.Append("</JABBERID>");
795 Xml.Append("</vCard>");
796
797 await Client.IqSetAsync(this.client.BareJID, Xml.ToString());
798 }
799
800 if (!string.IsNullOrEmpty(TabID))
801 await ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Checking server features.", false, "User");
802
803 ServiceDiscoveryEventArgs e = await Client.ServiceDiscoveryAsync(null, string.Empty, string.Empty);
804
805 if (e.Ok)
806 {
807 this.offlineMessages = e.HasFeature("msgoffline");
808 this.blocking = e.HasFeature(Networking.XMPP.Abuse.AbuseClient.NamespaceBlocking);
809 this.reporting = e.HasFeature(Networking.XMPP.Abuse.AbuseClient.NamespaceReporting);
810 this.abuse = e.HasFeature(Networking.XMPP.Abuse.AbuseClient.NamespaceAbuseReason);
811 this.spam = e.HasFeature(Networking.XMPP.Abuse.AbuseClient.NamespaceSpamReason);
812 this.mail = e.HasFeature("urn:xmpp:smtp");
813 }
814 else
815 {
816 this.offlineMessages = false;
817 this.blocking = false;
818 this.reporting = false;
819 this.abuse = false;
820 this.spam = false;
821 this.mail = false;
822 }
823
824 if (!string.IsNullOrEmpty(TabID))
825 await ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Checking account features.", false, "User");
826
827 e = await Client.ServiceDiscoveryAsync(null, Client.BareJID, string.Empty);
828
829 this.pep = e.Ok && this.ContainsIdentity("pep", "pubsub", e);
830
831 if (!string.IsNullOrEmpty(TabID))
832 await ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Checking server components.", false, "User");
833
834 ServiceItemsDiscoveryEventArgs e2 = await Client.ServiceItemsDiscoveryAsync(null, string.Empty, string.Empty);
835
836 this.thingRegistry = string.Empty;
837 this.provisioning = string.Empty;
838 this.events = string.Empty;
839 this.muc = string.Empty;
840 this.pubSub = string.Empty;
841 this.legal = string.Empty;
842 this.software = string.Empty;
843 this.geo = string.Empty;
844
845 if (e2.Ok)
846 {
847 foreach (Item Item in e2.Items)
848 {
849 if (!string.IsNullOrEmpty(TabID))
850 await ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Checking component features for " + Item.JID, false, "User");
851
852 e = await Client.ServiceDiscoveryAsync(null, Item.JID, string.Empty);
853
854 if (e.HasAnyFeature(Networking.XMPP.Provisioning.ThingRegistryClient.NamespacesDiscovery))
855 this.thingRegistry = Item.JID;
856
857 if (e.HasAnyFeature(Networking.XMPP.Provisioning.ProvisioningClient.NamespacesProvisioningDevice))
858 this.provisioning = Item.JID;
859
860 if (e.HasFeature(Networking.XMPP.MUC.MultiUserChatClient.NamespaceMuc))
861 this.muc = Item.JID;
862
863 if (e.HasFeature(Networking.XMPP.PubSub.PubSubClient.NamespacePubSub) && this.ContainsIdentity("service", "pubsub", e))
864 this.pubSub = Item.JID;
865
867 this.events = Item.JID;
868
869 if (e.HasAnyFeature(Networking.XMPP.Contracts.ContractsClient.NamespacesLegalIdentities))
870 this.legal = Item.JID;
871
872 if (e.HasAnyFeature(Networking.XMPP.Software.SoftwareUpdateClient.NamespacesSoftwareUpdates))
873 this.software = Item.JID;
874
875 if (e.HasAnyFeature(Networking.XMPP.Geo.GeoClient.NamespacesGeoSpatial))
876 this.geo = Item.JID;
877 }
878 }
879
880 Dictionary<string, object> ConnectionInfo = new Dictionary<string, object>()
881 {
882 { "msg", "Connection successful." },
883 { "offlineMsg", this.offlineMessages },
884 { "blocking", this.blocking },
885 { "reporting", this.reporting },
886 { "abuse", this.abuse },
887 { "spam", this.spam },
888 { "mail", this.mail },
889 { "pep", this.pep ? this.bareJid : string.Empty },
890 { "thingRegistry", this.thingRegistry },
891 { "provisioning", this.provisioning },
892 { "eventLog", this.events },
893 { "pubSub", this.pubSub },
894 { "muc", this.muc },
895 { "legal", this.legal },
896 { "software", this.software },
897 { "geo", this.geo }
898 };
899
900 if (!string.IsNullOrEmpty(TabID))
901 await ClientEvents.PushEvent(new string[] { TabID }, "ConnectionOK1", JSON.Encode(ConnectionInfo, false), true, "User");
902
903 if (!(this.client is null))
904 {
905 this.client.OnStateChanged -= this.Client_OnStateChanged;
906 await this.client.OfflineAndDisposeAsync();
907 this.client = null;
908 }
909
910 this.Step = 2;
911 this.Updated = DateTime.Now;
912 await Database.Update(this);
913
914 this.testConnection?.TrySetResult(true);
915 return;
916
917 case XmppState.Connecting:
918 Msg = "Connecting to server.";
919 break;
920
921 case XmppState.Error:
922 bool Error = false;
923 Msg = string.Empty;
924
925 if (this.Step == 0 && this.transportMethod == XmppTransportMethod.C2S)
926 {
927 this.customBinding = true;
928
929 if (!string.IsNullOrEmpty(TabID))
930 {
931 await ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Unable to connect properly. Looking for alternative ways to connect.", false, "User");
932 await ClientEvents.PushEvent(new string[] { TabID }, "ShowCustomProperties", "{\"visible\":true}", true, "User");
933 }
934
935 using HttpClient HttpClient = new HttpClient(new HttpClientHandler()
936 {
937#if !NETFW
938 ServerCertificateCustomValidationCallback = this.RemoteCertificateValidationCallback,
939#endif
940 UseCookies = false,
941 AutomaticDecompression = (DecompressionMethods)(-1) // All
942 })
943 {
944 Timeout = TimeSpan.FromMilliseconds(60000)
945 };
946
947 try
948 {
949 Uri Uri = new Uri("http://" + this.host + "/.well-known/host-meta");
950 HttpResponseMessage Response = await HttpClient.GetAsync(Uri);
951 if (!Response.IsSuccessStatusCode)
952 {
953 ContentResponse Temp = await Content.Getters.WebGetter.ProcessResponse(Response, Uri);
954 Temp.AssertOk();
955 }
956
957 Stream Stream = await Response.Content.ReadAsStreamAsync(); // Regardless of status code, we check for XML content.
958 byte[] Bin = await Response.Content.ReadAsByteArrayAsync();
959 string CharSet = Response.Content.Headers.ContentType.CharSet;
960 System.Text.Encoding Encoding;
961
962 if (string.IsNullOrEmpty(CharSet))
963 Encoding = System.Text.Encoding.UTF8;
964 else
965 Encoding = InternetContent.GetEncoding(CharSet);
966
967 string XmlResponse = Strings.GetString(Bin, Encoding);
968 XmlDocument Doc = XML.ParseXml(XmlResponse, true);
969
970 if (!(Doc.DocumentElement is null) && Doc.DocumentElement.LocalName == "XRD")
971 {
972 string BoshUrl = null;
973 string WsUrl = null;
974
975 foreach (XmlNode N in Doc.DocumentElement.ChildNodes)
976 {
977 if (N is XmlElement E && E.LocalName == "Link")
978 {
979 switch (XML.Attribute(E, "rel"))
980 {
981 case "urn:xmpp:alt-connections:xbosh":
982 BoshUrl = XML.Attribute(E, "href");
983 break;
984
985 case "urn:xmpp:alt-connections:websocket":
986 WsUrl = XML.Attribute(E, "href");
987 break;
988 }
989 }
990 }
991
992 if (!string.IsNullOrEmpty(WsUrl))
993 {
994 this.wsUrl = WsUrl;
995 this.transportMethod = XmppTransportMethod.WS;
996
997 if (!string.IsNullOrEmpty(TabID))
998 await ClientEvents.PushEvent(new string[] { TabID }, "ShowTransport", "{\"method\":\"WS\"}", true, "User");
999
1000 this.Connect(TabID);
1001
1002 return;
1003 }
1004 else if (!string.IsNullOrEmpty(BoshUrl))
1005 {
1006 this.boshUrl = BoshUrl;
1007 this.transportMethod = XmppTransportMethod.BOSH;
1008
1009 if (!string.IsNullOrEmpty(TabID))
1010 await ClientEvents.PushEvent(new string[] { TabID }, "ShowTransport", "{\"method\":\"BOSH\"}", true, "User");
1011
1012 this.Connect(TabID);
1013
1014 return;
1015 }
1016 }
1017 }
1018 catch (Exception)
1019 {
1020 // Ignore.
1021 }
1022
1023 Msg = "No alternative binding methods found.";
1024 Error = true;
1025 }
1026 else
1027 {
1028 Msg = "Unable to connect properly.";
1029 Error = true;
1030
1031 if (Client.TryGetTag("StartedAuthentication", out Obj) && Obj is bool b && b)
1032 {
1033 if (!string.IsNullOrEmpty(TabID))
1034 {
1035 if (this.createAccount)
1036 await ClientEvents.PushEvent(new string[] { TabID }, "ShowFail2", Msg, false, "User");
1037 else
1038 await ClientEvents.PushEvent(new string[] { TabID }, "ShowFail1", Msg, false, "User");
1039 }
1040 return;
1041 }
1042 }
1043
1044 if (Error)
1045 {
1046 if (string.IsNullOrEmpty(TabID))
1047 this.LogEnvironmentError(Msg, GATEWAY_XMPP_HOST, this.host);
1048 else
1049 await ClientEvents.PushEvent(new string[] { TabID }, "ConnectionError", Msg, false, "User");
1050
1051 if (!(this.client is null))
1052 {
1053 await this.client.OfflineAndDisposeAsync();
1054 this.client = null;
1055 }
1056
1057 this.testConnection?.TrySetResult(false);
1058 return;
1059 }
1060 break;
1061
1062 case XmppState.FetchingRoster:
1063 Msg = "Fetching roster from server.";
1064 break;
1065
1066 case XmppState.Offline:
1067 Msg = "Offline.";
1068 break;
1069
1070 case XmppState.Registering:
1071 Msg = "Registering account.";
1072 break;
1073
1074 case XmppState.RequestingSession:
1075 Msg = "Requesting session.";
1076 break;
1077
1078 case XmppState.SettingPresence:
1079 Msg = "Setting presence.";
1080 break;
1081
1082 case XmppState.StartingEncryption:
1083 Msg = "Starting encryption.";
1084 Client.SetTag("StartedEncryption", true);
1085 break;
1086
1087 case XmppState.StreamNegotiation:
1088 Msg = "Negotiating stream.";
1089 break;
1090
1091 case XmppState.StreamOpened:
1092 Msg = "Stream opened.";
1093 break;
1094
1095 default:
1096 Msg = NewState.ToString();
1097 break;
1098 }
1099
1100 if (!string.IsNullOrEmpty(TabID))
1101 await ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", Msg, false, "User");
1102 }
1103 catch (Exception ex)
1104 {
1105 Log.Exception(ex);
1106
1107 if (!string.IsNullOrEmpty(TabID))
1108 await ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", ex.Message, false, "User");
1109 }
1110 }
1111
1112 private async Task CheckAdminAccount()
1113 {
1114 if (string.IsNullOrEmpty(this.password0) && (!string.IsNullOrEmpty(this.passwordType) || string.IsNullOrEmpty(this.password)))
1115 return;
1116
1117 User User = await Users.GetUser(this.account, true);
1118
1119 User.PasswordHash = Convert.ToBase64String(Users.ComputeHash(this.account,
1120 string.IsNullOrEmpty(this.password0) ? this.password : this.password0));
1121
1122 if (User.RoleIds.Length == 0)
1123 User.RoleIds = new string[] { "Administrator" };
1124
1125 await Database.Update(User);
1126
1127 Role Role = await Roles.GetRole("Administrator");
1128 if (Role.Privileges.Length == 0)
1129 {
1130 Role.Privileges = new PrivilegePattern[]
1131 {
1132 new PrivilegePattern(".*", true)
1133 };
1134
1135 if (string.IsNullOrEmpty(Role.Description))
1136 Role.Description = "Administrator role. Has all privileges by default.";
1137
1138 await Database.Update(Role);
1139 }
1140 }
1141
1142 private bool ContainsIdentity(string Type, string Cateogory, ServiceDiscoveryEventArgs e)
1143 {
1144 foreach (Identity Identity in e.Identities)
1145 {
1146 if (Identity.Type == Type && Identity.Category == Cateogory)
1147 return true;
1148 }
1149
1150 return false;
1151 }
1152
1153 private bool RemoteCertificateValidationCallback(object Sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
1154 {
1155 bool Valid;
1156
1157 if (sslPolicyErrors == SslPolicyErrors.None)
1158 Valid = true;
1159 else
1160 Valid = this.trustServer;
1161
1162 return Valid;
1163 }
1164
1168 public static string[] FeaturedServers
1169 {
1170 get
1171 {
1172 if (featuredServers is null)
1173 {
1174 string[] Result = new string[clp.Count];
1175 clp.Keys.CopyTo(Result, 0);
1176 Array.Sort(Result);
1177 featuredServers = Result;
1178 }
1179
1180 return featuredServers;
1181 }
1182 }
1183
1184 private static string[] featuredServers = null;
1185
1189 public const string GATEWAY_XMPP_HOST = nameof(GATEWAY_XMPP_HOST);
1190
1195 public const string GATEWAY_XMPP_TRANSPORT = nameof(GATEWAY_XMPP_TRANSPORT);
1196
1201 public const string GATEWAY_XMPP_PORT = nameof(GATEWAY_XMPP_PORT);
1202
1206 public const string GATEWAY_XMPP_BOSHURL = nameof(GATEWAY_XMPP_BOSHURL);
1207
1211 public const string GATEWAY_XMPP_WSURL = nameof(GATEWAY_XMPP_WSURL);
1212
1216 public const string GATEWAY_XMPP_CREATE = nameof(GATEWAY_XMPP_CREATE);
1217
1222
1227
1231 public const string GATEWAY_XMPP_ACCOUNT = nameof(GATEWAY_XMPP_ACCOUNT);
1232
1236 public const string GATEWAY_XMPP_PASSWORD = nameof(GATEWAY_XMPP_PASSWORD);
1237
1242
1247 public const string GATEWAY_XMPP_LOG = nameof(GATEWAY_XMPP_LOG);
1248
1253 public const string GATEWAY_XMPP_TRUST = nameof(GATEWAY_XMPP_TRUST);
1254
1259 public const string GATEWAY_XMPP_OBS_AUTH = nameof(GATEWAY_XMPP_OBS_AUTH);
1260
1265 public const string GATEWAY_XMPP_CLEAR_PWD = nameof(GATEWAY_XMPP_CLEAR_PWD);
1266
1271 public override async Task<bool> EnvironmentConfiguration()
1272 {
1273 if (!this.TryGetEnvironmentVariable(GATEWAY_XMPP_HOST, false, out this.host))
1274 return false;
1275
1276 if (this.TryGetEnvironmentVariable(GATEWAY_XMPP_TRANSPORT, false, out string Value))
1277 {
1278 if (!Enum.TryParse(Value, out XmppTransportMethod Method))
1279 {
1280 this.LogEnvironmentError("Invalid transport method.", GATEWAY_XMPP_TRANSPORT, Value);
1281 return false;
1282 }
1283
1284 this.transportMethod = Method;
1285 }
1286
1287 switch (this.transportMethod)
1288 {
1289 case XmppTransportMethod.C2S:
1290 Value = Environment.GetEnvironmentVariable(GATEWAY_XMPP_PORT);
1291 if (!string.IsNullOrEmpty(Value))
1292 {
1293 if (!int.TryParse(Value, out int i) || i <= 0 || i > 65535)
1294 {
1295 this.LogEnvironmentVariableInvalidRangeError(1, 65535, GATEWAY_XMPP_PORT, Value);
1296 return false;
1297 }
1298
1299 this.port = i;
1300 this.customBinding = false;
1301 }
1302 break;
1303
1304 case XmppTransportMethod.BOSH:
1305 if (!this.TryGetEnvironmentVariable(GATEWAY_XMPP_BOSHURL, true, out this.boshUrl))
1306 return false;
1307
1308 this.customBinding = true;
1309 break;
1310
1311 case XmppTransportMethod.WS:
1312 if (!this.TryGetEnvironmentVariable(GATEWAY_XMPP_WSURL, true, out this.wsUrl))
1313 return false;
1314
1315 this.customBinding = true;
1316 break;
1317
1318 default:
1319 this.LogEnvironmentError("Unhandled binding method.", GATEWAY_XMPP_TRANSPORT, this.transportMethod);
1320 return false;
1321 }
1322
1323 if (!this.TryGetEnvironmentVariable(GATEWAY_XMPP_CREATE, out this.createAccount, false))
1324 return false;
1325
1326 if (this.createAccount)
1327 {
1328 Value = Environment.GetEnvironmentVariable(GATEWAY_XMPP_CREATE_KEY);
1329 if (string.IsNullOrEmpty(Value))
1330 {
1331 if (!clp.ContainsKey(this.host))
1332 {
1333 this.LogEnvironmentError("Host is not a featured broker. If an account is to be created, an API Key must be provided.",
1334 GATEWAY_XMPP_CREATE_KEY, Value);
1335 return false;
1336 }
1337 }
1338 else
1339 {
1340 if (!this.TryGetEnvironmentVariable(GATEWAY_XMPP_CREATE_SECRET, true, out string Value2))
1341 return false;
1342
1343 clp[this.host] = new KeyValuePair<string, string>(Value, Value2);
1344 }
1345 }
1346
1347 if (!this.TryGetEnvironmentVariable(GATEWAY_XMPP_ACCOUNT, true, out this.account))
1348 return false;
1349
1350 if (!this.TryGetEnvironmentVariable(GATEWAY_XMPP_PASSWORD, false, out this.password))
1351 {
1352 if (this.createAccount)
1353 this.password = RandomPassword.CreateRandomPassword();
1354 else
1355 {
1356 this.LogEnvironmentVariableMissingError(GATEWAY_XMPP_PASSWORD, Value);
1357 return false;
1358 }
1359 }
1360
1361 this.password0 = this.password;
1362 this.passwordType = string.Empty;
1363
1364 if (this.TryGetEnvironmentVariable(GATEWAY_XMPP_ACCOUNT_NAME, false, out Value))
1365 this.accountHumanReadableName = Value;
1366
1367 if (!this.TryGetEnvironmentVariable(GATEWAY_XMPP_LOG, out this.sniffer, false))
1368 return false;
1369
1370 if (!this.TryGetEnvironmentVariable(GATEWAY_XMPP_TRUST, out this.trustServer, false))
1371 return false;
1372
1373 if (!this.TryGetEnvironmentVariable(GATEWAY_XMPP_OBS_AUTH, out this.allowInsecureMechanisms, false))
1374 return false;
1375
1376 if (!this.TryGetEnvironmentVariable(GATEWAY_XMPP_CLEAR_PWD, out this.storePasswordInsteadOfHash, false))
1377 return false;
1378
1379 this.testConnection = new TaskCompletionSource<bool>();
1380 try
1381 {
1382 Task _ = Task.Delay(30000).ContinueWith(Prev => this.testConnection?.TrySetException(new TimeoutException()));
1383
1384 this.Connect(null);
1385
1386 if (await this.testConnection.Task)
1387 return true;
1388 else
1389 return false;
1390 }
1391 catch (Exception ex)
1392 {
1393 Log.Exception(ex);
1394 return false;
1395 }
1396 finally
1397 {
1398 this.testConnection = null;
1399 }
1400 }
1401 }
1402}
Contains information about a response to a content request.
bool HasError
If an error occurred.
object Decoded
Decoded object.
void AssertOk()
Asserts response is OK.
Static class managing encoding and decoding of internet content.
static Encoding GetEncoding(string CharacterSet)
Gets a character encoding from its name.
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
Plain text encoder/decoder.
const string DefaultContentType
text/plain
Helps with common XML-related tasks.
Definition: XML.cs:21
static string Attribute(XmlElement E, string Name)
Gets the value of an XML attribute.
Definition: XML.cs:1062
static string Encode(string s)
Encodes a string for use in XML.
Definition: XML.cs:29
static XmlDocument ParseXml(string Xml)
Parses an XML Document from its string representation.
Definition: XML.cs:713
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:14
static IEventSink[] Sinks
Registered sinks.
Definition: Log.cs:132
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
Creates an even sink that stores incoming (logged) events in the local object database,...
void SetDefaultFacility(string DefaultFacility, string DefaultFacilityKey)
Sets the default facility. The default facility can only be reset by a caller presenting the same key...
Event sink sending events to a destination over the XMPP network.
const string NamespaceEventLogging
urn:xmpp:eventlog
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 byte[] NextBytes(int NrBytes)
Generates an array of random bytes.
Definition: Gateway.cs:4335
static string AppDataFolder
Application data folder.
Definition: Gateway.cs:3132
static string CreateRandomPassword(int NrBytes, int NrBuckets)
Creates a random password.
void LogEnvironmentVariableInvalidRangeError(int Min, int Max, string EnvironmentVariable, object Value)
Logs an error to the event log, telling the operator an environment variable value is not within a va...
void LogEnvironmentVariableMissingError(string EnvironmentVariable, object Value)
Logs an error to the event log, telling the operator an environment variable value is missing.
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.
string LegalIdentities
JID of legal identities component.
const string GATEWAY_XMPP_PASSWORD
Password of account. If creating an account, this variable is optional. If not available,...
override void SetStaticInstance(ISystemConfiguration Configuration)
Sets the static instance of the configuration.
const string GATEWAY_XMPP_TRANSPORT
XMPP transport method(a.k.a.binding). Can be C2S (default if variable not available),...
string SoftwareUpdates
JID of software updates component.
override Task< string > Title(Language Language)
Gets a title for the system configuration.
XmppTransportMethod TransportMethod
Transport method
const string GATEWAY_XMPP_ACCOUNT_NAME
Optional Human-readable name of account.
override async Task ConfigureSystem()
Is called during startup to configure the system.
const string GATEWAY_XMPP_WSURL
URL to use when connecting to host. (If WS binding has been selected).
static string[] FeaturedServers
Array of XMPP servers featured by the installation.
override Task UnregisterSetup(HttpServer WebServer)
Unregisters the setup object.
const string GATEWAY_XMPP_BOSHURL
URL to use when connecting to host. (If BOSH binding has been selected).
XmppCredentials GetCredentials()
Gets connection credentials.
bool StorePasswordInsteadOfHash
If password should be stored instead of hash.
string Geo
JID of geo-spatial publish/subscribe component.
bool Reporting
If reporting is supported by the server.
override int Priority
Priority of the setting. Configurations are sorted in ascending order.
string ThingRegistry
JID of Thing Registry.
bool TrustServer
If server is to be trusted (true), or if certificate should be validated before connection is accepte...
string PubSub
JID of publish/subscribe component.
bool Abuse
If reporting abuse is supported by the server.
string WebSocketUrl
Web-socket URL to bind to.
static XmppConfiguration Instance
Current instance of configuration.
const string GATEWAY_XMPP_CREATE_SECRET
API-Key secret to use when creating account, if host is not one of the featured hosts.
override string ConfigPrivilege
Minimum required privilege for a user to be allowed to change the configuration defined by the class.
bool AllowInsecureMechanisms
If insecure authentication methods should be allowed.
bool PersonalEventing
If the Personal Eventing Protocol (PEP) is supported by the server.
override async Task< bool > EnvironmentConfiguration()
Environment configuration by configuring values available in environment variables.
const string GATEWAY_XMPP_CREATE_KEY
API-Key to use when creating account, if host is not one of the featured hosts.
bool Sniffer
If communication should be sniffed.
const string GATEWAY_XMPP_HOST
XMPP broker to connect to. (If C2S binding has been selected).
const string GATEWAY_XMPP_TRUST
Optional. true or 1 if gateway should trust server certificate, even if it does not validate,...
override Task InitSetup(HttpServer WebServer)
Initializes the setup object.
const string GATEWAY_XMPP_OBS_AUTH
Optional. true or 1 if gateway should be allowed to use obsolete and insecure authentication mechanis...
bool Mail
If mail is supported by the server.
bool CreateAccount
If an account can be created.
bool OfflineMessages
If offline messages are supported by the server.
string PasswordType
Type of password. Empty string = clear text, otherwise, type of hash.
bool Spam
If reporting spam is supported by the server.
string AccountHumanReadableName
Human readable account name, if any.
const string GATEWAY_XMPP_PORT
Optional Port number to use when connecting to host. (If C2S binding has been selected....
string[] EncryptedProperties
Array of properties that are encrypted.
const string GATEWAY_XMPP_CLEAR_PWD
Optional. true or 1 if gateway should store password as-is in the database, false or 0 if only the pa...
bool CustomBinding
If a custom binding is used.
string MultiUserChat
JID of Multi-User Chat service.
const string GATEWAY_XMPP_CREATE
If an account is to be created.
const string GATEWAY_XMPP_ACCOUNT
Name of account.
const string GATEWAY_XMPP_LOG
Optional. true or 1 if gateway should log communication to program data folder, false or 0 if communi...
bool Blocking
If blocking accounts is supported by the server.
string Provisioning
JID of provisioning server.
virtual void Add(ISniffer Sniffer)
ICommunicationLayer.Add
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...
Task Write(byte[] Data)
Returns binary data in the response.
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
Outputs sniffed data to the Console Output, serialized by ConsoleOut.
Outputs sniffed data to an XML file.
bool Ok
If the response is an OK result response (true), or an error response (false).
Contains information about an identity of an entity.
Definition: Identity.cs:11
Contains information about an item of an entity.
Definition: Item.cs:11
bool HasFeature(string Feature)
Checks if the remote entity supports a specific feature.
bool HasAnyFeature(params string[] Features)
Checks if the remote entity supports any of a set of features.
Manages an XMPP client connection. Implements XMPP, as defined in https://tools.ietf....
Definition: XmppClient.cs:58
Task OfflineAndDisposeAsync()
Sends an offline presence, and then disposes the object by calling DisposeAsync.
Definition: XmppClient.cs:1107
void SetTag(string TagName, object Tag)
Sets a tag value.
Definition: XmppClient.cs:7312
Task Connect()
Connects the client.
Definition: XmppClient.cs:641
Class containing credentials for an XMPP client connection.
const int DefaultPort
Default XMPP Server port.
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
Static class managing binary representations of strings.
Definition: Strings.cs:10
static string GetString(byte[] Data, int Offset, int Count, Encoding DefaultEncoding)
Gets a string from its binary representation, taking any Byte Order Mark (BOM) into account.
Definition: Strings.cs:148
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
Contains methods for simple hash calculations.
Definition: Hashes.cs:57
static string BinaryToString(byte[] Data)
Converts an array of bytes to a string with their hexadecimal representations (in lower case).
Definition: Hashes.cs:63
Contains a reference to a privilege
Corresponds to a role in the system.
Definition: Role.cs:15
string Description
Description of privilege.
Definition: Role.cs:52
PrivilegePattern[] Privileges
Privileges
Definition: Role.cs:62
Maintains the collection of all roles in the system.
Definition: Roles.cs:14
static Task< Role > GetRole(string RoleId)
Gets the Role object corresponding to a Role ID.
Definition: Roles.cs:23
Corresponds to a user in the system.
Definition: User.cs:24
string[] RoleIds
Role IDs
Definition: User.cs:94
Maintains the collection of all users in the system.
Definition: Users.cs:24
static byte[] ComputeHash(string UserName, string Password)
Computes a hash of a password.
Definition: Users.cs:157
static async Task< User > GetUser(string UserName, bool CreateIfNew)
Gets the User object corresponding to a User Name.
Definition: Users.cs:65
Interface for objects that contain a reference to a host.
Interface for all event sinks.
Definition: IEventSink.cs:9
Interface for system configurations. The gateway will scan all module for system configuration classe...
Interface for sniffers. Sniffers can be added to ICommunicationLayer classes to eavesdrop on communic...
Definition: ISniffer.cs:10
Interface for objects containing encrypted properties. Mark the properties that are encrypted with th...
Definition: ImplTypes.g.cs:58
XmppTransportMethod
How to bind to the XMPP network.
LineEnding
Type of line ending.
Definition: LineEnding.cs:7
BinaryPresentationMethod
How binary data is to be presented.
XmppState
State of XMPP connection.
Definition: XmppState.cs:7
Definition: App.xaml.cs:4