using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using BTCPayServer.Configuration; using BTCPayServer.HostedServices; using BTCPayServer.Logging; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace BTCPayServer.Services { public class TorServices : BaseAsyncService { private readonly BTCPayNetworkProvider _btcPayNetworkProvider; private readonly IOptions _options; public TorServices(BTCPayNetworkProvider btcPayNetworkProvider, IOptions options, Logs logs) : base(logs) { _btcPayNetworkProvider = btcPayNetworkProvider; _options = options; } public TorService[] Services { get; internal set; } = Array.Empty(); private bool firstRun = true; internal async Task Refresh() { if (firstRun) { firstRun = false; } else { await Task.Delay(TimeSpan.FromSeconds(120), Cancellation); } List result = new List(); try { if (!File.Exists(_options.Value.TorrcFile)) { Logs.PayServer.LogWarning("Torrc file is not found"); Services = Array.Empty(); return; } var torrcContent = await File.ReadAllTextAsync(_options.Value.TorrcFile); if (!Torrc.TryParse(torrcContent, out var torrc)) { Logs.PayServer.LogWarning("Torrc file could not be parsed"); Services = Array.Empty(); return; } var torrcDir = Path.GetDirectoryName(_options.Value.TorrcFile); var services = torrc.ServiceDirectories.SelectMany(d => d.ServicePorts.Select(p => (Directory: GetDirectory(d, torrcDir), 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 = ParseService(service.ServiceName, onionHost, service.VirtualPort); result.Add(torService); } catch (Exception ex) { Logs.PayServer.LogWarning(ex, $"Error while reading hidden service {service.ServiceName} configuration"); } } } catch (Exception ex) { Logs.PayServer.LogWarning(ex, $"Error while reading torrc file"); } Services = result.ToArray(); } private TorService ParseService(string serviceName, string onionHost, int virtualPort) { var torService = new TorService() {Name = serviceName, OnionHost = onionHost, VirtualPort = virtualPort}; if (Enum.TryParse(serviceName, true, out var serviceType)) torService.ServiceType = serviceType; else if (TryParseCryptoSpecificService(serviceName, out var network, out serviceType)) { torService.ServiceType = serviceType; torService.Network = network; } else { torService.ServiceType = TorServiceType.Other; } return torService; } private static DirectoryInfo GetDirectory(HiddenServiceDir hs, string relativeTo) { if (Path.IsPathRooted(hs.DirectoryPath)) return new DirectoryInfo(hs.DirectoryPath); return new DirectoryInfo(Path.Combine(relativeTo, hs.DirectoryPath)); } private bool TryParseCryptoSpecificService(string name, out BTCPayNetworkBase network, out TorServiceType serviceType) { network = null; serviceType = TorServiceType.Other; var splitted = name.Trim().Split('-'); return splitted.Length == 2 && Enum.TryParse(splitted[1], true, out serviceType) && _btcPayNetworkProvider.TryGetNetwork(splitted[0], out network); } public override async Task StartAsync(CancellationToken cancellationToken) { if (string.IsNullOrEmpty(_options.Value.TorrcFile) && _options.Value.TorServices != null) { LoadFromConfig(); } else if(!string.IsNullOrEmpty(_options.Value.TorrcFile)) { await Refresh(); await base.StartAsync(cancellationToken); } } internal override Task[] InitializeTasks() { return new[] {CreateLoopTask(Refresh)}; } private void LoadFromConfig() { Services = _options.Value.TorServices.Select(p => p.Split(":", StringSplitOptions.RemoveEmptyEntries)) .Where(p => p.Length == 3) .Select(strings => int.TryParse(strings[2], out var port) ? ParseService(strings[0], strings[1], port) : null) .Where(p => p != null) .ToArray(); } } public class TorService { public TorServiceType ServiceType { get; set; } = TorServiceType.Other; public BTCPayNetworkBase Network { get; set; } public string Name { get; set; } public string OnionHost { get; set; } public int VirtualPort { get; set; } } public enum TorServiceType { BTCPayServer, P2P, RPC, Other } }