You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

407 lines
13 KiB

2 months ago
  1. #coding: utf-8
  2. # +-------------------------------------------------------------------
  3. # | 宝塔Linux面板
  4. # +-------------------------------------------------------------------
  5. # | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
  6. # +-------------------------------------------------------------------
  7. # | Author: hwliang <hwl@bt.cn>
  8. # +-------------------------------------------------------------------
  9. #+--------------------------------------------------------------------
  10. #| 插件和模块加载器
  11. #+--------------------------------------------------------------------
  12. import public,os,sys,json,hashlib
  13. def plugin_run(plugin_name,def_name,args):
  14. '''
  15. @name
  16. @param plugin_name<string>
  17. @param def_name<string>
  18. @param args<dict_obj>
  19. @return mixed
  20. '''
  21. if not plugin_name or not def_name: return public.returnMsg(False,'parameter incorrect: module_name and def_name cannot be empty.')
  22. # 获取插件目录
  23. plugin_path = public.get_plugin_path(plugin_name)
  24. is_php = os.path.exists(os.path.join(plugin_path,'index.php'))
  25. # 检查插件目录是否合法
  26. if is_php:
  27. plugin_file = os.path.join(plugin_path,'index.php')
  28. else:
  29. plugin_file = os.path.join(plugin_path, plugin_name + '_main.py')
  30. if not public.path_safe_check(plugin_file): return public.returnMsg(False,'parameter incorrect: module_name and def_name cannot contains special symbols.')
  31. # 检查插件入口文件是否存在
  32. if not os.path.exists(plugin_file): return public.returnMsg(False,'plugin not found')
  33. # 添加插件目录到系统路径
  34. public.sys_path_append(plugin_path)
  35. if not is_php:
  36. # 引用插件入口文件
  37. _name = "{}_main".format(plugin_name)
  38. plugin_main = __import__(_name)
  39. # 检查类名是否符合规范
  40. if not hasattr(plugin_main,_name):
  41. return public.returnMsg(False,'plugin class name is invalid')
  42. try:
  43. if sys.version_info[0] == 2:
  44. reload(plugin_main)
  45. else:
  46. from imp import reload
  47. reload(plugin_main)
  48. except:
  49. pass
  50. # 实例化插件类
  51. plugin_obj = getattr(plugin_main,_name)()
  52. # 检查方法是否存在
  53. if not hasattr(plugin_obj,def_name):
  54. return public.returnMsg(False,'not find method [%s] in plugin [%s]' % (def_name,plugin_name))
  55. if args is not None and 'plugin_get_object' in args and args.plugin_get_object == 1:
  56. return getattr(plugin_obj, def_name)
  57. # 执行方法
  58. return getattr(plugin_obj,def_name)(args)
  59. else:
  60. if args is not None and 'plugin_get_object' in args and args.plugin_get_object == 1:
  61. return None
  62. import panelPHP
  63. args.s = def_name
  64. args.name = plugin_name
  65. return panelPHP.panelPHP(plugin_name).exec_php_script(args)
  66. def get_module_list():
  67. '''
  68. @name
  69. @return list
  70. '''
  71. module_list = []
  72. class_path = public.get_class_path()
  73. for name in os.listdir(class_path):
  74. path = os.path.join(class_path,name)
  75. # 过滤无效文件
  76. if not name or name.endswith('.py') or name[0] == '.' or not name.endswith('Model') or os.path.isfile(path):continue
  77. module_list.append(name)
  78. return module_list
  79. def module_run(module_name,def_name,args):
  80. '''
  81. @name
  82. @param module_name<string>
  83. @param def_name<string>
  84. @param args<dict_obj>
  85. @return mixed
  86. '''
  87. if not module_name or not def_name: return public.returnMsg(False,'parameter incorrect: module_name and def_name cannot be empty.')
  88. model_index = args.get('model_index',None)
  89. class_path = public.get_class_path()
  90. panel_path = public.get_panel_path()
  91. module_file = None
  92. if 'model_index' in args:
  93. # 新模块目录
  94. if model_index in ['mod']:
  95. module_file = os.path.join(panel_path,'mod','project',module_name + 'Mod.py')
  96. elif model_index:
  97. # 旧模块目录
  98. module_file = os.path.join(class_path,model_index+"Model",module_name + 'Model.py')
  99. else:
  100. module_file = os.path.join(class_path,"projectModel",module_name + 'Model.py')
  101. else:
  102. # 如果没指定模块名称,则遍历所有模块目录
  103. module_list = get_module_list()
  104. for name in module_list:
  105. module_file = os.path.join(class_path,name,module_name + 'Model.py')
  106. if os.path.exists(module_file): break
  107. # 判断模块入口文件是否存在
  108. if not os.path.exists(module_file):
  109. return public.returnMsg(False,'module file [%s] not exist' % module_name)
  110. # 判断模块路径是否合法
  111. if not public.path_safe_check(module_file):
  112. return public.returnMsg(False,'parameter incorrect: module_name and def_name cannot contains special symbols.')
  113. def_object = public.get_script_object(module_file)
  114. if not def_object: return public.returnMsg(False,'module [%s] not found' % module_name)
  115. # 模块实例化并返回方法对象
  116. try:
  117. run_object = getattr(def_object.main(),def_name,None)
  118. except:
  119. return public.returnMsg(False,'module [%s] failed to instance class' % module_name)
  120. if not run_object: return public.returnMsg(False,'not found method [%s] in module [%s]' % (def_name,module_name))
  121. if 'module_get_object' in args and args.module_get_object == 1:
  122. return run_object
  123. # 执行方法
  124. result = run_object(args)
  125. return result
  126. def get_plugin_list(upgrade_force = False):
  127. '''
  128. @name
  129. @param upgrade_force<bool>
  130. @return dict
  131. '''
  132. api_root_url = public.OfficialApiBase()
  133. api_url = api_root_url+ '/api/panel/getSoftListEn'
  134. panel_path = public.get_panel_path()
  135. data_path = os.path.join(panel_path,'data')
  136. if not os.path.exists(data_path):
  137. os.makedirs(data_path,384)
  138. plugin_list = {}
  139. plugin_list_file = os.path.join(data_path,'plugin_list.json')
  140. if os.path.exists(plugin_list_file) and not upgrade_force:
  141. plugin_list_body = public.readFile(plugin_list_file)
  142. try:
  143. plugin_list = json.loads(plugin_list_body)
  144. except:
  145. plugin_list = {}
  146. if not os.path.exists(plugin_list_file) or upgrade_force or not plugin_list:
  147. try:
  148. res = public.HttpGet(api_url)
  149. except Exception as ex:
  150. raise public.error_conn_cloud(str(ex))
  151. if not res: raise Exception(False,'failed to get soft list')
  152. plugin_list = json.loads(res)
  153. if type(plugin_list)!=dict or 'list' not in plugin_list:
  154. if type(plugin_list)==str:
  155. raise Exception(plugin_list)
  156. else:
  157. raise Exception('failed to parse soft list')
  158. content = json.dumps(plugin_list)
  159. public.writeFile(plugin_list_file,content)
  160. plugin_bin_file = os.path.join(data_path,'plugin_bin.pl')
  161. encode_content = __encode_plugin_list(content)
  162. if encode_content:
  163. public.writeFile(plugin_bin_file,encode_content)
  164. return plugin_list
  165. def __encode_plugin_list(content):
  166. try:
  167. userInfo = public.get_user_info()
  168. if not userInfo or 'server_id' not in userInfo: return None
  169. block_size = 51200
  170. uid = str(userInfo['uid'])
  171. server_id = userInfo['server_id']
  172. key = server_id[10:26] + uid + server_id
  173. key = hashlib.md5(key.encode()).hexdigest()
  174. iv = key + server_id
  175. iv = hashlib.md5(iv.encode()).hexdigest()
  176. key = key[8:24]
  177. iv = iv[8:24]
  178. blocks = [content[i:i + block_size] for i in range(0, len(content), block_size)]
  179. encrypted_content = ''
  180. for block in blocks:
  181. encrypted_content += __aes_encrypt(block, key, iv) + '\n'
  182. return encrypted_content
  183. except:
  184. pass
  185. return None
  186. def get_module(filename):
  187. if not filename: return public.returnMsg(False,'parameter error: get_module(filename<str>)')
  188. if filename[0:2] == './': return public.returnMsg(False,'filename cannot be relative path.')
  189. if not public.path_safe_check(filename): return public.returnMsg(False,'filename cannot contains special symbols.')
  190. if not os.path.exists(filename): return public.returnMsg(False,'file does not exist')
  191. return __get_class_module(filename)
  192. def __get_class_module(filename):
  193. _obj = sys.modules.get(filename, None)
  194. if _obj: return _obj
  195. _code = public.readFile(filename)
  196. if _code.find('import') == -1:
  197. en_arr = _code.split('\n')
  198. de_text = ''
  199. for data in en_arr:
  200. data = str.strip(data)
  201. if not data: continue
  202. de_text += __aes_decrypt_module(data)
  203. _code = de_text
  204. if not _code or _code.find('import') == -1:
  205. return public.returnMsg(False,'load failed: decode error')
  206. _code_object = compile(_code, filename, 'exec')
  207. from types import ModuleType
  208. _obj = sys.modules.setdefault(filename, ModuleType(filename))
  209. _obj.__file__ = filename
  210. _obj.__package__ = ''
  211. exec(_code_object, _obj.__dict__)
  212. return _obj
  213. def start_total():
  214. '''
  215. @name
  216. @return dict
  217. '''
  218. pass
  219. def get_soft_list(args):
  220. '''
  221. @name
  222. @param args<dict_obj>
  223. @return dict
  224. '''
  225. pass
  226. def db_encrypt(data):
  227. '''
  228. @name
  229. @param args<dict_obj>
  230. @return dict
  231. '''
  232. try:
  233. key = __get_db_sgin()
  234. iv = __get_db_iv()
  235. str_arr = data.split('\n')
  236. res_str = ''
  237. for data in str_arr:
  238. if not data: continue
  239. res_str += __aes_encrypt(data, key, iv)
  240. except:
  241. res_str = data
  242. result = {
  243. 'status' : True,
  244. 'msg' : res_str
  245. }
  246. return result
  247. def db_decrypt(data):
  248. '''
  249. @name
  250. @param args<dict_obj>
  251. @return dict
  252. '''
  253. try:
  254. key = __get_db_sgin()
  255. iv = __get_db_iv()
  256. str_arr = data.split('\n')
  257. res_str = ''
  258. for data in str_arr:
  259. if not data: continue
  260. res_str += __aes_decrypt(data, key, iv)
  261. except:
  262. res_str = data
  263. result = {
  264. 'status' : True,
  265. 'msg' : res_str
  266. }
  267. return result
  268. def __get_db_sgin():
  269. keystr = '3gP7+k_7lSNg3$+Fj!PKW+6$KYgHtw#R'
  270. key = ''
  271. for i in range(31):
  272. if i & 1 == 0:
  273. key += keystr[i]
  274. return key
  275. def __get_db_iv():
  276. div_file = "{}/data/div.pl".format(public.get_panel_path())
  277. if not os.path.exists(div_file):
  278. str = public.GetRandomString(16)
  279. str = __aes_encrypt_module(str)
  280. div = public.get_div(str)
  281. public.WriteFile(div_file, div)
  282. if os.path.exists(div_file):
  283. div = public.ReadFile(div_file)
  284. div = __aes_decrypt_module(div)
  285. else:
  286. keystr = '4jHCpBOFzL4*piTn^-4IHBhj-OL!fGlB'
  287. div = ''
  288. for i in range(31):
  289. if i & 1 == 0:
  290. div += keystr[i]
  291. return div
  292. def __aes_encrypt_module(data):
  293. key = 'Z2B87NEAS2BkxTrh'
  294. iv = 'WwadH66EGWpeeTT6'
  295. return __aes_encrypt(data, key, iv)
  296. def __aes_decrypt_module(data):
  297. key = 'Z2B87NEAS2BkxTrh'
  298. iv = 'WwadH66EGWpeeTT6'
  299. return __aes_decrypt(data, key, iv)
  300. def __aes_decrypt(data, key, iv):
  301. from Crypto.Cipher import AES
  302. import base64
  303. encodebytes = base64.decodebytes(data.encode('utf-8'))
  304. aes = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8'))
  305. de_text = aes.decrypt(encodebytes)
  306. unpad = lambda s: s[0:-s[-1]]
  307. de_text = unpad(de_text)
  308. return de_text.decode('utf-8')
  309. def __aes_encrypt(data, key, iv):
  310. from Crypto.Cipher import AES
  311. import base64
  312. data = (lambda s: s + (16 - len(s) % 16) * chr(16 - len(s) % 16).encode('utf-8'))(data.encode('utf-8'))
  313. aes = AES.new(key.encode('utf8'), AES.MODE_CBC, iv.encode('utf8'))
  314. encryptedbytes = aes.encrypt(data)
  315. en_text = base64.b64encode(encryptedbytes)
  316. return en_text.decode('utf-8')
  317. def plugin_end():
  318. '''
  319. @name
  320. @return dict
  321. '''
  322. pass
  323. def daemon_task():
  324. '''
  325. @name
  326. @return dict
  327. '''
  328. pass
  329. def daemon_panel():
  330. '''
  331. @name
  332. @return dict
  333. '''
  334. pass
  335. def flush_auth_key():
  336. '''
  337. @name
  338. @return dict
  339. '''
  340. pass
  341. def get_auth_state():
  342. '''
  343. @name
  344. @return 0. 1. 2. -1.
  345. '''
  346. try:
  347. softList = get_plugin_list()
  348. if softList['ltd'] > -1:
  349. return 2
  350. elif softList['pro'] > -1:
  351. return 1
  352. else:
  353. return 0
  354. except:
  355. return -1