Commit f46cd8cd authored by ibuler's avatar ibuler

[Merge] 修改冲突

parents 518a92d0 26b5ad36
...@@ -2,16 +2,22 @@ ...@@ -2,16 +2,22 @@
# #
import os import os
import re
import socket import socket
import selectors
import telnetlib
import paramiko import paramiko
from paramiko.ssh_exception import SSHException from paramiko.ssh_exception import SSHException
from .ctx import app_service from .ctx import app_service
from .utils import get_logger, get_private_key_fingerprint from .utils import get_logger, get_private_key_fingerprint, net_input
logger = get_logger(__file__) logger = get_logger(__file__)
TIMEOUT = 10 TIMEOUT = 10
BUF_SIZE = 1024
MANUAL_LOGIN = 'manual'
AUTO_LOGIN = 'auto'
class SSHConnection: class SSHConnection:
...@@ -149,3 +155,133 @@ class SSHConnection: ...@@ -149,3 +155,133 @@ class SSHConnection:
logger.error(e) logger.error(e)
continue continue
return sock return sock
class TelnetConnection:
def __init__(self, asset, system_user, client):
self.client = client
self.asset = asset
self.system_user = system_user
self.sock = None
self.sel = selectors.DefaultSelector()
self.incorrect_pattern = re.compile(
r'incorrect|failed|失败', re.I
)
self.username_pattern = re.compile(
r'login:\s*$|username:\s*$|用户名:\s*$|账\s*号:\s*$', re.I
)
self.password_pattern = re.compile(
r'password:\s*$|passwd:\s*$|密\s*码:\s*$', re.I
)
self.success_pattern = re.compile(
r'Last\s*login|success|成功', re.I
)
def get_socket(self):
logger.info('Get telnet server socket. {}'.format(self.client.user))
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.connect((self.asset.ip, self.asset.port))
# Send SGA and ECHO options to Telnet Server
self.sock.send(telnetlib.IAC + telnetlib.DO + telnetlib.SGA)
self.sock.send(telnetlib.IAC + telnetlib.DO + telnetlib.ECHO)
self.sel.register(self.sock, selectors.EVENT_READ)
while True:
events = self.sel.select()
for sock in [key.fileobj for key, _ in events]:
data = sock.recv(BUF_SIZE)
if sock == self.sock:
logger.info(b'[Telnet server send]: ' + data)
if not data:
self.sock.close()
msg = 'The server <{}> closes the connection.'.format(
self.asset.hostname
)
logger.info(msg)
return None, msg
if data.startswith(telnetlib.IAC):
self.option_negotiate(data)
else:
result = self.login_auth(data)
if result:
msg = 'Successful asset connection.<{}>/<{}>/<{}>.'.format(
self.client.user, self.system_user.username,
self.asset.hostname
)
logger.info(msg)
self.client.send(b'\r\n' + data)
return self.sock, None
elif result is False:
self.sock.close()
msg = 'Authentication failed.\r\n'
logger.info(msg)
return None, msg
elif result is None:
continue
def option_negotiate(self, data):
"""
Telnet server option negotiate before connection
:param data: option negotiate data
:return:
"""
logger.info(b'[Server options negotiate]: ' + data)
data_list = data.split(telnetlib.IAC)
new_data_list = []
for x in data_list:
if x == telnetlib.DO + telnetlib.ECHO:
new_data_list.append(telnetlib.WONT + telnetlib.ECHO)
elif x == telnetlib.WILL + telnetlib.ECHO:
new_data_list.append(telnetlib.DO + telnetlib.ECHO)
elif x == telnetlib.WILL + telnetlib.SGA:
new_data_list.append(telnetlib.DO + telnetlib.SGA)
elif x == telnetlib.DO + telnetlib.TTYPE:
new_data_list.append(telnetlib.WILL + telnetlib.TTYPE)
elif x == telnetlib.SB + telnetlib.TTYPE + b'\x01':
new_data_list.append(telnetlib.SB + telnetlib.TTYPE + b'\x00' + b'XTERM-256COLOR')
elif telnetlib.DO in x:
new_data_list.append(x.replace(telnetlib.DO, telnetlib.WONT))
elif telnetlib.WILL in x:
new_data_list.append(x.replace(telnetlib.WILL, telnetlib.DONT))
elif telnetlib.WONT in x:
new_data_list.append(x.replace(telnetlib.WONT, telnetlib.DONT))
elif telnetlib.DONT in x:
new_data_list.append(x.replace(telnetlib.DONT, telnetlib.WONT))
else:
new_data_list.append(x)
new_data = telnetlib.IAC.join(new_data_list)
logger.info(b'[Client options negotiate]: ' + new_data)
self.sock.send(new_data)
def login_auth(self, raw_data):
logger.info('[Telnet login auth]: ({})'.format(self.client.user))
try:
data = raw_data.decode('utf-8')
except UnicodeDecodeError:
try:
data = raw_data.decode('gbk')
except UnicodeDecodeError:
logger.info(b'[Decode error]: ' + b'>>' + raw_data + b'<<')
return None
if self.incorrect_pattern.search(data):
logger.info(b'[Login incorrect prompt]: ' + b'>>' + raw_data + b'<<')
return False
elif self.username_pattern.search(data):
logger.info(b'[Username prompt]: ' + b'>>' + raw_data + b'<<')
self.sock.send(self.system_user.username.encode('utf-8') + b'\r\n')
return None
elif self.password_pattern.search(data):
logger.info(b'[Password prompt]: ' + b'>>' + raw_data + b'<<')
self.sock.send(self.system_user.password.encode('utf-8') + b'\r\n')
return None
elif self.success_pattern.search(data):
logger.info(b'[Login Success prompt]: ' + b'>>' + raw_data + b'<<')
return True
else:
logger.info(b'[No match]: ' + b'>>' + raw_data + b'<<')
return None
...@@ -86,8 +86,178 @@ class Client: ...@@ -86,8 +86,178 @@ class Client:
# print("GC: Client object has been gc") # print("GC: Client object has been gc")
class BaseServer:
"""
Base Server
Achieve command record
sub-class: Server, Telnet Server
"""
def __init__(self):
self.send_bytes = 0
self.recv_bytes = 0
self.stop_evt = threading.Event()
self.input_data = SizedList(maxsize=1024)
self.output_data = SizedList(maxsize=1024)
self._in_input_state = True
self._input_initial = False
self._in_vim_state = False
self._input = ""
self._output = ""
self._session_ref = None
def set_session(self, session):
self._session_ref = weakref.ref(session)
@property
def session(self):
if self._session_ref:
return self._session_ref()
else:
return None
def parse(self, b):
if isinstance(b, str):
b = b.encode("utf-8")
if not self._input_initial:
self._input_initial = True
if self._have_enter_char(b):
self._in_input_state = False
self._input = self._parse_input()
else:
if not self._in_input_state:
self._output = self._parse_output()
logger.debug("\n{}\nInput: {}\nOutput: {}\n{}".format(
"#" * 30 + " Command " + "#" * 30,
self._input, self._output,
"#" * 30 + " End " + "#" * 30,
))
if self._input:
self.session.put_command(self._input, self._output)
self.input_data.clean()
self.output_data.clean()
self._in_input_state = True
@staticmethod
def _have_enter_char(s):
for c in char.ENTER_CHAR:
if c in s:
return True
return False
def _parse_output(self):
if not self.output_data:
return ''
parser = utils.TtyIOParser()
return parser.parse_output(self.output_data)
def _parse_input(self):
if not self.input_data or self.input_data[0] == char.RZ_PROTOCOL_CHAR:
return
parser = utils.TtyIOParser()
return parser.parse_input(self.input_data)
class TelnetServer(BaseServer):
"""
Telnet server
"""
def __init__(self, sock, asset, system_user):
super(TelnetServer, self).__init__()
self.sock = sock
self.asset = asset
self.system_user = system_user
def fileno(self):
return self.sock.fileno()
def send(self, b):
self.parse(b)
return self.sock.send(b)
def recv(self, size):
data = self.sock.recv(size)
self.session.put_replay(data)
if self._input_initial:
if self._in_input_state:
self.input_data.append(data)
else:
self.output_data.append(data)
return data
def close(self):
logger.info("Closed server {}".format(self))
self.parse(b'')
self.stop_evt.set()
self.sock.close()
def __getattr__(self, item):
return getattr(self.sock, item)
def __str__(self):
return "<To: {}>".format(str(self.asset))
class Server(BaseServer):
"""
SSH Server
Server object like client, a wrapper object, a connection to the asset,
Because we don't want to using python dynamic feature, such asset
have the chan and system_user attr.
"""
# Todo: Server name is not very suitable
def __init__(self, chan, sock, asset, system_user):
super(Server, self).__init__()
self.chan = chan
self.sock = sock
self.asset = asset
self.system_user = system_user
def fileno(self):
return self.chan.fileno()
def send(self, b):
self.parse(b)
return self.chan.send(b)
def recv(self, size):
data = self.chan.recv(size)
self.session.put_replay(data)
if self._input_initial:
if self._in_input_state:
self.input_data.append(data)
else:
self.output_data.append(data)
return data
def close(self):
logger.info("Closed server {}".format(self))
self.parse(b'')
self.stop_evt.set()
self.chan.close()
self.chan.transport.close()
if self.sock:
self.sock.transport.close()
def __getattr__(self, item):
return getattr(self.chan, item)
def __str__(self):
return "<To: {}>".format(str(self.asset))
# def __del__(self):
# print("GC: Server object has been gc")
'''
class Server: class Server:
""" """
SSH Server
Server object like client, a wrapper object, a connection to the asset, Server object like client, a wrapper object, a connection to the asset,
Because we don't want to using python dynamic feature, such asset Because we don't want to using python dynamic feature, such asset
have the chan and system_user attr. have the chan and system_user attr.
...@@ -199,6 +369,7 @@ class Server: ...@@ -199,6 +369,7 @@ class Server:
# def __del__(self): # def __del__(self):
# print("GC: Server object has been gc") # print("GC: Server object has been gc")
'''
class WSProxy: class WSProxy:
......
...@@ -8,8 +8,8 @@ import time ...@@ -8,8 +8,8 @@ import time
from paramiko.ssh_exception import SSHException from paramiko.ssh_exception import SSHException
from .session import Session from .session import Session
from .models import Server from .models import Server, TelnetServer
from .connection import SSHConnection from .connection import SSHConnection, TelnetConnection
from .ctx import current_app, app_service from .ctx import current_app, app_service
from .utils import wrap_with_line_feed as wr, wrap_with_warning as warning, \ from .utils import wrap_with_line_feed as wr, wrap_with_warning as warning, \
get_logger, net_input get_logger, net_input
...@@ -18,6 +18,8 @@ from .utils import wrap_with_line_feed as wr, wrap_with_warning as warning, \ ...@@ -18,6 +18,8 @@ from .utils import wrap_with_line_feed as wr, wrap_with_warning as warning, \
logger = get_logger(__file__) logger = get_logger(__file__)
TIMEOUT = 10 TIMEOUT = 10
BUF_SIZE = 4096 BUF_SIZE = 4096
MANUAL_LOGIN = 'manual'
AUTO_LOGIN = 'auto'
class ProxyServer: class ProxyServer:
...@@ -34,13 +36,24 @@ class ProxyServer: ...@@ -34,13 +36,24 @@ class ProxyServer:
""" """
password, private_key = \ password, private_key = \
app_service.get_system_user_auth_info(system_user) app_service.get_system_user_auth_info(system_user)
if not password and not private_key: if system_user.login_mode == MANUAL_LOGIN or (not password and not private_key):
prompt = "{}'s password: ".format(system_user.username) prompt = "{}'s password: ".format(system_user.username)
password = net_input(self.client, prompt=prompt, sensitive=True) password = net_input(self.client, prompt=prompt, sensitive=True)
system_user.password = password system_user.password = password
system_user.private_key = private_key system_user.private_key = private_key
def proxy(self, asset, system_user): def proxy(self, asset, system_user):
if asset.protocol != system_user.protocol:
msg = 'System user <{}> and asset <{}> protocol are inconsistent.'.format(
system_user.name, asset.hostname
)
self.client.send(warning(wr(msg, before=1, after=0)))
return
if system_user.login_mode == MANUAL_LOGIN or not system_user.username:
system_user_name = net_input(self.client, prompt='username: ', before=1)
system_user.username = system_user_name
self.get_system_user_auth(system_user) self.get_system_user_auth(system_user)
self.send_connecting_message(asset, system_user) self.send_connecting_message(asset, system_user)
self.server = self.get_server_conn(asset, system_user) self.server = self.get_server_conn(asset, system_user)
...@@ -74,15 +87,26 @@ class ProxyServer: ...@@ -74,15 +87,26 @@ class ProxyServer:
if not self.validate_permission(asset, system_user): if not self.validate_permission(asset, system_user):
self.client.send(warning('No permission')) self.client.send(warning('No permission'))
return None return None
if True: if system_user.protocol == asset.protocol == 'telnet':
server = self.get_telnet_server_conn(asset, system_user)
elif system_user.protocol == asset.protocol == 'ssh':
server = self.get_ssh_server_conn(asset, system_user) server = self.get_ssh_server_conn(asset, system_user)
else: else:
server = self.get_ssh_server_conn(asset, system_user) server = None
return server return server
# Todo: Support telnet # Todo: Support telnet
def get_telnet_server_conn(self, asset, system_user): def get_telnet_server_conn(self, asset, system_user):
pass telnet = TelnetConnection(asset, system_user, self.client)
sock, msg = telnet.get_socket()
if not sock:
self.client.send(warning(wr(msg, before=1, after=0)))
server = None
else:
server = TelnetServer(sock, asset, system_user)
# self.client.send(b'\r\n')
self.connecting = False
return server
def get_ssh_server_conn(self, asset, system_user): def get_ssh_server_conn(self, asset, system_user):
request = self.client.request request = self.client.request
...@@ -116,6 +140,8 @@ class ProxyServer: ...@@ -116,6 +140,8 @@ class ProxyServer:
break break
def watch_win_size_change_async(self): def watch_win_size_change_async(self):
if not isinstance(self.server, Server):
return
thread = threading.Thread(target=self.watch_win_size_change) thread = threading.Thread(target=self.watch_win_size_change)
thread.daemon = True thread.daemon = True
thread.start() thread.start()
......
...@@ -19,7 +19,7 @@ itsdangerous==0.24 ...@@ -19,7 +19,7 @@ itsdangerous==0.24
Jinja2==2.10 Jinja2==2.10
jmespath==0.9.3 jmespath==0.9.3
jms-storage==0.0.18 jms-storage==0.0.18
jumpserver-python-sdk==0.0.42 jumpserver-python-sdk==0.0.43
MarkupSafe==1.0 MarkupSafe==1.0
oss2==2.4.0 oss2==2.4.0
paramiko==2.4.0 paramiko==2.4.0
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment