mirror of
https://github.com/aaPanel/aaPanel.git
synced 2026-08-17 21:25:47 +02:00
1. Add disk IO information to the homepage 2. Add a clear list button in the upload window 3. Optimize the background task overhead of the panel 4. Optimize the disk information caching mechanism 5. Panel Pro edition is online 6. Optimize panel resource usage 7. Optimize SSL certificate renewal 8. Fix the problem of reporting an error when the security entrance is empty 9. Fix the problem of infinite recursion when copying directories in extreme cases
1050 lines
36 KiB
Python
1050 lines
36 KiB
Python
#coding: utf-8
|
|
# +-------------------------------------------------------------------
|
|
# | 宝塔Linux面板
|
|
# +-------------------------------------------------------------------
|
|
# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
|
|
# +-------------------------------------------------------------------
|
|
# | Author: hwliang <hwl@bt.cn>
|
|
# +-------------------------------------------------------------------
|
|
import json
|
|
import time
|
|
import os
|
|
import sys
|
|
import socket
|
|
import threading
|
|
import re
|
|
|
|
|
|
if not 'class/' in sys.path:
|
|
sys.path.insert(0,'class/')
|
|
from io import BytesIO, StringIO
|
|
|
|
def returnMsg(status,msg,value=None):
|
|
if value:
|
|
msg = public.getMsg(msg,value)
|
|
return {'status':status,'msg':msg}
|
|
|
|
import public
|
|
|
|
class ssh_terminal:
|
|
_panel_path = '/www/server/panel'
|
|
_save_path = _panel_path + '/config/ssh_info/'
|
|
_host = None
|
|
_port = 22
|
|
_user = None
|
|
_pass = None
|
|
_pkey = None
|
|
_ws = None
|
|
_ssh = None
|
|
_last_cmd = ""
|
|
_last_cmd_tip = 0
|
|
_log_type = public.getMsg('TYPE_TERMINAL')
|
|
_history_len = 0
|
|
_client = ""
|
|
_rep_ssh_config = False
|
|
_sshd_config_backup = None
|
|
_rep_ssh_service = False
|
|
_tp = None
|
|
_old_conf = None
|
|
_debug_file = 'logs/terminal.log'
|
|
|
|
def connect(self):
|
|
'''
|
|
@name 连接服务器
|
|
@author hwliang<2020-08-07>
|
|
@return dict{
|
|
status: bool 状态
|
|
msg: string 详情
|
|
}
|
|
'''
|
|
if not self._host: return returnMsg(False,'WRONG_CONN_ADDR')
|
|
|
|
if not self._user: self._user = 'root'
|
|
if not self._port: self._port = 22
|
|
self.is_local()
|
|
|
|
if self._host in ['127.0.0.1','localhost']:
|
|
self._port = public.get_ssh_port()
|
|
|
|
num = 0
|
|
while num < 5:
|
|
num +=1
|
|
try:
|
|
self.debug(public.getMsg('RECONN_TIMES',(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.set_sshd_config(True)
|
|
self.debug(public.getMsg('RECONN_FAILED',(e,)))
|
|
if self._host in ['127.0.0.1','localhost']:
|
|
return returnMsg(False,'CONN_FAIL',("Authentication failed ," + self._user + "@" + self._host + ":" +str(self._port),))
|
|
return returnMsg(False,'CONN_FAIL1',(self._host,self._port))
|
|
else:
|
|
time.sleep(0.2)
|
|
|
|
|
|
import paramiko
|
|
|
|
self._tp = paramiko.Transport(sock)
|
|
|
|
try:
|
|
self._tp.start_client()
|
|
if not self._pass and not self._pkey:
|
|
self.set_sshd_config(True)
|
|
return public.returnMsg(False,'SSH_LOGIN_INFO_ERR',(self._host,str(self._port)))
|
|
self._tp.banner_timeout=60
|
|
if self._pkey:
|
|
self.debug(public.getMsg('AUTH_PRI_KEY'))
|
|
if sys.version_info[0] == 2:
|
|
try:
|
|
self._pkey = self._pkey.encode('utf-8')
|
|
except:
|
|
pass
|
|
p_file = BytesIO(self._pkey)
|
|
else:
|
|
p_file = StringIO(self._pkey)
|
|
try:
|
|
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:
|
|
self.debug(public.getMsg('AUTH_PASSWD'))
|
|
self._tp.auth_password(username=self._user, password=self._pass)
|
|
except Exception as e:
|
|
if self._old_conf:
|
|
s_file = '/www/server/panel/config/t_info.json'
|
|
if os.path.exists(s_file): os.remove(s_file)
|
|
self.set_sshd_config(True)
|
|
self._tp.close()
|
|
e = str(e)
|
|
if e.find('Authentication failed') != -1:
|
|
self.debug(public.getMsg('AUTH_FAIL',(str(e),)))
|
|
return returnMsg(False,'SSH_LOGIN_ERR1',(str(e + "," + self._user + "@" + self._host + ":" +str(self._port)),))
|
|
if e.find('Bad authentication type; allowed types') != -1:
|
|
self.debug(public.getMsg('AUTH_FAIL',(str(e),)))
|
|
if self._host in ['127.0.0.1','localhost'] and self._pass == 'none':
|
|
return returnMsg(False,'USER_OR_PASSWD_ERR',(str("Authentication failed ," + self._user + "@" + self._host + ":" +str(self._port)),))
|
|
return returnMsg(False,'SSH_LOGIN_ERR2',(str(e)))
|
|
if e.find('Connection reset by peer') != -1:
|
|
self.debug(public.getMsg('SSH_LOGIN_ERR3'))
|
|
return returnMsg(False,'SSH_LOGIN_ERR3')
|
|
if e.find('Error reading SSH protocol banner') != -1:
|
|
self.debug('SSH_LOGIN_ERR10')
|
|
return returnMsg(False,public.getMsg('SSH_LOGIN_ERR4',(str(e),)))
|
|
if not e:
|
|
self.debug('SSH_LOGIN_ERR11')
|
|
return returnMsg(False,"SSH_LOGIN_ERR5")
|
|
err = public.get_error_info()
|
|
self.debug(err)
|
|
return returnMsg(False,public.getMsg("SSH_LOGIN_ERR6",(str(err),)))
|
|
|
|
self.debug('SSH_LOGIN_INFO3')
|
|
self._ssh = self._tp.open_session()
|
|
self._ssh.get_pty(term='xterm', width=100, height=34)
|
|
self._ssh.invoke_shell()
|
|
self._connect_time = time.time()
|
|
self._last_send = []
|
|
from BTPanel import request
|
|
self._client = public.GetClientIp() +':' + str(request.environ.get('REMOTE_PORT'))
|
|
public.WriteLog(self._log_type,'SSH_LOGIN',(self._host,str(self._port)))
|
|
self.history_send("LOGIN_SUCCESS2")
|
|
self.set_sshd_config(True)
|
|
self.debug('SSH_LOGIN_INFO2')
|
|
return returnMsg(True,'CONNECTION_SUCCEEDED')
|
|
|
|
|
|
def get_login_user(self):
|
|
'''
|
|
@name 获取本地登录用户
|
|
@author hwliang<2020-08-07>
|
|
@return string
|
|
'''
|
|
|
|
if self._user != 'root': return self._user
|
|
l_user = 'root'
|
|
ssh_config_file = '/etc/ssh/sshd_config'
|
|
ssh_config = public.readFile(ssh_config_file)
|
|
if not ssh_config: return l_user
|
|
|
|
if ssh_config.find('PermitRootLogin yes') != -1: return l_user
|
|
|
|
|
|
user_list = self.get_ulist()
|
|
login_user = ''
|
|
for u_info in user_list:
|
|
if u_info['user'] == 'root': continue
|
|
if u_info['login'] == '/bin/bash':
|
|
login_user = u_info['user']
|
|
break
|
|
|
|
if not login_user:
|
|
return l_user
|
|
|
|
return login_user
|
|
|
|
|
|
def get_ulist(self):
|
|
'''
|
|
@name 获取本地用户列表
|
|
@author hwliang<2020-08-07>
|
|
@return list
|
|
'''
|
|
u_data = public.readFile('/etc/passwd')
|
|
u_list = []
|
|
for i in u_data.split("\n"):
|
|
u_tmp = i.split(':')
|
|
if len(u_tmp) < 3: continue
|
|
u_info = {}
|
|
u_info['user'],u_info['pass'],u_info['uid'],u_info['gid'],u_info['user_msg'],u_info['home'],u_info['login'] = u_tmp
|
|
u_list.append(u_info)
|
|
return u_list
|
|
|
|
def is_local(self):
|
|
'''
|
|
@name 处理本地连接
|
|
@author hwliang<2020-08-07>
|
|
@ps 如果host为127.0.0.1或localhost,则尝试自动使用publicKey登录
|
|
@return void
|
|
'''
|
|
|
|
if self._pass: return
|
|
if self._pkey: return
|
|
if self._host in ['127.0.0.1','localhost']:
|
|
try:
|
|
self._port = public.get_ssh_port()
|
|
self.set_sshd_config()
|
|
s_file = '/www/server/panel/config/t_info.json'
|
|
if os.path.exists(s_file):
|
|
ssh_info = json.loads(public.en_hexb(public.readFile(s_file)))
|
|
self._host = ssh_info['host'].strip()
|
|
if 'username' in ssh_info:
|
|
self._user = ssh_info['username']
|
|
if 'pkey' in ssh_info:
|
|
self._pkey = ssh_info['pkey']
|
|
if 'password' in ssh_info:
|
|
self._pass = ssh_info['password']
|
|
self._old_conf = True
|
|
return
|
|
|
|
login_user = self.get_login_user()
|
|
if self._user == 'root' and login_user == 'root':
|
|
id_rsa_file = ['/root/.ssh/id_rsa','/root/.ssh/id_rsa_bt']
|
|
for ifile in id_rsa_file:
|
|
if os.path.exists(ifile):
|
|
self._pkey = public.readFile(ifile)
|
|
host_path = self._save_path + self._host
|
|
if not os.path.exists(host_path):
|
|
os.makedirs(host_path,384)
|
|
return
|
|
|
|
|
|
if not self._pass or not self._pkey or not self._user:
|
|
home_path = '/home/' + login_user
|
|
if login_user == 'root':
|
|
home_path = '/root'
|
|
self._user = login_user
|
|
id_rsa_file = [home_path + '/.ssh/id_rsa',home_path + '/.ssh/id_rsa_bt']
|
|
for ifile in id_rsa_file:
|
|
if os.path.exists(ifile):
|
|
self._pkey = public.readFile(ifile)
|
|
return
|
|
|
|
self._pass = 'none'
|
|
return
|
|
# _ssh_ks = home_path + '/.ssh'
|
|
# if not os.path.exists(_ssh_ks):
|
|
# os.makedirs(_ssh_ks,384)
|
|
# os.system("ssh-keygen -t rsa -P '' -f {}/.ssh/id_rsa |echo y".format(home_path))
|
|
# pub_file = home_path + '/.ssh/id_rsa.pub'
|
|
# az_file = home_path + '/.ssh/authorized_keys'
|
|
# rsa_file = home_path + '/.ssh/id_rsa'
|
|
# public.ExecShell('cat {} >> {} && chmod 600 {} {}'.format(pub_file, az_file, az_file,rsa_file))
|
|
# os.remove(pub_file)
|
|
# public.ExecShell("chown -R {}:{} {}".format(self._user,self._user,_ssh_ks))
|
|
# public.ExecShell("chmod -R 600 {}".format(_ssh_ks))
|
|
# self._pkey = public.readFile(rsa_file)
|
|
|
|
|
|
except:
|
|
return
|
|
|
|
def get_sys_version(self):
|
|
'''
|
|
@name 获取操作系统版本
|
|
@author hwliang<2020-08-13>
|
|
@return bool
|
|
'''
|
|
version = public.readFile('/etc/redhat-release')
|
|
if not version:
|
|
version = public.readFile('/etc/issue').strip().split("\n")[0].replace('\\n','').replace('\l','').strip()
|
|
else:
|
|
version = version.replace('release ','').replace('Linux','').replace('(Core)','').strip()
|
|
return version
|
|
|
|
def get_ssh_status(self):
|
|
'''
|
|
@name 获取SSH服务状态
|
|
@author hwliang<2020-08-13>
|
|
@return bool
|
|
'''
|
|
version = self.get_sys_version()
|
|
if os.path.exists('/usr/bin/apt-get'):
|
|
if os.path.exists('/etc/init.d/sshd'):
|
|
status = public.ExecShell("service sshd status | grep -P '(dead|stop|not running)'|grep -v grep")
|
|
else:
|
|
status = public.ExecShell("service ssh status | grep -P '(dead|stop|not running)'|grep -v grep")
|
|
else:
|
|
if version.find(' 7.') != -1 or version.find(' 8.') != -1 or version.find('Fedora') != -1:
|
|
status = public.ExecShell("systemctl status sshd.service | grep 'dead'|grep -v grep")
|
|
else:
|
|
status = public.ExecShell("/etc/init.d/sshd status | grep -e 'stopped' -e '已停'|grep -v grep")
|
|
if len(status[0]) > 3:
|
|
status = False
|
|
else:
|
|
status = True
|
|
return status
|
|
|
|
def is_running(self,rep = False):
|
|
'''
|
|
@name 处理SSH服务状态
|
|
@author hwliang<2020-08-13>
|
|
@param rep<bool> 是否恢复原来的SSH服务状态
|
|
@return bool
|
|
'''
|
|
try:
|
|
if rep and self._rep_ssh_service:
|
|
self.restart_ssh('stop')
|
|
return True
|
|
|
|
ssh_status = self.get_ssh_status()
|
|
if not ssh_status:
|
|
self.restart_ssh('start')
|
|
self._rep_ssh_service = True
|
|
return True
|
|
return False
|
|
except:
|
|
return False
|
|
|
|
|
|
def set_sshd_config(self,rep = False):
|
|
'''
|
|
@name 设置本地SSH配置文件,以支持pubkey认证
|
|
@author hwliang<2020-08-13>
|
|
@param rep<bool> 是否恢复ssh配置文件
|
|
@return bool
|
|
'''
|
|
self.is_running(rep)
|
|
return False
|
|
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 = public.readFile(sshd_config_file)
|
|
|
|
if not sshd_config:
|
|
return False
|
|
|
|
if rep:
|
|
if self._sshd_config_backup:
|
|
public.writeFile(sshd_config_file,self._sshd_config_backup)
|
|
self.restart_ssh()
|
|
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:
|
|
public.writeFile(sshd_config_file,sshd_config)
|
|
self._rep_ssh_config = True
|
|
self.restart_ssh()
|
|
else:
|
|
self._sshd_config_backup = None
|
|
|
|
return True
|
|
except:
|
|
return False
|
|
|
|
def restart_ssh(self,act = 'reload'):
|
|
'''
|
|
重启ssh 无参数传递
|
|
'''
|
|
version = public.readFile('/etc/redhat-release')
|
|
if not os.path.exists('/etc/redhat-release'):
|
|
public.ExecShell('service ssh ' + act)
|
|
elif version.find(' 7.') != -1 or version.find(' 8.') != -1:
|
|
public.ExecShell("systemctl " + act + " sshd.service")
|
|
else:
|
|
public.ExecShell("/etc/init.d/sshd " + act)
|
|
|
|
def resize(self, data):
|
|
'''
|
|
@name 调整终端大小
|
|
@author hwliang<2020-08-07>
|
|
@param data<dict> 终端尺寸数据
|
|
{
|
|
cols: int 列
|
|
rows: int 行
|
|
}
|
|
@return bool
|
|
'''
|
|
try:
|
|
data = json.loads(data)
|
|
self._ssh.resize_pty(width=data['cols'], height=data['rows'])
|
|
return True
|
|
except:
|
|
return False
|
|
|
|
|
|
def recv(self):
|
|
'''
|
|
@name 读取tty缓冲区数据
|
|
@author hwliang<2020-08-07>
|
|
@return void
|
|
'''
|
|
n = 0
|
|
try:
|
|
while not self._ws.closed:
|
|
resp_line = self._ssh.recv(1024)
|
|
if not resp_line:
|
|
if not self._tp.is_active():
|
|
self.debug(public.getMsg('SSH_LOGIN_ERR14'))
|
|
self._ws.send(public.getMsg('RECONNECT_SSH'))
|
|
self.close()
|
|
return
|
|
|
|
if not resp_line:
|
|
n+=1
|
|
if n > 5: break
|
|
continue
|
|
n = 0
|
|
if self._ws.closed:
|
|
return
|
|
try:
|
|
result = resp_line.decode('utf-8','ignore')
|
|
except:
|
|
try:
|
|
result = resp_line.decode()
|
|
except:
|
|
result = str(resp_line)
|
|
|
|
self._ws.send(result)
|
|
|
|
self.history_recv(result)
|
|
except Exception as e:
|
|
e = str(e)
|
|
if e.find('closed') != -1:
|
|
self.debug('SSH_LOGIN_INFO')
|
|
elif not self._ws.closed:
|
|
self.debug(public.getMsg('SSH_LOGIN_ERR15',(str(e),)))
|
|
|
|
if self._ws.closed:
|
|
self.debug(public.getMsg('SSH_LOGIN_INFO1'))
|
|
self.close()
|
|
|
|
def send(self):
|
|
'''
|
|
@name 写入数据到缓冲区
|
|
@author hwliang<2020-08-07>
|
|
@return void
|
|
'''
|
|
try:
|
|
while not self._ws.closed:
|
|
client_data = self._ws.receive()
|
|
if not client_data: continue
|
|
if len(client_data) > 10:
|
|
if client_data.find('{"host":"') != -1:
|
|
continue
|
|
if client_data.find('"resize":1') != -1:
|
|
self.resize(client_data)
|
|
continue
|
|
self._ssh.send(client_data)
|
|
self.history_send(client_data)
|
|
except Exception as ex:
|
|
ex = str(ex)
|
|
if ex.find('_io.BufferedReader') != -1:
|
|
self.debug(public.getMsg('SSH_LOGIN_ERR16'))
|
|
self.send()
|
|
return
|
|
elif ex.find('closed') != -1:
|
|
self.debug('SSH_LOGIN_INFO')
|
|
else:
|
|
self.debug(public.getMsg('SSH_LOGIN_ERR17',(str(ex),)))
|
|
|
|
if self._ws.closed:
|
|
self.debug(public.getMsg('SSH_LOGIN_INFO1'))
|
|
self.close()
|
|
|
|
|
|
def history_recv(self,recv_data):
|
|
'''
|
|
@name 从接收实体保存命令
|
|
@author hwliang<2020-08-12>
|
|
@param recv_data<string> 数据实体
|
|
@return void
|
|
'''
|
|
#处理TAB补登
|
|
if self._last_cmd_tip == 1:
|
|
if not recv_data.startswith('\r\n'):
|
|
self._last_cmd += recv_data.replace('\u0007','').strip()
|
|
self._last_cmd_tip = 0
|
|
|
|
#上下切换命令
|
|
if self._last_cmd_tip == 2:
|
|
self._last_cmd = recv_data.strip().replace("\x08","").replace("\x07","").replace("\x1b[K","")
|
|
self._last_cmd_tip = 0
|
|
|
|
def history_send(self,send_data):
|
|
'''
|
|
@name 从发送实体保存命令
|
|
@author hwliang<2020-08-12>
|
|
@param send_data<string> 数据实体
|
|
@return void
|
|
'''
|
|
if not send_data: return
|
|
his_path = self._save_path + self._host
|
|
if not os.path.exists(his_path): return
|
|
his_file = his_path + '/history.pl'
|
|
|
|
#上下切换命令
|
|
if send_data in ["\x1b[A","\x1b[B"]:
|
|
self._last_cmd_tip = 2
|
|
return
|
|
|
|
#退格
|
|
if send_data == "\x7f":
|
|
self._last_cmd = self._last_cmd[:-1]
|
|
return
|
|
|
|
#过滤特殊符号
|
|
if send_data in ["\x1b[C","\x1b[D","\x1b[K","\x07","\x08","\x03","\x01","\x02","\x04","\x05","\x06","\u0007"]:
|
|
return
|
|
|
|
#Tab补全处理
|
|
if send_data == '\t':
|
|
self._last_cmd_tip = 1
|
|
return
|
|
|
|
if send_data[-1] in ['\r','\n']:
|
|
if not self._last_cmd: return
|
|
his_shell = [int(time.time()),self._client,self._user,self._last_cmd]
|
|
public.writeFile(his_file, json.dumps(his_shell) + "\n","a+")
|
|
self._last_cmd = ""
|
|
|
|
#超过5M则保留最新的200行
|
|
if os.stat(his_file).st_size > 5242880:
|
|
his_tmp = public.GetNumLines(his_file,200)
|
|
public.writeFile(his_file, his_tmp)
|
|
else:
|
|
self._last_cmd += send_data
|
|
|
|
|
|
def close(self):
|
|
'''
|
|
@name 释放连接
|
|
@author hwliang<2020-08-07>
|
|
@return void
|
|
'''
|
|
try:
|
|
if self._ssh:
|
|
self._ssh.close()
|
|
#self._ssh = None
|
|
if not self._ws.closed:
|
|
self._ws.close()
|
|
except:
|
|
pass
|
|
|
|
|
|
def set_attr(self,ssh_info):
|
|
'''
|
|
@name 设置对象属性,并连接服务器
|
|
@author hwliang<2020-08-07>
|
|
@return void
|
|
'''
|
|
self._host = ssh_info['host'].strip()
|
|
self._port = int(ssh_info['port'])
|
|
if 'username' in ssh_info:
|
|
self._user = ssh_info['username']
|
|
if 'pkey' in ssh_info:
|
|
self._pkey = ssh_info['pkey']
|
|
if 'password' in ssh_info:
|
|
self._pass = ssh_info['password']
|
|
|
|
result = self.connect()
|
|
return result
|
|
|
|
|
|
def heartbeat(self):
|
|
'''
|
|
@name 心跳包
|
|
@author hwliang<2020-09-10>
|
|
@return void
|
|
'''
|
|
while True:
|
|
time.sleep(30)
|
|
if self._tp.is_active():
|
|
self._tp.send_ignore()
|
|
else:
|
|
break
|
|
if not self._ws.closed:
|
|
self._ws.send("")
|
|
else:
|
|
break
|
|
|
|
def debug(self,msg):
|
|
'''
|
|
@name 写debug日志
|
|
@author hwliang<2020-09-10>
|
|
@return void
|
|
'''
|
|
msg = "{} - {}:{} => {} \n".format(public.format_date(),self._host,self._port,msg)
|
|
public.writeFile(self._debug_file,msg,'a+')
|
|
|
|
def run(self,web_socket, ssh_info=None):
|
|
'''
|
|
@name 启动SSH客户端对象
|
|
@author hwliang<2020-08-07>
|
|
@param web_socket<websocket> websocket句柄对像
|
|
@param ssh_info<dict> SSH信息{
|
|
host: 主机地址,
|
|
port: 端口
|
|
username: 用户名
|
|
password: 密码
|
|
pkey: 密钥(如果不为空,将使用密钥连接)
|
|
}
|
|
@return void
|
|
'''
|
|
self._ws = web_socket
|
|
if not self._ssh:
|
|
if not ssh_info:
|
|
return
|
|
result = self.set_attr(ssh_info)
|
|
else:
|
|
result = returnMsg(True,'ALREADY_CONNECTED')
|
|
if result['status']:
|
|
sendt = threading.Thread(target=self.send)
|
|
recvt = threading.Thread(target=self.recv)
|
|
ht = threading.Thread(target=self.heartbeat)
|
|
sendt.start()
|
|
recvt.start()
|
|
ht.start()
|
|
sendt.join()
|
|
recvt.join()
|
|
ht.join()
|
|
self.close()
|
|
else:
|
|
self._ws.send(result['msg'])
|
|
|
|
def __del__(self):
|
|
'''
|
|
自动释放
|
|
'''
|
|
self.close()
|
|
|
|
|
|
|
|
class ssh_host_admin(ssh_terminal):
|
|
_panel_path = '/www/server/panel'
|
|
_save_path = _panel_path + '/config/ssh_info/'
|
|
_pass_file = _panel_path + '/data/a_pass.pl'
|
|
_user_command_file = _save_path + '/user_command.json'
|
|
_sys_command_file = _save_path + '/sys_command.json'
|
|
_pass_str = None
|
|
|
|
def __init__(self):
|
|
if not os.path.exists(self._save_path):
|
|
os.makedirs(self._save_path,384)
|
|
if not os.path.exists(self._pass_file):
|
|
public.writeFile(self._pass_file,public.GetRandomString(16))
|
|
public.set_mode(self._pass_file,600)
|
|
if not self._pass_str:
|
|
self._pass_str = public.readFile(self._pass_file)
|
|
|
|
def get_host_list(self,args = None):
|
|
'''
|
|
@name 获取本机保存的SSH信息列表
|
|
@author hwliang<2020-08-07>
|
|
@param args<dict_obj or None>
|
|
@return list
|
|
'''
|
|
|
|
host_list = []
|
|
for name in os.listdir(self._save_path):
|
|
info_file = self._save_path + name +'/info.json'
|
|
if not os.path.exists(info_file): continue
|
|
info_tmp = self.get_ssh_info(name)
|
|
host_info = {}
|
|
host_info['host'] = name
|
|
host_info['port'] = info_tmp['port']
|
|
host_info['ps'] = info_tmp['ps']
|
|
host_info['sort'] = int(info_tmp['sort'])
|
|
|
|
host_list.append(host_info)
|
|
|
|
host_list = sorted(host_list,key=lambda x: x['sort'],reverse=False)
|
|
return host_list
|
|
|
|
def get_host_find(self,args):
|
|
'''
|
|
@name 获取指定SSH信息
|
|
@author hwliang<2020-08-07>
|
|
@param args<dict_obj>{
|
|
host: 主机地址
|
|
}
|
|
@return dict
|
|
'''
|
|
args.host = args.host.strip()
|
|
info_file = self._save_path + args.host +'/info.json'
|
|
if not os.path.exists(info_file):
|
|
return public.returnMsg(False,'SSH_LOGIN_ERR7')
|
|
info_tmp = self.get_ssh_info(args.host)
|
|
host_info = {}
|
|
host_info['host'] = args.host
|
|
host_info['port'] = info_tmp['port']
|
|
host_info['ps'] = info_tmp['ps']
|
|
host_info['sort'] = info_tmp['sort']
|
|
host_info['username'] = info_tmp['username']
|
|
host_info['password'] = info_tmp['password']
|
|
host_info['pkey'] = info_tmp['pkey']
|
|
return host_info
|
|
|
|
def modify_host(self,args):
|
|
'''
|
|
@name 修改SSH信息
|
|
@author hwliang<2020-08-07>
|
|
@param args<dict_obj>{
|
|
host: 被修改的主机地址,
|
|
new_host: 新的主机地址,
|
|
port: 端口
|
|
ps: 备注
|
|
sort: 排序(可选)
|
|
username: 用户名
|
|
password: 密码
|
|
pkey: 密钥(如果不为空,将使用密钥连接)
|
|
}
|
|
@return dict
|
|
'''
|
|
args.new_host = args.new_host.strip()
|
|
args.host = args.host.strip()
|
|
if args.host != args.new_host:
|
|
info_file = self._save_path + args.new_host +'/info.json'
|
|
if os.path.exists(info_file):
|
|
return public.returnMsg(False,'SSH_LOGIN_ERR8')
|
|
|
|
info_file = self._save_path + args.host +'/info.json'
|
|
|
|
if not os.path.exists(info_file):
|
|
return public.returnMsg(False,'SSH_LOGIN_ERR7')
|
|
|
|
if not 'sort' in args:
|
|
r_data = public.aes_decrypt(public.readFile(info_file),self._pass_str)
|
|
info_tmp = json.loads(r_data)
|
|
args.sort = info_tmp['sort']
|
|
|
|
host_info = {}
|
|
host_info['host'] = args.new_host
|
|
host_info['port'] = int(args['port'])
|
|
host_info['ps'] = args['ps']
|
|
host_info['sort'] = args['sort']
|
|
host_info['username'] = args['username']
|
|
host_info['password'] = args['password']
|
|
host_info['pkey'] = args['pkey']
|
|
if not host_info['pkey']: host_info['pkey'] = ''
|
|
result = self.set_attr(host_info)
|
|
if not result['status']: return result
|
|
self.save_ssh_info(args.host,host_info)
|
|
if args.host != args.new_host:
|
|
public.ExecShell('mv {} {}'.format(self._save_path + args.host,self._save_path + args.new_host))
|
|
public.WriteLog(self._log_type,'MODIFY_SSH_INFO',(args.host,))
|
|
return public.returnMsg(True,'EDIT_SUCCESS')
|
|
|
|
def create_host(self,args):
|
|
'''
|
|
@name 添加SSH信息
|
|
@author hwliang<2020-08-07>
|
|
@param args<dict_obj>{
|
|
host: 主机地址,
|
|
port: 端口
|
|
ps: 备注
|
|
sort: 排序(可选,默认0)
|
|
username: 用户名
|
|
password: 密码
|
|
pkey: 密钥(如果不为空,将使用密钥连接)
|
|
}
|
|
@return dict
|
|
'''
|
|
args.host = args.host.strip()
|
|
host_path = self._save_path + args.host
|
|
info_file = host_path +'/info.json'
|
|
if os.path.exists(info_file):
|
|
args.new_host = args.host
|
|
return self.modify_host(args)
|
|
#return public.returnMsg(False,'指定SSH信息已经添加过了!')
|
|
if not os.path.exists(host_path):
|
|
os.makedirs(host_path,384)
|
|
if not 'sort' in args: args.sort = 0
|
|
if not 'ps' in args: args.ps = args.host
|
|
host_info = {}
|
|
host_info['host'] = args.host
|
|
host_info['port'] = int(args['port'])
|
|
host_info['ps'] = args['ps']
|
|
host_info['sort'] = int(args['sort'])
|
|
host_info['username'] = args['username']
|
|
host_info['password'] = args['password']
|
|
host_info['pkey'] = args['pkey']
|
|
result = self.set_attr(host_info)
|
|
if not result['status']: return result
|
|
self.save_ssh_info(args.host,host_info)
|
|
public.WriteLog(self._log_type,'ADD_SSH_INFO',(str(args.host),))
|
|
return public.returnMsg(True,'SET_SUCCESS')
|
|
|
|
|
|
def remove_host(self,args):
|
|
'''
|
|
@name 删除指定SSH信息
|
|
@author hwliang<2020-08-07>
|
|
@param args<dict_obj>{
|
|
host: 主机地址
|
|
}
|
|
@return dict
|
|
'''
|
|
args.host = args.host.strip()
|
|
if not args.host: return public.returnMsg(False,'INIT_ARGS_ERR')
|
|
host_path = self._save_path + args.host
|
|
if not os.path.exists(host_path):
|
|
return public.returnMsg(False,'SSH_LOGIN_ERR7!')
|
|
public.ExecShell("rm -rf {}".format(host_path))
|
|
public.WriteLog(self._log_type,'DEL_SSH_INFO',(str(args.host),))
|
|
return public.returnMsg(True,'SET_SUCCESS')
|
|
|
|
|
|
def get_ssh_info(self,host):
|
|
'''
|
|
@name 获取并解密指定SSH信息
|
|
@author hwliang<2020-08-07>
|
|
@param host<string> 主机地址
|
|
@return dict or False
|
|
'''
|
|
info_file = self._save_path + host + '/info.json'
|
|
if not os.path.exists(info_file): return False
|
|
r_data = public.aes_decrypt(public.readFile(info_file),self._pass_str)
|
|
return json.loads(r_data)
|
|
|
|
def save_ssh_info(self,host,host_info):
|
|
'''
|
|
@name 获取并解密指定SSH信息
|
|
@author hwliang<2020-08-07>
|
|
@param host<string> 主机地址
|
|
@param host_info<dict> ssh信息字典
|
|
@return bool
|
|
'''
|
|
host_path = self._save_path + host
|
|
if not os.path.exists(host_path):
|
|
os.makedirs(host_path,384)
|
|
info_file = host_path +'/info.json'
|
|
r_data = public.aes_encrypt(json.dumps(host_info),self._pass_str)
|
|
public.writeFile(info_file,r_data)
|
|
return True
|
|
|
|
def set_sort(self,args):
|
|
'''
|
|
@name 获取并解密指定SSH信息
|
|
@author hwliang<2020-08-07>
|
|
@param args<dict_obj>{
|
|
sort_list<json>{
|
|
主机host : 排序编号,
|
|
主机host : 排序编号,
|
|
...
|
|
}
|
|
}
|
|
@return bool
|
|
'''
|
|
if not 'sort_list' in args:
|
|
return public.returnMsg(False,'SSH_LOGIN_ERR9')
|
|
sort_list = json.loads(args.sort_list)
|
|
for name in sort_list.keys():
|
|
info_file = self._save_path + name + '/info.json'
|
|
if not os.path.exists(info_file): continue
|
|
|
|
ssh_info = self.get_ssh_info(name)
|
|
ssh_info['sort'] = int(sort_list[name])
|
|
self.save_ssh_info(name,ssh_info)
|
|
return public.returnMsg(True,'SET_SUCCESS')
|
|
|
|
def get_command_list(self,args = None, user_cmd = False , sys_cmd = False):
|
|
'''
|
|
@name 获取常用命令列表
|
|
@author hwliang<2020-08-08>
|
|
@param args<dict_obj>
|
|
@param user_cmd<bool> 是否不获取用户配置
|
|
@param sys_cmd<bool> 是否不获取系统配置
|
|
@return list
|
|
'''
|
|
|
|
sys_command = []
|
|
if not sys_cmd:
|
|
if os.path.exists(self._sys_command_file):
|
|
sys_command = json.loads(public.readFile(self._sys_command_file))
|
|
|
|
user_command = []
|
|
if not user_cmd:
|
|
if os.path.exists(self._user_command_file):
|
|
user_command = json.loads(public.readFile(self._user_command_file))
|
|
|
|
command = sys_command + user_command
|
|
return command
|
|
|
|
|
|
def command_exists(self,command,title):
|
|
'''
|
|
@name 判断命令是否存在
|
|
@author hwliang<2020-08-08>
|
|
@param command<list> 常用命令列表
|
|
@param title<string> 命令标题
|
|
@return bool
|
|
'''
|
|
for cmd in command:
|
|
if cmd['title'] == title: return True
|
|
return False
|
|
|
|
def save_command(self,command,sys_cmd=False):
|
|
'''
|
|
@name 保存常用命令
|
|
@author hwliang<2020-08-08>
|
|
@param command<list> 常用命令列表
|
|
@param sys_cmd<bool> 是否为系统配置
|
|
@return void
|
|
'''
|
|
s_file = self._user_command_file
|
|
if sys_cmd:
|
|
s_file = self._sys_command_file
|
|
public.writeFile(s_file,json.dumps(command))
|
|
|
|
def create_command(self,args):
|
|
'''
|
|
@name 创建常用命令
|
|
@author hwliang<2020-08-08>
|
|
@param args<dict_obj>{
|
|
title<string> 标题
|
|
shell<string> 命令文本
|
|
}
|
|
@return dict
|
|
'''
|
|
args.title = args.title.strip()
|
|
command = self.get_command_list(sys_cmd=True)
|
|
|
|
if self.command_exists(command,args.title):
|
|
return public.returnMsg(False,'COMMAND_NAME_EXIST')
|
|
|
|
cmd = {
|
|
"title": args.title,
|
|
"shell": args.shell.strip()
|
|
}
|
|
|
|
command.append(cmd)
|
|
self.save_command(command)
|
|
public.WriteLog(self._log_type,'ADD_COMMAND_COMMAND',(str(args.title),))
|
|
return public.returnMsg(True,'SET_SUCCESS')
|
|
|
|
def get_command_find(self,args = None, title=None):
|
|
'''
|
|
@name 获取指定命令信息
|
|
@author hwliang<2020-08-08>
|
|
@param args<dict_obj>{
|
|
title<string> 标题
|
|
} 可选
|
|
@param title 标题 可选
|
|
@return dict
|
|
'''
|
|
if args: title = args.title.strip()
|
|
command = self.get_command_list()
|
|
for cmd in command:
|
|
if cmd['title'] == title:
|
|
return cmd
|
|
return public.returnMsg(False,'COMMAND_NOTEXIST')
|
|
|
|
def modify_command(self,args):
|
|
'''
|
|
@name 修改常用命令
|
|
@author hwliang<2020-08-08>
|
|
@param args<dict_obj>{
|
|
title<string> 标题
|
|
new_title<string> 新标题
|
|
shell<string> 命令文本
|
|
}
|
|
@return dict
|
|
'''
|
|
args.title = args.title.strip()
|
|
command = self.get_command_list(sys_cmd=True)
|
|
if not self.command_exists(command,args.title):
|
|
return public.returnMsg(False,'COMMAND_NOTEXIST')
|
|
for i in range(len(command)):
|
|
if command[i]['title'] == args.title:
|
|
command[i]['title'] = args.new_title
|
|
command[i]['shell'] = args.shell.strip()
|
|
break
|
|
self.save_command(command)
|
|
public.WriteLog(self._log_type,'EDIT_COMMAND_COMMAND',(str(args.title),))
|
|
return public.returnMsg(True,'SET_SUCCESS')
|
|
|
|
def remove_command(self,args):
|
|
'''
|
|
@name 删除指定命令
|
|
@author hwliang<2020-08-08>
|
|
@param args<dict_obj>{
|
|
title<string> 标题
|
|
}
|
|
@return dict
|
|
'''
|
|
args.title = args.title.strip()
|
|
command = self.get_command_list(sys_cmd=True)
|
|
if not self.command_exists(command,args.title):
|
|
return public.returnMsg(False,'COMMAND_NOTEXIST')
|
|
for i in range(len(command)):
|
|
if command[i]['title'] == args.title:
|
|
del(command[i])
|
|
break
|
|
|
|
self.save_command(command)
|
|
public.WriteLog(self._log_type,'DEL_COMMAND_COMMAND',(str(args.title),))
|
|
return public.returnMsg(True,'SET_SUCCESS') |