Unverified Commit 2444c6fd authored by 老广's avatar 老广 Committed by GitHub

Merge pull request #151 from jumpserver/dev

Dev
parents f98e6815 1eb34b69
......@@ -94,7 +94,7 @@ class Config(dict):
def __init__(self, root_path, defaults=None):
self.defaults = defaults or {}
self.root_path = root_path
super().__init__({})
super(Config, self).__init__({})
def from_envvar(self, variable_name, silent=False):
"""Loads a configuration from an environment variable pointing to
......@@ -272,7 +272,7 @@ class Config(dict):
def __getitem__(self, item):
try:
value = super().__getitem__(item)
value = super(Config, self).__getitem__(item)
except KeyError:
value = None
if value is not None:
......
......@@ -4,9 +4,13 @@
import os
import re
import socket
import selectors
import telnetlib
try:
import selectors
except ImportError:
import selectors2 as selectors
import paramiko
from paramiko.ssh_exception import SSHException
......@@ -54,7 +58,7 @@ class SSHConnection:
asset.ip, port=asset.port, username=system_user.username,
password=system_user.password, pkey=system_user.private_key,
timeout=config['SSH_TIMEOUT'],
compress=True, auth_timeout=config['SSH_TIMEOUT'],
compress=False, auth_timeout=config['SSH_TIMEOUT'],
look_for_keys=False, sock=sock
)
except paramiko.AuthenticationException:
......@@ -62,7 +66,7 @@ class SSHConnection:
ssh.connect(
asset.ip, port=asset.port, username=system_user.username,
password=system_user.password, timeout=config['SSH_TIMEOUT'],
compress=True, auth_timeout=config['SSH_TIMEOUT'],
compress=False, auth_timeout=config['SSH_TIMEOUT'],
look_for_keys=False, sock=sock, allow_agent=False,
)
transport = ssh.get_transport()
......@@ -86,7 +90,7 @@ class SSHConnection:
password_short, key_fingerprint,
))
return None, None, error + '\n' + str(e)
except (socket.error, TimeoutError) as e:
except (socket.error, socket.timeout) as e:
return None, None, error + '\n' + str(e)
return ssh, sock, None
......@@ -197,7 +201,7 @@ class TelnetConnection:
)
def get_socket(self):
logger.info('Get telnet server socket. {}'.format(self.client.user))
logger.debug('Get telnet server socket. {}'.format(self.client.user))
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.settimeout(10)
self.sock.connect((self.asset.ip, self.asset.port))
......@@ -211,7 +215,7 @@ class TelnetConnection:
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)
logger.debug(b'[Telnet server send]: ' + data)
if not data:
self.sock.close()
......@@ -247,7 +251,7 @@ class TelnetConnection:
:param data: option negotiate data
:return:
"""
logger.info(b'[Server options negotiate]: ' + data)
logger.debug(b'[Server options negotiate]: ' + data)
data_list = data.split(telnetlib.IAC)
new_data_list = []
for x in data_list:
......@@ -272,11 +276,11 @@ class TelnetConnection:
else:
new_data_list.append(x)
new_data = telnetlib.IAC.join(new_data_list)
logger.info(b'[Client options negotiate]: ' + new_data)
logger.debug(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))
logger.debug('[Telnet login auth]: ({})'.format(self.client.user))
try:
data = raw_data.decode('utf-8')
......@@ -284,23 +288,23 @@ class TelnetConnection:
try:
data = raw_data.decode('gbk')
except UnicodeDecodeError:
logger.info(b'[Decode error]: ' + b'>>' + raw_data + b'<<')
logger.debug(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'<<')
logger.debug(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'<<')
logger.debug(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'<<')
logger.debug(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'<<')
logger.debug(b'[Login Success prompt]: ' + b'>>' + raw_data + b'<<')
return True
else:
logger.info(b'[No match]: ' + b'>>' + raw_data + b'<<')
logger.debug(b'[No match]: ' + b'>>' + raw_data + b'<<')
return None
# -*- coding: utf-8 -*-
#
import os
from flask_socketio import SocketIO
from flask import Flask
......@@ -17,8 +18,10 @@ socket_io = SocketIO()
socket_io.on_namespace(ProxyNamespace('/ssh'))
socket_io.on_namespace(ElfinderNamespace('/elfinder'))
# init_kwargs = {'async_mode': 'threading'}
init_kwargs = {'async_mode': 'eventlet'}
if os.environ.get('USE_EVENTLET', '1') == '1':
init_kwargs = {'async_mode': 'eventlet'}
else:
init_kwargs = {'async_mode': 'threading'}
socket_io.init_app(app, **init_kwargs),
socket_io.on_error_default(lambda x: logger.exception(x))
......
# -*- coding: utf-8 -*-
#
# -*- coding: utf-8 -*-
#
import base64
import os
import hashlib
......
# -*- coding: utf-8 -*-
#
import stat
import threading
......@@ -14,9 +16,9 @@ class SFTPVolume(BaseVolume):
def __init__(self, sftp):
self.sftp = sftp
self.root_name = 'Home'
super().__init__()
self._stat_cache = {}
self.lock = threading.Lock()
super(SFTPVolume, self).__init__()
def close(self):
self.sftp.close()
......
......@@ -28,7 +28,7 @@ class ProxyNamespace(BaseNamespace):
...
}
"""
super().__init__(*args, **kwargs)
super(BaseNamespace, self).__init__(*args, **kwargs)
self.win_size = None
def new_connection(self):
......@@ -46,7 +46,7 @@ class ProxyNamespace(BaseNamespace):
def on_connect(self):
logger.debug("On connect event trigger")
self.get_current_user()
super().on_connect()
super(ProxyNamespace, self).on_connect()
self.new_connection()
def on_host(self, message):
......
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
from __future__ import unicode_literals
import socket
import threading
......@@ -28,20 +29,20 @@ PROXY = 'proxy'
class InteractiveServer:
_sentinel = object()
_user_assets_cached = {}
def __init__(self, client):
self.client = client
self.closed = False
self._results = None
self.nodes = None
self.offset = 0
self.limit = 100
self.assets = []
self.finish = False
self.assets = None
self.get_user_assets_finished = False
self.page = 1
self.total_assets = 0 # 用户被授权的所有资产
self.total_asset_count = 0 # 用户被授权的所有资产数量
self.total_count = 0 # 分页展示中的资产总数量
self.node_tree = None # 授权节点树
self.load_user_assets_from_cache()
self.get_user_assets_async()
self.get_user_nodes_async()
......@@ -82,7 +83,7 @@ class InteractiveServer:
self.client.send(char.CLEAR_CHAR)
self.display_logo()
header = _("\n{T}{T}{title} {user}, Welcome to use Jumpserver open source fortress system {end}{R}{R}")
menus = [
menu = [
_("{T}1) Enter {green}ID{end} directly login or enter {green}part IP, Hostname, Comment{end} to search login(if unique).{R}"),
_("{T}2) Enter {green}/{end} + {green}IP, Hostname{end} or {green}Comment {end} search, such as: /ip.{R}"),
_("{T}3) Enter {green}p{end} to display the host you have permission.{R}"),
......@@ -97,8 +98,8 @@ class InteractiveServer:
title="\033[1;32m", user=self.client.user, end="\033[0m",
T='\t', R='\r\n\r'
))
for menu in menus:
self.client.send(menu.format(
for item in menu:
self.client.send(item.format(
green="\033[32m", end="\033[0m",
T='\t', R='\r\n\r'
))
......@@ -143,7 +144,7 @@ class InteractiveServer:
def search_and_display_assets(self, q):
assets = self.search_assets(q)
self.display_assets(assets)
self.display_assets_paging(assets)
def search_and_proxy_assets(self, opt):
assets = self.search_assets(opt)
......@@ -158,25 +159,13 @@ class InteractiveServer:
return
self.proxy(asset)
else:
self.display_assets(assets)
self.display_assets_paging(assets)
def refresh_assets_nodes(self):
self.get_user_assets_async()
self.get_user_nodes_async()
def search_assets(self, q):
if self.finish:
assets = self.search_assets_from_local(q)
else:
assets = self.search_assets_from_core(q)
return assets
def search_assets_from_core(self, q):
assets = app_service.get_search_user_granted_assets(self.client.user, q)
assets = self.filter_system_users(assets)
return assets
def search_assets_from_local(self, q):
result = []
# 所有的
......@@ -204,20 +193,17 @@ class InteractiveServer:
# Display assets
#
def display_assets(self, assets=None):
if assets is None:
while not self.assets and not self.finish:
time.sleep(0.2)
assets = self.assets
self.display_assets_paging(assets)
def display_assets(self):
while self.assets is None and not self.get_user_assets_finished:
time.sleep(0.5)
if self.assets:
self.display_assets_paging(self.assets)
def display_assets_paging(self, assets):
if len(assets) == 0:
self.client.send(wr(_("No Assets"), before=0))
return
self.total_count = self.total_assets if assets is self.assets else len(assets)
self.total_count = len(assets)
action = None
gen = self._page_generator(assets)
......@@ -238,14 +224,8 @@ class InteractiveServer:
start, page = 0, 1
while True:
_assets = assets[start:start+self.page_size]
# 等待加载
if (assets is self.assets) and (not self.finish) and (not self.need_paging):
time.sleep(1)
continue
# 最后一页
elif _assets and (page == self.total_pages) and (
assets is not self.assets
or (assets is self.assets and self.finish)):
if page == self.total_pages:
return page, _assets
# 执行动作
else:
......@@ -273,33 +253,8 @@ class InteractiveServer:
def display_a_page_assets(self, page, assets):
self.client.send(char.CLEAR_CHAR)
self.page = page
self.results = assets
self.display_results()
def display_page_bottom_prompt(self):
self.client.send(wr(_('Tips: Enter the asset ID and log directly into the asset.'), before=1))
prompt_page_up = _("Page up: P/p")
prompt_page_down = _("Page down: Enter|N/n")
prompt_back = _("BACK: b/q")
prompts = [prompt_page_up, prompt_page_down, prompt_back]
prompt = '\t'.join(prompts)
self.client.send(wr(prompt, before=1))
def get_user_action(self):
opt = net_input(self.client, prompt=':')
if opt in ('p', 'P'):
return PAGE_UP
elif opt in ('b', 'q'):
return BACK
elif opt.isdigit() and self.results and 0 < int(opt) <= len(self.results):
self.proxy(self.results[int(opt)-1])
return BACK
else:
return PAGE_DOWN
def display_results(self):
sort_by = config["ASSET_LIST_SORT_BY"]
self.results = sort_assets(self.results, sort_by)
self.results = sort_assets(assets, sort_by)
fake_data = [_("ID"), _("Hostname"), _("IP"), _("LoginAs")]
id_length = max(len(str(len(self.results))), 4)
hostname_length = item_max_length(self.results, 15,
......@@ -323,44 +278,59 @@ class InteractiveServer:
]
self.client.send(wr(format_with_zh(size_list, *data)))
self.client.send(wr(title(_("Page: {}, Count: {}, Total Page: {}, Total Count: {}").format(
self.page, len(self.results), self.total_pages, self.total_count)), before=1)
self.client.send(wr(title(
_("Page: {}, Count: {}, Total Page: {}, Total Count: {}").format(
self.page, len(self.results), self.total_pages,
self.total_count)), before=1)
)
def display_page_bottom_prompt(self):
msg = wr(_('Tips: Enter the asset ID and log directly into the asset.'), before=1)
self.client.send(msg)
prompt_page_up = _("Page up: P/p")
prompt_page_down = _("Page down: Enter|N/n")
prompt_back = _("BACK: b/q")
prompts = [prompt_page_up, prompt_page_down, prompt_back]
prompt = '\t'.join(prompts)
self.client.send(wr(prompt, before=1))
def get_user_action(self):
opt = net_input(self.client, prompt=':')
if opt in ('p', 'P'):
return PAGE_UP
elif opt in ('b', 'q'):
return BACK
elif opt.isdigit() and self.results and 0 < int(opt) <= len(self.results):
self.proxy(self.results[int(opt)-1])
return BACK
else:
return PAGE_DOWN
#
# Get assets
#
def load_user_assets_from_cache(self):
assets = self.__class__._user_assets_cached.get(
self.client.user.id
)
self.assets = assets
if assets:
self.total_asset_count = len(assets)
def set_user_assets_cache(self, assets):
self.__class__._user_assets_cached[self.client.user.id] = assets
def get_user_assets_async(self):
if self.need_paging:
thread = threading.Thread(target=self.get_user_assets_paging)
else:
thread = threading.Thread(target=self.get_user_assets_direct)
thread = threading.Thread(target=self.get_user_assets)
thread.start()
def get_user_assets_direct(self):
def get_user_assets(self):
assets = app_service.get_user_assets(self.client.user)
assets = self.filter_system_users(assets)
self.assets = assets
self.total_assets = len(assets)
self.finish = True
def get_user_assets_paging(self):
while not self.closed:
assets, total = app_service.get_user_assets_paging(
self.client.user, offset=self.offset, limit=self.limit
)
if not assets:
logger.info('Get user assets paging async finished.')
self.finish = True
break
logger.info('Get user assets paging async: {}'.format(len(assets)))
assets = self.filter_system_users(assets)
self.total_assets = total
self.assets.extend(assets)
self.offset += self.limit
self.set_user_assets_cache(assets)
self.load_user_assets_from_cache()
self.get_user_assets_finished = True
#
# Nodes
#
......@@ -415,7 +385,7 @@ class InteractiveServer:
return
assets = self.nodes[_id-1].assets_granted
self.display_assets(assets)
self.display_assets_paging(assets)
#
# System users
......@@ -487,7 +457,6 @@ class InteractiveServer:
def close(self):
logger.debug("Interactive server server close: {}".format(self))
self.closed = True
# current_app.remove_client(self.client)
def interact_async(self):
# 目前没用
......
......@@ -30,14 +30,14 @@ class SSHInterface(paramiko.ServerInterface):
self.user = None
def check_auth_interactive(self, username, submethods):
logger.info("Check auth interactive: %s %s" % (username, submethods))
logger.debug("Check auth interactive: %s %s" % (username, submethods))
instructions = 'Please enter 6 digits.'
interactive = paramiko.server.InteractiveQuery(instructions=instructions)
interactive.add_prompt(prompt='[MFA auth]: ')
return interactive
def check_auth_interactive_response(self, responses):
logger.info("Check auth interactive response: %s " % responses)
logger.debug("Check auth interactive response: %s " % responses)
# TODO:MFA Auth
otp_code = responses[0]
if not otp_code or not len(otp_code) == 6 or not otp_code.isdigit():
......@@ -90,7 +90,7 @@ class SSHInterface(paramiko.ServerInterface):
logger.debug("Public key auth <%s> failed, try to password" % username)
return paramiko.AUTH_FAILED
else:
logger.debug("Public key auth <%s> success" % username)
logger.info("Public key auth <%s> success" % username)
if self.otp_auth:
return paramiko.AUTH_PARTIALLY_SUCCESSFUL
return paramiko.AUTH_SUCCESSFUL
......@@ -149,14 +149,14 @@ class SSHInterface(paramiko.ServerInterface):
return 0
def check_port_forward_request(self, address, port):
logger.info(
logger.debug(
"Check channel port forward request: %s %s" % (address, port)
)
self.event.set()
return False
def check_channel_request(self, kind, chan_id):
logger.info("Check channel request: %s %d" % (kind, chan_id))
logger.debug("Check channel request: %s %d" % (kind, chan_id))
client = self.connection.new_client(chan_id)
client.request.kind = kind
return paramiko.OPEN_SUCCEEDED
......@@ -188,7 +188,7 @@ class SSHInterface(paramiko.ServerInterface):
def check_channel_pty_request(
self, channel, term, width, height,
pixelwidth, pixelheight, modes):
logger.info("Check channel pty request: %s %s %s %s %s" %
logger.debug("Check channel pty request: %s %s %s %s %s" %
(term, width, height, pixelwidth, pixelheight))
client = self.connection.get_client(channel)
client.request.type = 'pty'
......@@ -201,18 +201,18 @@ class SSHInterface(paramiko.ServerInterface):
return True
def check_channel_shell_request(self, channel):
logger.info("Check channel shell request: %s" % channel.get_id())
logger.debug("Check channel shell request: %s" % channel.get_id())
client = self.connection.get_client(channel)
client.request.meta['shell'] = True
return True
def check_channel_subsystem_request(self, channel, name):
logger.info("Check channel subsystem request: %s" % name)
logger.debug("Check channel subsystem request: %s" % name)
client = self.connection.get_client(channel)
client.request.type = 'subsystem'
client.request.meta['subsystem'] = name
self.event.set()
return super().check_channel_subsystem_request(channel, name)
return super(SSHInterface, self).check_channel_subsystem_request(channel, name)
def check_channel_window_change_request(self, channel, width, height,
pixelwidth, pixelheight):
......@@ -228,7 +228,7 @@ class SSHInterface(paramiko.ServerInterface):
def check_channel_x11_request(self, channel, single_connection,
auth_protocol, auth_cookie, screen_number):
logger.info("Check channel x11 request %s %s %s %s" %
logger.debug("Check channel x11 request %s %s %s %s" %
(single_connection, auth_protocol,
auth_cookie, screen_number))
client = self.connection.get_client(channel)
......
......@@ -51,12 +51,13 @@ def create_logger():
},
loggers={
'coco': main_setting,
'paramiko': main_setting,
'jms': main_setting,
# 'socket.io': main_setting,
# 'engineio': main_setting,
}
)
if level.lower() == 'debug':
config['loggers']['paramiko'] = main_setting
dictConfig(config)
logger = logging.getLogger()
return logger
......
......@@ -8,14 +8,14 @@ from .service import app_service
from .struct import SizedList, SelectEvent
from .utils import wrap_with_line_feed as wr, wrap_with_warning as warning, \
ugettext as _
from . import char
from . import utils
from . import char, utils
from .compat import str
BUF_SIZE = 4096
logger = utils.get_logger(__file__)
class Connection:
class Connection(object):
connections = {}
clients_num = 0
......@@ -80,6 +80,8 @@ class Connection:
@classmethod
def remove_connection(cls, cid):
connection = cls.get_connection(cid)
if not connection:
return
connection.close()
del cls.connections[cid]
......@@ -88,7 +90,7 @@ class Connection:
return cls.connections.get(cid)
class Request:
class Request(object):
def __init__(self):
self.type = None
self.x11 = None
......@@ -96,7 +98,7 @@ class Request:
self.meta = {'env': {}}
class Client:
class Client(object):
"""
Client is the request client. Nothing more to say
......@@ -129,12 +131,17 @@ class Client:
self.close()
return
@property
def closed(self):
return self.chan.closed
def recv(self, size):
return self.chan.recv(size)
def close(self):
logger.info("Client {} close".format(self))
return self.chan.close()
self.chan.close()
return
def __getattr__(self, item):
return getattr(self.chan, item)
......@@ -143,12 +150,12 @@ class Client:
return "<%s from %s:%s>" % (self.user, self.addr[0], self.addr[1])
class ServerFilter:
class ServerFilter(object):
def run(self, data):
pass
class BaseServer:
class BaseServer(object):
"""
Base Server
Achieve command record
......@@ -238,22 +245,31 @@ class BaseServer:
return data
if not self._input:
return data
if self._cmd_filter_rules is None:
msg = _("Warning: Failed to load filter rule, "
"please press Ctrl + D to exit retry.")
data = self.command_forbidden(msg)
return data
for rule in self._cmd_filter_rules:
action, cmd = rule.match(self._input)
if action == rule.ALLOW:
break
elif action == rule.DENY:
data = char.CLEAR_LINE_CHAR + b'\r'
msg = _("Command `{}` is forbidden ........").format(cmd)
msg = wr(warning(msg.encode()), before=1, after=1)
self.output_data.append(msg)
self.session.send_to_clients(msg)
self.session.put_command(self._input, msg.decode())
self.session.put_replay(msg)
self.input_data.clean()
self.command_forbidden(msg)
break
return data
def command_forbidden(self, msg):
data = char.CLEAR_LINE_CHAR + b'\r'
msg = wr(warning(msg.encode()), before=1, after=1)
self.output_data.append(msg)
self.session.send_to_clients(msg)
self.session.put_command(self._input, msg.decode())
self.session.put_replay(msg)
self.input_data.clean()
return data
def r_replay_filter(self, data):
if not self._zmodem_state:
self.session.put_replay(data)
......@@ -382,13 +398,14 @@ class Server(BaseServer):
super(Server, self).__init__(chan=chan)
def close(self):
super().close()
super(Server, self).close()
self.chan.transport.close()
logger.debug("Backend server closed")
if self.sock:
self.sock.transport.close()
class WSProxy:
class WSProxy(object):
def __init__(self, ws, client_id):
self.ws = ws
self.client_id = client_id
......
......@@ -52,7 +52,7 @@ class ProxyServer:
return False
return True
def manual_set_system_user_username_if_need(self):
def get_system_user_username_if_need(self):
if self.system_user.login_mode == MANUAL_LOGIN and \
not self.system_user.username:
username = net_input(self.client, prompt='username: ', before=1)
......@@ -63,15 +63,17 @@ class ProxyServer:
def proxy(self):
if not self.check_protocol():
return
self.manual_set_system_user_username_if_need()
self.get_system_user_username_if_need()
self.get_system_user_auth_or_manual_set()
self.server = self.get_server_conn()
if self.server is None:
return
session = Session.new_session(self.client, self.server)
session.bridge()
Session.remove_session(session.id)
self.server.close()
try:
session.bridge()
finally:
Session.remove_session(session.id)
self.server.close()
def validate_permission(self):
"""
......@@ -83,7 +85,7 @@ class ProxyServer:
)
def get_server_conn(self):
logger.info("Connect to {}".format(self.asset.hostname))
logger.info("Connect to {}:{} ...".format(self.asset.hostname, self.asset.port))
self.send_connecting_message()
if not self.validate_permission():
self.client.send(warning(_('No permission')))
......
......@@ -21,12 +21,12 @@ logger = get_logger(__file__)
BUF_SIZE = 1024
class ReplayRecorder(metaclass=abc.ABCMeta):
class ReplayRecorder(object):
time_start = None
storage = None
def __init__(self):
super().__init__()
super(ReplayRecorder, self).__init__()
self.file = None
self.file_path = None
self.get_storage()
......@@ -82,7 +82,7 @@ class ReplayRecorder(metaclass=abc.ABCMeta):
self.upload_replay(session_id, times-1)
else:
msg = 'Success push replay file: {}'.format(session_id)
logger.info(msg)
logger.debug(msg)
self.finish_replay(3, session_id)
os.unlink(self.file_path)
return True
......@@ -101,23 +101,32 @@ class ReplayRecorder(metaclass=abc.ABCMeta):
if app_service.finish_replay(session_id):
logger.info(
"Success finish session {}'s replay ".format(session_id)
"Success finished session {}'s replay ".format(session_id)
)
return True
else:
msg = "Failed finish session {}'s replay, try {} times"
msg = "Failed finished session {}'s replay, try {} times"
logger.error(msg.format(session_id, times))
return self.finish_replay(times - 1, session_id)
class CommandRecorder(metaclass=Singleton):
class CommandRecorder(object):
batch_size = 10
timeout = 5
no = 0
storage = None
_cache = []
def __new__(cls, *args, **kwargs):
if cls._cache:
return cls._cache[0]
else:
self = super(CommandRecorder, cls).__new__(cls, *args, **kwargs)
cls._cache.append(self)
return self
def __init__(self):
super().__init__()
super(CommandRecorder, self).__init__()
self.queue = MemoryQueue()
self.stop_evt = threading.Event()
self.push_to_server_async()
......
......@@ -3,9 +3,13 @@
#
import uuid
import datetime
import selectors
import time
try:
import selectors
except ImportError:
import selectors2 as selectors
from .utils import get_logger, wrap_with_warning as warn, \
wrap_with_line_feed as wr, ugettext as _, ignore_error
from .service import app_service
......@@ -68,14 +72,14 @@ class Session:
:param silent: If true not send welcome message
:return:
"""
logger.info("Session add watcher: {} -> {} ".format(self.id, watcher))
logger.debug("Session add watcher: {} -> {} ".format(self.id, watcher))
if not silent:
watcher.send("Welcome to watch session {}\r\n".format(self.id).encode())
self.sel.register(watcher, selectors.EVENT_READ)
self._watchers.append(watcher)
def remove_watcher(self, watcher):
logger.info("Session %s remove watcher %s" % (self.id, watcher))
logger.debug("Session %s remove watcher %s" % (self.id, watcher))
self.sel.unregister(watcher)
self._watchers.remove(watcher)
......@@ -86,7 +90,7 @@ class Session:
:param silent: If true not send welcome message
:return:
"""
logger.info("Session %s add share %s" % (self.id, sharer))
logger.debug("Session %s add share %s" % (self.id, sharer))
if not silent:
sharer.send("Welcome to join session: {}\r\n"
.format(self.id).encode("utf-8"))
......@@ -190,7 +194,7 @@ class Session:
break
elif sock == self.client.change_size_evt:
self.resize_win_size()
logger.info("Session stop event set: {}".format(self.id))
logger.debug("Session stop event set: {}".format(self.id))
def resize_win_size(self):
width, height = self.client.request.meta['width'], \
......@@ -201,7 +205,7 @@ class Session:
@ignore_error
def close(self):
if self.closed:
logger.info("Session has been closed: {} ".format(self.id))
logger.debug("Session has been closed: {} ".format(self.id))
return
logger.info("Close the session: {} ".format(self.id))
self.is_finished = True
......@@ -228,6 +232,3 @@ class Session:
def __repr__(self):
return self.id
# def __del__(self):
# print("GC: Session object has been GC")
......@@ -62,10 +62,11 @@ class SFTPServer(paramiko.SFTPServerInterface):
}
"""
super().__init__(server, **kwargs)
super(SFTPServer, self).__init__(server, **kwargs)
self.server = server
self._sftp = {}
self.hosts = self.get_permed_hosts()
self.is_finished = False
def get_permed_hosts(self):
hosts = {}
......@@ -88,7 +89,8 @@ class SFTPServer(paramiko.SFTPServerInterface):
return hosts
def session_ended(self):
super().session_ended()
self.is_finished = True
super(SFTPServer, self).session_ended()
for _, v in self._sftp.items():
sftp = v['client']
proxy = v.get('proxy')
......@@ -144,7 +146,7 @@ class SFTPServer(paramiko.SFTPServerInterface):
if len(data) == 1 and not data[0]:
return request
host, *path = data
host, path = data[0], data[1:]
request["host"] = host
unique, su = self.host_has_unique_su(host)
if unique:
......@@ -388,7 +390,7 @@ class InternalSFTPClient(SFTPServer):
def __init__(self, connection):
fake_server = FakeServer()
fake_server.connection = connection
super().__init__(fake_server)
super(InternalSFTPClient, self).__init__(fake_server)
def listdir_attr(self, path):
return self.list_folder.__wrapped__(self, path)
......@@ -408,15 +410,15 @@ class InternalSFTPClient(SFTPServer):
self.create_ftp_log(path, operate, success)
def stat(self, path):
attr = super().stat.__wrapped__(self, path)
attr = super(InternalSFTPClient, self).stat.__wrapped__(self, path)
return attr
def lstat(self, path):
attr = super().lstat.__wrapped__(self, path)
attr = super(InternalSFTPClient, self).lstat.__wrapped__(self, path)
return attr
def rmdir(self, path):
return super().rmdir.__wrapped__(self, path)
return super(InternalSFTPClient, self).rmdir.__wrapped__(self, path)
def get_channel(self):
return FakeChannel.new()
......
......@@ -5,6 +5,7 @@
import os
import socket
import threading
import time
import paramiko
......@@ -25,7 +26,6 @@ class SSHServer:
self.stop_evt = threading.Event()
self.workers = []
self.pipe = None
self.connections = []
@property
def host_key(self):
......@@ -57,11 +57,6 @@ class SSHServer:
except IndexError as e:
logger.error("Start SSH server error: {}".format(e))
def new_connection(self, addr, sock):
connection = Connection.new_connection(addr=addr, sock=sock)
self.connections.append(connection)
return connection
def handle_connection(self, sock, addr):
logger.debug("Handle new connection from: {}".format(addr))
transport = paramiko.Transport(sock, gss_kex=False)
......@@ -74,55 +69,60 @@ class SSHServer:
transport.set_subsystem_handler(
'sftp', paramiko.SFTPServer, SFTPServer
)
connection = self.new_connection(addr, sock=sock)
connection = Connection.new_connection(addr=addr, sock=sock)
server = SSHInterface(connection)
try:
transport.start_server(server=server)
except (paramiko.SSHException, socket.timeout):
while transport.is_active():
chan = transport.accept()
server.event.wait(5)
if chan is None:
continue
if not server.event.is_set():
logger.warning("Client not request invalid, exiting")
sock.close()
continue
else:
server.event.clear()
client = connection.clients.get(chan.get_id())
client.chan = chan
t = threading.Thread(target=self.dispatch, args=(client,))
t.daemon = True
t.start()
transport.close()
except (paramiko.SSHException, sock.timeout):
logger.warning("SSH negotiation failed")
return
except EOFError as e:
logger.warning("Handle EOF Error: {}".format(e))
return
while transport.is_active():
chan = transport.accept()
server.event.wait(5)
if chan is None:
continue
if not server.event.is_set():
logger.warning("Client not request invalid, exiting")
sock.close()
return
else:
server.event.clear()
client = connection.clients.get(chan.get_id())
client.chan = chan
t = threading.Thread(target=self.dispatch, args=(client,))
t.daemon = True
t.start()
Connection.remove_connection(connection.id)
finally:
Connection.remove_connection(connection.id)
sock.close()
@staticmethod
def dispatch(client):
supported = {'pty', 'x11', 'forward-agent'}
chan_type = client.request.type
kind = client.request.kind
if kind == 'session' and chan_type in supported:
logger.info("Request type `{}:{}`, dispatch to interactive mode".format(kind, chan_type))
try:
InteractiveServer(client).interact()
except Exception as e:
logger.error("Unexpected error occur: {}".format(e))
try:
if kind == 'session' and chan_type in supported:
logger.info("Dispatch client to interactive mode")
try:
InteractiveServer(client).interact()
except IndexError as e:
logger.error("Unexpected error occur: {}".format(e))
elif chan_type == 'subsystem':
while not client.closed:
time.sleep(5)
logger.debug("SFTP session finished")
else:
msg = "Request type `{}:{}` not support now".format(kind, chan_type)
logger.error(msg)
client.send(msg)
finally:
connection = Connection.get_connection(client.connection_id)
connection.remove_client(client.id)
elif chan_type == 'subsystem':
pass
else:
msg = "Request type `{}:{}` not support now".format(kind, chan_type)
logger.info(msg)
client.send(msg)
def shutdown(self):
self.stop_evt.set()
......
......@@ -21,7 +21,7 @@ class MultiQueueMixin:
self.put(i)
class MemoryQueue(MultiQueueMixin, queue.Queue):
class MemoryQueue(MultiQueueMixin, queue.Queue, object):
pass
......@@ -29,11 +29,11 @@ class SizedList(list):
def __init__(self, maxsize=0):
self.maxsize = maxsize
self.size = 0
super().__init__()
super(list, self).__init__()
def append(self, b):
if self.maxsize == 0 or self.size < self.maxsize:
super().append(b)
super(SizedList, self).append(b)
self.size += len(b)
def clean(self):
......
......@@ -12,7 +12,6 @@ from io import StringIO
from binascii import hexlify
from werkzeug.local import Local, LocalProxy
from functools import partial, wraps
import builtins
import paramiko
import pyte
......@@ -294,7 +293,7 @@ def get_logger(file_name):
return logging.getLogger('coco.'+file_name)
def net_input(client, prompt='Opt> ', sensitive=False, before=0, after=0, only_one_char=False):
def net_input(client, prompt='Opt> ', sensitive=False, before=0, after=0):
"""实现了一个ssh input, 提示用户输入, 获取并返回
:return user input string
......@@ -303,12 +302,8 @@ def net_input(client, prompt='Opt> ', sensitive=False, before=0, after=0, only_o
parser = TtyIOParser()
client.send(wrap_with_line_feed(prompt, before=before, after=after))
if only_one_char:
data = client.recv(1)
return data.decode()
while True:
data = client.recv(10)
data = client.recv(1)
if len(data) == 0:
break
# Client input backspace
......@@ -336,18 +331,8 @@ def net_input(client, prompt='Opt> ', sensitive=False, before=0, after=0, only_o
client.send(b'')
continue
# handle shell expect
multi_char_with_enter = False
if len(data) > 1 and data[-1] in char.ENTER_CHAR_ORDER:
if sensitive:
client.send(len(data) * '*')
else:
client.send(data)
input_data.append(data[:-1])
multi_char_with_enter = True
# If user types ENTER we should get user input
if data in char.ENTER_CHAR or multi_char_with_enter:
if data in char.ENTER_CHAR:
client.send(wrap_with_line_feed(b'', after=2))
option = parser.parse_input(input_data)
del input_data[:]
......@@ -435,11 +420,17 @@ def get_current_lang(attr):
def _gettext(lang):
import builtins
if lang == 'en':
trans_en.install()
else:
trans_zh.install()
return builtins.__dict__['_']
try:
return builtins.__dict__['_']
except KeyError:
def _f(x):
return x
return _f
def _find(attr):
......
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
import os
import eventlet
from eventlet.debug import hub_prevent_multiple_readers
eventlet.monkey_patch()
hub_prevent_multiple_readers(False)
if os.environ.get('USE_EVENTLET', '1') == '1':
import eventlet
from eventlet.debug import hub_prevent_multiple_readers
eventlet.monkey_patch()
hub_prevent_multiple_readers(False)
print("Use eventlet dispatch")
else:
print("Use local threading")
import os
import sys
import argparse
import time
......@@ -15,7 +19,8 @@ import signal
dirs = ('logs', 'keys')
for d in dirs:
os.makedirs(d, exist_ok=True)
if not os.path.isdir(d):
os.makedirs(d)
from coco import Coco
......
......@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-12-18 20:03+0800\n"
"POT-Creation-Date: 2018-12-21 16:48+0800\n"
"PO-Revision-Date: 2018-08-10 10:42+0800\n"
"Last-Translator: BaiJiangjie <bugatti_it@163.com>\n"
"Language-Team: Language locale/en/LC\n"
......@@ -84,75 +84,80 @@ msgstr ""
msgid "Terminal does not support login rdp, please use web terminal to access"
msgstr ""
#: coco/interactive.py:212
#: coco/interactive.py:217
msgid "No Assets"
msgstr ""
#: coco/interactive.py:275
#: coco/interactive.py:280
msgid "Tips: Enter the asset ID and log directly into the asset."
msgstr ""
#: coco/interactive.py:276
#: coco/interactive.py:281
msgid "Page up: P/p"
msgstr ""
#: coco/interactive.py:277
#: coco/interactive.py:282
msgid "Page down: Enter|N/n"
msgstr ""
#: coco/interactive.py:278
#: coco/interactive.py:283
msgid "BACK: b/q"
msgstr ""
#: coco/interactive.py:299
#: coco/interactive.py:303
msgid "ID"
msgstr ""
#: coco/interactive.py:299
#: coco/interactive.py:303
msgid "Hostname"
msgstr ""
#: coco/interactive.py:299
#: coco/interactive.py:303
msgid "IP"
msgstr ""
#: coco/interactive.py:299
#: coco/interactive.py:303
msgid "LoginAs"
msgstr ""
#: coco/interactive.py:313
#: coco/interactive.py:317
msgid "Comment"
msgstr ""
#: coco/interactive.py:322
#: coco/interactive.py:326
msgid "Page: {}, Count: {}, Total Page: {}, Total Count: {}"
msgstr ""
#: coco/interactive.py:394
#: coco/interactive.py:398
msgid "No Nodes"
msgstr ""
#: coco/interactive.py:398
#: coco/interactive.py:402
msgid "Node: [ ID.Name(Asset amount) ]"
msgstr ""
#: coco/interactive.py:400
#: coco/interactive.py:404
msgid "Tips: Enter g+NodeID to display the host under the node, such as g1"
msgstr ""
#: coco/interactive.py:408
#: coco/interactive.py:412
msgid "There is no matched node, please re-enter"
msgstr ""
#: coco/interactive.py:438
#: coco/interactive.py:442
msgid "Select a login:: "
msgstr ""
#: coco/interactive.py:461
#: coco/interactive.py:465
msgid "No system user"
msgstr ""
#: coco/models.py:247
#: coco/models.py:242
msgid ""
"Warning: Failed to load filter rule, please press Ctrl + D to exit retry."
msgstr ""
#: coco/models.py:251
msgid "Command `{}` is forbidden ........"
msgstr ""
......
......@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-12-18 20:04+0800\n"
"POT-Creation-Date: 2018-12-21 16:48+0800\n"
"PO-Revision-Date: 2018-08-10 10:42+0800\n"
"Last-Translator: BaiJiangjie <bugatti_it@163.com>\n"
"Language-Team: Language locale/zh\n"
......@@ -91,75 +91,80 @@ msgstr "{T}0) 输入 {green}q{end} 退出.{R}"
msgid "Terminal does not support login rdp, please use web terminal to access"
msgstr "终端不支持登录windows, 请使用web terminal访问"
#: coco/interactive.py:212
#: coco/interactive.py:217
msgid "No Assets"
msgstr "没有资产"
#: coco/interactive.py:275
#: coco/interactive.py:280
msgid "Tips: Enter the asset ID and log directly into the asset."
msgstr "提示: 输入资产ID,直接登录资产."
#: coco/interactive.py:276
#: coco/interactive.py:281
msgid "Page up: P/p"
msgstr "上一页: P/p"
#: coco/interactive.py:277
#: coco/interactive.py:282
msgid "Page down: Enter|N/n"
msgstr "下一页: Enter|N/n"
#: coco/interactive.py:278
#: coco/interactive.py:283
msgid "BACK: b/q"
msgstr "返回: B/b"
#: coco/interactive.py:299
#: coco/interactive.py:303
msgid "ID"
msgstr ""
#: coco/interactive.py:299
#: coco/interactive.py:303
msgid "Hostname"
msgstr "主机名"
#: coco/interactive.py:299
#: coco/interactive.py:303
msgid "IP"
msgstr ""
#: coco/interactive.py:299
#: coco/interactive.py:303
msgid "LoginAs"
msgstr "登录用户"
#: coco/interactive.py:313
#: coco/interactive.py:317
msgid "Comment"
msgstr "备注"
#: coco/interactive.py:322
#: coco/interactive.py:326
msgid "Page: {}, Count: {}, Total Page: {}, Total Count: {}"
msgstr "页码: {}, 数量: {}, 总页数: {}, 总数量: {}"
#: coco/interactive.py:394
#: coco/interactive.py:398
msgid "No Nodes"
msgstr "没有节点"
#: coco/interactive.py:398
#: coco/interactive.py:402
msgid "Node: [ ID.Name(Asset amount) ]"
msgstr "节点: [ ID.名称(资产数量) ]"
#: coco/interactive.py:400
#: coco/interactive.py:404
msgid "Tips: Enter g+NodeID to display the host under the node, such as g1"
msgstr "提示: 输入 g+节点ID 显示节点下主机. 如: g1"
#: coco/interactive.py:408
#: coco/interactive.py:412
msgid "There is no matched node, please re-enter"
msgstr "没有匹配分组,请重新输入"
#: coco/interactive.py:438
#: coco/interactive.py:442
msgid "Select a login:: "
msgstr "选择一个登录:"
#: coco/interactive.py:461
#: coco/interactive.py:465
msgid "No system user"
msgstr "没有系统用户"
#: coco/models.py:247
#: coco/models.py:242
msgid ""
"Warning: Failed to load filter rule, please press Ctrl + D to exit retry."
msgstr "警告: 加载过滤规则失败,请按 Ctrl + D 退出重试."
#: coco/models.py:251
msgid "Command `{}` is forbidden ........"
msgstr "命令 `{}` 是被禁止的 ..."
......
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