diff --git a/.gitignore b/.gitignore index 73b5a21507..a0b4813705 100644 --- a/.gitignore +++ b/.gitignore @@ -156,7 +156,9 @@ data/bind_domain.pl plugins/l2tp plugins/openlitespeed +plugins/migration_api plugins/system_safe +plugins/tamper_proof plugins/gdrive plugins/mtproxy plugins/zimg @@ -165,6 +167,7 @@ plugins/mail plugins/fastdfs plugins/v2ray plugins/frp +plugins/file_search debug.out diff --git a/README.md b/README.md index 35c715e82e..398990cbc2 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,6 @@ PHP[72-81]支持phpMyAdmin[5.2.0] # 特别赞助 - [找资源 - 阿里云盘资源搜索引擎](https://zhaoziyuan.la/) -- [奈飞丝 - 奈飞中国 Netflix MOD首发](https://naifeis.com/index.php#/register?code=k7P7V6Ur) # AD - VPS推荐 - 🙏 @@ -92,14 +91,17 @@ docker run -itd --name mw-server --privileged=true -p 7200:7200 -p 80:80 -p 443: ``` -### 版本更新 0.12.1 +### 版本更新 0.12.2 -* mysql/mariadb/mysql-apt/mysql-yum增加新的同步方式[同步账户]。 -* pureftp安装时自动放行端口。 -* 面板ssl,添加【申请Lets证书】功能。 -* 站点子域名绑定优化。 -* 修复添加数据库名字判断。 -* 增加PHP52的隐藏和显示指令。 +* 开放菜单权限配置。 +* 升级SSH终端2.0。 +* 增加已安装类型。 +* 加入切换linux软件源的命令。 +* iptables安装优化。 +* 网站统计POST获取数据优化。 +* mysql[apt/yum]迁移优化。 +* 优化防火墙导入。 +* 图标可设置。 * 各种细节优化。 diff --git a/app.py b/app.py index f635bc2227..a8cd3b38f2 100644 --- a/app.py +++ b/app.py @@ -40,12 +40,12 @@ PORT = int(f.read()) f.close() - HOST = '0.0.0.0' + # HOST = '0.0.0.0' + # app.run(host=HOST, port=PORT) + http_server = WSGIServer( (HOST, PORT), app, handler_class=WebSocketHandler) - http_server.serve_forever() - socketio.run(app, host=HOST, port=PORT) except Exception as ex: print(ex) diff --git a/class/core/config_api.py b/class/core/config_api.py index b8d0c54493..d74b95b476 100755 --- a/class/core/config_api.py +++ b/class/core/config_api.py @@ -27,7 +27,7 @@ class config_api: - __version = '0.12.1' + __version = '0.12.2' __api_addr = 'data/api.json' def __init__(self): @@ -781,12 +781,21 @@ def get(self): "id=?", (1,)).getField('username') # databases hook 获取 - database_file = 'data/hook_database.json' - if os.path.exists(database_file): - df = mw.readFile(database_file) + database_hook_file = 'data/hook_database.json' + if os.path.exists(database_hook_file): + df = mw.readFile(database_hook_file) df = json.loads(df) data['hook_database'] = df else: data['hook_database'] = [] + # menu hook 获取 + menu_hook_file = 'data/hook_menu.json' + if os.path.exists(menu_hook_file): + df = mw.readFile(menu_hook_file) + df = json.loads(df) + data['hook_menu'] = df + else: + data['hook_menu'] = [] + return data diff --git a/class/core/firewall_api.py b/class/core/firewall_api.py index 7a5f78df08..ec650207ee 100755 --- a/class/core/firewall_api.py +++ b/class/core/firewall_api.py @@ -33,7 +33,8 @@ class firewall_api: __isMac = False def __init__(self): - if os.path.exists('/usr/sbin/iptables'): + iptables_file = mw.systemdCfgDir() + '/iptables.service' + if os.path.exists(iptables_file): self.__isIptables = True if os.path.exists('/usr/sbin/firewalld'): self.__isFirewalld = True @@ -254,26 +255,15 @@ def setSshPortApi(self): conf = re.sub(rep, "Port " + port + "\n", conf) mw.writeFile(file, conf) + self.addAcceptPortArgs(port, 'SSH端口修改', 'port') if self.__isUfw: - mw.execShell('ufw allow ' + port + '/tcp') mw.execShell("service ssh restart") elif self.__isIptables: - mw.execShell( - 'iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport ' + port + ' -j ACCEPT') mw.execShell("/etc/init.d/sshd restart") elif self.__isFirewalld: - mw.execShell('setenforce 0') - mw.execShell( - 'sed -i "s#SELINUX=enforcing#SELINUX=disabled#" /etc/selinux/config') mw.execShell("systemctl restart sshd.service") else: - pass - - self.firewallReload() - # mw.M('firewall').where( - # "ps=?", ('SSH远程管理服务',)).setField('port', port) - msg = "改SSH端口为[{}]成功!".format(port) - mw.writeLog("防火墙管理", msg) + return mw.returnJson(False, '修改失败!') return mw.returnJson(True, '修改成功!') def setSshStatusApi(self): @@ -359,6 +349,8 @@ def setFwIptables(self, status): _list = mw.M('firewall').field('id,port,ps,addtime').limit( '0,1000').order('id desc').select() + mw.execShell('iptables -P INPUT DROP') + mw.execShell('iptables -P OUTPUT ACCEPT') for x in _list: port = x['port'] if mw.isIpAddr(port): diff --git a/class/core/mw.py b/class/core/mw.py index a111c912cc..fb85452320 100755 --- a/class/core/mw.py +++ b/class/core/mw.py @@ -454,6 +454,12 @@ def getDate(): return time.strftime('%Y-%m-%d %X', time.localtime()) +def getDataFromInt(val): + time_format = '%Y-%m-%d %H:%M:%S' + time_str = time.localtime(val) + return time.strftime(time_format, time_str) + + def writeLog(stype, msg, args=()): # 写日志 try: @@ -573,28 +579,37 @@ def dePunycode(domain): def enCrypt(key, strings): # 加密字符串 try: + import base64 + _key = md5(key).encode('utf-8') + _key = base64.urlsafe_b64encode(_key) + if type(strings) != bytes: strings = strings.encode('utf-8') + import cryptography from cryptography.fernet import Fernet - f = Fernet(key) + f = Fernet(_key) result = f.encrypt(strings) return result.decode('utf-8') except: - # print(get_error_info()) + print(getTracebackInfo()) return strings def deCrypt(key, strings): # 解密字符串 try: + import base64 + _key = md5(key).encode('utf-8') + _key = base64.urlsafe_b64encode(_key) + if type(strings) != bytes: - strings = strings.decode('utf-8') + strings = strings.encode('utf-8') from cryptography.fernet import Fernet - f = Fernet(key) + f = Fernet(_key) result = f.decrypt(strings).decode('utf-8') return result except: - # print(get_error_info()) + print(getTracebackInfo()) return strings @@ -1275,6 +1290,10 @@ def toSize(size): return str(round(size, 2)) + ' ' + b +def getPathSuffix(path): + return os.path.splitext(path)[-1] + + def getMacAddress(): # 获取mac import uuid @@ -1483,8 +1502,95 @@ def getMyORMDb(): o = ormDb.ORM() return o + +##################### ssh start ######################################### +def getSshDir(): + if isAppleSystem(): + user = execShell("who | sed -n '2, 1p' |awk '{print $1}'")[0].strip() + return '/Users/' + user + '/.ssh' + return '/root/.ssh' + + +def createRsa(): + # ssh-keygen -t rsa -P "" -C "midoks@163.com" + ssh_dir = getSshDir() + # mw.execShell("rm -f /root/.ssh/*") + if not os.path.exists(ssh_dir + '/authorized_keys'): + execShell('touch ' + ssh_dir + '/authorized_keys') + + if not os.path.exists(ssh_dir + '/id_rsa.pub') and os.path.exists(ssh_dir + '/id_rsa'): + execShell('echo y | ssh-keygen -q -t rsa -P "" -f ' + + ssh_dir + '/id_rsa') + else: + execShell('ssh-keygen -q -t rsa -P "" -f ' + ssh_dir + '/id_rsa') + + execShell('cat ' + ssh_dir + '/id_rsa.pub >> ' + + ssh_dir + '/authorized_keys') + execShell('chmod 600 ' + ssh_dir + '/authorized_keys') + + +def createSshInfo(): + ssh_dir = getSshDir() + if not os.path.exists(ssh_dir + '/id_rsa') or not os.path.exists(ssh_dir + '/id_rsa.pub'): + createRsa() + + # 检查是否写入authorized_keys + data = execShell("cat " + ssh_dir + "/id_rsa.pub | awk '{print $3}'") + if data[0] != "": + cmd = "cat " + ssh_dir + "/authorized_keys | grep " + data[0] + ak_data = execShell(cmd) + if ak_data[0] == "": + cmd = 'cat ' + ssh_dir + '/id_rsa.pub >> ' + ssh_dir + '/authorized_keys' + execShell(cmd) + execShell('chmod 600 ' + ssh_dir + '/authorized_keys') + + +def connectSsh(): + import paramiko + ssh = paramiko.SSHClient() + createSshInfo() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + + port = getSSHPort() + try: + ssh.connect('127.0.0.1', port, timeout=5) + except Exception as e: + ssh.connect('localhost', port, timeout=5) + except Exception as e: + ssh.connect(getHostAddr(), port, timeout=30) + except Exception as e: + return False + + shell = ssh.invoke_shell(term='xterm', width=83, height=21) + shell.setblocking(0) + return shell + + +def clearSsh(): + # 服务器IP + ip = getHostAddr() + sh = ''' +#!/bin/bash +PLIST=`who | grep localhost | awk '{print $2}'` +for i in $PLIST +do + ps -t /dev/$i |grep -v TTY | awk '{print $1}' | xargs kill -9 +done + +# getHostAddr +PLIST=`who | grep "${ip}" | awk '{print $2}'` +for i in $PLIST +do + ps -t /dev/$i |grep -v TTY | awk '{print $1}' | xargs kill -9 +done +''' + if not isAppleSystem(): + info = execShell(sh) + print(info[0], info[1]) +##################### ssh end ######################################### + # --------------------------------------------------------------------------------- -# 打印相关 +# 打印相关 START # --------------------------------------------------------------------------------- @@ -1503,3 +1609,7 @@ def echoEnd(tag): def echoInfo(msg): print("|-{}".format(msg)) + +# --------------------------------------------------------------------------------- +# 打印相关 END +# --------------------------------------------------------------------------------- diff --git a/class/core/plugins_api.py b/class/core/plugins_api.py index 54641a6f6a..3cb8ef0bb1 100755 --- a/class/core/plugins_api.py +++ b/class/core/plugins_api.py @@ -27,7 +27,7 @@ import threading import multiprocessing - +from flask import render_template from flask import request @@ -75,12 +75,35 @@ def listApi(self): data = self.getPluginList(sType, int(sPage)) return mw.getJson(data) + def menuGetAbsPath(self, tag, path): + if path[0:1] == '/': + return path + else: + return mw.getPluginDir() + '/' + tag + '/' + path + + def menuApi(self): + import config_api + data = config_api.config_api().get() + tag = request.args.get('tag', '') + menu_file = 'data/hook_menu.json' + content = '' + if os.path.exists(menu_file): + t = mw.readFile(menu_file) + tlist = json.loads(t) + for menu_data in tlist: + if 'path' in menu_data: + tpath = self.menuGetAbsPath(tag, menu_data['path']) + content = mw.readFile(tpath) + data['plugin_content'] = content + return render_template('plugin_menu.html', data=data) + def fileApi(self): name = request.args.get('name', '') if name.strip() == '': return '' f = request.args.get('f', '') + if f.strip() == '': return '' @@ -88,8 +111,16 @@ def fileApi(self): if not os.path.exists(file): return '' - c = open(file, 'rb').read() - return c + suffix = mw.getPathSuffix(file) + if suffix == '.css': + content = mw.readFile(file) + from flask import Response + from flask import make_response + v = Response(content, headers={ + 'Content-Type': 'text/css; charset="utf-8"'}) + return make_response(v) + content = open(file, 'rb').read() + return content def indexListApi(self): data = self.getIndexList() @@ -214,10 +245,7 @@ def hookInstallFile(self, hook_name, info): isNeedAdd = False if isNeedAdd: - tmp = {} - tmp['title'] = info['title'] - tmp['name'] = info['name'] - data.append(tmp) + data.append(info) mw.writeFile(hookPath, json.dumps(data)) def hookUninstallFile(self, hook_name, info): @@ -233,21 +261,39 @@ def hookUninstallFile(self, hook_name, info): mw.writeFile(hookPath, json.dumps(data)) def hookInstall(self, info): + valid_hook = ['backup', 'database'] + valid_list_hook = ['menu'] if 'hook' in info: hooks = info['hook'] - for x in hooks: - if x in ['backup', 'database']: - self.hookInstallFile(x, info) - return True + for h in hooks: + hooks_type = type(h) + if hooks_type == dict: + tag = h['tag'] + if tag in valid_list_hook: + self.hookInstallFile(tag, h[tag]) + elif hooks_type == str: + for x in hooks: + if x in valid_hook: + self.hookInstallFile(x, info) + return True return False def hookUninstall(self, info): + valid_hook = ['backup', 'database'] + valid_list_hook = ['menu'] if 'hook' in info: hooks = info['hook'] - for x in hooks: - if x in ['backup', 'database']: - self.hookUninstallFile(x, info) - return True + for h in hooks: + hooks_type = type(h) + if hooks_type == dict: + tag = h['tag'] + if tag in valid_list_hook: + self.hookUninstallFile(tag, h[tag]) + elif hooks_type == str: + for x in hooks: + if x in valid_hook: + self.hookUninstallFile(x, info) + return True return False def uninstallOldApi(self): @@ -681,6 +727,7 @@ def makeCoexist(self, data): def makeList(self, data, sType='0'): plugins_info = [] + # 相应类型 if (data['pid'] == sType): if type(data['versions']) == list and 'coexist' in data and data['coexist']: tmp_data = self.makeCoexist(data) @@ -691,6 +738,7 @@ def makeList(self, data, sType='0'): plugins_info.append(pg) return plugins_info + # 全部 if sType == '0': if type(data['versions']) == list and 'coexist' in data and data['coexist']: tmp_data = self.makeCoexist(data) @@ -700,6 +748,18 @@ def makeList(self, data, sType='0'): pg = self.getPluginInfo(data) plugins_info.append(pg) + # 已经安装 + if sType == '-1': + if type(data['versions']) == list and 'coexist' in data and data['coexist']: + tmp_data = self.makeCoexist(data) + for index in range(len(tmp_data)): + if tmp_data[index]['setup']: + plugins_info.append(tmp_data[index]) + else: + pg = self.getPluginInfo(data) + if pg['setup']: + plugins_info.append(pg) + # print plugins_info, data return plugins_info diff --git a/class/core/ssh_terminal.py b/class/core/ssh_terminal.py new file mode 100644 index 0000000000..a86d47094a --- /dev/null +++ b/class/core/ssh_terminal.py @@ -0,0 +1,456 @@ +# coding: utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +# --------------------------------------------------------------------------------- +# SSH终端操作 +# --------------------------------------------------------------------------------- + +import json +import time +import os +import sys +import socket +import threading +import re + +from io import BytesIO, StringIO + +import mw +import paramiko + +from flask_socketio import SocketIO, emit, send + + +class ssh_terminal: + + __debug_file = 'logs/terminal.log' + __log_type = 'SSH终端' + + # websocketio 唯一标识 + __sid = '' + + __host = None + __type = '0' + __port = 22 + __user = None + __pass = None + __pkey = None + __key_passwd = None + + __rep_ssh_config = False + __rep_ssh_service = False + __sshd_config_backup = None + + __ssh = None + __tp = None + __ps = None + + __ssh_list = {} + __ssh_last_request_time = {} + + def __init__(self): + ht = threading.Thread(target=self.heartbeat) + ht.start() + + def debug(self, msg): + msg = "{} - {}:{} => {} \n".format(mw.formatDate(), + self.__host, self.__port, msg) + if not mw.isDebugMode(): + return + mw.writeFile(self.__debug_file, msg, 'a+') + + def returnMsg(self, status, msg): + return {'status': status, 'msg': msg} + + def restartSsh(self, act='reload'): + ''' + 重启ssh 无参数传递 + ''' + version = mw.readFile('/etc/redhat-release') + if not os.path.exists('/etc/redhat-release'): + mw.execShell('service ssh ' + act) + elif version.find(' 7.') != -1 or version.find(' 8.') != -1: + mw.execShell("systemctl " + act + " sshd.service") + else: + mw.execShell("/etc/init.d/sshd " + act) + + def isRunning(self, rep=False): + try: + if rep and self.__rep_ssh_service: + self.restartSsh('stop') + return True + + status = self.getSshStatus() + if not status: + self.restartSsh('start') + self.__rep_ssh_service = True + return True + return False + except: + return False + + def setSshdConfig(self, rep=False): + self.isRunning(rep) + if rep and not self.__rep_ssh_config: + return False + + try: + sshd_config_file = '/etc/ssh/sshd_config' + if not os.path.exists(sshd_config_file): + return False + + sshd_config = mw.readFile(sshd_config_file) + if not sshd_config: + return False + + if rep: + if self.__sshd_config_backup: + mw.writeFile(sshd_config_file, self.__sshd_config_backup) + self.restartSsh() + return True + + pin = r'^\s*PubkeyAuthentication\s+(yes|no)' + pubkey_status = re.findall(pin, sshd_config, re.I) + if pubkey_status: + if pubkey_status[0] == 'yes': + pubkey_status = True + else: + pubkey_status = False + + pin = r'^\s*RSAAuthentication\s+(yes|no)' + rsa_status = re.findall(pin, sshd_config, re.I) + if rsa_status: + if rsa_status[0] == 'yes': + rsa_status = True + else: + rsa_status = False + + self._sshd_config_backup = sshd_config + is_write = False + if not pubkey_status: + sshd_config = re.sub( + r'\n#?PubkeyAuthentication\s\w+', '\nPubkeyAuthentication yes', sshd_config) + is_write = True + if not rsa_status: + sshd_config = re.sub( + r'\n#?RSAAuthentication\s\w+', '\nRSAAuthentication yes', sshd_config) + is_write = True + + if is_write: + mw.writeFile(sshd_config_file, sshd_config) + self.__rep_ssh_config = True + self.restartSsh() + else: + self.__sshd_config_backup = None + return True + except: + return False + + def setSid(self, sid): + self.__sid = sid + + def connect(self, sid): + # self.connectBySocket() + if self.__host in ['127.0.0.1', 'localhost']: + return self.connectLocalSsh(sid) + else: + return self.connectBySocket(sid) + + def connectLocalSsh(self, sid): + mw.createSshInfo() + self.__ps = paramiko.SSHClient() + self.__ps.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + + self.__port = mw.getSSHPort() + try: + self.__ps.connect(self.__host, self.__port, timeout=60) + except Exception as e: + self.__ps.connect('127.0.0.1', self.__port) + except Exception as e: + self.__ps.connect('localhost', self.__port) + except Exception as e: + self.setSshdConfig(True) + self.__ps.close() + e = str(e) + if e.find('websocket error!') != -1: + return self.returnMsg(True, '连接成功') + if e.find('Authentication timeout') != -1: + self.debug("认证超时{}".format(e)) + return self.returnMsg(False, '认证超时,请按回车重试!{}'.format(e)) + if e.find('Connection reset by peer') != -1: + self.debug('目标服务器主动拒绝连接') + return self.returnMsg(False, '目标服务器主动拒绝连接') + if e.find('Error reading SSH protocol banner') != -1: + self.debug('协议头响应超时') + return self.returnMsg(False, '协议头响应超时,与目标服务器之间的网络质量太糟糕:' + e) + if not e: + self.debug('SSH协议握手超时') + return self.returnMsg(False, "SSH协议握手超时,与目标服务器之间的网络质量太糟糕") + err = mw.getTracebackInfo() + self.debug(err) + return self.returnMsg(False, "未知错误: {}".format(err)) + + self.debug('local-ssh:认证成功,正在构建会话通道') + ssh = self.__ps.invoke_shell( + term='xterm', width=83, height=21) + ssh.setblocking(0) + self.__ssh_list[sid] = ssh + mw.writeLog(self.__log_type, '成功登录到SSH服务器 [{}:{}]'.format( + self.__host, self.__port)) + self.debug('local-ssh:通道已构建') + return self.returnMsg(True, '连接成功!') + + def connectBySocket(self, sid): + if not self.__host: + return self.returnMsg(False, '错误的连接地址') + if not self.__user: + self.__user = 'root' + if not self.__port: + self.__port = 22 + + self.setSshdConfig(True) + num = 0 + while num < 5: + num += 1 + try: + self.debug('正在尝试第{}次连接'.format(num)) + if self.__rep_ssh_config: + time.sleep(0.1) + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(2 + num) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 8192) + sock.connect((self.__host, self.__port)) + break + except Exception as e: + if num == 5: + self.setSshdConfig(True) + self.debug('重试连接失败,{}'.format(e)) + return self.returnMsg(False, '连接目标服务器失败, {}:{}'.format(self.__host, self.__port)) + else: + time.sleep(0.2) + + # print(self.__host, sock) + self.__tp = paramiko.Transport(sock) + try: + self.__tp.start_client() + self.__tp.banner_timeout = 60 + if not self.__pass and not self.__pkey: + return self.returnMsg(False, '密码或私钥不能都为空: {}:{}'.format(self.__host, self.__port)) + + if self.__pkey != '' and self.__type != '0': + self.debug('正在认证私钥') + p_file = StringIO(str(self.__pkey.replace('\\n', '\n'))) + # p_file = "/tmp/t_ssh_pkey.txt" + # mw.writeFile(p_file, self.__pkey.replace('\\n', '\n')) + # mw.execShell('chmod 600 ' + p_file) + try: + p_file.seek(0) + pkey = paramiko.RSAKey.from_private_key(p_file) + except: + try: + p_file.seek(0) # 重置游标 + pkey = paramiko.Ed25519Key.from_private_key( + p_file) + except: + try: + p_file.seek(0) + pkey = paramiko.ECDSAKey.from_private_key(p_file) + except: + p_file.seek(0) + pkey = paramiko.DSSKey.from_private_key(p_file) + + self.__tp.auth_publickey(username=self.__user, key=pkey) + else: + try: + self.__tp.auth_none(self.__user) + except Exception as e: + e = str(e) + if e.find('keyboard-interactive') >= 0: + self._auth_interactive() + else: + self.debug('正在认证密码') + self.__tp.auth_password( + username=self.__user, password=self.__pass) + except Exception as e: + self.setSshdConfig(True) + self.__tp.close() + e = str(e) + # print(e) + if e.find('Authentication timeout') != -1: + self.debug("认证超时{}".format(e)) + return self.returnMsg(False, '认证超时,请按回车重试!{}'.format(e)) + if e.find('Authentication failed') != -1: + self.debug('认证失败{}'.format(e)) + return self.returnMsg(False, '帐号或密码错误: {}'.format(e + "," + self.__user + "@" + self.__host + ":" + str(self.__port))) + if e.find('Bad authentication type; allowed types') != -1: + self.debug('认证失败{}'.format(e)) + if self.__host in ['127.0.0.1', 'localhost'] and self.__pass == 'none': + return self.returnMsg(False, '帐号或密码错误: {}'.format("Authentication failed ," + self.__user + "@" + self.__host + ":" + str(self.__port))) + return self.returnMsg(False, '不支持的身份验证类型: {}'.format(e)) + if e.find('Connection reset by peer') != -1: + self.debug('目标服务器主动拒绝连接') + return self.returnMsg(False, '目标服务器主动拒绝连接') + if e.find('Error reading SSH protocol banner') != -1: + self.debug('协议头响应超时') + return self.returnMsg(False, '协议头响应超时,与目标服务器之间的网络质量太糟糕:' + e) + if not e: + self.debug('SSH协议握手超时') + return self.returnMsg(False, "SSH协议握手超时,与目标服务器之间的网络质量太糟糕") + err = mw.getTracebackInfo() + self.debug(err) + return self.returnMsg(False, "未知错误: {}".format(err)) + + self.debug('认证成功,正在构建会话通道') + + ssh = self.__tp.open_session() + ssh.get_pty(term='xterm', width=100, height=34) + ssh.invoke_shell() + self.__ssh_list[sid] = ssh + mw.writeLog(self.__log_type, '成功登录到SSH服务器 [{}:{}]'.format( + self.__host, self.__port)) + self.debug('通道已构建') + return self.returnMsg(True, '连接成功.') + + def getSshInfo(self, file): + rdata = mw.readFile(file) + destr = mw.deCrypt('mdserver-web', rdata) + return json.loads(destr) + + def setAttr(self, sid, info): + self.__host = info['host'].strip() + + # 外部连接获取 + if not self.__host in ['127.0.0.1', 'localhost']: + dst_info = mw.getServerDir() + '/webssh/host/' + self.__host + '/info.json' + if os.path.exists(dst_info): + info = self.getSshInfo(dst_info) + + if 'type' in info: + self.__type = info['type'] + + if 'port' in info: + self.__port = int(info['port']) + if 'username' in info: + self.__user = info['username'] + if 'pkey' in info: + self.__pkey = info['pkey'] + if 'password' in info: + self.__pass = info['password'] + if 'pkey_passwd' in info: + self.__key_passwd = info['pkey_passwd'] + + # print(self.__host, self.__pass, self.__key_passwd) + try: + result = self.connect(sid) + # print(result) + except Exception as ex: + if str(ex).find("NoneType") == -1: + raise ex + return result + + def send(self): + pass + + def close(self): + try: + if self.__ssh: + self.__ssh.close() + if self.__tp: # 关闭宿主服务 + self.__tp.close() + if self.__ps: + self.__ps.close() + except: + pass + + def resize(self, sid, data): + try: + self.__ssh_list[sid].resize_pty( + width=data['cols'], height=data['rows']) + return True + except: + return False + + def wsSend(self, recv): + try: + t = recv.decode("utf-8") + return emit('server_response', {'data': t}) + except Exception as e: + return emit('server_response', {'data': recv}) + + def wsSendConnect(self): + return emit('connect', {'data': 'ok'}) + + def wsSendReConnect(self): + return emit('reconnect', {'data': 'ok'}) + + def heartbeat(self): + # limit_cos = 10 + while True: + time.sleep(3) + cur_time = time.time() + for x in list(self.__ssh_list.keys()): + ssh_last_time = self.__ssh_last_request_time[x] + sid_off_cos = cur_time - ssh_last_time + + # print("heartbeat off cos :", x, sid_off_cos) + if sid_off_cos > 3: + cur_ssh = self.__ssh_list[x] + if not cur_ssh: + del(self.__ssh_list[x]) + del(self.__ssh_last_request_time[x]) + continue + + if cur_ssh.exit_status_ready(): + del(self.__ssh_list[x]) + del(self.__ssh_last_request_time[x]) + continue + + cur_ssh.send("exit\r\n") + del(self.__ssh_list[x]) + del(self.__ssh_last_request_time[x]) + + def run(self, sid, info): + self.__sid = sid + if not self.__sid: + return self.wsSend('WebSocketIO无效') + + self.__ssh_last_request_time[sid] = time.time() + if not sid in self.__ssh_list: + if type(info) == dict and 'host' in info: + result = self.setAttr(sid, info) + if result['status']: + return self.wsSendConnect() + else: + return self.wsSend(result['msg']) + else: + return self.wsSendReConnect() + + result = self.returnMsg(False, '') + if sid in self.__ssh_list: + if 'resize' in info: + self.resize(sid, info) + result = self.returnMsg(True, '已连接') + if result['status']: + if type(info) == str: + time.sleep(0.1) + cur_ssh = self.__ssh_list[sid] + if cur_ssh.exit_status_ready(): + self.wsSend("logout\r\n") + del(self.__ssh_list[sid]) + return + cur_ssh.send(info) + try: + time.sleep(0.005) + recv = cur_ssh.recv(8192) + return self.wsSend(recv) + except Exception as ex: + return self.wsSend('') + else: + return self.wsSend(result['msg']) diff --git a/cli.sh b/cli.sh index 3ce41d7260..5016c4b710 100755 --- a/cli.sh +++ b/cli.sh @@ -47,7 +47,8 @@ mw_start_debug(){ if [ -f /www/server/mdserver-web/data/port.pl ];then port=$(cat /www/server/mdserver-web/data/port.pl) fi - gunicorn -b :${port} -k gevent -w 1 app:app + # gunicorn -b :${port} -k gevent -w 1 app:app + gunicorn -b :${port} -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker -w 1 app:app } mw_start_debug2(){ diff --git a/data/json/type.json b/data/json/type.json index 58365b2829..2943516b5d 100755 --- a/data/json/type.json +++ b/data/json/type.json @@ -4,6 +4,11 @@ "type":0, "ps":"" }, + { + "title":"已安装", + "type":-1, + "ps":"" + }, { "title":"运行环境", "type":1, diff --git a/plugins/backup_ftp/info.json b/plugins/backup_ftp/info.json index a899fb0a78..8e8cfb3cef 100755 --- a/plugins/backup_ftp/info.json +++ b/plugins/backup_ftp/info.json @@ -11,7 +11,7 @@ "checks":"server/backup_ftp", "path": "server/backup_ftp", "author":"midoks", - "home":"", + "home":"https://github.com/midoks/mdserver-web", "date":"2022-10-23", "pid": "4" } \ No newline at end of file diff --git a/plugins/imail/versions/0.0.19/install.sh b/plugins/imail/versions/0.0.19/install.sh index 2f927edcff..3e10c02e0d 100755 --- a/plugins/imail/versions/0.0.19/install.sh +++ b/plugins/imail/versions/0.0.19/install.sh @@ -10,8 +10,8 @@ serverPath=$(dirname "$rootPath") install_tmp=${rootPath}/tmp/mw_install.pl VERSION=0.0.19 -# bash install.sh install 0.0.16 -## cd /www/server/mdserver-web/plugins/imail && bash install.sh install 0.0.16 +# bash install.sh install 0.0.19 +## cd /www/server/mdserver-web/plugins/imail && bash install.sh install 0.0.19 bash ${rootPath}/scripts/getos.sh OSNAME=`cat ${rootPath}/data/osname.pl` @@ -24,7 +24,7 @@ get_arch() { TMP_ARCH=`arch` if [ "$TMP_ARCH" == "x86_64" ];then ARCH="amd64" - else if [ "$TMP_ARCH" == "aarch64" ];then + elif [ "$TMP_ARCH" == "aarch64" ];then ARCH="arm64" else echo $ARCH diff --git a/plugins/mariadb/js/mariadb.js b/plugins/mariadb/js/mariadb.js index 3d450c91a9..171fcd643c 100755 --- a/plugins/mariadb/js/mariadb.js +++ b/plugins/mariadb/js/mariadb.js @@ -797,7 +797,7 @@ function openPhpmyadmin(name,username,password){ data = syncPost('/plugins/run',{'name':'phpmyadmin','func':'get_cfg'}); var rdata = $.parseJSON(data.data); if (rdata.choose != 'mariadb'){ - layer.msg('当前为['+rdata.choose+']模式,若要使用请切换模式.',{icon:2,shade: [0.3, '#000']}); + layer.msg('当前为['+rdata.choose+']模式,若要使用请修改phpMyAdmin访问切换.',{icon:2,shade: [0.3, '#000']}); return; } var phpmyadmin_cfg = rdata; diff --git a/plugins/mysql-apt/js/mysql-apt.js b/plugins/mysql-apt/js/mysql-apt.js index ba8930bc9f..476c8f4ce2 100755 --- a/plugins/mysql-apt/js/mysql-apt.js +++ b/plugins/mysql-apt/js/mysql-apt.js @@ -819,7 +819,7 @@ function openPhpmyadmin(name,username,password){ data = syncPost('/plugins/run',{'name':'phpmyadmin','func':'get_cfg'}); var rdata = $.parseJSON(data.data); if (rdata.choose != 'mysql-apt'){ - layer.msg('当前为['+rdata.choose+']模式,若要使用请切换模式.',{icon:2,shade: [0.3, '#000']}); + layer.msg('当前为['+rdata.choose+']模式,若要使用请修改phpMyAdmin访问切换.',{icon:2,shade: [0.3, '#000']}); return; } diff --git a/plugins/mysql-yum/js/mysql-yum.js b/plugins/mysql-yum/js/mysql-yum.js index abed6ed4a4..258b670266 100755 --- a/plugins/mysql-yum/js/mysql-yum.js +++ b/plugins/mysql-yum/js/mysql-yum.js @@ -819,7 +819,7 @@ function openPhpmyadmin(name,username,password){ data = syncPost('/plugins/run',{'name':'phpmyadmin','func':'get_cfg'}); var rdata = $.parseJSON(data.data); if (rdata.choose != 'mysql-yum'){ - layer.msg('当前为['+rdata.choose+']模式,若要使用请切换模式.',{icon:2,shade: [0.3, '#000']}); + layer.msg('当前为['+rdata.choose+']模式,若要使用请修改phpMyAdmin访问切换.',{icon:2,shade: [0.3, '#000']}); return; } diff --git a/plugins/mysql/js/mysql.js b/plugins/mysql/js/mysql.js index 2173510392..97fb0c74f9 100755 --- a/plugins/mysql/js/mysql.js +++ b/plugins/mysql/js/mysql.js @@ -819,7 +819,7 @@ function openPhpmyadmin(name,username,password){ data = syncPost('/plugins/run',{'name':'phpmyadmin','func':'get_cfg'}); var rdata = $.parseJSON(data.data); if (rdata.choose != 'mysql'){ - layer.msg('当前为['+rdata.choose+']模式,若要使用请切换模式.',{icon:2,shade: [0.3, '#000']}); + layer.msg('当前为['+rdata.choose+']模式,若要使用请修改phpMyAdmin访问切换.',{icon:2,shade: [0.3, '#000']}); return; } diff --git a/plugins/webssh/img/ico-cmd.png b/plugins/webssh/img/ico-cmd.png new file mode 100644 index 0000000000..c88796d6bd Binary files /dev/null and b/plugins/webssh/img/ico-cmd.png differ diff --git a/plugins/webssh/index.html b/plugins/webssh/index.html index a53f8768ef..59bacbcf16 100755 --- a/plugins/webssh/index.html +++ b/plugins/webssh/index.html @@ -1,7 +1,5 @@ + + + {% for menu in data['hook_menu'] %} + {% if menu['js_path'] %} + + + {% endif %} + {% endfor %} @@ -33,12 +41,18 @@

{{data['ip']}}