2017-09-13 08:47:34 +02:00
using BTCPayServer.Logging ;
using System.Linq ;
using Microsoft.Extensions.Logging ;
using NBitcoin ;
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Net ;
using System.Text ;
2017-09-22 18:31:29 +02:00
using StandardConfiguration ;
using Microsoft.Extensions.Configuration ;
2018-01-07 18:36:41 +01:00
using NBXplorer ;
2018-03-20 18:09:25 +01:00
using BTCPayServer.Payments.Lightning ;
2018-08-12 14:38:45 +02:00
using Renci.SshNet ;
2018-08-12 16:23:26 +02:00
using NBitcoin.DataEncoders ;
2018-08-13 02:43:59 +02:00
using BTCPayServer.SSH ;
2018-08-30 04:50:39 +02:00
using BTCPayServer.Lightning ;
2017-09-13 08:47:34 +02:00
namespace BTCPayServer.Configuration
{
2018-01-12 08:00:31 +01:00
public class NBXplorerConnectionSetting
{
public string CryptoCode { get ; internal set ; }
public Uri ExplorerUri { get ; internal set ; }
public string CookieFile { get ; internal set ; }
}
2017-10-27 10:53:04 +02:00
public class BTCPayServerOptions
{
2018-04-19 09:54:25 +02:00
public NetworkType NetworkType
2017-10-27 10:53:04 +02:00
{
get ; set ;
}
public string ConfigurationFile
{
get ;
private set ;
}
public string DataDir
{
get ;
private set ;
}
public List < IPEndPoint > Listen
{
get ;
set ;
}
2017-09-13 08:47:34 +02:00
2018-01-12 08:00:31 +01:00
public List < NBXplorerConnectionSetting > NBXplorerConnectionSettings
{
get ;
set ;
} = new List < NBXplorerConnectionSetting > ( ) ;
2017-10-27 10:53:04 +02:00
public void LoadArgs ( IConfiguration conf )
{
2018-04-19 09:54:25 +02:00
NetworkType = DefaultConfiguration . GetNetworkType ( conf ) ;
var defaultSettings = BTCPayDefaultSettings . GetDefaultSettings ( NetworkType ) ;
2018-01-11 14:52:28 +01:00
DataDir = conf . GetOrDefault < string > ( "datadir" , defaultSettings . DefaultDataDirectory ) ;
2018-04-19 09:54:25 +02:00
Logs . Configuration . LogInformation ( "Network: " + NetworkType . ToString ( ) ) ;
2017-09-13 08:47:34 +02:00
2018-01-11 14:52:28 +01:00
var supportedChains = conf . GetOrDefault < string > ( "chains" , "btc" )
. Split ( ',' , StringSplitOptions . RemoveEmptyEntries )
2018-01-12 03:54:57 +01:00
. Select ( t = > t . ToUpperInvariant ( ) ) ;
2018-04-19 09:54:25 +02:00
NetworkProvider = new BTCPayNetworkProvider ( NetworkType ) . Filter ( supportedChains . ToArray ( ) ) ;
2018-02-25 16:48:12 +01:00
foreach ( var chain in supportedChains )
{
if ( NetworkProvider . GetNetwork ( chain ) = = null )
throw new ConfigException ( $"Invalid chains \" { chain } \ "" ) ;
}
2018-01-11 14:52:28 +01:00
var validChains = new List < string > ( ) ;
2018-02-25 16:48:12 +01:00
foreach ( var net in NetworkProvider . GetAll ( ) )
2018-01-07 18:36:41 +01:00
{
2018-02-25 16:48:12 +01:00
NBXplorerConnectionSetting setting = new NBXplorerConnectionSetting ( ) ;
setting . CryptoCode = net . CryptoCode ;
setting . ExplorerUri = conf . GetOrDefault < Uri > ( $"{net.CryptoCode}.explorer.url" , net . NBXplorerNetwork . DefaultSettings . DefaultUrl ) ;
setting . CookieFile = conf . GetOrDefault < string > ( $"{net.CryptoCode}.explorer.cookiefile" , net . NBXplorerNetwork . DefaultSettings . DefaultCookieFile ) ;
NBXplorerConnectionSettings . Add ( setting ) ;
2018-03-20 18:09:25 +01:00
{
2018-07-23 04:53:39 +02:00
var lightning = conf . GetOrDefault < string > ( $"{net.CryptoCode}.lightning" , string . Empty ) ;
if ( lightning . Length ! = 0 )
2018-03-20 18:09:25 +01:00
{
2018-07-23 04:53:39 +02:00
if ( ! LightningConnectionString . TryParse ( lightning , true , out var connectionString , out var error ) )
{
throw new ConfigException ( $"Invalid setting {net.CryptoCode}.lightning, " + Environment . NewLine +
$"If you have a lightning server use: 'type=clightning;server=/root/.lightning/lightning-rpc', " + Environment . NewLine +
$"If you have a lightning charge server: 'type=charge;server=https://charge.example.com;api-token=yourapitoken'" + Environment . NewLine +
$"If you have a lnd server: 'type=lnd-rest;server=https://lnd:lnd@lnd.example.com;macaroon=abf239...;certthumbprint=2abdf302...'" + Environment . NewLine +
$" lnd server: 'type=lnd-rest;server=https://lnd:lnd@lnd.example.com;macaroonfilepath=/root/.lnd/admin.macaroon;certthumbprint=2abdf302...'" + Environment . NewLine +
error ) ;
}
if ( connectionString . IsLegacy )
{
Logs . Configuration . LogWarning ( $"Setting {net.CryptoCode}.lightning will work but use an deprecated format, please replace it by '{connectionString.ToString()}'" ) ;
}
InternalLightningByCryptoCode . Add ( net . CryptoCode , connectionString ) ;
2018-07-01 08:45:08 +02:00
}
2018-07-23 04:53:39 +02:00
}
{
var lightning = conf . GetOrDefault < string > ( $"{net.CryptoCode}.external.lnd.grpc" , string . Empty ) ;
if ( lightning . Length ! = 0 )
2018-07-01 08:45:08 +02:00
{
2018-07-23 04:53:39 +02:00
if ( ! LightningConnectionString . TryParse ( lightning , false , out var connectionString , out var error ) )
{
throw new ConfigException ( $"Invalid setting {net.CryptoCode}.external.lnd.grpc, " + Environment . NewLine +
$"lnd server: 'type=lnd-grpc;server=https://lnd.example.com;macaroon=abf239...;certthumbprint=2abdf302...'" + Environment . NewLine +
$"lnd server: 'type=lnd-grpc;server=https://lnd.example.com;macaroonfilepath=/root/.lnd/admin.macaroon;certthumbprint=2abdf302...'" + Environment . NewLine +
error ) ;
}
ExternalServicesByCryptoCode . Add ( net . CryptoCode , new ExternalLNDGRPC ( connectionString ) ) ;
2018-03-20 18:09:25 +01:00
}
}
2018-01-07 18:36:41 +01:00
}
2018-01-12 03:54:57 +01:00
Logs . Configuration . LogInformation ( "Supported chains: " + String . Join ( ',' , supportedChains . ToArray ( ) ) ) ;
2018-02-25 16:48:12 +01:00
2017-10-27 10:53:04 +02:00
PostgresConnectionString = conf . GetOrDefault < string > ( "postgres" , null ) ;
2018-02-21 07:48:25 +01:00
BundleJsCss = conf . GetOrDefault < bool > ( "bundlejscss" , true ) ;
2017-12-02 15:22:23 +01:00
ExternalUrl = conf . GetOrDefault < Uri > ( "externalurl" , null ) ;
2018-04-05 08:50:23 +02:00
2018-08-13 02:43:59 +02:00
var sshSettings = ParseSSHConfiguration ( conf ) ;
2018-08-12 16:23:26 +02:00
if ( ( ! string . IsNullOrEmpty ( sshSettings . Password ) | | ! string . IsNullOrEmpty ( sshSettings . KeyFile ) ) & & ! string . IsNullOrEmpty ( sshSettings . Server ) )
2018-08-12 14:38:45 +02:00
{
2018-08-13 02:43:59 +02:00
int waitTime = 0 ;
while ( ! string . IsNullOrEmpty ( sshSettings . KeyFile ) & & ! File . Exists ( sshSettings . KeyFile ) )
{
if ( waitTime + + < 5 )
System . Threading . Thread . Sleep ( 1000 ) ;
else
throw new ConfigException ( $"sshkeyfile does not exist" ) ;
}
2018-08-12 14:38:45 +02:00
if ( sshSettings . Port > ushort . MaxValue | |
sshSettings . Port < ushort . MinValue )
throw new ConfigException ( $"ssh port is invalid" ) ;
if ( ! string . IsNullOrEmpty ( sshSettings . Password ) & & ! string . IsNullOrEmpty ( sshSettings . KeyFile ) )
throw new ConfigException ( $"sshpassword or sshkeyfile should be provided, but not both" ) ;
try
{
sshSettings . CreateConnectionInfo ( ) ;
}
catch
{
throw new ConfigException ( $"sshkeyfilepassword is invalid" ) ;
}
SSHSettings = sshSettings ;
}
2018-08-12 16:23:26 +02:00
var fingerPrints = conf . GetOrDefault < string > ( "sshtrustedfingerprints" , "" ) ;
if ( ! string . IsNullOrEmpty ( fingerPrints ) )
{
2018-08-13 02:43:59 +02:00
foreach ( var fingerprint in fingerPrints . Split ( ';' , StringSplitOptions . RemoveEmptyEntries ) )
2018-08-12 16:23:26 +02:00
{
2018-08-13 02:43:59 +02:00
if ( ! SSHFingerprint . TryParse ( fingerprint , out var f ) )
throw new ConfigException ( $"Invalid ssh fingerprint format {fingerprint}" ) ;
TrustedFingerprints . Add ( f ) ;
2018-08-12 16:23:26 +02:00
}
}
2018-04-05 08:50:23 +02:00
RootPath = conf . GetOrDefault < string > ( "rootpath" , "/" ) ;
2018-04-09 07:31:39 +02:00
if ( ! RootPath . StartsWith ( "/" , StringComparison . InvariantCultureIgnoreCase ) )
RootPath = "/" + RootPath ;
2018-03-20 18:09:25 +01:00
var old = conf . GetOrDefault < Uri > ( "internallightningnode" , null ) ;
2018-07-23 04:53:39 +02:00
if ( old ! = null )
2018-03-20 18:09:25 +01:00
throw new ConfigException ( $"internallightningnode should not be used anymore, use btclightning instead" ) ;
2017-10-27 10:53:04 +02:00
}
2018-08-12 16:23:26 +02:00
2018-08-13 02:43:59 +02:00
private SSHSettings ParseSSHConfiguration ( IConfiguration conf )
2018-08-12 16:23:26 +02:00
{
2018-08-13 02:43:59 +02:00
var externalUrl = conf . GetOrDefault < Uri > ( "externalurl" , null ) ;
var settings = new SSHSettings ( ) ;
settings . Server = conf . GetOrDefault < string > ( "sshconnection" , null ) ;
if ( settings . Server ! = null )
2018-08-12 16:23:26 +02:00
{
2018-08-13 02:43:59 +02:00
var parts = settings . Server . Split ( ':' ) ;
if ( parts . Length = = 2 & & int . TryParse ( parts [ 1 ] , out int port ) )
{
settings . Port = port ;
settings . Server = parts [ 0 ] ;
}
else
{
settings . Port = 22 ;
}
2018-08-12 16:23:26 +02:00
2018-08-13 02:43:59 +02:00
parts = settings . Server . Split ( '@' ) ;
if ( parts . Length = = 2 )
{
settings . Username = parts [ 0 ] ;
settings . Server = parts [ 1 ] ;
}
else
{
settings . Username = "root" ;
}
2018-08-12 16:23:26 +02:00
}
2018-08-13 02:43:59 +02:00
else if ( externalUrl ! = null )
2018-08-12 16:23:26 +02:00
{
2018-08-13 02:43:59 +02:00
settings . Port = 22 ;
settings . Username = "root" ;
settings . Server = externalUrl . DnsSafeHost ;
2018-08-12 16:23:26 +02:00
}
2018-08-13 02:43:59 +02:00
settings . Password = conf . GetOrDefault < string > ( "sshpassword" , "" ) ;
settings . KeyFile = conf . GetOrDefault < string > ( "sshkeyfile" , "" ) ;
settings . KeyFilePassword = conf . GetOrDefault < string > ( "sshkeyfilepassword" , "" ) ;
return settings ;
2018-08-12 16:23:26 +02:00
}
2018-08-13 02:43:59 +02:00
internal bool IsTrustedFingerprint ( byte [ ] fingerPrint , byte [ ] hostKey )
2018-08-12 16:23:26 +02:00
{
2018-08-13 02:43:59 +02:00
return TrustedFingerprints . Any ( f = > f . Match ( fingerPrint , hostKey ) ) ;
2018-08-12 16:23:26 +02:00
}
2018-04-05 08:50:23 +02:00
public string RootPath { get ; set ; }
2018-03-20 18:09:25 +01:00
public Dictionary < string , LightningConnectionString > InternalLightningByCryptoCode { get ; set ; } = new Dictionary < string , LightningConnectionString > ( ) ;
2018-07-23 04:53:39 +02:00
public ExternalServices ExternalServicesByCryptoCode { get ; set ; } = new ExternalServices ( ) ;
2018-02-26 10:58:02 +01:00
2018-02-25 16:48:12 +01:00
public BTCPayNetworkProvider NetworkProvider { get ; set ; }
2017-10-27 10:53:04 +02:00
public string PostgresConnectionString
{
get ;
set ;
}
2017-12-02 15:22:23 +01:00
public Uri ExternalUrl
{
get ;
set ;
}
2018-02-21 07:48:25 +01:00
public bool BundleJsCss
{
get ;
set ;
}
2018-08-13 02:43:59 +02:00
public List < SSHFingerprint > TrustedFingerprints { get ; set ; } = new List < SSHFingerprint > ( ) ;
2018-08-12 14:38:45 +02:00
public SSHSettings SSHSettings
{
get ;
set ;
}
2018-04-09 07:41:52 +02:00
internal string GetRootUri ( )
{
if ( ExternalUrl = = null )
return null ;
UriBuilder builder = new UriBuilder ( ExternalUrl ) ;
builder . Path = RootPath ;
return builder . ToString ( ) ;
}
2017-10-27 10:53:04 +02:00
}
2018-08-12 14:38:45 +02:00
2018-07-23 04:53:39 +02:00
public class ExternalServices : MultiValueDictionary < string , ExternalService >
{
public IEnumerable < T > GetServices < T > ( string cryptoCode ) where T : ExternalService
{
if ( ! this . TryGetValue ( cryptoCode . ToUpperInvariant ( ) , out var services ) )
return Array . Empty < T > ( ) ;
return services . OfType < T > ( ) ;
}
}
public class ExternalService
{
}
public class ExternalLNDGRPC : ExternalService
{
public ExternalLNDGRPC ( LightningConnectionString connectionString )
{
ConnectionString = connectionString ;
}
public LightningConnectionString ConnectionString { get ; set ; }
}
2017-09-13 08:47:34 +02:00
}