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;
2using System.Collections.Generic;
3using System.IO;
4using System.Net;
5using System.Net.Http;
6using System.Net.Security;
7using System.Security.Cryptography.X509Certificates;
8using System.Text;
9using System.Xml;
10using System.Threading.Tasks;
11using Waher.Content;
14using Waher.Events;
24using Waher.Security;
26
28{
33 {
37 C2S = 0,
38
42 BOSH = 1,
43
47 WS = 2
48 }
49
53 public partial class XmppConfiguration : SystemMultiStepConfiguration, IHostReference
54 {
55 private static XmppConfiguration instance = null;
56 private static string defaultFacility = string.Empty;
57 private static string defaultFacilityKey = string.Empty;
58
59 private HttpResource connectToHost = null;
60 private HttpResource randomizePassword = null;
61
62 private XmppTransportMethod transportMethod = XmppTransportMethod.C2S;
63 private string host = string.Empty;
64 private int port = XmppCredentials.DefaultPort;
65 private string boshUrl = string.Empty;
66 private string wsUrl = string.Empty;
67 private string account = string.Empty;
68 private string accountHumanReadableName = string.Empty;
69 private string password = string.Empty;
70 private string password0 = string.Empty;
71 private string passwordType = string.Empty;
72 private string thingRegistry = string.Empty;
73 private string provisioning = string.Empty;
74 private string events = string.Empty;
75 private string muc = string.Empty;
76 private string pubSub = string.Empty;
77 private string legal = string.Empty;
78 private string software = string.Empty;
79 private string bareJid = string.Empty;
80 private bool sniffer = false;
81 private bool trustServer = false;
82 private bool allowInsecureMechanisms = false;
83 private bool storePasswordInsteadOfHash = false;
84 private bool customBinding = false;
85 private bool offlineMessages = false;
86 private bool blocking = false;
87 private bool reporting = false;
88 private bool abuse = false;
89 private bool spam = false;
90 private bool pep = false;
91 private bool mail = false;
92
93 private TaskCompletionSource<bool> testConnection = null;
94 private XmppClient client = null;
95 private bool createAccount = false;
96
100 public static XmppConfiguration Instance => instance;
101
105 [DefaultValue(XmppTransportMethod.C2S)]
107 {
108 get => this.transportMethod;
109 set => this.transportMethod = value;
110 }
111
115 [DefaultValueStringEmpty]
116 public string Host
117 {
118 get => this.host;
119 set => this.host = value;
120 }
121
125 [DefaultValue(XmppCredentials.DefaultPort)]
126 public int Port
127 {
128 get => this.port;
129 set => this.port = value;
130 }
131
135 [DefaultValueStringEmpty]
136 public string BoshUrl
137 {
138 get => this.boshUrl;
139 set => this.boshUrl = value;
140 }
141
145 [DefaultValueStringEmpty]
146 public string WebSocketUrl
147 {
148 get => this.wsUrl;
149 set => this.wsUrl = value;
150 }
151
155 [DefaultValueStringEmpty]
156 public string Account
157 {
158 get => this.account;
159 set => this.account = value;
160 }
161
165 [DefaultValueStringEmpty]
166 public string Password
167 {
168 get => this.password;
169 set => this.password = value;
170 }
171
175 [DefaultValueStringEmpty]
176 public string PasswordType
177 {
178 get => this.passwordType;
179 set => this.passwordType = value;
180 }
181
186 {
187 get => this.accountHumanReadableName;
188 set => this.accountHumanReadableName = value;
189 }
190
194 [DefaultValueStringEmpty]
195 public string ThingRegistry
196 {
197 get => this.thingRegistry;
198 set => this.thingRegistry = value;
199 }
200
204 [DefaultValueStringEmpty]
205 public string Provisioning
206 {
207 get => this.provisioning;
208 set => this.provisioning = value;
209 }
210
214 [DefaultValueStringEmpty]
215 public string Events
216 {
217 get => this.events;
218 set => this.events = value;
219 }
220
224 [DefaultValueStringEmpty]
225 public string MultiUserChat
226 {
227 get => this.muc;
228 set => this.muc = value;
229 }
230
234 [DefaultValueStringEmpty]
235 public string PubSub
236 {
237 get => this.pubSub;
238 set => this.pubSub = value;
239 }
240
244 [DefaultValueStringEmpty]
245 public string LegalIdentities
246 {
247 get => this.legal;
248 set => this.legal = value;
249 }
250
254 [DefaultValueStringEmpty]
255 public string SoftwareUpdates
256 {
257 get => this.software;
258 set => this.software = value;
259 }
260
264 [DefaultValueStringEmpty]
265 public string BareJid
266 {
267 get => this.bareJid;
268 set => this.bareJid = value;
269 }
270
274 [DefaultValue(false)]
275 public bool Sniffer
276 {
277 get => this.sniffer;
278 set => this.sniffer = value;
279 }
280
284 [DefaultValue(false)]
285 public bool TrustServer
286 {
287 get => this.trustServer;
288 set => this.trustServer = value;
289 }
290
294 [DefaultValue(false)]
296 {
297 get => this.allowInsecureMechanisms;
298 set => this.allowInsecureMechanisms = value;
299 }
300
304 [DefaultValue(false)]
306 {
307 get => this.storePasswordInsteadOfHash;
308 set => this.storePasswordInsteadOfHash = value;
309 }
310
314 [DefaultValue(false)]
315 public bool CustomBinding
316 {
317 get => this.customBinding;
318 set => this.customBinding = value;
319 }
320
324 [DefaultValue(false)]
325 public bool OfflineMessages
326 {
327 get => this.offlineMessages;
328 set => this.offlineMessages = value;
329 }
330
334 [DefaultValue(false)]
335 public bool Blocking
336 {
337 get => this.blocking;
338 set => this.blocking = value;
339 }
340
344 [DefaultValue(false)]
345 public bool Reporting
346 {
347 get => this.reporting;
348 set => this.reporting = value;
349 }
350
354 [DefaultValue(false)]
355 public bool Abuse
356 {
357 get => this.abuse;
358 set => this.abuse = value;
359 }
360
364 [DefaultValue(false)]
365 public bool Spam
366 {
367 get => this.spam;
368 set => this.spam = value;
369 }
370
374 [DefaultValue(false)]
376 {
377 get => this.pep;
378 set => this.pep = value;
379 }
380
384 [DefaultValue(false)]
385 public bool Mail
386 {
387 get => this.mail;
388 set => this.mail = value;
389 }
390
394 [IgnoreMember]
395 public bool CreateAccount => this.createAccount;
396
400 public override string Resource => "/Settings/XMPP.md";
401
405 public override int Priority => 300;
406
412 public override Task<string> Title(Language Language)
413 {
414 return Language.GetStringAsync(typeof(Gateway), 6, "XMPP");
415 }
416
420 public override async Task ConfigureSystem()
421 {
422 await Gateway.ConfigureXmpp(this);
423 await this.CheckAdminAccount();
424 }
425
430 public override void SetStaticInstance(ISystemConfiguration Configuration)
431 {
432 instance = Configuration as XmppConfiguration;
433 }
434
439 public override Task InitSetup(HttpServer WebServer)
440 {
441 this.connectToHost = WebServer.Register("/Settings/ConnectToHost", null, this.ConnectToHost, true, false, true);
442 this.randomizePassword = WebServer.Register("/Settings/RandomizePassword", null, this.RandomizePassword, true, false, true);
443
444 return base.InitSetup(WebServer);
445 }
446
451 public override Task UnregisterSetup(HttpServer WebServer)
452 {
453 WebServer.Unregister(this.connectToHost);
454 WebServer.Unregister(this.randomizePassword);
455
456 return base.UnregisterSetup(WebServer);
457 }
458
462 protected override string ConfigPrivilege => "Admin.Communication.XMPP";
463
464 private async Task ConnectToHost(HttpRequest Request, HttpResponse Response)
465 {
466 Gateway.AssertUserAuthenticated(Request, this.ConfigPrivilege);
467
468 if (!Request.HasData)
469 throw new BadRequestException();
470
471 object Obj = await Request.DecodeDataAsync();
472 if (!(Obj is Dictionary<string, object> Parameters))
473 throw new BadRequestException();
474
475 if (!Parameters.TryGetValue("host", out Obj) || !(Obj is string HostName))
476 throw new BadRequestException();
477
478 string TabID = Request.Header["X-TabID"];
479 if (string.IsNullOrEmpty(TabID))
480 throw new BadRequestException();
481
482 if (!Parameters.TryGetValue("port", out Obj) || !(Obj is int Port) || Port < 1 || Port > 65535)
483 throw new BadRequestException();
484
485 if (!Parameters.TryGetValue("boshUrl", out Obj) || !(Obj is string BoshUrl))
486 throw new BadRequestException();
487
488 if (!Parameters.TryGetValue("wsUrl", out Obj) || !(Obj is string WsUrl))
489 throw new BadRequestException();
490
491 if (!Parameters.TryGetValue("customBinding", out Obj) || !(Obj is bool CustomBinding))
492 throw new BadRequestException();
493
494 if (!Parameters.TryGetValue("trustServer", out Obj) || !(Obj is bool TrustServer))
495 throw new BadRequestException();
496
497 if (!Parameters.TryGetValue("insecureMechanisms", out Obj) || !(Obj is bool InsecureMechanisms))
498 throw new BadRequestException();
499
500 if (!Parameters.TryGetValue("storePassword", out Obj) || !(Obj is bool StorePasswordInsteadOfHash))
501 throw new BadRequestException();
502
503 if (!Parameters.TryGetValue("sniffer", out Obj) || !(Obj is bool Sniffer))
504 throw new BadRequestException();
505
506 if (!Parameters.TryGetValue("transport", out Obj) || !(Obj is string s2) || !Enum.TryParse(s2, out XmppTransportMethod Method))
507 throw new BadRequestException();
508
509 if (!Parameters.TryGetValue("account", out Obj) || !(Obj is string Account))
510 throw new BadRequestException();
511
512 if (!Parameters.TryGetValue("password", out Obj) || !(Obj is string Password))
513 throw new BadRequestException();
514
515 if (!Parameters.TryGetValue("createAccount", out Obj) || !(Obj is bool CreateAccount))
516 throw new BadRequestException();
517
518 if (!Parameters.TryGetValue("accountName", out Obj) || !(Obj is string AccountName))
519 throw new BadRequestException();
520
521 this.host = HostName;
522 this.port = Port;
523 this.boshUrl = BoshUrl.Trim();
524 this.wsUrl = WsUrl.Trim();
525 this.customBinding = CustomBinding;
526 this.trustServer = TrustServer;
527 this.allowInsecureMechanisms = InsecureMechanisms;
528 this.storePasswordInsteadOfHash = StorePasswordInsteadOfHash;
529 this.sniffer = Sniffer;
530 this.transportMethod = Method;
531 this.account = Account;
532 this.createAccount = CreateAccount;
533 this.accountHumanReadableName = AccountName;
534
535 if (this.password != Password)
536 {
537 this.password = this.password0 = Password;
538 this.passwordType = string.Empty;
539 }
540
541 if (!(this.client is null))
542 {
543 await this.client.OfflineAndDisposeAsync();
544 this.client = null;
545 }
546
547 this.Connect(TabID);
548
549 Response.StatusCode = 200;
550 }
551
552 private Task RandomizePassword(HttpRequest Request, HttpResponse Response)
553 {
554 Response.StatusCode = 200;
555 Response.ContentType = PlainTextCodec.DefaultContentType;
556
557 return Response.Write(RandomPassword.CreateRandomPassword());
558 }
559
565 {
566 XmppCredentials Credentials = new XmppCredentials()
567 {
568 Host = this.host,
569 Port = this.port,
570 Account = this.account,
571 Password = this.password,
572 PasswordType = this.passwordType,
573 ThingRegistry = this.thingRegistry,
574 Provisioning = this.provisioning,
575 Events = this.events,
576 TrustServer = this.trustServer,
577 Sniffer = this.sniffer,
578 AllowEncryption = true,
579 AllowCramMD5 = this.allowInsecureMechanisms,
580 AllowDigestMD5 = this.allowInsecureMechanisms,
581 AllowPlain = this.allowInsecureMechanisms,
582 AllowRegistration = this.createAccount,
583 AllowScramSHA1 = true,
584 AllowScramSHA256 = true,
585 RequestRosterOnStartup = true
586 };
587
588 if (this.createAccount && clp.TryGetValue(this.host, out KeyValuePair<string, string> P))
589 {
590 Credentials.FormSignatureKey = P.Key;
591 Credentials.FormSignatureSecret = P.Value;
592 }
593
594 switch (this.transportMethod)
595 {
596 case XmppTransportMethod.BOSH:
597 Credentials.UriEndpoint = this.boshUrl;
598 break;
599
600 case XmppTransportMethod.WS:
601 Credentials.UriEndpoint = this.wsUrl;
602 break;
603 }
604
605 return Credentials;
606 }
607
608 private void Connect(string TabID)
609 {
610 this.client = new XmppClient(this.GetCredentials(), "en", typeof(Gateway).Assembly);
611 this.client.SetTag("TabID", TabID);
612 this.client.SetTag("StartedEncryption", false);
613 this.client.SetTag("EncyptionSuccessful", false);
614 this.client.SetTag("StartedAuthentication", false);
615
616 if (this.sniffer)
617 {
619
620 if (Gateway.ConsoleOutput)
621 {
622 Sniffer = new ConsoleOutSniffer(BinaryPresentationMethod.ByteCount, LineEnding.PadWithSpaces);
623 this.client.Add(Sniffer);
624 }
625
626 Sniffer = new XmlFileSniffer(Gateway.AppDataFolder + "XMPP" + Path.DirectorySeparatorChar +
627 "XMPP Log %YEAR%-%MONTH%-%DAY%T%HOUR%.xml",
628 Gateway.AppDataFolder + "Transforms" + Path.DirectorySeparatorChar + "SnifferXmlToHtml.xslt",
629 7, BinaryPresentationMethod.ByteCount);
630 this.client.Add(Sniffer);
631 }
632
633 this.client.OnStateChanged += this.Client_OnStateChanged;
634 this.client.Connect();
635 }
636
637 private async Task Client_OnStateChanged(object Sender, XmppState NewState)
638 {
639 if (!(Sender is XmppClient Client))
640 return;
641
642 if (!Client.TryGetTag("TabID", out object Obj) || !(Obj is string TabID))
643 TabID = null;
644
645 try
646 {
647 string Msg;
648
649 switch (NewState)
650 {
651 case XmppState.Authenticating:
652 Client.SetTag("StartedAuthentication", true);
653 Client.SetTag("EncyptionSuccessful", true);
654 if (this.Step == 0)
655 {
656 if (!string.IsNullOrEmpty(TabID))
657 {
658 await ClientEvents.PushEvent(new string[] { TabID }, "ConnectionOK0", "Connection established.", false, "User");
659
660 if (!(this.client is null))
661 {
662 await this.client.OfflineAndDisposeAsync();
663 this.client = null;
664 }
665
666 this.Step = 1;
667 this.Updated = DateTime.Now;
668 await Database.Update(this);
669
670 return;
671 }
672 }
673
674 Msg = "Authenticating user.";
675 break;
676
677 case XmppState.Binding:
678 Msg = "Binding to resource.";
679 break;
680
681 case XmppState.Connected:
682 this.bareJid = Client.BareJID;
683 this.password = Client.PasswordHash;
684 this.passwordType = Client.PasswordHashMethod;
685
686 if (this.bareJid != defaultFacility)
687 {
688 defaultFacility = this.bareJid;
689
690 if (string.IsNullOrEmpty(defaultFacilityKey))
691 defaultFacilityKey = Hashes.BinaryToString(Gateway.NextBytes(32));
692
693 foreach (IEventSink Sink in Log.Sinks)
694 {
696 {
697 try
698 {
699 PersistedEventLog.SetDefaultFacility(defaultFacility, defaultFacilityKey);
700 }
701 catch (Exception ex)
702 {
703 Log.Exception(ex);
704 }
705
706 break;
707 }
708 }
709 }
710
711 if (this.createAccount && !string.IsNullOrEmpty(this.accountHumanReadableName))
712 {
713 if (!string.IsNullOrEmpty(TabID))
714 await ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Setting vCard.", false, "User");
715
716 StringBuilder Xml = new StringBuilder();
717
718 Xml.Append("<vCard xmlns='vcard-temp'>");
719 Xml.Append("<FN>");
720 Xml.Append(XML.Encode(this.accountHumanReadableName));
721 Xml.Append("</FN>");
722 Xml.Append("<JABBERID>");
723 Xml.Append(XML.Encode(this.client.BareJID));
724 Xml.Append("</JABBERID>");
725 Xml.Append("</vCard>");
726
727 await Client.IqSetAsync(this.client.BareJID, Xml.ToString());
728 }
729
730 if (!string.IsNullOrEmpty(TabID))
731 await ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Checking server features.", false, "User");
732
733 ServiceDiscoveryEventArgs e = await Client.ServiceDiscoveryAsync(null, string.Empty, string.Empty);
734
735 if (e.Ok)
736 {
737 this.offlineMessages = e.HasFeature("msgoffline");
738 this.blocking = e.HasFeature(Networking.XMPP.Abuse.AbuseClient.NamespaceBlocking);
739 this.reporting = e.HasFeature(Networking.XMPP.Abuse.AbuseClient.NamespaceReporting);
740 this.abuse = e.HasFeature(Networking.XMPP.Abuse.AbuseClient.NamespaceAbuseReason);
741 this.spam = e.HasFeature(Networking.XMPP.Abuse.AbuseClient.NamespaceSpamReason);
742 this.mail = e.HasFeature("urn:xmpp:smtp");
743 }
744 else
745 {
746 this.offlineMessages = false;
747 this.blocking = false;
748 this.reporting = false;
749 this.abuse = false;
750 this.spam = false;
751 this.mail = false;
752 }
753
754 if (!string.IsNullOrEmpty(TabID))
755 await ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Checking account features.", false, "User");
756
757 e = await Client.ServiceDiscoveryAsync(null, Client.BareJID, string.Empty);
758
759 this.pep = e.Ok && this.ContainsIdentity("pep", "pubsub", e);
760
761 if (!string.IsNullOrEmpty(TabID))
762 await ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Checking server components.", false, "User");
763
764 ServiceItemsDiscoveryEventArgs e2 = await Client.ServiceItemsDiscoveryAsync(null, string.Empty, string.Empty);
765
766 this.thingRegistry = string.Empty;
767 this.provisioning = string.Empty;
768 this.events = string.Empty;
769 this.muc = string.Empty;
770 this.pubSub = string.Empty;
771 this.legal = string.Empty;
772 this.software = string.Empty;
773
774 if (e2.Ok)
775 {
776 foreach (Item Item in e2.Items)
777 {
778 if (!string.IsNullOrEmpty(TabID))
779 await ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Checking component features for " + Item.JID, false, "User");
780
781 e = await Client.ServiceDiscoveryAsync(null, Item.JID, string.Empty);
782
783 if (e.HasAnyFeature(Networking.XMPP.Provisioning.ThingRegistryClient.NamespacesDiscovery))
784 this.thingRegistry = Item.JID;
785
786 if (e.HasAnyFeature(Networking.XMPP.Provisioning.ProvisioningClient.NamespacesProvisioningDevice))
787 this.provisioning = Item.JID;
788
789 if (e.HasFeature(Networking.XMPP.MUC.MultiUserChatClient.NamespaceMuc))
790 this.muc = Item.JID;
791
792 if (e.HasFeature(Networking.XMPP.PubSub.PubSubClient.NamespacePubSub) && this.ContainsIdentity("service", "pubsub", e))
793 this.pubSub = Item.JID;
794
796 this.events = Item.JID;
797
798 if (e.HasAnyFeature(Networking.XMPP.Contracts.ContractsClient.NamespacesLegalIdentities))
799 this.legal = Item.JID;
800
801 if (e.HasAnyFeature(Networking.XMPP.Software.SoftwareUpdateClient.NamespacesSoftwareUpdates))
802 this.software = Item.JID;
803 }
804 }
805
806 Dictionary<string, object> ConnectionInfo = new Dictionary<string, object>()
807 {
808 { "msg", "Connection successful." },
809 { "offlineMsg", this.offlineMessages },
810 { "blocking", this.blocking },
811 { "reporting", this.reporting },
812 { "abuse", this.abuse },
813 { "spam", this.spam },
814 { "mail", this.mail },
815 { "pep", this.pep ? this.bareJid : string.Empty },
816 { "thingRegistry", this.thingRegistry },
817 { "provisioning", this.provisioning },
818 { "eventLog", this.events },
819 { "pubSub", this.pubSub },
820 { "muc", this.muc },
821 { "legal", this.legal },
822 { "software", this.software }
823 };
824
825 if (!string.IsNullOrEmpty(TabID))
826 await ClientEvents.PushEvent(new string[] { TabID }, "ConnectionOK1", JSON.Encode(ConnectionInfo, false), true, "User");
827
828 if (!(this.client is null))
829 {
830 await this.client.OfflineAndDisposeAsync();
831 this.client = null;
832 }
833
834 this.Step = 2;
835 this.Updated = DateTime.Now;
836 await Database.Update(this);
837
838 this.testConnection?.TrySetResult(true);
839 return;
840
841 case XmppState.Connecting:
842 Msg = "Connecting to server.";
843 break;
844
845 case XmppState.Error:
846 bool Error = false;
847 Msg = string.Empty;
848
849 if (this.Step == 0 && this.transportMethod == XmppTransportMethod.C2S)
850 {
851 this.customBinding = true;
852
853 if (!string.IsNullOrEmpty(TabID))
854 {
855 await ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Unable to connect properly. Looking for alternative ways to connect.", false, "User");
856 await ClientEvents.PushEvent(new string[] { TabID }, "ShowCustomProperties", "{\"visible\":true}", true, "User");
857 }
858
859 using (HttpClient HttpClient = new HttpClient(new HttpClientHandler()
860 {
861#if !NETFW
862 ServerCertificateCustomValidationCallback = this.RemoteCertificateValidationCallback,
863#endif
864 UseCookies = false,
865 AutomaticDecompression = (DecompressionMethods)(-1) // All
866 })
867 {
868 Timeout = TimeSpan.FromMilliseconds(60000)
869 })
870 {
871 try
872 {
873 Uri Uri = new Uri("http://" + this.host + "/.well-known/host-meta");
874 HttpResponseMessage Response = await HttpClient.GetAsync(Uri);
875 if (!Response.IsSuccessStatusCode)
876 await Content.Getters.WebGetter.ProcessResponse(Response, Uri);
877
878 Stream Stream = await Response.Content.ReadAsStreamAsync(); // Regardless of status code, we check for XML content.
879 byte[] Bin = await Response.Content.ReadAsByteArrayAsync();
880 string CharSet = Response.Content.Headers.ContentType.CharSet;
881 System.Text.Encoding Encoding;
882
883 if (string.IsNullOrEmpty(CharSet))
884 Encoding = System.Text.Encoding.UTF8;
885 else
886 Encoding = InternetContent.GetEncoding(CharSet);
887
888 string XmlResponse = CommonTypes.GetString(Bin, Encoding);
889 XmlDocument Doc = new XmlDocument()
890 {
891 PreserveWhitespace = true
892 };
893 Doc.LoadXml(XmlResponse);
894
895 if (!(Doc.DocumentElement is null) && Doc.DocumentElement.LocalName == "XRD")
896 {
897 string BoshUrl = null;
898 string WsUrl = null;
899
900 foreach (XmlNode N in Doc.DocumentElement.ChildNodes)
901 {
902 if (N is XmlElement E && E.LocalName == "Link")
903 {
904 switch (XML.Attribute(E, "rel"))
905 {
906 case "urn:xmpp:alt-connections:xbosh":
907 BoshUrl = XML.Attribute(E, "href");
908 break;
909
910 case "urn:xmpp:alt-connections:websocket":
911 WsUrl = XML.Attribute(E, "href");
912 break;
913 }
914 }
915 }
916
917 if (!string.IsNullOrEmpty(WsUrl))
918 {
919 this.wsUrl = WsUrl;
920 this.transportMethod = XmppTransportMethod.WS;
921
922 if (!string.IsNullOrEmpty(TabID))
923 await ClientEvents.PushEvent(new string[] { TabID }, "ShowTransport", "{\"method\":\"WS\"}", true, "User");
924
925 this.Connect(TabID);
926
927 return;
928 }
929 else if (!string.IsNullOrEmpty(BoshUrl))
930 {
931 this.boshUrl = BoshUrl;
932 this.transportMethod = XmppTransportMethod.BOSH;
933
934 if (!string.IsNullOrEmpty(TabID))
935 await ClientEvents.PushEvent(new string[] { TabID }, "ShowTransport", "{\"method\":\"BOSH\"}", true, "User");
936
937 this.Connect(TabID);
938
939 return;
940 }
941 }
942 }
943 catch (Exception)
944 {
945 // Ignore.
946 }
947
948 Msg = "No alternative binding methods found.";
949 Error = true;
950 }
951 }
952 else
953 {
954 Msg = "Unable to connect properly.";
955 Error = true;
956
957 if (Client.TryGetTag("StartedAuthentication", out Obj) && Obj is bool b && b)
958 {
959 if (!string.IsNullOrEmpty(TabID))
960 {
961 if (this.createAccount)
962 await ClientEvents.PushEvent(new string[] { TabID }, "ShowFail2", Msg, false, "User");
963 else
964 await ClientEvents.PushEvent(new string[] { TabID }, "ShowFail1", Msg, false, "User");
965 }
966 return;
967 }
968 }
969
970 if (Error)
971 {
972 if (string.IsNullOrEmpty(TabID))
973 this.LogEnvironmentError(Msg, GATEWAY_XMPP_HOST, this.host);
974 else
975 await ClientEvents.PushEvent(new string[] { TabID }, "ConnectionError", Msg, false, "User");
976
977 if (!(this.client is null))
978 {
979 await this.client.OfflineAndDisposeAsync();
980 this.client = null;
981 }
982
983 this.testConnection?.TrySetResult(false);
984 return;
985 }
986 break;
987
988 case XmppState.FetchingRoster:
989 Msg = "Fetching roster from server.";
990 break;
991
992 case XmppState.Offline:
993 Msg = "Offline.";
994 break;
995
996 case XmppState.Registering:
997 Msg = "Registering account.";
998 break;
999
1000 case XmppState.RequestingSession:
1001 Msg = "Requesting session.";
1002 break;
1003
1004 case XmppState.SettingPresence:
1005 Msg = "Setting presence.";
1006 break;
1007
1008 case XmppState.StartingEncryption:
1009 Msg = "Starting encryption.";
1010 Client.SetTag("StartedEncryption", true);
1011 break;
1012
1013 case XmppState.StreamNegotiation:
1014 Msg = "Negotiating stream.";
1015 break;
1016
1017 case XmppState.StreamOpened:
1018 Msg = "Stream opened.";
1019 break;
1020
1021 default:
1022 Msg = NewState.ToString();
1023 break;
1024 }
1025
1026 if (!string.IsNullOrEmpty(TabID))
1027 await ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", Msg, false, "User");
1028 }
1029 catch (Exception ex)
1030 {
1031 Log.Exception(ex);
1032
1033 if (!string.IsNullOrEmpty(TabID))
1034 await ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", ex.Message, false, "User");
1035 }
1036 }
1037
1038 private async Task CheckAdminAccount()
1039 {
1040 if (string.IsNullOrEmpty(this.password0) && (!string.IsNullOrEmpty(this.passwordType) || string.IsNullOrEmpty(this.password)))
1041 return;
1042
1043 User User = await Users.GetUser(this.account, true);
1044
1045 User.PasswordHash = Convert.ToBase64String(Users.ComputeHash(this.account,
1046 string.IsNullOrEmpty(this.password0) ? this.password : this.password0));
1047
1048 if (User.RoleIds.Length == 0)
1049 User.RoleIds = new string[] { "Administrator" };
1050
1051 await Database.Update(User);
1052
1053 Role Role = await Roles.GetRole("Administrator");
1054 if (Role.Privileges.Length == 0)
1055 {
1056 Role.Privileges = new PrivilegePattern[]
1057 {
1058 new PrivilegePattern(".*", true)
1059 };
1060
1061 if (string.IsNullOrEmpty(Role.Description))
1062 Role.Description = "Administrator role. Has all privileges by default.";
1063
1064 await Database.Update(Role);
1065 }
1066 }
1067
1068 private bool ContainsIdentity(string Type, string Cateogory, ServiceDiscoveryEventArgs e)
1069 {
1070 foreach (Identity Identity in e.Identities)
1071 {
1072 if (Identity.Type == Type && Identity.Category == Cateogory)
1073 return true;
1074 }
1075
1076 return false;
1077 }
1078
1079 private bool RemoteCertificateValidationCallback(object Sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
1080 {
1081 bool Valid;
1082
1083 if (sslPolicyErrors == SslPolicyErrors.None)
1084 Valid = true;
1085 else
1086 Valid = this.trustServer;
1087
1088 return Valid;
1089 }
1090
1094 public static string[] FeaturedServers
1095 {
1096 get
1097 {
1098 if (featuredServers is null)
1099 {
1100 string[] Result = new string[clp.Count];
1101 clp.Keys.CopyTo(Result, 0);
1102 Array.Sort(Result);
1103 featuredServers = Result;
1104 }
1105
1106 return featuredServers;
1107 }
1108 }
1109
1110 private static string[] featuredServers = null;
1111
1115 public const string GATEWAY_XMPP_HOST = nameof(GATEWAY_XMPP_HOST);
1116
1121 public const string GATEWAY_XMPP_TRANSPORT = nameof(GATEWAY_XMPP_TRANSPORT);
1122
1127 public const string GATEWAY_XMPP_PORT = nameof(GATEWAY_XMPP_PORT);
1128
1132 public const string GATEWAY_XMPP_BOSHURL = nameof(GATEWAY_XMPP_BOSHURL);
1133
1137 public const string GATEWAY_XMPP_WSURL = nameof(GATEWAY_XMPP_WSURL);
1138
1142 public const string GATEWAY_XMPP_CREATE = nameof(GATEWAY_XMPP_CREATE);
1143
1148
1153
1157 public const string GATEWAY_XMPP_ACCOUNT = nameof(GATEWAY_XMPP_ACCOUNT);
1158
1162 public const string GATEWAY_XMPP_PASSWORD = nameof(GATEWAY_XMPP_PASSWORD);
1163
1168
1173 public const string GATEWAY_XMPP_LOG = nameof(GATEWAY_XMPP_LOG);
1174
1179 public const string GATEWAY_XMPP_TRUST = nameof(GATEWAY_XMPP_TRUST);
1180
1185 public const string GATEWAY_XMPP_OBS_AUTH = nameof(GATEWAY_XMPP_OBS_AUTH);
1186
1191 public const string GATEWAY_XMPP_CLEAR_PWD = nameof(GATEWAY_XMPP_CLEAR_PWD);
1192
1197 public override async Task<bool> EnvironmentConfiguration()
1198 {
1199 if (!this.TryGetEnvironmentVariable(GATEWAY_XMPP_HOST, false, out this.host))
1200 return false;
1201
1202 if (this.TryGetEnvironmentVariable(GATEWAY_XMPP_TRANSPORT, false, out string Value))
1203 {
1204 if (!Enum.TryParse(Value, out XmppTransportMethod Method))
1205 {
1206 this.LogEnvironmentError("Invalid transport method.", GATEWAY_XMPP_TRANSPORT, Value);
1207 return false;
1208 }
1209
1210 this.transportMethod = Method;
1211 }
1212
1213 switch (this.transportMethod)
1214 {
1215 case XmppTransportMethod.C2S:
1216 Value = Environment.GetEnvironmentVariable(GATEWAY_XMPP_PORT);
1217 if (!string.IsNullOrEmpty(Value))
1218 {
1219 if (!int.TryParse(Value, out int i) || i <= 0 || i > 65535)
1220 {
1221 this.LogEnvironmentVariableInvalidRangeError(1, 65535, GATEWAY_XMPP_PORT, Value);
1222 return false;
1223 }
1224
1225 this.port = i;
1226 this.customBinding = false;
1227 }
1228 break;
1229
1230 case XmppTransportMethod.BOSH:
1231 if (!this.TryGetEnvironmentVariable(GATEWAY_XMPP_BOSHURL, true, out this.boshUrl))
1232 return false;
1233
1234 this.customBinding = true;
1235 break;
1236
1237 case XmppTransportMethod.WS:
1238 if (!this.TryGetEnvironmentVariable(GATEWAY_XMPP_WSURL, true, out this.wsUrl))
1239 return false;
1240
1241 this.customBinding = true;
1242 break;
1243
1244 default:
1245 this.LogEnvironmentError("Unhandled binding method.", GATEWAY_XMPP_TRANSPORT, this.transportMethod);
1246 return false;
1247 }
1248
1249 if (!this.TryGetEnvironmentVariable(GATEWAY_XMPP_CREATE, out this.createAccount, false))
1250 return false;
1251
1252 if (this.createAccount)
1253 {
1254 Value = Environment.GetEnvironmentVariable(GATEWAY_XMPP_CREATE_KEY);
1255 if (string.IsNullOrEmpty(Value))
1256 {
1257 if (!clp.ContainsKey(this.host))
1258 {
1259 this.LogEnvironmentError("Host is not a featured broker. If an account is to be created, an API Key must be provided.",
1260 GATEWAY_XMPP_CREATE_KEY, Value);
1261 return false;
1262 }
1263 }
1264 else
1265 {
1266 if (!this.TryGetEnvironmentVariable(GATEWAY_XMPP_CREATE_SECRET, true, out string Value2))
1267 return false;
1268
1269 clp[this.host] = new KeyValuePair<string, string>(Value, Value2);
1270 }
1271 }
1272
1273 if (!this.TryGetEnvironmentVariable(GATEWAY_XMPP_ACCOUNT, true, out this.account))
1274 return false;
1275
1276 if (!this.TryGetEnvironmentVariable(GATEWAY_XMPP_PASSWORD, false, out this.password))
1277 {
1278 if (this.createAccount)
1279 this.password = RandomPassword.CreateRandomPassword();
1280 else
1281 {
1282 this.LogEnvironmentVariableMissingError(GATEWAY_XMPP_PASSWORD, Value);
1283 return false;
1284 }
1285 }
1286
1287 this.password0 = this.password;
1288 this.passwordType = string.Empty;
1289
1290 if (this.TryGetEnvironmentVariable(GATEWAY_XMPP_ACCOUNT_NAME, false, out Value))
1291 this.accountHumanReadableName = Value;
1292
1293 if (!this.TryGetEnvironmentVariable(GATEWAY_XMPP_LOG, out this.sniffer, false))
1294 return false;
1295
1296 if (!this.TryGetEnvironmentVariable(GATEWAY_XMPP_TRUST, out this.trustServer, false))
1297 return false;
1298
1299 if (!this.TryGetEnvironmentVariable(GATEWAY_XMPP_OBS_AUTH, out this.allowInsecureMechanisms, false))
1300 return false;
1301
1302 if (!this.TryGetEnvironmentVariable(GATEWAY_XMPP_CLEAR_PWD, out this.storePasswordInsteadOfHash, false))
1303 return false;
1304
1305 this.testConnection = new TaskCompletionSource<bool>();
1306 try
1307 {
1308 Task _ = Task.Delay(30000).ContinueWith(Prev => this.testConnection?.TrySetException(new TimeoutException()));
1309
1310 this.Connect(null);
1311
1312 if (await this.testConnection.Task)
1313 return true;
1314 else
1315 return false;
1316 }
1317 catch (Exception ex)
1318 {
1319 Log.Exception(ex);
1320 return false;
1321 }
1322 finally
1323 {
1324 this.testConnection = null;
1325 }
1326 }
1327 }
1328}
Helps with parsing of commong data types.
Definition: CommonTypes.cs:13
static string GetString(byte[] Data, Encoding DefaultEncoding)
Gets a string from its binary representation, taking any Byte Order Mark (BOM) into account.
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:14
static string Encode(string s)
Encodes a string for inclusion in JSON.
Definition: JSON.cs:507
Plain text encoder/decoder.
const string DefaultContentType
text/plain
Helps with common XML-related tasks.
Definition: XML.cs:19
static string Attribute(XmlElement E, string Name)
Gets the value of an XML attribute.
Definition: XML.cs:914
static string Encode(string s)
Encodes a string for use in XML.
Definition: XML.cs:27
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:13
static IEventSink[] Sinks
Registered sinks.
Definition: Log.cs:122
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
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: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 byte[] NextBytes(int NrBytes)
Generates an array of random bytes.
Definition: Gateway.cs:3534
static string AppDataFolder
Application data folder.
Definition: Gateway.cs:2369
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.
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....
override string Resource
Resource to be redirected to, to perform the configuration.
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: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
string ContentType
The Content-Type entity-header field indicates the media type of the entity-body sent to the recipien...
async Task Write(byte[] Data)
Returns binary data in the response.
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
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:59
Task OfflineAndDisposeAsync()
Sends an offline presence, and then disposes the object by calling DisposeAsync.
Definition: XmppClient.cs:1119
void SetTag(string TagName, object Tag)
Sets a tag value.
Definition: XmppClient.cs:7249
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: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
Contains methods for simple hash calculations.
Definition: Hashes.cs:59
static string BinaryToString(byte[] Data)
Converts an array of bytes to a string with their hexadecimal representations (in lower case).
Definition: Hashes.cs:65
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:21
string[] RoleIds
Role IDs
Definition: User.cs:63
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:11
XmppTransportMethod
How to bind to the XMPP network.
LineEnding
Type of line ending.
BinaryPresentationMethod
How binary data is to be presented.
XmppState
State of XMPP connection.
Definition: XmppState.cs:7
Definition: App.xaml.cs:4