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.

281 lines
14 KiB

1 year ago
  1. <?php
  2. namespace app\lib;
  3. use Exception;
  4. use ZipArchive;
  5. class BtPlugins
  6. {
  7. private $btapi;
  8. private $os;
  9. //需屏蔽的插件名称列表
  10. private static $block_plugins = ['dns'];
  11. public function __construct($os){
  12. $this->os = $os;
  13. if($os == 'Windows'){
  14. $bt_url = config_get('wbt_url');
  15. $bt_key = config_get('wbt_key');
  16. }else{
  17. $bt_url = config_get('bt_url');
  18. $bt_key = config_get('bt_key');
  19. }
  20. if(!$bt_url || !$bt_key) throw new Exception('请先配置好宝塔面板接口信息');
  21. $this->btapi = new Btapi($bt_url, $bt_key);
  22. }
  23. //获取插件列表
  24. public function get_plugin_list(){
  25. $result = $this->btapi->get_plugin_list();
  26. if($result && isset($result['list']) && isset($result['type'])){
  27. if(empty($result['list']) || empty($result['type'])){
  28. throw new Exception('获取插件列表失败:插件列表为空');
  29. }
  30. foreach($result['list'] as $k=>$v){
  31. if(in_array($v['name'], self::$block_plugins)) unset($result['list'][$k]);
  32. }
  33. return $result;
  34. }else{
  35. throw new Exception('获取插件列表失败:'.(isset($result['msg'])?$result['msg']:'面板连接失败'));
  36. }
  37. }
  38. //下载插件(自动判断是否第三方)
  39. public function download_plugin($plugin_name, $version, $plugin_info){
  40. if($plugin_info['type'] == 10 && isset($plugin_info['versions'][0]['download'])){
  41. if($plugin_info['price'] == 0){
  42. $this->btapi->create_plugin_other_order($plugin_info['id']);
  43. }
  44. $fname = $plugin_info['versions'][0]['download'];
  45. $filemd5 = $plugin_info['versions'][0]['md5'];
  46. $this->download_plugin_other($fname, $filemd5);
  47. if(isset($plugin_info['min_image']) && strpos($plugin_info['min_image'], 'fname=')){
  48. $fname = substr($plugin_info['min_image'], strpos($plugin_info['min_image'], '?fname=')+7);
  49. $this->download_plugin_other($fname);
  50. }
  51. }else{
  52. $this->download_plugin_package($plugin_name, $version);
  53. }
  54. }
  55. //下载插件包
  56. private function download_plugin_package($plugin_name, $version){
  57. $filepath = get_data_dir($this->os).'plugins/package/'.$plugin_name.'-'.$version.'.zip';
  58. $result = $this->btapi->get_plugin_filename($plugin_name, $version);
  59. if($result && isset($result['status'])){
  60. if($result['status'] == true){
  61. $filename = $result['filename'];
  62. $this->download_file($filename, $filepath);
  63. if(file_exists($filepath)){
  64. $zip = new ZipArchive;
  65. if ($zip->open($filepath) === true)
  66. {
  67. $zip->extractTo(get_data_dir($this->os).'plugins/folder/'.$plugin_name.'-'.$version);
  68. $zip->close();
  69. $main_filepath = get_data_dir($this->os).'plugins/folder/'.$plugin_name.'-'.$version.'/'.$plugin_name.'/'.$plugin_name.'_main.py';
  70. if(file_exists($main_filepath) && filesize($main_filepath)>10){
  71. if(!strpos(file_get_contents($main_filepath), 'import ')){ //加密py文件,需要解密
  72. $this->decode_plugin_main($plugin_name, $version, $main_filepath);
  73. $this->noauth_plugin_main($main_filepath);
  74. $zip->open($filepath, ZipArchive::CREATE);
  75. $zip->addFile($main_filepath, $plugin_name.'/'.$plugin_name.'_main.py');
  76. $zip->close();
  77. }
  78. }
  79. }else{
  80. unlink($filepath);
  81. throw new Exception('插件包解压缩失败');
  82. }
  83. return true;
  84. }else{
  85. throw new Exception('下载插件包失败,本地文件不存在');
  86. }
  87. }else{
  88. throw new Exception('下载插件包失败:'.($result['msg']?$result['msg']:'未知错误'));
  89. }
  90. }else{
  91. throw new Exception('下载插件包失败,接口返回错误');
  92. }
  93. }
  94. //下载插件主程序文件
  95. public function download_plugin_main($plugin_name, $version){
  96. $filepath = get_data_dir($this->os).'plugins/main/'.$plugin_name.'-'.$version.'.dat';
  97. $result = $this->btapi->get_plugin_main_filename($plugin_name, $version);
  98. if($result && isset($result['status'])){
  99. if($result['status'] == true){
  100. $filename = $result['filename'];
  101. $this->download_file($filename, $filepath);
  102. if(file_exists($filepath)){
  103. return true;
  104. }else{
  105. throw new Exception('下载插件主程序文件失败,本地文件不存在');
  106. }
  107. }else{
  108. throw new Exception('下载插件主程序文件失败:'.($result['msg']?$result['msg']:'未知错误'));
  109. }
  110. }else{
  111. throw new Exception('下载插件主程序文件失败,接口返回错误');
  112. }
  113. }
  114. //解密并下载插件主程序文件
  115. private function decode_plugin_main($plugin_name, $version, $main_filepath){
  116. if($this->decode_plugin_main_local($main_filepath)) return true;
  117. $result = $this->btapi->get_decode_plugin_main($plugin_name, $version);
  118. if($result && isset($result['status'])){
  119. if($result['status'] == true){
  120. $filename = $result['filename'];
  121. $this->download_file($filename, $main_filepath);
  122. return true;
  123. }else{
  124. throw new Exception('解密插件主程序文件失败:'.($result['msg']?$result['msg']:'未知错误'));
  125. }
  126. }else{
  127. throw new Exception('解密插件主程序文件失败,接口返回错误');
  128. }
  129. }
  130. //本地解密插件主程序文件
  131. public function decode_plugin_main_local($main_filepath){
  132. $userinfo = $this->btapi->get_user_info();
  133. if(isset($userinfo['uid'])){
  134. $src = file_get_contents($main_filepath);
  135. if($src===false)throw new Exception('文件打开失败');
  136. if(!$src || strpos($src, 'import ')!==false)return true;
  137. $uid = $userinfo['uid'];
  138. $serverid = $userinfo['serverid'];
  139. $key = md5(substr($serverid, 10, 16).$uid.$serverid);
  140. $iv = md5($key.$serverid);
  141. $key = substr($key, 8, 16);
  142. $iv = substr($iv, 8, 16);
  143. $data_arr = explode("\n", $src);
  144. $de_text = '';
  145. foreach($data_arr as $data){
  146. $data = trim($data);
  147. if(!empty($data) && strlen($data)!=24){
  148. $tmp = openssl_decrypt($data, 'aes-128-cbc', $key, 0, $iv);
  149. if($tmp) $de_text .= $tmp;
  150. }
  151. }
  152. if(!empty($de_text) && strpos($de_text, 'import ')!==false){
  153. file_put_contents($main_filepath, $de_text);
  154. return true;
  155. }
  156. return false;
  157. }else{
  158. throw new Exception('解密插件主程序文件失败,获取用户信息失败');
  159. }
  160. }
  161. //去除插件主程序文件授权校验
  162. private function noauth_plugin_main($main_filepath){
  163. $data = file_get_contents($main_filepath);
  164. if(!$data) return false;
  165. $data = str_replace('\'http://www.bt.cn/api/panel/get_soft_list_test', 'public.GetConfigValue(\'home\')+\'/api/panel/get_soft_list_test', $data);
  166. $data = str_replace('\'https://www.bt.cn/api/panel/get_soft_list_test', 'public.GetConfigValue(\'home\')+\'/api/panel/get_soft_list_test', $data);
  167. $data = str_replace('\'http://www.bt.cn/api/panel/get_soft_list', 'public.GetConfigValue(\'home\')+\'/api/panel/get_soft_list', $data);
  168. $data = str_replace('\'https://www.bt.cn/api/panel/get_soft_list', 'public.GetConfigValue(\'home\')+\'/api/panel/get_soft_list', $data);
  169. $data = str_replace('\'http://www.bt.cn/api/panel/notpro', 'public.GetConfigValue(\'home\')+\'/api/panel/notpro', $data);
  170. $data = str_replace('\'https://www.bt.cn/api/panel/notpro', 'public.GetConfigValue(\'home\')+\'/api/panel/notpro', $data);
  171. $data = str_replace('\'http://www.bt.cn/api/wpanel/get_soft_list_test', 'public.GetConfigValue(\'home\')+\'/api/wpanel/get_soft_list_test', $data);
  172. $data = str_replace('\'https://www.bt.cn/api/wpanel/get_soft_list_test', 'public.GetConfigValue(\'home\')+\'/api/wpanel/get_soft_list_test', $data);
  173. $data = str_replace('\'http://www.bt.cn/api/wpanel/get_soft_list', 'public.GetConfigValue(\'home\')+\'/api/wpanel/get_soft_list', $data);
  174. $data = str_replace('\'https://www.bt.cn/api/wpanel/get_soft_list', 'public.GetConfigValue(\'home\')+\'/api/wpanel/get_soft_list', $data);
  175. $data = str_replace('\'http://www.bt.cn/api/wpanel/notpro', 'public.GetConfigValue(\'home\')+\'/api/wpanel/notpro', $data);
  176. $data = str_replace('\'https://www.bt.cn/api/wpanel/notpro', 'public.GetConfigValue(\'home\')+\'/api/wpanel/notpro', $data);
  177. $data = str_replace('\'http://www.bt.cn/api/bt_waf/getSpiders', 'public.GetConfigValue(\'home\')+\'/api/bt_waf/getSpiders', $data);
  178. $data = str_replace('\'https://www.bt.cn/api/bt_waf/getSpiders', 'public.GetConfigValue(\'home\')+\'/api/bt_waf/getSpiders', $data);
  179. $data = str_replace('\'http://www.bt.cn/api/bt_waf/addSpider', 'public.GetConfigValue(\'home\')+\'/api/bt_waf/addSpider', $data);
  180. $data = str_replace('\'https://www.bt.cn/api/bt_waf/addSpider', 'public.GetConfigValue(\'home\')+\'/api/bt_waf/addSpider', $data);
  181. $data = str_replace('\'https://www.bt.cn/api/bt_waf/getVulScanInfoList', 'public.GetConfigValue(\'home\')+\'/api/bt_waf/getVulScanInfoList', $data);
  182. $data = str_replace('\'https://www.bt.cn/api/bt_waf/reportInterceptFail', 'public.GetConfigValue(\'home\')+\'/api/bt_waf/reportInterceptFail', $data);
  183. $data = str_replace('\'https://www.bt.cn/api/v2/contact/nps/questions', 'public.GetConfigValue(\'home\')+\'/panel/notpro', $data);
  184. $data = str_replace('\'https://www.bt.cn/api/v2/contact/nps/submit', 'public.GetConfigValue(\'home\')+\'/panel/notpro', $data);
  185. file_put_contents($main_filepath, $data);
  186. }
  187. //下载插件其他文件
  188. private function download_plugin_other($fname, $filemd5 = null){
  189. $filepath = get_data_dir().'plugins/other/'.$fname;
  190. @mkdir(dirname($filepath), 0777, true);
  191. $result = $this->btapi->get_plugin_other_filename($fname);
  192. if($result && isset($result['status'])){
  193. if($result['status'] == true){
  194. $filename = $result['filename'];
  195. $this->download_file($filename, $filepath);
  196. if(file_exists($filepath)){
  197. if($filemd5 && md5_file($filepath) != $filemd5){
  198. $msg = filesize($filepath) < 300 ? file_get_contents($filepath) : '插件文件MD5校验失败';
  199. @unlink($filepath);
  200. throw new Exception($msg);
  201. }
  202. return true;
  203. }else{
  204. throw new Exception('下载插件文件失败,本地文件不存在');
  205. }
  206. }else{
  207. throw new Exception('下载插件文件失败:'.($result['msg']?$result['msg']:'未知错误'));
  208. }
  209. }else{
  210. throw new Exception('下载插件文件失败,接口返回错误');
  211. }
  212. }
  213. //下载文件
  214. private function download_file($filename, $filepath){
  215. try{
  216. $this->btapi->download($filename, $filepath);
  217. }catch(Exception $e){
  218. @unlink($filepath);
  219. //宝塔bug小文件下载失败,改用base64下载
  220. $result = $this->btapi->get_file($filename);
  221. if($result && isset($result['status']) && $result['status']==true){
  222. $filedata = base64_decode($result['data']);
  223. if(strlen($filedata) < 4096 && substr($filedata,0,1)=='{' && substr($filedata,-1,1)=='}'){
  224. $arr = json_decode($filedata, true);
  225. if($arr){
  226. throw new Exception('获取文件失败:'.($arr['msg']?$arr['msg']:'未知错误'));
  227. }
  228. }
  229. if(!$filedata){
  230. throw new Exception('获取文件失败:文件内容为空');
  231. }
  232. file_put_contents($filepath, $filedata);
  233. }elseif($result){
  234. throw new Exception('获取文件失败:'.($result['msg']?$result['msg']:'未知错误'));
  235. }else{
  236. throw new Exception('获取文件失败:未知错误');
  237. }
  238. }
  239. }
  240. //获取一键部署列表
  241. public function get_deplist(){
  242. $result = $this->btapi->get_deplist();
  243. if($result && isset($result['list']) && isset($result['type'])){
  244. if(empty($result['list']) || empty($result['type'])){
  245. throw new Exception('获取一键部署列表失败:一键部署列表为空');
  246. }
  247. return $result;
  248. }else{
  249. throw new Exception('获取一键部署列表失败:'.(isset($result['msg'])?$result['msg']:'面板连接失败'));
  250. }
  251. }
  252. //获取蜘蛛IP列表
  253. public function btwaf_getspiders(){
  254. $result = $this->btapi->btwaf_getspiders();
  255. if(isset($result['status']) && $result['status']){
  256. return $result['data'];
  257. }else{
  258. throw new Exception(isset($result['msg'])?$result['msg']:'获取失败');
  259. }
  260. }
  261. }