From ea02d77e69b9dd10b26178ae5bbce5fbb5060313 Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Sun, 17 Mar 2019 20:49:26 +0900 Subject: [PATCH] Parse torrc file to know virtual port of hidden services --- BTCPayServer.Tests/UnitTest1.cs | 44 +++++++- .../Configuration/BTCPayServerOptions.cs | 4 +- .../Configuration/DefaultConfiguration.cs | 2 +- BTCPayServer/Controllers/ServerController.cs | 12 ++- .../ServerViewModels/ServicesViewModel.cs | 2 +- BTCPayServer/Services/TorServices.cs | 50 ++++++--- BTCPayServer/Services/Torrc.cs | 100 ++++++++++++++++++ BTCPayServer/Views/Server/Services.cshtml | 4 +- 8 files changed, 192 insertions(+), 26 deletions(-) create mode 100644 BTCPayServer/Services/Torrc.cs diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index b3744f3af..8c87d90e9 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -146,6 +146,46 @@ namespace BTCPayServer.Tests #pragma warning restore CS0618 } + + [Fact] + [Trait("Fast", "Fast")] + public void CanParseTorrc() + { + var nl = "\n"; + var input = "# For the hidden service BTCPayServer" + nl + + "HiddenServiceDir /var/lib/tor/hidden_services/BTCPayServer" + nl + + "# Redirecting to nginx" + nl + + "HiddenServicePort 80 172.19.0.10:81"; + nl = Environment.NewLine; + var expected = "HiddenServiceDir /var/lib/tor/hidden_services/BTCPayServer" + nl + + "HiddenServicePort 80 172.19.0.10:81" + nl; + Assert.True(Torrc.TryParse(input, out var torrc)); + Assert.Equal(expected, torrc.ToString()); + nl = "\r\n"; + input = "# For the hidden service BTCPayServer" + nl + + "HiddenServiceDir /var/lib/tor/hidden_services/BTCPayServer" + nl + + "# Redirecting to nginx" + nl + + "HiddenServicePort 80 172.19.0.10:81"; + + Assert.True(Torrc.TryParse(input, out torrc)); + Assert.Equal(expected, torrc.ToString()); + + input = "# For the hidden service BTCPayServer" + nl + + "HiddenServiceDir /var/lib/tor/hidden_services/BTCPayServer" + nl + + "# Redirecting to nginx" + nl + + "HiddenServicePort 80 172.19.0.10:80" + nl + + "HiddenServiceDir /var/lib/tor/hidden_services/Woocommerce" + nl + + "# Redirecting to nginx" + nl + + "HiddenServicePort 80 172.19.0.11:80"; + nl = Environment.NewLine; + expected = "HiddenServiceDir /var/lib/tor/hidden_services/BTCPayServer" + nl + + "HiddenServicePort 80 172.19.0.10:80" + nl + + "HiddenServiceDir /var/lib/tor/hidden_services/Woocommerce" + nl + + "HiddenServicePort 80 172.19.0.11:80" + nl; + Assert.True(Torrc.TryParse(input, out torrc)); + Assert.Equal(expected, torrc.ToString()); + } + [Fact] [Trait("Fast", "Fast")] public void CanCalculateCryptoDue() @@ -881,7 +921,7 @@ namespace BTCPayServer.Tests using (var tester = ServerTester.Create()) { tester.Start(); - foreach(var req in new[] + foreach (var req in new[] { "invoices/", "invoices", @@ -2365,7 +2405,7 @@ donation: Assert.True(ExternalConnectionString.TryParse("server=https://tow/test", out connStr, out error)); expanded = await connStr.Expand(new Uri("https://toto.com"), ExternalServiceTypes.Charge); Assert.Equal(new Uri("https://tow/test"), expanded.Server); - + // Error if directory not exists Assert.True(ExternalConnectionString.TryParse($"server={unusedUri};macaroondirectorypath=pouet", out connStr, out error)); await Assert.ThrowsAsync(() => connStr.Expand(unusedUri, ExternalServiceTypes.LNDGRPC)); diff --git a/BTCPayServer/Configuration/BTCPayServerOptions.cs b/BTCPayServer/Configuration/BTCPayServerOptions.cs index ed5b61ee6..c582eef13 100644 --- a/BTCPayServer/Configuration/BTCPayServerOptions.cs +++ b/BTCPayServer/Configuration/BTCPayServerOptions.cs @@ -149,7 +149,7 @@ namespace BTCPayServer.Configuration PostgresConnectionString = conf.GetOrDefault("postgres", null); MySQLConnectionString = conf.GetOrDefault("mysql", null); BundleJsCss = conf.GetOrDefault("bundlejscss", true); - TorHiddenServicesDirectory = conf.GetOrDefault("torservices", null); + TorrcFile = conf.GetOrDefault("torrcfile", null); var sshSettings = ParseSSHConfiguration(conf); if ((!string.IsNullOrEmpty(sshSettings.Password) || !string.IsNullOrEmpty(sshSettings.KeyFile)) && !string.IsNullOrEmpty(sshSettings.Server)) @@ -274,6 +274,6 @@ namespace BTCPayServer.Configuration get; set; } - public string TorHiddenServicesDirectory { get; set; } + public string TorrcFile { get; set; } } } diff --git a/BTCPayServer/Configuration/DefaultConfiguration.cs b/BTCPayServer/Configuration/DefaultConfiguration.cs index 243ef7ae7..9b41aaf2f 100644 --- a/BTCPayServer/Configuration/DefaultConfiguration.cs +++ b/BTCPayServer/Configuration/DefaultConfiguration.cs @@ -40,7 +40,7 @@ namespace BTCPayServer.Configuration app.Option("--sshkeyfile", "SSH private key file to manage BTCPay (default: empty)", CommandOptionType.SingleValue); app.Option("--sshkeyfilepassword", "Password of the SSH keyfile (default: empty)", CommandOptionType.SingleValue); app.Option("--sshtrustedfingerprints", "SSH Host public key fingerprint or sha256 (default: empty, it will allow untrusted connections)", CommandOptionType.SingleValue); - app.Option("--torservices", "Path to folder containing hidden services directories (default: empty)", CommandOptionType.SingleValue); + app.Option("--torrcfile", "Path to torrc file containing hidden services directories (default: empty)", CommandOptionType.SingleValue); app.Option("--debuglog", "A rolling log file for debug messages.", CommandOptionType.SingleValue); app.Option("--debugloglevel", "The severity you log (default:information)", CommandOptionType.SingleValue); app.Option("--disable-registration", "Disables new user registrations (default:true)", CommandOptionType.SingleValue); diff --git a/BTCPayServer/Controllers/ServerController.cs b/BTCPayServer/Controllers/ServerController.cs index 6663bd84d..3fb17cd2e 100644 --- a/BTCPayServer/Controllers/ServerController.cs +++ b/BTCPayServer/Controllers/ServerController.cs @@ -466,7 +466,17 @@ namespace BTCPayServer.Controllers Link = this.Url.Action(nameof(SSHService)) }); } - result.TorServices = await _torServices.GetServices(); + foreach(var torService in await _torServices.GetServices()) + { + if (torService.VirtualPort == 80) + { + result.OtherExternalServices.Add(new ServicesViewModel.OtherExternalService() + { + Name = torService.Name, + Link = $"http://{torService.OnionHost}" + }); + } + } return View(result); } diff --git a/BTCPayServer/Models/ServerViewModels/ServicesViewModel.cs b/BTCPayServer/Models/ServerViewModels/ServicesViewModel.cs index 8b83e221f..4da4f5fd3 100644 --- a/BTCPayServer/Models/ServerViewModels/ServicesViewModel.cs +++ b/BTCPayServer/Models/ServerViewModels/ServicesViewModel.cs @@ -18,6 +18,6 @@ namespace BTCPayServer.Models.ServerViewModels public List ExternalServices { get; set; } = new List(); public List OtherExternalServices { get; set; } = new List(); - public TorService[] TorServices { get; set; } = Array.Empty(); + public List TorServices { get; set; } = new List(); } } diff --git a/BTCPayServer/Services/TorServices.cs b/BTCPayServer/Services/TorServices.cs index 52848f8ce..ab9d9bf7e 100644 --- a/BTCPayServer/Services/TorServices.cs +++ b/BTCPayServer/Services/TorServices.cs @@ -17,29 +17,44 @@ namespace BTCPayServer.Services public async Task GetServices() { - if (string.IsNullOrEmpty(_Options.TorHiddenServicesDirectory) || !Directory.Exists(_Options.TorHiddenServicesDirectory)) + if (string.IsNullOrEmpty(_Options.TorrcFile) || !File.Exists(_Options.TorrcFile)) return Array.Empty(); List result = new List(); - var servicesDirs = Directory.GetDirectories(_Options.TorHiddenServicesDirectory); - var services = servicesDirs - .Select(d => new DirectoryInfo(d)) - .Select(d => (ServiceName: d.Name, ReadingLines: System.IO.File.ReadAllLinesAsync(Path.Combine(d.FullName, "hostname")))) - .ToArray(); - foreach (var service in services) + try { - try - { - var onionHost = (await service.ReadingLines)[0].Trim(); - var torService = new TorService() { Name = service.ServiceName, OnionHost = onionHost }; - if (service.ServiceName.Equals("BTCPayServer", StringComparison.OrdinalIgnoreCase)) - torService.ServiceType = TorServiceType.BTCPayServer; - result.Add(torService); - } - catch - { + var torrcContent = await File.ReadAllTextAsync(_Options.TorrcFile); + if (!Torrc.TryParse(torrcContent, out var torrc)) + return Array.Empty(); + var services = torrc.ServiceDirectories.SelectMany(d => d.ServicePorts.Select(p => (Directory: new DirectoryInfo(d.DirectoryPath), VirtualPort: p.VirtualPort))) + .Select(d => (ServiceName: d.Directory.Name, + ReadingLines: System.IO.File.ReadAllLinesAsync(Path.Combine(d.Directory.FullName, "hostname")), + VirtualPort: d.VirtualPort)) + .ToArray(); + foreach (var service in services) + { + try + { + var onionHost = (await service.ReadingLines)[0].Trim(); + var torService = new TorService() + { + Name = service.ServiceName, + OnionHost = onionHost, + VirtualPort = service.VirtualPort + }; + if (service.ServiceName.Equals("BTCPayServer", StringComparison.OrdinalIgnoreCase)) + torService.ServiceType = TorServiceType.BTCPayServer; + result.Add(torService); + } + catch + { + + } } } + catch + { + } return result.ToArray(); } } @@ -49,6 +64,7 @@ namespace BTCPayServer.Services public TorServiceType ServiceType { get; set; } = TorServiceType.Other; public string Name { get; set; } public string OnionHost { get; set; } + public int VirtualPort { get; set; } } public enum TorServiceType diff --git a/BTCPayServer/Services/Torrc.cs b/BTCPayServer/Services/Torrc.cs new file mode 100644 index 000000000..94bf960aa --- /dev/null +++ b/BTCPayServer/Services/Torrc.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; + +namespace BTCPayServer.Services +{ + public class Torrc + { + public static bool TryParse(string str, out Torrc value) + { + value = null; + List serviceDirectories = new List(); + var lines = str.Split(new char[] { '\n' }); + HiddenServiceDir currentDirectory = null; + foreach (var line in lines) + { + if (HiddenServiceDir.TryParse(line, out var dir)) + { + serviceDirectories.Add(dir); + currentDirectory = dir; + } + else if (HiddenServicePortDefinition.TryParse(line, out var portDef) && currentDirectory != null) + { + currentDirectory.ServicePorts.Add(portDef); + } + } + value = new Torrc() { ServiceDirectories = serviceDirectories }; + return true; + } + + public List ServiceDirectories { get; set; } = new List(); + + public override string ToString() + { + StringBuilder builder = new StringBuilder(); + foreach(var serviceDir in ServiceDirectories) + { + builder.AppendLine(serviceDir.ToString()); + foreach (var port in serviceDir.ServicePorts) + builder.AppendLine(port.ToString()); + } + return builder.ToString(); + } + } + + public class HiddenServiceDir + { + public static bool TryParse(string str, out HiddenServiceDir serviceDir) + { + serviceDir = null; + if (!str.Trim().StartsWith("HiddenServiceDir ", StringComparison.OrdinalIgnoreCase)) + return false; + var parts = str.Split(new char[] { ' ', '\t' }, StringSplitOptions.None); + if (parts.Length != 2) + return false; + serviceDir = new HiddenServiceDir() { DirectoryPath = parts[1].Trim() }; + return true; + } + + public string DirectoryPath { get; set; } + public List ServicePorts { get; set; } = new List(); + + public override string ToString() + { + return $"HiddenServiceDir {DirectoryPath}"; + } + } + public class HiddenServicePortDefinition + { + public static bool TryParse(string str, out HiddenServicePortDefinition portDefinition) + { + portDefinition = null; + if (!str.Trim().StartsWith("HiddenServicePort ", StringComparison.OrdinalIgnoreCase)) + return false; + var parts = str.Split(new char[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); + if (parts.Length != 3) + return false; + if (!int.TryParse(parts[1].Trim(), out int virtualPort)) + return false; + var addressPort = parts[2].Trim().Split(':', StringSplitOptions.RemoveEmptyEntries); + if (addressPort.Length != 2) + return false; + if (!int.TryParse(addressPort[1].Trim(), out int port)) + return false; + if (!IPAddress.TryParse(addressPort[0].Trim(), out IPAddress address)) + return false; + portDefinition = new HiddenServicePortDefinition() { VirtualPort = virtualPort, Endpoint = new IPEndPoint(address, port) }; + return true; + } + public int VirtualPort { get; set; } + public IPEndPoint Endpoint { get; set; } + public override string ToString() + { + return $"HiddenServicePort {VirtualPort} {Endpoint}"; + } + } +} diff --git a/BTCPayServer/Views/Server/Services.cshtml b/BTCPayServer/Views/Server/Services.cshtml index 0ac810809..1d2e3178f 100644 --- a/BTCPayServer/Views/Server/Services.cshtml +++ b/BTCPayServer/Views/Server/Services.cshtml @@ -85,7 +85,7 @@

TOR hidden services

- TOR services hosted on this server, services using auto service registration (like lightning services) will not appear here. + TOR services hosted on this server, only http servers are listed here.
@@ -100,7 +100,7 @@ { - + See information }
@s.Name@s.OnionHost