From 259daa8ad8aba39d56802b36334827bba6101f38 Mon Sep 17 00:00:00 2001 From: flaskfarm Date: Mon, 17 Oct 2022 13:28:55 +0900 Subject: [PATCH] update --- .gitignore | 2 +- lib/cli/code_encode.py | 46 ++++++++++ lib/framework/init_main.py | 2 +- lib/framework/init_plugin.py | 86 ++---------------- lib/plugin/logic.py | 44 ++++----- lib/plugin/model_setting.py | 37 +++++--- lib/support/__init__.py | 3 +- lib/support/base/aes.py | 65 ++++++++++++- lib/support/base/file.py | 17 ---- lib/support/base/sc.py | 79 ++++++++++++++++ .../base/{subprocess.py => sub_process.py} | 0 lib/support/sc/Windows/sc.cp310-win_amd64.pyd | Bin 0 -> 20480 bytes lib/system/mod_setting.py | 1 + 13 files changed, 243 insertions(+), 139 deletions(-) create mode 100644 lib/cli/code_encode.py create mode 100644 lib/support/base/sc.py rename lib/support/base/{subprocess.py => sub_process.py} (100%) create mode 100644 lib/support/sc/Windows/sc.cp310-win_amd64.pyd diff --git a/.gitignore b/.gitignore index 11ea802..237a098 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # Byte-compiled / optimized / DLL files __pycache__/ -*.py[cod] +*.py[co] *$py.class # C extensions diff --git a/lib/cli/code_encode.py b/lib/cli/code_encode.py new file mode 100644 index 0000000..77e549d --- /dev/null +++ b/lib/cli/code_encode.py @@ -0,0 +1,46 @@ + +import argparse +import os +import sys + +sys.path.append(os.path.dirname(os.path.dirname(__file__))) + +from support import SupportFile, SupportSC, logger + + +class CodeEncode: + def start_folder(self, folderpath): + for name in os.listdir(folderpath): + filepath = os.path.join(folderpath, name) + if os.path.isfile(filepath) and name not in ['setup.py', '__init__.py'] and name.endswith('.py'): + self.encode_file(filepath) + + def encode_file(self, filepath): + if filepath.endswith(".py") == False: + logger.info("is not .py file") + return + text = SupportFile.read_file(filepath) + data = SupportSC.encode(text, 1) + SupportFile.write_file(filepath + 'f', data) + logger.info(f"Create {os.path.basename(filepath + 'f')}") + + + def process_args(self): + parser = argparse.ArgumentParser() + parser.add_argument('--mode', default='encode') + parser.add_argument('--source', required=True, help=u'absolute path. folder or file') + args = parser.parse_args() + if SupportSC.LIBRARY_LOADING == False: + logger.error("sc import fail") + return + if os.path.exists(args.source): + if os.path.isdir(args.source): + self.start_folder(args.source) + elif os.path.isfile(args.source): + self.encode_file(args.source) + + +if __name__== "__main__": + CodeEncode().process_args() + + # python C:\work\FlaskFarm\flaskfarm\lib\cli\code_encode.py --source=C:\work\FlaskFarm\data\LOADING\klive_plus\source_spotv.py diff --git a/lib/framework/init_main.py b/lib/framework/init_main.py index 417f830..15c1b34 100644 --- a/lib/framework/init_main.py +++ b/lib/framework/init_main.py @@ -204,7 +204,7 @@ class Framework: from .init_web import jinja_initialize jinja_initialize(self.app) - + from .init_plugin import PluginManager self.PluginManager = PluginManager PluginManager.plugin_update() diff --git a/lib/framework/init_plugin.py b/lib/framework/init_plugin.py index ef751c4..1fafe7f 100644 --- a/lib/framework/init_plugin.py +++ b/lib/framework/init_plugin.py @@ -21,19 +21,8 @@ class PluginManager: @classmethod def get_plugin_name_list(cls): - #if not app.config['config']['auth_status']: - # return - """ - plugin_path = os.path.join(frame.config['path_app'], 'plugins') - sys.path.insert(0, plugin_path) - from system import SystemModelSetting - plugins = os.listdir(plugin_path) - """ plugins = [] - - #2019-07-17 - try: plugin_path = os.path.join(F.config['path_data'], 'plugins') if os.path.exists(plugin_path) == True and os.path.isdir(plugin_path) == True: @@ -115,51 +104,18 @@ class PluginManager: F.logger.debug(plugins) for plugin_name in plugins: - - #logger.debug(len(system.LogicPlugin.current_loading_plugin_list)) - #if plugin_name.startswith('_'): - # continue - #if plugin_name == 'terminal' and platform.system() == 'Windows': - # continue - #if plugin_name in except_plugin_list: - # F.logger.debug('Except plugin : %s' % frame.plugin_menu) - # continue F.logger.debug(f'[+] PLUGIN LOADING Start.. [{plugin_name}]') entity = cls.all_package_list[plugin_name] entity['version'] = '3' try: mod = __import__('%s' % (plugin_name), fromlist=[]) mod_plugin_info = None - # 2021-12-31 - #import system - #if plugin_name not in system.LogicPlugin.current_loading_plugin_list: - # system.LogicPlugin.current_loading_plugin_list[plugin_name] = {'status':'loading'} - try: mod_plugin_info = getattr(mod, 'plugin_info') entity['module'] = mod - """ - if 'category' not in mod_plugin_info and 'category_name' in mod_plugin_info: - mod_plugin_info['category'] = mod_plugin_info['category_name'] - if 'policy_point' in mod_plugin_info: - if mod_plugin_info['policy_point'] > app.config['config']['point']: - system.LogicPlugin.current_loading_plugin_list[plugin_name]['status'] = 'violation_policy_point' - continue - if 'policy_level' in mod_plugin_info: - if mod_plugin_info['policy_level'] > app.config['config']['level']: - system.LogicPlugin.current_loading_plugin_list[plugin_name]['status'] = 'violation_policy_level' - continue - if 'category' in mod_plugin_info and mod_plugin_info['category'] == 'beta': - if SystemModelSetting.get_bool('use_beta') == False: - system.LogicPlugin.current_loading_plugin_list[plugin_name]['status'] = 'violation_beta' - continue - """ except Exception as exception: - #logger.error('Exception:%s', exception) - #logger.error(traceback.format_exc()) - - #mod_plugin_info = getattr(mod, 'setup') F.logger.info(f'[!] PLUGIN_INFO not exist : [{plugin_name}] - is FF') + if mod_plugin_info == None: try: mod = __import__(f'{plugin_name}.setup', fromlist=['setup']) @@ -168,13 +124,10 @@ class PluginManager: F.logger.error(f'Exception:{str(e)}') F.logger.error(traceback.format_exc()) F.logger.warning(f'[!] NOT normal plugin : [{plugin_name}]') - #entity['version'] = 'not_plugin' - try: if entity['version'] != '4': mod_blue_print = getattr(mod, 'blueprint') - else: entity['setup_mod'] = mod entity['P'] = getattr(mod, 'P') @@ -196,24 +149,7 @@ class PluginManager: cls.all_package_list[plugin_name]['status'] = 'import fail' cls.all_package_list[plugin_name]['log'] = traceback.format_exc() - #from tool_base import d - #logger.error(d(system.LogicPlugin.current_loading_plugin_list)) - # 2021-07-01 모듈에 있는 DB 테이블 생성이 안되는 문제 - # 기존 구조 : db.create_all() => 모듈 plugin_load => celery task 등록 후 리턴 - # 변경 구조 : 모듈 plugin_load => db.create_all() => celery인 경우 리턴 - - # plugin_load 를 해야 하위 로직에 있는 DB가 로딩된다. - # plugin_load 에 db는 사용하는 코드가 있으면 안된다. (테이블도 없을 때 에러발생) - try: - #logger.warning('module plugin_load in celery ') - cls.plugin_list['mod']['module'].plugin_load() - except Exception as exception: - F.logger.debug(f'mod plugin_load error!!') - #logger.error('Exception:%s', exception) - #logger.error(traceback.format_exc()) - - # import가 끝나면 DB를 만든다. - # 플러그인 로드시 DB 초기화를 할 수 있다. + if not F.config['run_celery']: try: @@ -225,17 +161,6 @@ class PluginManager: F.logger.debug('db.create_all error') if not F.config['run_flask']: - # 2021-06-03 - # 모듈의 로직에 있는 celery 함수는 등록해주어야한다. - #try: - # logger.warning('module plugin_load in celery ') - # plugin_instance_list['mod'].plugin_load() - #except Exception as exception: - # logger.error('module plugin_load error') - # logger.error('Exception:%s', exception) - # logger.error(traceback.format_exc()) - # 2021-07-01 - # db때문에 위에서 로딩함. return for key, entity in cls.plugin_list.items(): @@ -259,6 +184,13 @@ class PluginManager: cls.all_package_list[key]['loading'] = False cls.all_package_list[key]['status'] = 'plugin_load error' cls.all_package_list[key]['log'] = traceback.format_exc() + + if key in cls.plugin_menus: + del cls.plugin_menus[key] + from framework.init_menu import MenuManager + MenuManager.init_menu() + + # mod는 위에서 로딩 if key != 'mod': t = threading.Thread(target=func, args=(mod_plugin_load, key)) diff --git a/lib/plugin/logic.py b/lib/plugin/logic.py index ae3c114..92f1019 100644 --- a/lib/plugin/logic.py +++ b/lib/plugin/logic.py @@ -16,34 +16,30 @@ class Logic(object): self.P = P def plugin_load(self): - try: - self.P.logger.debug('%s plugin_load', self.P.package_name) - self.db_init() + self.P.logger.debug('%s plugin_load', self.P.package_name) + self.db_init() + for module in self.P.module_list: + module.migration() + if module.page_list is not None: + for page_instance in module.page_list: + page_instance.migration() + + for module in self.P.module_list: + module.plugin_load() + if module.page_list is not None: + for page_instance in module.page_list: + page_instance.plugin_load() + if self.P.ModelSetting is not None: for module in self.P.module_list: - module.migration() + key = f'{module.name}_auto_start' + if self.P.ModelSetting.has_key(key) and self.P.ModelSetting.get_bool(key): + self.scheduler_start(module.name) if module.page_list is not None: for page_instance in module.page_list: - page_instance.migration() + key = f'{module.name}_{page_instance.name}_auto_start' + if self.P.ModelSetting.has_key(key) and self.P.ModelSetting.get_bool(key): + self.scheduler_start_sub(module.name, page_instance.name) - for module in self.P.module_list: - module.plugin_load() - if module.page_list is not None: - for page_instance in module.page_list: - page_instance.plugin_load() - if self.P.ModelSetting is not None: - for module in self.P.module_list: - key = f'{module.name}_auto_start' - if self.P.ModelSetting.has_key(key) and self.P.ModelSetting.get_bool(key): - self.scheduler_start(module.name) - if module.page_list is not None: - for page_instance in module.page_list: - key = f'{module.name}_{page_instance.name}_auto_start' - if self.P.ModelSetting.has_key(key) and self.P.ModelSetting.get_bool(key): - self.scheduler_start_sub(module.name, page_instance.name) - - except Exception as e: - self.P.logger.error(f'Exception:{str(e)}') - self.P.logger.error(traceback.format_exc()) def db_init(self): try: diff --git a/lib/plugin/model_setting.py b/lib/plugin/model_setting.py index d21e8af..fdbc35d 100644 --- a/lib/plugin/model_setting.py +++ b/lib/plugin/model_setting.py @@ -1,4 +1,5 @@ import traceback +from datetime import datetime from framework import F @@ -32,8 +33,8 @@ def get_model_setting(package_name, logger, table_name=None): if ret is not None: return ret.value.strip() return None - except Exception as exception: - logger.error('Exception:%s %s', exception, key) + except Exception as e: + logger.error(f"Exception:{str(e)} [{key}]") logger.error(traceback.format_exc()) @staticmethod @@ -45,16 +46,24 @@ def get_model_setting(package_name, logger, table_name=None): def get_int(key): try: return int(ModelSetting.get(key)) - except Exception as exception: - logger.error('Exception:%s %s', exception, key) + except Exception as e: + logger.error(f"Exception:{str(e)} [{key}]") logger.error(traceback.format_exc()) @staticmethod def get_bool(key): try: return (ModelSetting.get(key) == 'True') - except Exception as exception: - logger.error('Exception:%s %s', exception, key) + except Exception as e: + logger.error(f"Exception:{str(e)} [{key}]") + logger.error(traceback.format_exc()) + + @staticmethod + def get_datetime(key): + try: + return datetime.strptime(ModelSetting.get(key), '%Y-%m-%d %H:%M:%S.%f') + except Exception as e: + logger.error(f"Exception:{str(e)} [{key}]") logger.error(traceback.format_exc()) @staticmethod @@ -68,8 +77,8 @@ def get_model_setting(package_name, logger, table_name=None): else: F.db.session.add(ModelSetting(key, value.strip())) F.db.session.commit() - except Exception as exception: - logger.error('Exception:%s %s', exception, key) + except Exception as e: + logger.error(f"Exception:{str(e)} [{key}]") logger.error(traceback.format_exc()) @staticmethod @@ -78,8 +87,8 @@ def get_model_setting(package_name, logger, table_name=None): ret = ModelSetting.db_list_to_dict(F.db.session.query(ModelSetting).all()) ret['package_name'] = package_name return ret - except Exception as exception: - logger.error('Exception:%s', exception) + except Exception as e: + logger.error(f"Exception:{str(e)}") logger.error(traceback.format_exc()) @@ -99,8 +108,8 @@ def get_model_setting(package_name, logger, table_name=None): entity.value = value F.db.session.commit() return True, change_list - except Exception as exception: - logger.error('Exception:%s', exception) + except Exception as e: + logger.error(f"Exception:{str(e)}") logger.error(traceback.format_exc()) logger.debug('Error Key:%s Value:%s', key, value) return False, [] @@ -115,8 +124,8 @@ def get_model_setting(package_name, logger, table_name=None): values = [x.split(comment)[0].strip() for x in value.split(delimeter)] values = ModelSetting.get_list_except_empty(values) return values - except Exception as exception: - logger.error('Exception:%s', exception) + except Exception as e: + logger.error(f"Exception:{str(e)}") logger.error(traceback.format_exc()) logger.error('Error Key:%s Value:%s', key, value) diff --git a/lib/support/__init__.py b/lib/support/__init__.py index 1746d13..a7ce849 100644 --- a/lib/support/__init__.py +++ b/lib/support/__init__.py @@ -15,8 +15,9 @@ logger = get_logger() from .base.aes import SupportAES from .base.discord import SupportDiscord from .base.file import SupportFile +from .base.sc import SupportSC from .base.string import SupportString -from .base.subprocess import SupportSubprocess +from .base.sub_process import SupportSubprocess from .base.telegram import SupportTelegram from .base.util import (AlchemyEncoder, SingletonClass, SupportUtil, default_headers, pt) diff --git a/lib/support/base/aes.py b/lib/support/base/aes.py index e590e2b..37c5ec3 100644 --- a/lib/support/base/aes.py +++ b/lib/support/base/aes.py @@ -1,10 +1,16 @@ -import os, base64, traceback -from Crypto.Cipher import AES +import base64 +import os +import traceback + from Crypto import Random +from Crypto.Cipher import AES + from . import logger + BS = 16 -pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS) -unpad = lambda s : s[0:-s[-1]] +pad = lambda s: s + (BS - len(s.encode('utf-8')) % BS) * chr(BS - len(s.encode('utf-8')) % BS) +#unpad = lambda s : s[0:-s[-1]] +unpad = lambda s : s[:-ord(s[len(s)-1:])] key = b'140b41b22a29beb4061bda66b6747e14' class SupportAES(object): @@ -39,5 +45,56 @@ class SupportAES(object): iv = enc[:16] if len(iv) != 16: iv = os.urandom(16) + if mykey is not None and type(mykey) == type(''): + mykey = mykey.encode() cipher = AES.new(key if mykey is None else mykey, AES.MODE_CBC, iv ) return unpad(cipher.decrypt( enc[16:] )).decode() + + @classmethod + def md5(cls, text): + import hashlib + enc = hashlib.md5() + enc.update(text.encode()) + return enc.hexdigest() + + + + + @classmethod + def encrypt_(cls, raw, mykey=None, iv=None): + try: + Random.atfork() + except Exception as exception: + logger.error('Exception:%s', exception) + logger.error(traceback.format_exc()) + raw = pad(raw) + if type(raw) == type(''): + raw = raw.encode() + if mykey is not None and type(mykey) == type(''): + mykey = mykey.encode() + if iv == None: + iv = Random.new().read( AES.block_size ) + elif iv is not None and type(iv) == type(''): + iv = iv.encode() + cipher = AES.new(key if mykey is None else mykey, AES.MODE_CBC, iv ) + try: + tmp = cipher.encrypt( raw ) + except Exception as exception: + logger.error('Exception:%s', exception) + logger.error(traceback.format_exc()) + tmp = cipher.encrypt( raw.encode() ) + ret = base64.b64encode( tmp ) + ret = ret.decode() + return ret + + @classmethod + def decrypt_(cls, enc, mykey=None, iv=None): + enc = base64.b64decode(enc) + if iv == None: + iv = os.urandom(16) + elif iv is not None and type(iv) == type(''): + iv = iv.encode() + if mykey is not None and type(mykey) == type(''): + mykey = mykey.encode() + cipher = AES.new(key if mykey is None else mykey, AES.MODE_CBC, iv ) + return unpad(cipher.decrypt( enc )).decode() diff --git a/lib/support/base/file.py b/lib/support/base/file.py index 326392a..dbea798 100644 --- a/lib/support/base/file.py +++ b/lib/support/base/file.py @@ -94,23 +94,6 @@ class SupportFile(object): - @classmethod - def write(cls, data, filepath, mode='w'): - try: - import codecs - ofp = codecs.open(filepath, mode, encoding='utf8') - if isinstance(data, bytes) and mode == 'w': - data = data.decode('utf-8') - ofp.write(data) - ofp.close() - return True - except Exception as exception: - logger.debug('Exception:%s', exception) - logger.debug(traceback.format_exc()) - return False - - - @classmethod diff --git a/lib/support/base/sc.py b/lib/support/base/sc.py new file mode 100644 index 0000000..f67bede --- /dev/null +++ b/lib/support/base/sc.py @@ -0,0 +1,79 @@ +import argparse +import os +import platform +import re +import sys +import traceback + +from . import logger + + +class SupportSC: + LIBRARY_LOADING = False + try: + if platform.system() == 'Linux': + if (platform.platform().find('86') == -1 and platform.platform().find('64') == -1) or platform.platform().find('arch') != -1 or platform.platform().find('arm') != -1: + sys.path.insert(2, os.path.join(os.path.dirname(os.path.abspath(__file__)), 'lib', 'sc', 'LinuxArm')) + else: + sys.path.insert(2, os.path.join(os.path.dirname(os.path.abspath(__file__)), 'lib', 'sc', 'Linux')) + if platform.system() == 'Windows': + sys.path.append(os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'sc', 'Windows')) + import sc + LIBRARY_LOADING = True + except: + pass + + + @classmethod + def encode(cls, text, mode=0): + try: + import sc + return sc.encode(text, mode) + except Exception as e: + logger.error(f'Exception:{str(e)}') + logger.error(traceback.format_exc()) + return None + + + @classmethod + def decode(cls, text): + try: + import sc + return sc.decode(text) + except: + return None + + @classmethod + def load_module(cls, module_name, module_code): + try: + import sc + mod = sc.load_module(module_name, module_code) + sys.modules[mod] = mod + logger.warning(f"C-LOADING : {module_name}") + return mod + except Exception as e: + logger.error(f'Exception:{str(e)}') + logger.error(traceback.format_exc()) + + @classmethod + def load_module_P(cls, plugin_ins, filename): + return cls.load_module_f(plugin_ins.setting['filepath'], filename) + + @classmethod + def load_module_f(cls, package_filepath, filename): + dirname = os.path.dirname(package_filepath) + filepath = os.path.join(dirname, filename + '.pyf') + if os.path.exists(filepath) and os.path.exists(filepath): + from support import SupportFile + code = SupportFile.read_file(filepath) + return cls.load_module(f"{os.path.basename(dirname)}.{filename}", code) + + @classmethod + def td(self, mediacode, ts, url): + try: + import sc + ret = sc.td1(mediacode, str(ts), url).strip() + ret = re.sub('[^ -~]+', '', ret).strip() + return ret + except: + return None diff --git a/lib/support/base/subprocess.py b/lib/support/base/sub_process.py similarity index 100% rename from lib/support/base/subprocess.py rename to lib/support/base/sub_process.py diff --git a/lib/support/sc/Windows/sc.cp310-win_amd64.pyd b/lib/support/sc/Windows/sc.cp310-win_amd64.pyd new file mode 100644 index 0000000000000000000000000000000000000000..98cabed375a036aa8e1112002993054b53cf47b0 GIT binary patch literal 20480 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