From d3100f617821f27d6fb7c94f37ffefada916c66d Mon Sep 17 00:00:00 2001 From: Labrys of Knossos Date: Sat, 31 Dec 2022 17:05:58 -0500 Subject: [PATCH 1/5] Add database permissions logging upon failed access. --- core/main_db.py | 34 +++++++++++++++++++++--- core/permissions.py | 64 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 core/permissions.py diff --git a/core/main_db.py b/core/main_db.py index d9d7c1b9..2397607f 100644 --- a/core/main_db.py +++ b/core/main_db.py @@ -7,14 +7,17 @@ from __future__ import ( unicode_literals, ) +import os.path import re import sqlite3 +import sys import time from six import text_type, PY2 import core from core import logger +from core import permissions if PY2: class Row(sqlite3.Row, object): @@ -60,10 +63,29 @@ def db_filename(filename='nzbtomedia.db', suffix=None): class DBConnection(object): def __init__(self, filename='nzbtomedia.db', suffix=None, row_type=None): - self.filename = filename - self.connection = sqlite3.connect(db_filename(filename), 20) - self.connection.row_factory = Row + path = db_filename(filename) + try: + self.connection = sqlite3.connect(path, 20) + except sqlite3.OperationalError as error: + if os.path.exists(path): + logger.error('Please check permissions on database: {0}'.format(path)) + else: + logger.error('Database file does not exist') + logger.error('Please check permissions on directory: {0}'.format(path)) + path = os.path.dirname(path) + mode = permissions.mode(path) + owner, group = permissions.ownership(path) + logger.error( + "=== PERMISSIONS ===========================\n" + " Path : {0}\n" + " Mode : {1}\n" + " Owner: {2}\n" + " Group: {3}\n" + "===========================================".format(path, mode[-3:], owner, group), + ) + else: + self.connection.row_factory = Row def check_db_version(self): result = None @@ -256,7 +278,11 @@ class DBSanityCheck(object): def upgrade_database(connection, schema): logger.log(u'Checking database structure...', logger.MESSAGE) - _process_upgrade(connection, schema) + try: + _process_upgrade(connection, schema) + except Exception as error: + logger.error(error) + sys.exit(1) def pretty_name(class_name): diff --git a/core/permissions.py b/core/permissions.py new file mode 100644 index 00000000..39789672 --- /dev/null +++ b/core/permissions.py @@ -0,0 +1,64 @@ +import os +import sys +import logging + +log = logging.getLogger(__name__) +log.addHandler(logging.NullHandler()) + +WINDOWS = sys.platform == 'win32' +POSIX = not WINDOWS + +try: + import pwd + import grp +except ImportError: + if POSIX: + raise + +try: + from win32security import GetNamedSecurityInfo + from win32security import LookupAccountSid + from win32security import GROUP_SECURITY_INFORMATION + from win32security import OWNER_SECURITY_INFORMATION + from win32security import SE_FILE_OBJECT +except ImportError: + if WINDOWS: + raise + + +def mode(path): + """Get permissions.""" + return oct(os.stat(path).st_mode & 0o777) + + +def nt_ownership(path): + """Get the owner and group for a file or directory.""" + def fully_qualified_name(sid): + """Return a fully qualified account name.""" + name, domain, _acct_type = LookupAccountSid(None, sid) + return '{}\\{}'.format(domain, name) + + security_descriptor = GetNamedSecurityInfo( + os.fspath(path), + SE_FILE_OBJECT, + OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION, + ) + owner_sid = security_descriptor.GetSecurityDescriptorOwner() + group_sid = security_descriptor.GetSecurityDescriptorGroup() + owner = fully_qualified_name(owner_sid) + group = fully_qualified_name(group_sid) + return owner, group + + +def posix_ownership(path): + """Get the owner and group for a file or directory.""" + stat_result = os.stat(path) + owner = pwd.getpwuid(stat_result.st_uid) + group = grp.getgrgid(stat_result.st_gid) + return owner, group + + +if WINDOWS: + ownership = nt_ownership +else: + ownership = posix_ownership From 1fdfd128baa100ab866e13986360e37ff8ae01f9 Mon Sep 17 00:00:00 2001 From: Labrys of Knossos Date: Sat, 31 Dec 2022 18:21:33 -0500 Subject: [PATCH 2/5] Add comments. --- core/main_db.py | 2 +- core/permissions.py | 30 +++++++++++++++++++++++++++--- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/core/main_db.py b/core/main_db.py index 2397607f..089ef3a8 100644 --- a/core/main_db.py +++ b/core/main_db.py @@ -82,7 +82,7 @@ class DBConnection(object): " Mode : {1}\n" " Owner: {2}\n" " Group: {3}\n" - "===========================================".format(path, mode[-3:], owner, group), + "===========================================".format(path, mode, owner, group), ) else: self.connection.row_factory = Row diff --git a/core/permissions.py b/core/permissions.py index 39789672..0b016d30 100644 --- a/core/permissions.py +++ b/core/permissions.py @@ -28,36 +28,60 @@ except ImportError: def mode(path): """Get permissions.""" - return oct(os.stat(path).st_mode & 0o777) + stat_result = os.stat(path) # Get information from path + permissions_mask = 0o777 # Set mask for permissions info + + # Get only the permissions part of st_mode as an integer + int_mode = stat_result.st_mode & permissions_mask + oct_mode = oct(int_mode) # Convert to octal representation + + return oct_mode[2:] # Return mode but strip octal prefix def nt_ownership(path): """Get the owner and group for a file or directory.""" def fully_qualified_name(sid): """Return a fully qualified account name.""" + # Look up the account information for the given SID + # https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-lookupaccountsida name, domain, _acct_type = LookupAccountSid(None, sid) + # Return account information formatted as DOMAIN\ACCOUNT_NAME return '{}\\{}'.format(domain, name) + # Get the Windows security descriptor for the path + # https://learn.microsoft.com/en-us/windows/win32/api/aclapi/nf-aclapi-getnamedsecurityinfoa security_descriptor = GetNamedSecurityInfo( - os.fspath(path), - SE_FILE_OBJECT, + path, # Name of the item to query + SE_FILE_OBJECT, # Type of item to query (file or directory) + # Add OWNER and GROUP security information to result OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION, ) + # Get the Security Identifier for the owner and group from the security descriptor + # https://learn.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-getsecuritydescriptorowner + # https://learn.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-getsecuritydescriptorgroup owner_sid = security_descriptor.GetSecurityDescriptorOwner() group_sid = security_descriptor.GetSecurityDescriptorGroup() + + # Get the fully qualified account name (e.g. DOMAIN\ACCOUNT_NAME) owner = fully_qualified_name(owner_sid) group = fully_qualified_name(group_sid) + return owner, group def posix_ownership(path): """Get the owner and group for a file or directory.""" + # Get path information stat_result = os.stat(path) + + # Get account name from path stat result owner = pwd.getpwuid(stat_result.st_uid) group = grp.getgrgid(stat_result.st_gid) + return owner, group +# Select the ownership function appropriate for the platform if WINDOWS: ownership = nt_ownership else: From 3078da31af5246644d752857ff1cdd4787161bb6 Mon Sep 17 00:00:00 2001 From: Labrys of Knossos Date: Sat, 31 Dec 2022 22:26:19 -0500 Subject: [PATCH 3/5] Fix posix_ownership. --- core/permissions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/permissions.py b/core/permissions.py index 0b016d30..b1731dac 100644 --- a/core/permissions.py +++ b/core/permissions.py @@ -75,8 +75,8 @@ def posix_ownership(path): stat_result = os.stat(path) # Get account name from path stat result - owner = pwd.getpwuid(stat_result.st_uid) - group = grp.getgrgid(stat_result.st_gid) + owner = pwd.getpwuid(stat_result.st_uid).pw_name + group = grp.getgrgid(stat_result.st_gid).gr_name return owner, group From c4cc554ea1e9b342538853a8108964bde7929e2f Mon Sep 17 00:00:00 2001 From: Clinton Hall Date: Tue, 18 Apr 2023 20:59:28 +1200 Subject: [PATCH 4/5] update to sonarr api v3 --- core/auto_process/tv.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/auto_process/tv.py b/core/auto_process/tv.py index 84a93427..7ce3ed62 100644 --- a/core/auto_process/tv.py +++ b/core/auto_process/tv.py @@ -317,9 +317,9 @@ def process(section, dir_name, input_name=None, failed=False, client_agent='manu else: url = '{0}{1}:{2}{3}/api/v{4}/{5}/'.format(protocol, host, port, web_root, api_version, apikey) elif section == 'NzbDrone': - url = '{0}{1}:{2}{3}/api/command'.format(protocol, host, port, web_root) - url2 = '{0}{1}:{2}{3}/api/config/downloadClient'.format(protocol, host, port, web_root) - headers = {'X-Api-Key': apikey} + url = '{0}{1}:{2}{3}/api/v3/command'.format(protocol, host, port, web_root) + url2 = '{0}{1}:{2}{3}/api/v3/config/downloadClient'.format(protocol, host, port, web_root) + headers = {'X-Api-Key': apikey, "Content-Type": "application/json"} # params = {'sortKey': 'series.title', 'page': 1, 'pageSize': 1, 'sortDir': 'asc'} if remote_path: logger.debug('remote_path: {0}'.format(remote_dir(dir_name)), section) From e72c0b92289281a9195a7a0c5ebcde3a5ff72103 Mon Sep 17 00:00:00 2001 From: kandarz Date: Sun, 2 Jul 2023 21:59:24 -0700 Subject: [PATCH 5/5] Add 'dvb_subtitle' codec to list of ignored codecs when using 'mov_text' (#1974) Add 'dvb_subtitle' codec to list of ignored codecs when using 'mov_text'. DVB subtitles are bitmap based. --- core/transcoder.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/transcoder.py b/core/transcoder.py index 394f4ec1..c1a661c0 100644 --- a/core/transcoder.py +++ b/core/transcoder.py @@ -479,7 +479,7 @@ def build_commands(file, new_dir, movie_name, bitbucket): burnt = 1 if not core.ALLOWSUBS: break - if sub['codec_name'] in ['dvd_subtitle', 'VobSub'] and core.SCODEC == 'mov_text': # We can't convert these. + if sub['codec_name'] in ['dvd_subtitle', 'dvb_subtitle', 'VobSub'] and core.SCODEC == 'mov_text': # We can't convert these. continue map_cmd.extend(['-map', '0:{index}'.format(index=sub['index'])]) s_mapped.extend([sub['index']]) @@ -490,7 +490,7 @@ def build_commands(file, new_dir, movie_name, bitbucket): break if sub['index'] in s_mapped: continue - if sub['codec_name'] in ['dvd_subtitle', 'VobSub'] and core.SCODEC == 'mov_text': # We can't convert these. + if sub['codec_name'] in ['dvd_subtitle', 'dvb_subtitle', 'VobSub'] and core.SCODEC == 'mov_text': # We can't convert these. continue map_cmd.extend(['-map', '0:{index}'.format(index=sub['index'])]) s_mapped.extend([sub['index']]) @@ -516,7 +516,7 @@ def build_commands(file, new_dir, movie_name, bitbucket): continue if core.SCODEC == 'mov_text': subcode = [stream['codec_name'] for stream in sub_details['streams']] - if set(subcode).intersection(['dvd_subtitle', 'VobSub']): # We can't convert these. + if set(subcode).intersection(['dvd_subtitle', 'dvb_subtitle', 'VobSub']): # We can't convert these. continue command.extend(['-i', subfile]) lan = os.path.splitext(os.path.splitext(subfile)[0])[1][1:].split('-')[0]