/// <summary> /// Starts the gateway. /// </summary> /// <param name="ConsoleOutput">If console output is permitted.</param> public static bool Start(bool ConsoleOutput) { gatewayRunning = new Semaphore(1, 1, "Waher.IoTGateway.Running"); if (!gatewayRunning.WaitOne(1000)) { return(false); // Is running in another process. } Semaphore StartingServer = new Semaphore(1, 1, "Waher.IoTGateway.Starting"); if (!StartingServer.WaitOne(1000)) { gatewayRunning.Release(); gatewayRunning.Dispose(); gatewayRunning = null; StartingServer.Dispose(); return(false); // Being started in another process. } try { Initialize(); appDataFolder = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData); if (!appDataFolder.EndsWith(new string(Path.DirectorySeparatorChar, 1))) { appDataFolder += Path.DirectorySeparatorChar; } appDataFolder += "IoT Gateway" + Path.DirectorySeparatorChar; Log.Register(new XmlFileEventSink("XML File Event Sink", appDataFolder + "Events" + Path.DirectorySeparatorChar + "Event Log %YEAR%-%MONTH%-%DAY%T%HOUR%.xml", appDataFolder + "Transforms" + Path.DirectorySeparatorChar + "EventXmlToHtml.xslt", 7)); Log.Informational("Server starting up."); beforeUninstallCommandNr = Gateway.RegisterServiceCommand(BeforeUninstall); rootFolder = appDataFolder + "Root" + Path.DirectorySeparatorChar; if (!Directory.Exists(rootFolder)) { appDataFolder = string.Empty; rootFolder = "Root" + Path.DirectorySeparatorChar; } Types.SetModuleParameter("AppData", appDataFolder); Types.SetModuleParameter("Root", rootFolder); scheduler = new Scheduler(); rnd = RandomNumberGenerator.Create(); Task.Run(() => CodeContent.GraphViz.Init()); XmlDocument Config = new XmlDocument(); string GatewayConfigFileName = appDataFolder + "Gateway.config"; if (!File.Exists(GatewayConfigFileName)) { GatewayConfigFileName = "Gateway.config"; } Config.Load(GatewayConfigFileName); XSL.Validate("Gateway.config", Config, "GatewayConfiguration", "http://waher.se/Schema/GatewayConfiguration.xsd", XSL.LoadSchema(typeof(Gateway).Namespace + ".Schema.GatewayConfiguration.xsd", typeof(Gateway).Assembly)); domain = Config.DocumentElement["Domain"].InnerText; XmlElement DatabaseConfig = Config.DocumentElement["Database"]; if (!CommonTypes.TryParse(DatabaseConfig.Attributes["encrypted"].Value, out bool Encrypted)) { Encrypted = true; } databaseProvider = new FilesProvider(appDataFolder + DatabaseConfig.Attributes["folder"].Value, DatabaseConfig.Attributes["defaultCollectionName"].Value, int.Parse(DatabaseConfig.Attributes["blockSize"].Value), int.Parse(DatabaseConfig.Attributes["blocksInCache"].Value), int.Parse(DatabaseConfig.Attributes["blobBlockSize"].Value), Encoding.UTF8, int.Parse(DatabaseConfig.Attributes["timeoutMs"].Value), Encrypted, false); Database.Register(databaseProvider); PersistedEventLog PersistedEventLog = new PersistedEventLog(7, new TimeSpan(4, 15, 0)); Log.Register(PersistedEventLog); PersistedEventLog.Queue(new Event(EventType.Informational, "Server starting up.", string.Empty, string.Empty, string.Empty, EventLevel.Minor, string.Empty, string.Empty, string.Empty)); xmppConfigFileName = Config.DocumentElement["XmppClient"].Attributes["configFileName"].Value; if (!File.Exists(xmppConfigFileName)) { xmppConfigFileName = appDataFolder + xmppConfigFileName; } if (ConsoleOutput) { xmppConfiguration = SimpleXmppConfiguration.GetConfigUsingSimpleConsoleDialog(xmppConfigFileName, Guid.NewGuid().ToString().Replace("-", string.Empty), // Default user name. Guid.NewGuid().ToString().Replace("-", string.Empty), // Default password. FormSignatureKey, FormSignatureSecret, typeof(Gateway).Assembly); } else if (File.Exists(xmppConfigFileName)) { xmppConfiguration = new SimpleXmppConfiguration(xmppConfigFileName); RuntimeSettings.Set("XMPP.CONFIG", xmppConfiguration.ExportSimpleXmppConfiguration()); } else { string XmppConfig = RuntimeSettings.Get("XMPP.CONFIG", string.Empty); XmlDocument Doc = new XmlDocument(); Doc.LoadXml(XmppConfig); xmppConfiguration = new SimpleXmppConfiguration(Doc); } xmppClient = xmppConfiguration.GetClient("en", typeof(Gateway).Assembly, false); xmppClient.AllowRegistration(FormSignatureKey, FormSignatureSecret); xmppClient.OnValidateSender += XmppClient_OnValidateSender; Types.SetModuleParameter("XMPP", xmppClient); if (xmppConfiguration.Sniffer) { ISniffer Sniffer; if (ConsoleOutput) { Sniffer = new ConsoleOutSniffer(BinaryPresentationMethod.ByteCount, LineEnding.PadWithSpaces); xmppClient.Add(Sniffer); } Sniffer = new XmlFileSniffer(appDataFolder + "XMPP" + Path.DirectorySeparatorChar + "XMPP Log %YEAR%-%MONTH%-%DAY%T%HOUR%.xml", appDataFolder + "Transforms" + Path.DirectorySeparatorChar + "SnifferXmlToHtml.xslt", 7, BinaryPresentationMethod.ByteCount); xmppClient.Add(Sniffer); } if (!string.IsNullOrEmpty(xmppConfiguration.Events)) { Log.Register(new XmppEventSink("XMPP Event Sink", xmppClient, xmppConfiguration.Events, false)); } if (!string.IsNullOrEmpty(xmppConfiguration.ThingRegistry)) { thingRegistryClient = new ThingRegistryClient(xmppClient, xmppConfiguration.ThingRegistry); thingRegistryClient.Claimed += ThingRegistryClient_Claimed; thingRegistryClient.Disowned += ThingRegistryClient_Disowned; thingRegistryClient.Removed += ThingRegistryClient_Removed; } if (!string.IsNullOrEmpty(xmppConfiguration.Provisioning)) { provisioningClient = new ProvisioningClient(xmppClient, xmppConfiguration.Provisioning); } DateTime Now = DateTime.Now; int MsToNext = 60000 - (Now.Second * 1000 + Now.Millisecond); connectionTimer = new Timer(CheckConnection, null, MsToNext, 60000); xmppClient.OnStateChanged += XmppClient_OnStateChanged; xmppClient.OnPresenceSubscribe += XmppClient_OnPresenceSubscribe; xmppClient.OnPresenceUnsubscribe += XmppClient_OnPresenceUnsubscribe; xmppClient.OnRosterItemUpdated += XmppClient_OnRosterItemUpdated; ibbClient = new Networking.XMPP.InBandBytestreams.IbbClient(xmppClient, MaxChunkSize); Types.SetModuleParameter("IBB", ibbClient); socksProxy = new Networking.XMPP.P2P.SOCKS5.Socks5Proxy(xmppClient); Types.SetModuleParameter("SOCKS5", socksProxy); string CertificateLocalFileName = Config.DocumentElement["Certificate"].Attributes["configFileName"].Value; string CertificateFileName; string CertificateXml; string CertificatePassword; byte[] CertificateRaw; try { CertificateRaw = Convert.FromBase64String(RuntimeSettings.Get("CERTIFICATE.BASE64", string.Empty)); CertificatePassword = RuntimeSettings.Get("CERTIFICATE.PWD", string.Empty); certificate = new X509Certificate2(CertificateRaw, CertificatePassword); } catch (Exception) { certificate = null; } if (File.Exists(CertificateFileName = appDataFolder + CertificateLocalFileName)) { CertificateXml = File.ReadAllText(CertificateFileName); } else if (File.Exists(CertificateFileName = CertificateLocalFileName) && certificate == null) { CertificateXml = File.ReadAllText(CertificateFileName); } else { CertificateFileName = null; CertificateXml = null; } if (CertificateXml != null) { XmlDocument CertificateConfig = new XmlDocument(); CertificateConfig.LoadXml(CertificateXml); XSL.Validate(CertificateLocalFileName, CertificateConfig, "CertificateConfiguration", "http://waher.se/Schema/CertificateConfiguration.xsd", XSL.LoadSchema(typeof(Gateway).Namespace + ".Schema.CertificateConfiguration.xsd", typeof(Gateway).Assembly)); CertificateLocalFileName = CertificateConfig.DocumentElement["FileName"].InnerText; if (File.Exists(appDataFolder + CertificateLocalFileName)) { CertificateLocalFileName = appDataFolder + CertificateLocalFileName; } CertificateRaw = File.ReadAllBytes(CertificateLocalFileName); CertificatePassword = CertificateConfig.DocumentElement["Password"].InnerText; certificate = new X509Certificate2(CertificateRaw, CertificatePassword); RuntimeSettings.Set("CERTIFICATE.BASE64", Convert.ToBase64String(CertificateRaw)); RuntimeSettings.Set("CERTIFICATE.PWD", CertificatePassword); if (CertificateLocalFileName != "certificate.pfx" || CertificatePassword != "testexamplecom") { try { File.Delete(CertificateLocalFileName); } catch (Exception) { Log.Warning("Unable to delete " + CertificateLocalFileName + " after importing it into the encrypted database."); } try { File.Delete(CertificateFileName); } catch (Exception) { Log.Warning("Unable to delete " + CertificateFileName + " after importing it into the encrypted database."); } } } foreach (XmlNode N in Config.DocumentElement["Ports"].ChildNodes) { if (N.LocalName == "Port") { XmlElement E = (XmlElement)N; string Protocol = XML.Attribute(E, "protocol"); if (!string.IsNullOrEmpty(Protocol) && int.TryParse(E.InnerText, out int Port)) { ports.AddLast(new KeyValuePair <string, int>(Protocol, Port)); } } } webServer = new HttpServer(GetConfigPorts("HTTP"), GetConfigPorts("HTTPS"), certificate); Types.SetModuleParameter("HTTP", webServer); StringBuilder sb = new StringBuilder(); foreach (int Port in webServer.OpenPorts) { sb.AppendLine(Port.ToString()); } try { File.WriteAllText(appDataFolder + "Ports.txt", sb.ToString()); } catch (Exception ex) { Log.Critical(ex); } HttpFolderResource HttpFolderResource; HttpxProxy HttpxProxy; webServer.Register(new HttpFolderResource("/Graphics", "Graphics", false, false, true, false)); // TODO: Add authentication mechanisms for PUT & DELETE. webServer.Register(new HttpFolderResource("/highlight", "Highlight", false, false, true, false)); // Syntax highlighting library, provided by http://highlightjs.org webServer.Register(HttpFolderResource = new HttpFolderResource(string.Empty, rootFolder, false, false, true, true)); // TODO: Add authentication mechanisms for PUT & DELETE. webServer.Register(HttpxProxy = new HttpxProxy("/HttpxProxy", xmppClient, MaxChunkSize)); webServer.Register("/", (req, resp) => { throw new TemporaryRedirectException(Config.DocumentElement["DefaultPage"].InnerText); }); webServer.Register(clientEvents = new ClientEvents()); HttpFolderResource.AllowTypeConversion(); MarkdownToHtmlConverter.EmojiSource = Emoji1_24x24; XmlElement FileFolders = Config.DocumentElement["FileFolders"]; if (FileFolders != null) { foreach (XmlNode N in FileFolders.ChildNodes) { if (N is XmlElement E && E.LocalName == "FileFolder") { string WebFolder = XML.Attribute(E, "webFolder"); string FolderPath = XML.Attribute(E, "folderPath"); webServer.Register(new HttpFolderResource(WebFolder, FolderPath, false, false, true, true)); } } } httpxServer = new HttpxServer(xmppClient, webServer, MaxChunkSize); Types.SetModuleParameter("HTTPX", HttpxProxy); Types.SetModuleParameter("HTTPXS", httpxServer); HttpxProxy.IbbClient = ibbClient; httpxServer.IbbClient = ibbClient; HttpxProxy.Socks5Proxy = socksProxy; httpxServer.Socks5Proxy = socksProxy; if (xmppConfiguration.Sniffer) { ISniffer Sniffer; Sniffer = new XmlFileSniffer(appDataFolder + "HTTP" + Path.DirectorySeparatorChar + "HTTP Log %YEAR%-%MONTH%-%DAY%T%HOUR%.xml", appDataFolder + "Transforms" + Path.DirectorySeparatorChar + "SnifferXmlToHtml.xslt", 7, BinaryPresentationMethod.ByteCount); webServer.Add(Sniffer); } coapEndpoint = new CoapEndpoint(); Types.SetModuleParameter("CoAP", coapEndpoint); concentratorServer = new ConcentratorServer(xmppClient, provisioningClient, new MeteringTopology()); Types.SetModuleParameter("Concentrator", concentratorServer); Types.SetModuleParameter("Sensor", concentratorServer.SensorServer); Types.SetModuleParameter("Control", concentratorServer.ControlServer); } catch (Exception ex) { Log.Critical(ex); gatewayRunning.Release(); gatewayRunning.Dispose(); gatewayRunning = null; StartingServer.Release(); StartingServer.Dispose(); ExceptionDispatchInfo.Capture(ex).Throw(); } Task.Run(async() => { try { try { string BinaryFolder = AppDomain.CurrentDomain.BaseDirectory; string[] LanguageFiles = Directory.GetFiles(BinaryFolder, "*.lng", SearchOption.AllDirectories); string FileName; if (LanguageFiles.Length > 0) { XmlSchema Schema = XSL.LoadSchema(Translator.SchemaResource, typeof(Translator).Assembly); foreach (string LanguageFile in LanguageFiles) { try { FileName = LanguageFile; if (FileName.StartsWith(BinaryFolder)) { FileName = FileName.Substring(BinaryFolder.Length); } DateTime LastWriteTime = File.GetLastWriteTime(LanguageFile); DateTime LastImportedTime = await RuntimeSettings.GetAsync(FileName, DateTime.MinValue); if (LastWriteTime > LastImportedTime) { Log.Informational("Importing language file.", FileName); string Xml = File.ReadAllText(LanguageFile); XmlDocument Doc = new XmlDocument(); Doc.LoadXml(Xml); XSL.Validate(FileName, Doc, Translator.SchemaRoot, Translator.SchemaNamespace, Schema); using (XmlReader r = new XmlNodeReader(Doc)) { await Translator.ImportAsync(r); } RuntimeSettings.Set(FileName, LastWriteTime); } } catch (Exception ex) { Log.Critical(ex, LanguageFile); } } } Types.StartAllModules(int.MaxValue); } finally { StartingServer.Release(); StartingServer.Dispose(); } } catch (Exception ex) { Log.Critical(ex); } finally { xmppClient.Connect(); } }); return(true); }
private async Task Client_OnStateChanged(object Sender, XmppState NewState) { if (!(Sender is XmppClient Client)) { return; } if (!Client.TryGetTag("TabID", out object Obj) || !(Obj is string TabID)) { return; } try { string Msg; switch (NewState) { case XmppState.Authenticating: Client.SetTag("StartedAuthentication", true); Client.SetTag("EncyptionSuccessful", true); if (this.Step == 0) { ClientEvents.PushEvent(new string[] { TabID }, "ConnectionOK0", "Connection established.", false, "User"); this.client.Dispose(); this.client = null; this.Step = 1; this.Updated = DateTime.Now; await Database.Update(this); return; } else { Msg = "Authenticating user."; } break; case XmppState.Binding: Msg = "Binding to resource."; break; case XmppState.Connected: this.bareJid = Client.BareJID; if (this.bareJid != defaultFacility) { defaultFacility = this.bareJid; if (string.IsNullOrEmpty(defaultFacilityKey)) { defaultFacilityKey = Hashes.BinaryToString(Gateway.NextBytes(32)); } foreach (IEventSink Sink in Log.Sinks) { if (Sink is PersistedEventLog PersistedEventLog) { try { PersistedEventLog.SetDefaultFacility(defaultFacility, defaultFacilityKey); } catch (Exception ex) { Log.Critical(ex); } break; } } } if (this.createAccount && !string.IsNullOrEmpty(this.accountHumanReadableName)) { ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Setting vCard.", false, "User"); StringBuilder Xml = new StringBuilder(); Xml.Append("<vCard xmlns='vcard-temp'>"); Xml.Append("<FN>"); Xml.Append(XML.Encode(this.accountHumanReadableName)); Xml.Append("</FN>"); Xml.Append("<JABBERID>"); Xml.Append(XML.Encode(this.client.BareJID)); Xml.Append("</JABBERID>"); Xml.Append("</vCard>"); await Client.IqSetAsync(this.client.BareJID, Xml.ToString()); } ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Checking server features.", false, "User"); ServiceDiscoveryEventArgs e = await Client.ServiceDiscoveryAsync(null, string.Empty, string.Empty); if (e.Ok) { this.offlineMessages = e.HasFeature("msgoffline"); this.blocking = e.HasFeature(Networking.XMPP.Abuse.AbuseClient.NamespaceBlocking); this.reporting = e.HasFeature(Networking.XMPP.Abuse.AbuseClient.NamespaceReporting); this.abuse = e.HasFeature(Networking.XMPP.Abuse.AbuseClient.NamespaceAbuseReason); this.spam = e.HasFeature(Networking.XMPP.Abuse.AbuseClient.NamespaceSpamReason); this.mail = e.HasFeature("urn:xmpp:smtp"); } else { this.offlineMessages = false; this.blocking = false; this.reporting = false; this.abuse = false; this.spam = false; this.mail = false; } ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Checking account features.", false, "User"); e = await Client.ServiceDiscoveryAsync(null, Client.BareJID, string.Empty); this.pep = e.Ok && this.ContainsIdentity("pep", "pubsub", e); ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Checking server components.", false, "User"); ServiceItemsDiscoveryEventArgs e2 = await Client.ServiceItemsDiscoveryAsync(null, string.Empty, string.Empty); this.thingRegistry = string.Empty; this.provisioning = string.Empty; this.events = string.Empty; this.pubSub = string.Empty; this.legal = string.Empty; this.software = string.Empty; if (e2.Ok) { foreach (Item Item in e2.Items) { ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Checking component features for " + Item.JID, false, "User"); e = await Client.ServiceDiscoveryAsync(null, Item.JID, string.Empty); if (e.HasFeature(Networking.XMPP.Provisioning.ThingRegistryClient.NamespaceDiscovery)) { this.thingRegistry = Item.JID; } if (e.HasFeature(Networking.XMPP.Provisioning.ProvisioningClient.NamespaceProvisioningDevice)) { this.provisioning = Item.JID; } if (e.HasFeature(Networking.XMPP.PubSub.PubSubClient.NamespacePubSub) && this.ContainsIdentity("service", "pubsub", e)) { this.pubSub = Item.JID; } if (e.HasFeature(Waher.Events.XMPP.XmppEventSink.NamespaceEventLogging)) { this.events = Item.JID; } if (e.HasFeature(Networking.XMPP.Contracts.ContractsClient.NamespaceLegalIdentities)) { this.legal = Item.JID; } if (e.HasFeature(Networking.XMPP.Software.SoftwareUpdateClient.NamespaceSoftwareUpdates)) { this.software = Item.JID; } } } Dictionary <string, object> ConnectionInfo = new Dictionary <string, object>() { { "msg", "Connection successful." }, { "offlineMsg", this.offlineMessages }, { "blocking", this.blocking }, { "reporting", this.reporting }, { "abuse", this.abuse }, { "spam", this.spam }, { "mail", this.mail }, { "pep", this.pep ? this.bareJid : string.Empty }, { "thingRegistry", this.thingRegistry }, { "provisioning", this.provisioning }, { "eventLog", this.events }, { "pubSub", this.pubSub }, { "legal", this.legal }, { "software", this.software } }; ClientEvents.PushEvent(new string[] { TabID }, "ConnectionOK1", JSON.Encode(ConnectionInfo, false), true, "User"); this.client.Dispose(); this.client = null; this.Step = 2; this.Updated = DateTime.Now; await Database.Update(this); return; case XmppState.Connecting: Msg = "Connecting to server."; break; case XmppState.Error: bool Error = false; Msg = string.Empty; if (this.Step == 0 && this.transportMethod == XmppTransportMethod.C2S) { this.customBinding = true; ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Unable to connect properly. Looking for alternative ways to connect.", false, "User"); ClientEvents.PushEvent(new string[] { TabID }, "ShowCustomProperties", "{\"visible\":true}", true, "User"); using (HttpClient HttpClient = new HttpClient(new HttpClientHandler() { #if !NETFW ServerCertificateCustomValidationCallback = this.RemoteCertificateValidationCallback, #endif UseCookies = false }) { Timeout = TimeSpan.FromMilliseconds(60000) }) { try { HttpResponseMessage Response = await HttpClient.GetAsync("http://" + this.host + "/.well-known/host-meta"); Response.EnsureSuccessStatusCode(); Stream Stream = await Response.Content.ReadAsStreamAsync(); // Regardless of status code, we check for XML content. byte[] Bin = await Response.Content.ReadAsByteArrayAsync(); string CharSet = Response.Content.Headers.ContentType.CharSet; Encoding Encoding; if (string.IsNullOrEmpty(CharSet)) { Encoding = Encoding.UTF8; } else { Encoding = InternetContent.GetEncoding(CharSet); } string XmlResponse = Encoding.GetString(Bin); XmlDocument Doc = new XmlDocument(); Doc.LoadXml(XmlResponse); if (Doc.DocumentElement != null && Doc.DocumentElement.LocalName == "XRD") { string BoshUrl = null; string WsUrl = null; foreach (XmlNode N in Doc.DocumentElement.ChildNodes) { if (N is XmlElement E && E.LocalName == "Link") { switch (XML.Attribute(E, "rel")) { case "urn:xmpp:alt-connections:xbosh": BoshUrl = XML.Attribute(E, "href"); break; case "urn:xmpp:alt-connections:websocket": WsUrl = XML.Attribute(E, "href"); break; } } } if (!string.IsNullOrEmpty(WsUrl)) { this.wsUrl = WsUrl; this.transportMethod = XmppTransportMethod.WS; ClientEvents.PushEvent(new string[] { TabID }, "ShowTransport", "{\"method\":\"WS\"}", true, "User"); this.Connect(TabID); return; } else if (!string.IsNullOrEmpty(BoshUrl)) { this.boshUrl = BoshUrl; this.transportMethod = XmppTransportMethod.BOSH; ClientEvents.PushEvent(new string[] { TabID }, "ShowTransport", "{\"method\":\"BOSH\"}", true, "User"); this.Connect(TabID); return; } } } catch (Exception) { // Ignore. } Msg = "No alternative binding methods found."; Error = true; } } else { Msg = "Unable to connect properly."; Error = true; if (Client.TryGetTag("StartedAuthentication", out Obj) && Obj is bool b && b) { if (this.createAccount) { ClientEvents.PushEvent(new string[] { TabID }, "ShowFail2", Msg, false, "User"); } else { ClientEvents.PushEvent(new string[] { TabID }, "ShowFail1", Msg, false, "User"); } return; } } if (Error) { ClientEvents.PushEvent(new string[] { TabID }, "ConnectionError", Msg, false, "User"); this.client.Dispose(); this.client = null; return; } break; case XmppState.FetchingRoster: Msg = "Fetching roster from server."; break; case XmppState.Offline: Msg = "Offline."; break; case XmppState.Registering: Msg = "Registering account."; break; case XmppState.RequestingSession: Msg = "Requesting session."; break; case XmppState.SettingPresence: Msg = "Setting presence."; break; case XmppState.StartingEncryption: Msg = "Starting encryption."; Client.SetTag("StartedEncryption", true); break; case XmppState.StreamNegotiation: Msg = "Negotiating stream."; break; case XmppState.StreamOpened: Msg = "Stream opened."; break; default: Msg = NewState.ToString(); break; } ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", Msg, false, "User"); } catch (Exception ex) { Log.Critical(ex); ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", ex.Message, false, "User"); } }