2020-02-20 18:55:46 +00:00
using System ;
2018-06-10 12:31:55 +00:00
using System.Collections ;
2018-05-01 11:17:59 +00:00
using System.Collections.Generic ;
2019-04-27 10:59:33 +00:00
using System.Diagnostics ;
2018-05-01 11:17:59 +00:00
using System.Globalization ;
using System.IO ;
using System.Linq ;
using System.Net ;
2018-06-10 12:31:55 +00:00
using System.Reflection ;
2018-05-01 11:17:59 +00:00
using System.Runtime.InteropServices ;
using System.Text ;
2018-06-10 12:31:55 +00:00
using System.Text.RegularExpressions ;
2018-05-01 11:17:59 +00:00
using System.Threading ;
2020-02-09 18:08:34 +00:00
using Jackett.Common.Models.Config ;
using Jackett.Common.Services.Interfaces ;
using Jackett.Common.Utils ;
using Microsoft.AspNetCore.Http ;
using Microsoft.AspNetCore.WebUtilities ;
using NLog ;
2018-05-01 11:17:59 +00:00
2018-05-01 11:41:34 +00:00
namespace Jackett.Server.Services
2018-05-01 11:17:59 +00:00
{
public class ServerService : IServerService
{
2020-02-10 22:16:19 +00:00
private readonly IIndexerManagerService indexerService ;
private readonly IProcessService processService ;
private readonly ISerializeService serializeService ;
private readonly IConfigurationService configService ;
private readonly Logger logger ;
private readonly Common . Utils . Clients . WebClient client ;
private readonly IUpdateService updater ;
private readonly List < string > _notices = new List < string > ( ) ;
private readonly ServerConfig config ;
private readonly IProtectionService _protectionService ;
2019-04-27 10:59:33 +00:00
private bool isDotNetCoreCapable ;
2018-05-01 11:17:59 +00:00
public ServerService ( IIndexerManagerService i , IProcessService p , ISerializeService s , IConfigurationService c , Logger l , Common . Utils . Clients . WebClient w , IUpdateService u , IProtectionService protectionService , ServerConfig serverConfig )
{
indexerService = i ;
processService = p ;
serializeService = s ;
configService = c ;
logger = l ;
client = w ;
updater = u ;
config = serverConfig ;
_protectionService = protectionService ;
2018-05-01 11:41:34 +00:00
}
2018-05-01 11:17:59 +00:00
public List < string > notices
{
get
{
return _notices ;
}
}
public Uri ConvertToProxyLink ( Uri link , string serverUrl , string indexerId , string action = "dl" , string file = "t" )
{
2018-06-10 11:54:12 +00:00
if ( link = = null | | ( link . IsAbsoluteUri & & link . Scheme = = "magnet" & & action ! = "bh" ) ) // no need to convert a magnet link to a proxy link unless it's a blackhole link
2018-05-01 11:17:59 +00:00
return link ;
var encryptedLink = _protectionService . Protect ( link . ToString ( ) ) ;
2018-05-01 11:41:34 +00:00
var encodedLink = WebEncoders . Base64UrlEncode ( Encoding . UTF8 . GetBytes ( encryptedLink ) ) ;
2020-02-10 22:16:19 +00:00
var urlEncodedFile = WebUtility . UrlEncode ( file ) ;
2018-05-01 11:17:59 +00:00
var proxyLink = string . Format ( "{0}{1}/{2}/?jackett_apikey={3}&path={4}&file={5}" , serverUrl , action , indexerId , config . APIKey , encodedLink , urlEncodedFile ) ;
return new Uri ( proxyLink ) ;
}
public string BasePath ( )
{
if ( config . BasePathOverride = = null | | config . BasePathOverride = = "" )
{
return "" ;
}
var path = config . BasePathOverride ;
if ( path . EndsWith ( "/" ) )
{
path = path . TrimEnd ( '/' ) ;
}
if ( ! path . StartsWith ( "/" ) )
{
path = "/" + path ;
}
return path ;
}
2018-05-01 11:41:34 +00:00
2018-05-01 11:17:59 +00:00
public void Initalize ( )
{
try
{
var x = Environment . OSVersion ;
var runtimedir = RuntimeEnvironment . GetRuntimeDirectory ( ) ;
logger . Info ( "Environment version: " + Environment . Version . ToString ( ) + " (" + runtimedir + ")" ) ;
logger . Info ( "OS version: " + Environment . OSVersion . ToString ( ) + ( Environment . Is64BitOperatingSystem ? " (64bit OS)" : "" ) + ( Environment . Is64BitProcess ? " (64bit process)" : "" ) ) ;
2020-02-10 22:16:19 +00:00
var variants = new Variants ( ) ;
var variant = variants . GetVariant ( ) ;
2019-01-21 10:18:59 +00:00
logger . Info ( "Jackett variant: " + variant . ToString ( ) ) ;
2018-05-01 11:17:59 +00:00
try
{
2020-02-10 22:16:19 +00:00
ThreadPool . GetMaxThreads ( out var workerThreads , out var completionPortThreads ) ;
2018-05-01 11:17:59 +00:00
logger . Info ( "ThreadPool MaxThreads: " + workerThreads + " workerThreads, " + completionPortThreads + " completionPortThreads" ) ;
}
catch ( Exception e )
{
logger . Error ( "Error while getting MaxThreads details: " + e ) ;
}
2018-06-17 02:39:03 +00:00
logger . Info ( "App config/log directory: " + configService . GetAppDataFolder ( ) ) ;
2018-05-01 11:17:59 +00:00
try
{
var issuefile = "/etc/issue" ;
if ( File . Exists ( issuefile ) )
{
2020-02-10 22:16:19 +00:00
using ( var reader = new StreamReader ( issuefile ) )
2018-05-01 11:17:59 +00:00
{
2020-02-10 22:16:19 +00:00
var firstLine = reader . ReadLine ( ) ;
2018-05-01 11:17:59 +00:00
if ( firstLine ! = null )
logger . Info ( "issue: " + firstLine ) ;
}
}
}
catch ( Exception e )
{
logger . Error ( e , "Error while reading the issue file" ) ;
}
2018-06-10 12:31:55 +00:00
2020-02-10 22:16:19 +00:00
var monotype = Type . GetType ( "Mono.Runtime" ) ;
2018-09-24 09:24:17 +00:00
if ( monotype ! = null & & ! DotNetCoreUtil . IsRunningOnDotNetCore )
2018-06-10 12:31:55 +00:00
{
2020-02-10 22:16:19 +00:00
var displayName = monotype . GetMethod ( "GetDisplayName" , BindingFlags . NonPublic | BindingFlags . Static ) ;
2018-06-10 12:31:55 +00:00
var monoVersion = "unknown" ;
if ( displayName ! = null )
monoVersion = displayName . Invoke ( null , null ) . ToString ( ) ;
logger . Info ( "mono version: " + monoVersion ) ;
var monoVersionO = new Version ( monoVersion . Split ( ' ' ) [ 0 ] ) ;
2018-06-14 09:18:17 +00:00
if ( monoVersionO . Major < 5 | | ( monoVersionO . Major = = 5 & & monoVersionO . Minor < 8 ) )
2018-06-10 12:31:55 +00:00
{
2018-06-14 09:18:17 +00:00
//Hard minimum of 5.8
//5.4 throws a SIGABRT, looks related to this which was fixed in 5.8 https://bugzilla.xamarin.com/show_bug.cgi?id=60625
2018-06-10 12:31:55 +00:00
logger . Error ( "Your mono version is too old. Please update to the latest version from http://www.mono-project.com/download/" ) ;
Environment . Exit ( 2 ) ;
}
if ( monoVersionO . Major < 5 | | ( monoVersionO . Major = = 5 & & monoVersionO . Minor < 8 ) )
{
2020-02-10 22:16:19 +00:00
var notice = "A minimum Mono version of 5.8 is required. Please update to the latest version from http://www.mono-project.com/download/" ;
2018-06-10 12:31:55 +00:00
_notices . Add ( notice ) ;
logger . Error ( notice ) ;
}
try
{
// Check for mono-devel
// Is there any better way which doesn't involve a hard cashes?
var mono_devel_file = Path . Combine ( runtimedir , "mono-api-info.exe" ) ;
if ( ! File . Exists ( mono_devel_file ) )
{
var notice = "It looks like the mono-devel package is not installed, please make sure it's installed to avoid crashes." ;
_notices . Add ( notice ) ;
logger . Error ( notice ) ;
}
}
catch ( Exception e )
{
logger . Error ( e , "Error while checking for mono-devel" ) ;
}
try
{
// Check for ca-certificates-mono
var mono_cert_file = Path . Combine ( runtimedir , "cert-sync.exe" ) ;
if ( ! File . Exists ( mono_cert_file ) )
{
var notice = "The ca-certificates-mono package is not installed, HTTPS trackers won't work. Please install it." ;
_notices . Add ( notice ) ;
logger . Error ( notice ) ;
}
}
catch ( Exception e )
{
logger . Error ( e , "Error while checking for ca-certificates-mono" ) ;
}
try
{
Encoding . GetEncoding ( "windows-1255" ) ;
}
catch ( NotSupportedException e )
{
logger . Debug ( e ) ;
logger . Error ( e . Message + " Most likely the mono-locale-extras package is not installed." ) ;
Environment . Exit ( 2 ) ;
}
2020-02-20 18:55:46 +00:00
if ( monoVersionO . Major < 6 )
2018-06-10 12:31:55 +00:00
{
2020-02-20 18:55:46 +00:00
//We don't check on Mono 6 since Mono.Security was removed
// check if the certificate store was initialized using Mono.Security.X509.X509StoreManager.TrustedRootCertificates.Count
try
2018-06-10 12:31:55 +00:00
{
2020-02-20 18:55:46 +00:00
var monoSecurity = Assembly . Load ( "Mono.Security" ) ;
var monoX509StoreManager = monoSecurity . GetType ( "Mono.Security.X509.X509StoreManager" ) ;
if ( monoX509StoreManager ! = null )
{
var TrustedRootCertificatesProperty = monoX509StoreManager . GetProperty ( "TrustedRootCertificates" ) ;
var TrustedRootCertificates = ( ICollection ) TrustedRootCertificatesProperty . GetValue ( null ) ;
2018-06-10 12:31:55 +00:00
2020-02-20 18:55:46 +00:00
logger . Info ( "TrustedRootCertificates count: " + TrustedRootCertificates . Count ) ;
2018-06-10 12:31:55 +00:00
2020-02-20 18:55:46 +00:00
if ( TrustedRootCertificates . Count = = 0 )
{
var CACertificatesFiles = new string [ ] {
2018-06-10 12:31:55 +00:00
"/etc/ssl/certs/ca-certificates.crt" , // Debian based
"/etc/pki/tls/certs/ca-bundle.c" , // RedHat based
"/etc/ssl/ca-bundle.pem" , // SUSE
} ;
2020-02-20 18:55:46 +00:00
var notice = "The mono certificate store is not initialized.<br/>\n" ;
var logSpacer = " " ;
var CACertificatesFile = CACertificatesFiles . Where ( f = > File . Exists ( f ) ) . FirstOrDefault ( ) ;
var CommandRoot = "curl -sS https://curl.haxx.se/ca/cacert.pem | cert-sync /dev/stdin" ;
var CommandUser = "curl -sS https://curl.haxx.se/ca/cacert.pem | cert-sync --user /dev/stdin" ;
if ( CACertificatesFile ! = null )
{
CommandRoot = "cert-sync " + CACertificatesFile ;
CommandUser = "cert-sync --user " + CACertificatesFile ;
}
notice + = logSpacer + "Please run the following command as root:<br/>\n" ;
notice + = logSpacer + "<pre>" + CommandRoot + "</pre><br/>\n" ;
notice + = logSpacer + "If you don't have root access or you're running MacOS, please run the following command as the jackett user (" + Environment . UserName + "):<br/>\n" ;
notice + = logSpacer + "<pre>" + CommandUser + "</pre>" ;
_notices . Add ( notice ) ;
logger . Error ( Regex . Replace ( notice , "<.*?>" , string . Empty ) ) ;
2018-06-10 12:31:55 +00:00
}
}
}
2020-02-20 18:55:46 +00:00
catch ( Exception e )
{
logger . Error ( e , "Error while chekcing the mono certificate store" ) ;
}
2018-06-10 12:31:55 +00:00
}
}
2018-05-01 11:17:59 +00:00
}
catch ( Exception e )
{
logger . Error ( "Error while getting environment details: " + e ) ;
}
try
{
if ( Environment . UserName = = "root" )
2018-05-01 11:41:34 +00:00
{
2018-05-01 11:17:59 +00:00
var notice = "Jackett is running with root privileges. You should run Jackett as an unprivileged user." ;
_notices . Add ( notice ) ;
logger . Error ( notice ) ;
}
}
catch ( Exception e )
{
logger . Error ( e , "Error while checking the username" ) ;
}
2018-10-20 10:09:29 +00:00
//Warn user that they are using an old version of Jackett
try
{
2020-02-10 22:16:19 +00:00
var compiledData = BuildDate . GetBuildDateTime ( ) ;
2018-10-20 10:09:29 +00:00
2018-10-28 07:27:10 +00:00
if ( compiledData < DateTime . Now . AddMonths ( - 3 ) )
2018-10-20 10:09:29 +00:00
{
2020-02-10 22:16:19 +00:00
var version = configService . GetVersion ( ) ;
var notice = $"Your version of Jackett v{version} is very old. Multiple indexers are likely to fail when using an old version. Update to the latest version of Jackett." ;
2018-10-28 07:27:10 +00:00
_notices . Add ( notice ) ;
logger . Error ( notice ) ;
2018-10-20 10:09:29 +00:00
}
}
catch ( Exception e )
{
logger . Error ( e , "Error while checking build date of Jackett.Common" ) ;
}
2019-04-27 10:59:33 +00:00
//Alert user that they no longer need to use Mono
try
{
2020-02-10 22:16:19 +00:00
var variants = new Variants ( ) ;
var variant = variants . GetVariant ( ) ;
2019-04-27 10:59:33 +00:00
if ( variant = = Variants . JackettVariant . Mono )
{
2020-02-10 22:16:19 +00:00
var process = new Process ( ) ;
2019-04-27 10:59:33 +00:00
process . StartInfo . FileName = "uname" ;
process . StartInfo . Arguments = "-m" ;
process . StartInfo . UseShellExecute = false ;
process . StartInfo . RedirectStandardOutput = true ;
process . Start ( ) ;
2020-02-10 22:16:19 +00:00
var output = process . StandardOutput . ReadToEnd ( ) ;
2019-04-27 10:59:33 +00:00
process . WaitForExit ( ) ;
logger . Debug ( $"uname output was: {output}" ) ;
output = output . ToLower ( ) ;
if ( output . Contains ( "armv7" ) | | output . Contains ( "armv8" ) | | output . Contains ( "x86_64" ) )
{
isDotNetCoreCapable = true ;
}
}
}
catch ( Exception e )
{
logger . Debug ( e , "Unable to get architecture" ) ;
}
2018-05-01 11:17:59 +00:00
CultureInfo . DefaultThreadCurrentCulture = new CultureInfo ( "en-US" ) ;
// Load indexers
indexerService . InitIndexers ( configService . GetCardigannDefinitionsFolders ( ) ) ;
client . Init ( ) ;
updater . CleanupTempDir ( ) ;
}
public void Start ( )
{
updater . StartUpdateChecker ( ) ;
}
public void ReserveUrls ( bool doInstall = true )
{
logger . Debug ( "Unreserving Urls" ) ;
config . GetListenAddresses ( false ) . ToList ( ) . ForEach ( u = > RunNetSh ( string . Format ( "http delete urlacl {0}" , u ) ) ) ;
config . GetListenAddresses ( true ) . ToList ( ) . ForEach ( u = > RunNetSh ( string . Format ( "http delete urlacl {0}" , u ) ) ) ;
if ( doInstall )
{
logger . Debug ( "Reserving Urls" ) ;
2018-07-02 11:09:42 +00:00
config . GetListenAddresses ( true ) . ToList ( ) . ForEach ( u = > RunNetSh ( string . Format ( "http add urlacl {0} sddl=D:(A;;GX;;;S-1-1-0)" , u ) ) ) ;
2018-05-01 11:17:59 +00:00
logger . Debug ( "Urls reserved" ) ;
}
}
private void RunNetSh ( string args )
{
processService . StartProcessAndLog ( "netsh.exe" , args ) ;
}
public void Stop ( )
{
2018-06-14 09:21:31 +00:00
// Only needed for Owin
2018-05-01 11:17:59 +00:00
}
2019-03-06 09:11:50 +00:00
public string GetServerUrl ( HttpRequest request )
2018-05-01 11:17:59 +00:00
{
2020-02-10 22:16:19 +00:00
var serverUrl = "" ;
2018-05-01 11:17:59 +00:00
2019-03-06 09:11:50 +00:00
var scheme = request . Scheme ;
var port = request . HttpContext . Request . Host . Port ;
2018-05-01 11:17:59 +00:00
2019-03-06 09:11:50 +00:00
// Check for protocol headers added by reverse proxys
// X-Forwarded-Proto: A de facto standard for identifying the originating protocol of an HTTP request
var X_Forwarded_Proto = request . Headers . Where ( x = > x . Key = = "X-Forwarded-Proto" ) . Select ( x = > x . Value ) . FirstOrDefault ( ) ;
if ( X_Forwarded_Proto . Count > 0 )
{
scheme = X_Forwarded_Proto . First ( ) ;
}
// Front-End-Https: Non-standard header field used by Microsoft applications and load-balancers
else if ( request . Headers . Where ( x = > x . Key = = "Front-End-Https" & & x . Value . FirstOrDefault ( ) = = "on" ) . Any ( ) )
{
scheme = "https" ;
}
2018-05-01 11:17:59 +00:00
2019-03-06 09:11:50 +00:00
//default to 443 if the Host header doesn't contain the port (needed for reverse proxy setups)
if ( scheme = = "https" & & ! request . HttpContext . Request . Host . Value . Contains ( ":" ) )
{
port = 443 ;
2018-05-01 11:17:59 +00:00
}
2019-03-06 09:11:50 +00:00
serverUrl = string . Format ( "{0}://{1}:{2}{3}/" , scheme , request . HttpContext . Request . Host . Host , port , BasePath ( ) ) ;
2018-05-01 11:17:59 +00:00
return serverUrl ;
}
public string GetBlackholeDirectory ( )
{
return config . BlackholeDir ;
}
public string GetApiKey ( )
{
return config . APIKey ;
}
2019-04-27 10:59:33 +00:00
public bool MonoUserCanRunNetCore ( )
{
2019-05-01 10:15:26 +00:00
return isDotNetCoreCapable ;
2019-04-27 10:59:33 +00:00
}
2018-05-01 11:17:59 +00:00
}
}