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 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
2020-02-25 16:08:03 +00:00
public List < string > notices { get ; } = new List < string > ( ) ;
2018-05-01 11:17:59 +00:00
public Uri ConvertToProxyLink ( Uri link , string serverUrl , string indexerId , string action = "dl" , string file = "t" )
{
2020-09-24 20:02:45 +00:00
// no need to convert a magnet link to a proxy link unless it's a blackhole link
if ( link = = null | | ( link . IsAbsoluteUri & & link . Scheme = = "magnet" & & action ! = "bh" ) )
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 ) ;
2020-09-24 20:02:45 +00:00
var proxyLink = $"{serverUrl}{action}/{indexerId}/?jackett_apikey={config.APIKey}&path={encodedLink}&file={urlEncodedFile}" ;
2018-05-01 11:17:59 +00:00
return new Uri ( proxyLink ) ;
}
public string BasePath ( )
{
2020-09-24 20:02:45 +00:00
if ( string . IsNullOrEmpty ( config . BasePathOverride ) )
2018-05-01 11:17:59 +00:00
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 ( ) ;
2020-09-24 20:02:45 +00:00
logger . Info ( $"Environment version: {Environment.Version} ({runtimedir})" ) ;
logger . Info ( $"OS version: {Environment.OSVersion}" +
2020-02-25 16:08:03 +00:00
( Environment . Is64BitOperatingSystem ? " (64bit OS)" : "" ) +
( Environment . Is64BitProcess ? " (64bit process)" : "" ) ) ;
2020-02-10 22:16:19 +00:00
var variants = new Variants ( ) ;
var variant = variants . GetVariant ( ) ;
2020-09-24 20:02:45 +00:00
logger . Info ( $"Jackett variant: {variant}" ) ;
2018-05-01 11:17:59 +00:00
try
{
2020-03-11 06:02:55 +00:00
var issueFile = "/etc/issue" ;
if ( File . Exists ( issueFile ) )
using ( var reader = new StreamReader ( issueFile ) )
{
var firstLine = reader . ReadLine ( ) ;
if ( firstLine ! = null )
2020-04-07 16:17:17 +00:00
logger . Info ( $"File {issueFile}: {firstLine}" ) ;
2020-03-11 06:02:55 +00:00
}
2018-05-01 11:17:59 +00:00
}
catch ( Exception e )
{
2020-09-24 20:02:45 +00:00
logger . Error ( $"Error while reading the issue file\n{e}" ) ;
2018-05-01 11:17:59 +00:00
}
2020-03-11 06:02:55 +00:00
try
{
2020-09-13 20:46:16 +00:00
var dockerMsg = "No" ;
const string cgroupFile = "/proc/1/cgroup" ;
if ( File . Exists ( cgroupFile ) & & File . ReadAllText ( cgroupFile ) . Contains ( "/docker/" ) )
{
// this file is created in the Docker image build
// https://github.com/linuxserver/docker-jackett/pull/105
const string dockerImageFile = "/etc/docker-image" ;
dockerMsg = File . Exists ( dockerImageFile )
? "Yes (image build: " + File . ReadAllText ( dockerImageFile ) . Trim ( ) + ")"
: "Yes (image build: unknown)" ;
}
logger . Info ( $"Running in Docker: {dockerMsg}" ) ;
2020-03-11 06:02:55 +00:00
}
catch ( Exception e )
{
2020-09-24 20:02:45 +00:00
logger . Error ( $"Error while reading the Docker cgroup file.\n{e}" ) ;
2020-03-11 06:02:55 +00:00
}
2018-06-17 02:39:03 +00:00
2018-05-01 11:17:59 +00:00
try
{
2020-03-11 06:02:55 +00:00
ThreadPool . GetMaxThreads ( out var workerThreads , out var completionPortThreads ) ;
logger . Info (
"ThreadPool MaxThreads: " + workerThreads + " workerThreads, " + completionPortThreads +
" completionPortThreads" ) ;
2018-05-01 11:17:59 +00:00
}
catch ( Exception e )
{
2020-03-11 06:02:55 +00:00
logger . Error ( "Error while getting MaxThreads details: " + e ) ;
2018-05-01 11:17:59 +00:00
}
2018-06-10 12:31:55 +00:00
2020-03-11 06:02:55 +00:00
logger . Info ( "App config/log directory: " + configService . GetAppDataFolder ( ) ) ;
2020-02-23 20:39:49 +00:00
logger . Info ( "Using Proxy: " + ( string . IsNullOrEmpty ( config . ProxyUrl ) ? "No" : config . ProxyType . ToString ( ) ) ) ;
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 ( ) ;
2020-03-11 06:02:55 +00:00
logger . Info ( "Mono version: " + monoVersion ) ;
2018-06-10 12:31:55 +00:00
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
2020-02-25 16:08:03 +00:00
logger . Error (
2020-03-11 06:02:55 +00:00
"Your Mono version is too old. Please update to the latest version from http://www.mono-project.com/download/" ) ;
2018-06-10 12:31:55 +00:00
Environment . Exit ( 2 ) ;
}
if ( monoVersionO . Major < 5 | | ( monoVersionO . Major = = 5 & & monoVersionO . Minor < 8 ) )
{
2020-09-24 20:02:45 +00:00
const string notice = "A minimum Mono version of 5.8 is required. Please update to the latest version from http://www.mono-project.com/download/" ;
2020-02-25 16:08:03 +00:00
notices . Add ( notice ) ;
2018-06-10 12:31:55 +00:00
logger . Error ( notice ) ;
}
try
{
// Check for mono-devel
// Is there any better way which doesn't involve a hard cashes?
2020-09-24 20:02:45 +00:00
var monoDevelFile = Path . Combine ( runtimedir , "mono-api-info.exe" ) ;
if ( ! File . Exists ( monoDevelFile ) )
2018-06-10 12:31:55 +00:00
{
2020-09-24 20:02:45 +00:00
const string notice = "It looks like the mono-devel package is not installed, please make sure it's installed to avoid crashes." ;
2020-02-25 16:08:03 +00:00
notices . Add ( notice ) ;
2018-06-10 12:31:55 +00:00
logger . Error ( notice ) ;
}
}
catch ( Exception e )
{
2020-09-24 20:02:45 +00:00
logger . Error ( $"Error while checking for mono-devel.\n{e}" ) ;
2018-06-10 12:31:55 +00:00
}
try
{
// Check for ca-certificates-mono
2020-09-24 20:02:45 +00:00
var monoCertFile = Path . Combine ( runtimedir , "cert-sync.exe" ) ;
if ( ! File . Exists ( monoCertFile ) )
2018-06-10 12:31:55 +00:00
{
2020-09-24 20:02:45 +00:00
const string notice = "The ca-certificates-mono package is not installed, HTTPS trackers won't work. Please install it." ;
2020-02-25 16:08:03 +00:00
notices . Add ( notice ) ;
2018-06-10 12:31:55 +00:00
logger . Error ( notice ) ;
}
}
catch ( Exception e )
{
2020-09-24 20:02:45 +00:00
logger . Error ( $"Error while checking for ca-certificates-mono.\n{e}" ) ;
2018-06-10 12:31:55 +00:00
}
try
{
Encoding . GetEncoding ( "windows-1255" ) ;
}
catch ( NotSupportedException e )
{
2020-09-24 20:02:45 +00:00
logger . Error ( $"Most likely the mono-locale-extras package is not installed.\n{e}" ) ;
2018-06-10 12:31:55 +00:00
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 )
{
2020-09-24 20:02:45 +00:00
var trustedRootCertificatesProperty = monoX509StoreManager . GetProperty ( "TrustedRootCertificates" ) ;
var trustedRootCertificates = ( ICollection ) trustedRootCertificatesProperty . GetValue ( null ) ;
2018-06-10 12:31:55 +00:00
2020-09-24 20:02:45 +00:00
logger . Info ( $"TrustedRootCertificates count: {trustedRootCertificates.Count}" ) ;
2018-06-10 12:31:55 +00:00
2020-09-24 20:02:45 +00:00
if ( trustedRootCertificates . Count = = 0 )
2020-02-20 18:55:46 +00:00
{
2020-09-24 20:02:45 +00:00
var caCertificatesFiles = new [ ]
2020-02-25 16:08:03 +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
2018-06-10 12:31:55 +00:00
} ;
2020-09-24 20:02:45 +00:00
const string logSpacer = " " ;
2020-02-20 18:55:46 +00:00
var notice = "The mono certificate store is not initialized.<br/>\n" ;
2020-09-24 20:02:45 +00:00
var caCertificatesFile = caCertificatesFiles . Where ( File . Exists ) . 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 )
2020-02-20 18:55:46 +00:00
{
2020-09-24 20:02:45 +00:00
commandRoot = "cert-sync " + caCertificatesFile ;
commandUser = "cert-sync --user " + caCertificatesFile ;
2020-02-20 18:55:46 +00:00
}
2020-02-25 16:08:03 +00:00
2020-02-20 18:55:46 +00:00
notice + = logSpacer + "Please run the following command as root:<br/>\n" ;
2020-09-24 20:02:45 +00:00
notice + = logSpacer + "<pre>" + commandRoot + "</pre><br/>\n" ;
2020-02-25 16:08:03 +00:00
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" ;
2020-09-24 20:02:45 +00:00
notice + = logSpacer + "<pre>" + commandUser + "</pre>" ;
2020-02-25 16:08:03 +00:00
notices . Add ( notice ) ;
2020-02-20 18:55:46 +00:00
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 )
{
2020-09-24 20:02:45 +00:00
logger . Error ( $"Error while chekcing the mono certificate store.\n{e}" ) ;
2020-02-20 18:55:46 +00:00
}
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." ;
2020-02-25 16:08:03 +00:00
notices . Add ( notice ) ;
2018-05-01 11:17:59 +00:00
logger . Error ( notice ) ;
}
}
catch ( Exception e )
{
2020-09-24 20:02:45 +00:00
logger . Error ( $"Error while checking the username.\n{e}" ) ;
2018-05-01 11:17:59 +00:00
}
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." ;
2020-02-25 16:08:03 +00:00
notices . Add ( notice ) ;
2018-10-28 07:27:10 +00:00
logger . Error ( notice ) ;
2018-10-20 10:09:29 +00:00
}
}
catch ( Exception e )
{
2020-09-24 20:02:45 +00:00
logger . Error ( $"Error while checking build date of Jackett.Common.\n{e}" ) ;
2018-10-20 10:09:29 +00:00
}
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-09-24 20:02:45 +00:00
var process = new Process
{
StartInfo =
{
FileName = "uname" ,
Arguments = "-m" ,
UseShellExecute = false ,
RedirectStandardOutput = true
}
} ;
2019-04-27 10:59:33 +00:00
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 )
{
2020-09-24 20:02:45 +00:00
logger . Debug ( $"Unable to get architecture.\n{e}" ) ;
2019-04-27 10:59:33 +00:00
}
2018-05-01 11:17:59 +00:00
CultureInfo . DefaultThreadCurrentCulture = new CultureInfo ( "en-US" ) ;
2020-05-15 23:43:42 +00:00
2018-05-01 11:17:59 +00:00
// Load indexers
indexerService . InitIndexers ( configService . GetCardigannDefinitionsFolders ( ) ) ;
client . Init ( ) ;
2020-05-15 23:43:42 +00:00
2018-05-01 11:17:59 +00:00
updater . CleanupTempDir ( ) ;
2020-05-15 23:43:42 +00:00
updater . CheckUpdaterLock ( ) ;
2018-05-01 11:17:59 +00:00
}
2020-02-25 16:08:03 +00:00
public void Start ( ) = > updater . StartUpdateChecker ( ) ;
2018-05-01 11:17:59 +00:00
public void ReserveUrls ( bool doInstall = true )
{
logger . Debug ( "Unreserving Urls" ) ;
2020-09-24 20:02:45 +00:00
config . GetListenAddresses ( false ) . ToList ( ) . ForEach ( u = > RunNetSh ( $"http delete urlacl {u}" ) ) ;
config . GetListenAddresses ( true ) . ToList ( ) . ForEach ( u = > RunNetSh ( $"http delete urlacl {u}" ) ) ;
2018-05-01 11:17:59 +00:00
if ( doInstall )
{
logger . Debug ( "Reserving Urls" ) ;
2020-09-24 20:02:45 +00:00
config . GetListenAddresses ( true ) . ToList ( ) . ForEach ( u = > RunNetSh ( $"http add urlacl {u} sddl=D:(A;;GX;;;S-1-1-0)" ) ) ;
2018-05-01 11:17:59 +00:00
logger . Debug ( "Urls reserved" ) ;
}
}
2020-02-25 16:08:03 +00:00
private void RunNetSh ( string args ) = > processService . StartProcessAndLog ( "netsh.exe" , args ) ;
2018-05-01 11:17:59 +00:00
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
{
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
2020-09-24 20:02:45 +00:00
var xForwardedProto = request . Headers . Where ( x = > x . Key = = "X-Forwarded-Proto" ) . Select ( x = > x . Value ) . FirstOrDefault ( ) ;
if ( xForwardedProto . Count > 0 )
scheme = xForwardedProto . First ( ) ;
2019-03-06 09:11:50 +00:00
// 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 ;
2020-09-24 20:02:45 +00:00
return $"{scheme}://{request.HttpContext.Request.Host.Host}:{port}{BasePath()}/" ;
2018-05-01 11:17:59 +00:00
}
2020-02-25 16:08:03 +00:00
public string GetBlackholeDirectory ( ) = > config . BlackholeDir ;
2018-05-01 11:17:59 +00:00
2020-02-25 16:08:03 +00:00
public string GetApiKey ( ) = > config . APIKey ;
2019-04-27 10:59:33 +00:00
2020-02-25 16:08:03 +00:00
public bool MonoUserCanRunNetCore ( ) = > isDotNetCoreCapable ;
2018-05-01 11:17:59 +00:00
}
}