mirror of https://github.com/midoks/mdserver-web
pull/350/head
parent
2c3ad7ea99
commit
832f3b45eb
@ -0,0 +1,446 @@ |
||||
# coding: utf-8 |
||||
|
||||
# --------------------------------------------------------------------------------- |
||||
# MW-Linux面板 |
||||
# --------------------------------------------------------------------------------- |
||||
# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. |
||||
# --------------------------------------------------------------------------------- |
||||
# Author: midoks <midoks@163.com> |
||||
# --------------------------------------------------------------------------------- |
||||
|
||||
# --------------------------------------------------------------------------------- |
||||
# 公共操作 |
||||
# --------------------------------------------------------------------------------- |
||||
|
||||
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 |
||||
__port = 22 |
||||
__user = None |
||||
__pass = None |
||||
__pkey = None |
||||
__key_passwd = None |
||||
|
||||
__rep_ssh_config = False |
||||
__rep_ssh_service = False |
||||
__sshd_config_backup = None |
||||
|
||||
__connecting = False |
||||
__ssh = None |
||||
__tp = None |
||||
__ps = None |
||||
|
||||
def __init__(self): |
||||
pass |
||||
|
||||
def debug(self, msg): |
||||
msg = "{} - {}:{} => {} \n".format(mw.formatDate(), |
||||
self.__host, self.__port, msg) |
||||
# self.history_send(msg) |
||||
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): |
||||
# self.connectBySocket() |
||||
if self.__host in ['127.0.0.1', 'localhost']: |
||||
return self.connectLocalSsh() |
||||
else: |
||||
return self.connectBySocket() |
||||
|
||||
def connectLocalSsh(self): |
||||
self.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:认证成功,正在构建会话通道') |
||||
self.__ssh = self.__ps.invoke_shell(term='xterm', width=83, height=21) |
||||
self.__ssh.setblocking(0) |
||||
self.__connect_time = time.time() |
||||
self.__last_send = [] |
||||
mw.writeLog(self.__log_type, '成功登录到SSH服务器 [{}:{}]'.format( |
||||
self.__host, self.__port)) |
||||
self.debug('local-ssh:通道已构建') |
||||
return self.returnMsg(True, '连接成功!') |
||||
|
||||
def connectBySocket(self): |
||||
if not self.__host: |
||||
return self.returnMsg(False, '错误的连接地址') |
||||
if not self.__user: |
||||
self.__user = 'root' |
||||
if not self.__port: |
||||
self.__port = 22 |
||||
|
||||
if self.__host in ['127.0.0.1', 'localhost']: |
||||
self.__port = mw.getSSHPort() |
||||
|
||||
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)) |
||||
if self.__host in ['127.0.0.1', 'localhost']: |
||||
return self.returnMsg(False, '连接目标服务器失败: {}'.format("Authentication failed ," + self.__user + "@" + self.__host + ":" + str(self.__port))) |
||||
return self.returnMsg(False, '连接目标服务器失败, {}:{}'.format(self.__host, self.__port)) |
||||
else: |
||||
time.sleep(0.2) |
||||
|
||||
# print(sock) |
||||
self.__tp = paramiko.Transport(sock) |
||||
try: |
||||
self.__tp.start_client() |
||||
self.__tp.banner_timeout = 60 |
||||
if not self.__pass or not self.__pkey: |
||||
return self.returnMsg(False, '密码或私钥不能都为空: {}:{}'.format(self.__host, self.__port)) |
||||
|
||||
if self.__pkey: |
||||
self.debug('正在认证私钥') |
||||
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: |
||||
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) |
||||
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('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('认证成功,正在构建会话通道') |
||||
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 = [] |
||||
mw.writeLog(self.__log_type, '成功登录到SSH服务器 [{}:{}]'.format( |
||||
self.__host, self.__port)) |
||||
self.debug('通道已构建') |
||||
return self.returnMsg(True, '连接成功.') |
||||
|
||||
def setAttr(self, info): |
||||
self.__host = info['host'].strip() |
||||
|
||||
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'] |
||||
try: |
||||
result = self.connect() |
||||
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 heartbeat(self): |
||||
while True: |
||||
time.sleep(0.1) |
||||
if self.__tp and self.__tp.is_active(): |
||||
self.__tp.send_ignore() |
||||
else: |
||||
break |
||||
|
||||
if self.__ps and self.__ps.is_active(): |
||||
self.__ps.send_ignore() |
||||
else: |
||||
break |
||||
|
||||
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 run(self, sid, info): |
||||
self.__sid = sid |
||||
if not self.__sid: |
||||
return self.wsSend('WebSocketIO无效') |
||||
|
||||
if self.__connecting and not 'host' in info: |
||||
return |
||||
|
||||
if not self.__ssh: |
||||
self.__connecting = True |
||||
result = self.setAttr(info) |
||||
self.__connecting = False |
||||
else: |
||||
result = self.returnMsg(True, '已连接') |
||||
|
||||
if result['status']: |
||||
if type(info) == str: |
||||
time.sleep(0.1) |
||||
self.__ssh.send(info) |
||||
|
||||
try: |
||||
time.sleep(0.005) |
||||
recv = self.__ssh.recv(8192) |
||||
return self.wsSend(recv) |
||||
except Exception as ex: |
||||
return self.wsSend('') |
||||
else: |
||||
return self.wsSend(result['msg']) |
||||
|
||||
def getSshDir(self): |
||||
if mw.isAppleSystem(): |
||||
user = mw.execShell( |
||||
"who | sed -n '2, 1p' |awk '{print $1}'")[0].strip() |
||||
return '/Users/' + user + '/.ssh' |
||||
return '/root/.ssh' |
||||
|
||||
def createRsa(self): |
||||
ssh_dir = self.getSshDir() |
||||
ssh_ak = ssh_dir + '/authorized_keys' |
||||
if not os.path.exists(ssh_ak): |
||||
mw.execShell('touch ' + ssh_ak) |
||||
if not os.path.exists(ssh_dir + '/id_rsa.pub') and os.path.exists(ssh_dir + '/id_rsa'): |
||||
cmd = 'echo y | ssh-keygen -q -t rsa -P "" -f ' + ssh_dir + '/id_rsa' |
||||
mw.execShell(cmd) |
||||
else: |
||||
cmd = 'ssh-keygen -q -t rsa -P "" -f ' + ssh_dir + '/id_rsa' |
||||
mw.execShell(cmd) |
||||
cmd = 'cat ' + ssh_dir + '/id_rsa.pub >> ' + ssh_dir + '/authorized_keys' |
||||
mw.execShell(cmd) |
||||
cmd = 'chmod 600 ' + ssh_dir + '/authorized_keys' |
||||
mw.execShell(cmd) |
||||
|
||||
def createSshInfo(self): |
||||
ssh_dir = self.getSshDir() |
||||
if not os.path.exists(ssh_dir + '/id_rsa') or not os.path.exists(ssh_dir + '/id_rsa.pub'): |
||||
self.createRsa() |
||||
# 检查是否写入authorized_keys |
||||
cmd = "cat " + ssh_dir + "/id_rsa.pub | awk '{print $3}'" |
||||
data = mw.execShell(cmd) |
||||
if data[0] != "": |
||||
cmd = "cat " + ssh_dir + "/authorized_keys | grep " + data[0] |
||||
ak_data = mw.execShell(cmd) |
||||
if ak_data[0] == "": |
||||
cmd = 'cat ' + ssh_dir + '/id_rsa.pub >> ' + ssh_dir + '/authorized_keys' |
||||
mw.execShell(cmd) |
||||
cmd = 'chmod 600 ' + ssh_dir + '/authorized_keys' |
||||
mw.execShell(cmd) |
@ -0,0 +1,143 @@ |
||||
function Terms_WebSocketIO (el, config) { |
||||
|
||||
console.log(config); |
||||
if (typeof config == "undefined") { |
||||
config = {}; |
||||
} |
||||
this.el = el; |
||||
this.id = config.ssh_info.id || ''; |
||||
this.ws = null; //websocket对象
|
||||
this.route = 'webssh_websocketio'; // 访问的方法
|
||||
this.term = null; //term对象
|
||||
this.info = null; // 请求数据
|
||||
this.last_body = null; |
||||
this.fontSize = 14; //终端字体大小
|
||||
this.ssh_info = config.ssh_info; |
||||
this.term_timer = null; |
||||
this.run(); |
||||
} |
||||
|
||||
var socket, gterm; |
||||
|
||||
Terms_WebSocketIO.prototype = { |
||||
// websocket持久化连接
|
||||
connect: function (callback) { |
||||
if(!this.ws){ |
||||
this.ws = io.connect(); |
||||
} |
||||
}, |
||||
|
||||
//连接服务器成功
|
||||
on_open: function (ws_event) { |
||||
var http_token = $("#request_token_head").attr('token'); |
||||
this.ssh_info['x-http-token'] = http_token; |
||||
|
||||
this.send(JSON.stringify(this.ssh_info || { 'x-http-token': http_token })) |
||||
this.term.FitAddon.fit(); |
||||
this.resize({ cols: this.term.cols, rows: this.term.rows }); |
||||
}, |
||||
|
||||
//服务器消息事件
|
||||
on_message: function (ws_event) { |
||||
// console.log(ws_event.data);
|
||||
this.term.write(ws_event.data); |
||||
if (ws_event.data == '\r\n登出\r\n' || ws_event.data == '登出\r\n' ||
|
||||
ws_event.data == '\r\nlogout\r\n' || ws_event.data == 'logout\r\n'|| |
||||
ws_event.data == '\r\nexit\r\n' || ws_event.data == 'exit\r\n') { |
||||
this.term.destroy(); |
||||
clearInterval(this.term_timer); |
||||
} |
||||
}, |
||||
|
||||
//websocket关闭事件
|
||||
on_close: function (ws_event) { |
||||
this.ws = null; |
||||
}, |
||||
|
||||
//websocket错误事件
|
||||
on_error: function (ws_event) { |
||||
if (ws_event.target.readyState === 3) { |
||||
// var msg = '错误: 无法创建WebSocket连接,请在面板设置页面关闭【开发者模式】';
|
||||
// layer.msg(msg,{time:5000})
|
||||
// if(Term.state === 3) return
|
||||
// Term.term.write(msg)
|
||||
// Term.state = 3;
|
||||
} else { |
||||
console.log(ws_event) |
||||
} |
||||
}, |
||||
//发送数据
|
||||
//@param event 唯一事件名称
|
||||
//@param data 发送的数据
|
||||
//@param callback 服务器返回结果时回调的函数,运行完后将被回收
|
||||
send: function (data, num) { |
||||
var that = this; |
||||
//如果没有连接,则尝试连接服务器
|
||||
if (!this.ws || this.bws.readyState == 3 || this.bws.readyState == 2) { |
||||
this.connect(); |
||||
} |
||||
//判断当前连接状态,如果!=1,则100ms后尝试重新发送
|
||||
if (this.ws.readyState === 1) { |
||||
this.ws.send(data); |
||||
} else { |
||||
if (this.state === 3) return; |
||||
if (!num) num = 0; |
||||
if (num < 5) { |
||||
num++; |
||||
setTimeout(function () { that.send(data, num++); }, 100) |
||||
} |
||||
} |
||||
}, |
||||
|
||||
//关闭连接
|
||||
close: function () { |
||||
this.ws.close(); |
||||
this.set_term_icon(0); |
||||
}, |
||||
resize: function (size) { |
||||
if (this.ws) { |
||||
size['resize'] = 1; |
||||
this.send(JSON.stringify(size)); |
||||
} |
||||
|
||||
}, |
||||
run: function (ssh_info) { |
||||
this.connect(); |
||||
|
||||
var that = this;
|
||||
var termCols = 83; |
||||
var termRows = 21; |
||||
this.term = new Terminal({ cols: termCols, rows: termRows, screenKeys: true, useStyle: true}); |
||||
|
||||
this.term.open($('#'+this.id)[0]); |
||||
this.term.setOption('cursorBlink', true); |
||||
this.term.setOption('fontSize', this.fontSize); |
||||
|
||||
// console.log(this.term.cols,this.term.rows);
|
||||
// this.term.fit();
|
||||
// console.log(this.term.cols,this.term.rows);
|
||||
|
||||
// this.term.FitAddon = new window.FitAddon();
|
||||
// this.term.loadAddon(this.term.FitAddon);
|
||||
// this.term.WebLinksAddon = new WebLinksAddon.WebLinksAddon();
|
||||
// this.term.loadAddon(this.term.WebLinksAddon);
|
||||
|
||||
this.ws.on('server_response', function (ev) { that.on_message(ev)}); |
||||
|
||||
if (this.ws) { |
||||
that.ws.emit('webssh_websocketio', ''); |
||||
this.term_timer = setInterval(function () { |
||||
that.ws.emit('webssh_websocketio', ''); |
||||
}, 500); |
||||
} |
||||
|
||||
this.term.on('data', function (data) { |
||||
that.ws.emit('webssh_websocketio', data); |
||||
}); |
||||
|
||||
// $('.terminal').detach().appendTo('#'+this.id);
|
||||
this.ws.emit('webssh_websocketio', this.ssh_info); |
||||
this.ws.emit('webssh_websocketio', '\n'); |
||||
this.term.focus(); |
||||
} |
||||
} |
Loading…
Reference in new issue