From 428dc323f36d1b042df86a003673f8ed007fb6d8 Mon Sep 17 00:00:00 2001 From: Apex Liu Date: Fri, 26 May 2017 02:18:01 +0800 Subject: [PATCH 01/14] =?UTF-8?q?1.=20=E4=BF=AE=E6=AD=A3mysql=E4=B8=ADvarc?= =?UTF-8?q?har=E7=94=A8=E4=BD=9C=E7=B4=A2=E5=BC=95=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E6=97=B6=EF=BC=8C=E9=95=BF=E5=BA=A6=E5=BF=85=E9=A1=BB=E5=B0=8F?= =?UTF-8?q?=E4=BA=8E=E7=AD=89=E4=BA=8E255=EF=BC=9B=202.=20temp:=20?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E9=A1=B5=E9=9D=A2=E5=8A=A0=E5=85=A5=E5=AF=BC?= =?UTF-8?q?=E5=85=A5=E5=AF=BC=E5=87=BA=E6=8C=89=E9=92=AE=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/eom_app/app/database/create.py | 18 +- .../app/eom_app/app/database/upgrade.py | 18 +- server/www/teleport/app/eom_app/app/db.py | 8 + .../app/eom_app/controller/__init__.py | 2 + .../teleport/app/eom_app/controller/config.py | 199 ++++++------------ .../www/teleport/static/js/ui/config/info.js | 74 ++++++- server/www/teleport/static/js/ui/log.js | 2 +- server/www/teleport/view/config/index.mako | 54 ++++- 8 files changed, 221 insertions(+), 154 deletions(-) diff --git a/server/www/teleport/app/eom_app/app/database/create.py b/server/www/teleport/app/eom_app/app/database/create.py index 47f8c86..3a045a3 100644 --- a/server/www/teleport/app/eom_app/app/database/create.py +++ b/server/www/teleport/app/eom_app/app/database/create.py @@ -29,7 +29,7 @@ def create_and_init(db, step_begin, step_end): _db_exec(db, step_begin, step_end, '创建表 auth', """CREATE TABLE `{}auth`( `auth_id` INTEGER PRIMARY KEY {}, -`account_name` varchar(256), +`account_name` varchar(255), `host_id` INTEGER, `host_auth_id` int(11) NOT NULL );""".format(db.table_prefix, db.auto_increment)) @@ -38,16 +38,16 @@ def create_and_init(db, step_begin, step_end): # 这也是升级到数据库版本5的标志! _db_exec(db, step_begin, step_end, '创建表 key', """CREATE TABLE `{}key` ( `cert_id` integer PRIMARY KEY {}, -`cert_name` varchar(256), +`cert_name` varchar(255), `cert_pub` varchar(2048) DEFAULT '', `cert_pri` varchar(4096) DEFAULT '', -`cert_desc` varchar(256) +`cert_desc` varchar(255) ); """.format(db.table_prefix, db.auto_increment)) _db_exec(db, step_begin, step_end, '创建表 config', """CREATE TABLE `{}config` ( -`name` varchar(256) NOT NULL, -`value` varchar(256), +`name` varchar(128) NOT NULL, +`value` varchar(255), PRIMARY KEY (`name` ASC) );""".format(db.table_prefix)) @@ -64,16 +64,16 @@ PRIMARY KEY (`name` ASC) `host_port` int(11) DEFAULT 0, `protocol` int(11) DEFAULT 0, `host_lock` int(11) DEFAULT 0, -`host_desc` varchar(256) DEFAULT '' +`host_desc` varchar(255) DEFAULT '' );""".format(db.table_prefix, db.auto_increment)) _db_exec(db, step_begin, step_end, '创建表 auth_info', """CREATE TABLE `{}auth_info`( `id` INTEGER PRIMARY KEY {}, `host_id` INTEGER, `auth_mode` INTEGER, -`user_name` varchar(256), -`user_pswd` varchar(256), -`user_param` varchar(256), +`user_name` varchar(255), +`user_pswd` varchar(255), +`user_param` varchar(255), `cert_id` INTEGER, `encrypt` INTEGER, `log_time` varchar(60) diff --git a/server/www/teleport/app/eom_app/app/database/upgrade.py b/server/www/teleport/app/eom_app/app/database/upgrade.py index 7d7d393..c9175f3 100644 --- a/server/www/teleport/app/eom_app/app/database/upgrade.py +++ b/server/www/teleport/app/eom_app/app/database/upgrade.py @@ -149,7 +149,7 @@ class DatabaseUpgrade: `group_id` int(11) DEFAULT 0, `host_sys_type` int(11) DEFAULT 1, `host_ip` varchar(32) DEFAULT '', - `pro_port` varchar(256) NULL, + `pro_port` varchar(255) NULL, `host_lock` int(11) DEFAULT 0, `host_desc` varchar(128) DEFAULT '' );""".format(self.db.table_prefix, self.db.auto_increment)): @@ -161,8 +161,8 @@ class DatabaseUpgrade: `host_id` INTEGER, `pro_type` INTEGER, `auth_mode` INTEGER, - `user_name` varchar(256), - `user_pswd` varchar(256), + `user_name` varchar(255), + `user_pswd` varchar(255), `cert_id` INTEGER, `encrypt` INTEGER, `log_time` varchar(60) @@ -381,7 +381,7 @@ class DatabaseUpgrade: # 先创建三个临时表 if not self.db.exec("""CREATE TABLE `{}auth_tmp` ( `auth_id` INTEGER PRIMARY KEY {}, - `account_name` varchar(256), + `account_name` varchar(255), `host_id` INTEGER, `host_auth_id` int(11) NOT NULL );""".format(self.db.table_prefix, self.db.auto_increment)): @@ -405,9 +405,9 @@ class DatabaseUpgrade: `id` INTEGER PRIMARY KEY {}, `host_id` INTEGER, `auth_mode` INTEGER, - `user_name` varchar(256), - `user_pswd` varchar(256), - `user_param` varchar(256), + `user_name` varchar(255), + `user_pswd` varchar(255), + `user_param` varchar(255), `cert_id` INTEGER, `encrypt` INTEGER, `log_time` varchar(60) @@ -492,8 +492,8 @@ class DatabaseUpgrade: if not self.db.is_table_exists('{}config'.format(self.db.table_prefix)): if not self.db.exec("""CREATE TABLE `{}config` ( - `name` varchar(256) NOT NULL, - `value` varchar(256), + `name` varchar(128) NOT NULL, + `value` varchar(255), PRIMARY KEY (`name` ASC) );""".format(self.db.table_prefix)): self.step_end(_step, -1, 'config表不存在且无法创建') diff --git a/server/www/teleport/app/eom_app/app/db.py b/server/www/teleport/app/eom_app/app/db.py index e0b8f50..9b4fcc2 100644 --- a/server/www/teleport/app/eom_app/app/db.py +++ b/server/www/teleport/app/eom_app/app/db.py @@ -302,6 +302,13 @@ class TPDatabase: log.e('Unknown database type.\n') return False + def export_to_sql(self): + # TODO: not implement. + ret = [] + ret.append('{}'.format(self.db_type)) + ret.append('export to sql not implement.') + return '\n'.join(ret) + class TPDatabasePool: def __init__(self): @@ -350,6 +357,7 @@ class TPDatabasePool: def _last_insert_id(self, conn): return -1 + class TPSqlitePool(TPDatabasePool): def __init__(self, db_file): super().__init__() diff --git a/server/www/teleport/app/eom_app/controller/__init__.py b/server/www/teleport/app/eom_app/controller/__init__.py index 21bee29..10a32f9 100644 --- a/server/www/teleport/app/eom_app/controller/__init__.py +++ b/server/www/teleport/app/eom_app/controller/__init__.py @@ -113,6 +113,8 @@ controllers = [ # (r'/set/os-operator', set.OsOperator), # (r'/set/info', config.InfoHandler), # (r'/set/db', config.DatabaseHandler), + (r'/config/export-database', config.ExportDatabaseHandler), + (r'/config/import-database', config.ImportDatabaseHandler), (r'/config/', config.IndexHandler), (r'/config', config.IndexHandler), diff --git a/server/www/teleport/app/eom_app/controller/config.py b/server/www/teleport/app/eom_app/controller/config.py index 10f69fa..2cf38f7 100644 --- a/server/www/teleport/app/eom_app/controller/config.py +++ b/server/www/teleport/app/eom_app/controller/config.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- +import os +import time import json import tornado.gen import tornado.httpclient @@ -8,6 +10,7 @@ from eom_ver import * from eom_app.app.db import get_db from eom_app.app.configs import app_cfg from eom_app.app.util import * +from eom_common.eomcore.logger import log from .base import TPBaseAdminAuthHandler, TPBaseAdminAuthJsonHandler cfg = app_cfg() @@ -24,7 +27,6 @@ class IndexHandler(TPBaseAdminAuthHandler): if 'code' in return_data: _code = return_data['code'] if _code == 0: - # core['detected'] = True cfg.update_core(return_data['data']) core_detected = True @@ -52,138 +54,77 @@ class IndexHandler(TPBaseAdminAuthHandler): } self.render('config/index.mako', page_param=json.dumps(param)) -# class InfoHandler(TPBaseAdminAuthHandler): -# @tornado.gen.coroutine -# def get(self): -# core_detected = False -# req = {'method': 'get_config', 'param': []} -# _yr = async_post_http(req) -# return_data = yield _yr -# if return_data is not None: -# if 'code' in return_data: -# _code = return_data['code'] -# if _code == 0: -# # core['detected'] = True -# cfg.update_core(return_data['data']) -# core_detected = True -# -# if not core_detected: -# cfg.update_core(None) -# -# _db = get_db() -# database = '未知' -# if _db.db_source['type'] == _db.DB_TYPE_SQLITE: -# database = 'SQLite({})'.format(_db.db_source['file']) -# elif _db.db_source['type'] == _db.DB_TYPE_MYSQL: -# database = 'MySQL' -# -# param = { -# 'core': cfg.core, -# 'web': { -# 'version': TS_VER, -# 'core_server_rpc': cfg['core_server_rpc'], -# 'database': database -# } -# } -# self.render('set/info.mako', page_param=json.dumps(param)) + +class ExportDatabaseHandler(TPBaseAdminAuthHandler): + def get(self): + self.set_header('Content-Type', 'application/octet-stream') + self.set_header('Content-Disposition', 'attachment; filename=teleport-database-export.sql') + + sql = get_db().export_to_sql() + + # self.write("分组ID, 操作系统, IP地址, 端口, 协议, 状态, 描述, 系统用户, 系统密码, 是否加密, 附加参数, 密钥ID, 认证类型\n".encode('gbk')) + self.write(sql) + self.finish() -# class DatabaseHandler(TPBaseAdminAuthHandler): -# def get(self): -# _db = get_db() -# # database = '未知' -# # if _db.db_source['type'] == _db.DB_TYPE_SQLITE: -# # database = 'SQLite({})'.format(_db.db_source['file']) -# # elif _db.db_source['type'] == _db.DB_TYPE_MYSQL: -# # database = 'MySQL' -# -# param = {'db': _db.db_source} -# self.render('set/database.mako', page_param=json.dumps(param)) +class ImportDatabaseHandler(TPBaseAdminAuthHandler): + # TODO: 导入操作可能会比较耗时,应该分离导入和获取导入状态两个过程,在页面上可以呈现导入进度,并列出导出成功/失败的项 -# def _restart_func(): -# time.sleep(1) -# -# PLATFORM = platform.system().lower() -# -# if PLATFORM == 'windows': -# sf = os.path.join(cfg.app_path, 'tools', 'restart.bat') -# os.system('cmd.exe /c "{}"'.format(sf)) -# else: -# # sf = os.path.join(cfg.app_path, 'tools', 'restart.sh') -# # os.system(sf) -# os.system('service eom_ts restart') -# -# # os.system(sf) + @tornado.gen.coroutine + def post(self): + """ + sql导入规则: + 以事务方式执行sql语句 + """ + ret = dict() + ret['code'] = 0 + ret['message'] = '' + # ret['data'] = {} + # ret['data']['msg'] = list() # 记录跳过的行(格式不正确,或者数据重复等) + sql_filename = '' + try: + upload_path = os.path.join(cfg.data_path, 'tmp') # 文件的暂存路径 + if not os.path.exists(upload_path): + os.mkdir(upload_path) + file_metas = self.request.files['sqlfile'] # 提取表单中‘name’为‘file’的文件元数据 + for meta in file_metas: + now = time.localtime(time.time()) + tmp_name = 'upload-{:04d}{:02d}{:02d}{:02d}{:02d}{:02d}.sql'.format(now.tm_year, now.tm_mon, now.tm_mday, now.tm_hour, now.tm_min, now.tm_sec) + sql_filename = os.path.join(upload_path, tmp_name) + with open(sql_filename, 'wb') as f: + f.write(meta['body']) -# def restart_service(): -# # todo: 使用eom_ts.exe运行脚本的方式(新进程)来重启服务,避免正在运行的本服务未退出的影响 -# -# t = threading.Thread(target=_restart_func) -# t.start() -# + # file encode maybe utf8 or gbk... check it out. + file_encode = None + with open(sql_filename, encoding='utf8') as f: + try: + f.readlines() + file_encode = 'utf8' + except: + pass -# class UpdateConfig(TPBaseAdminAuthJsonHandler): -# def post(self): -# args = self.get_argument('args', None) -# if args is not None: -# args = json.loads(args) -# else: -# self.write_json(-1) -# return -# -# change_list = args['cfg'] -# reboot = args['reboot'] -# -# try: -# ret = set.set_config(change_list) -# if ret: -# for i in range(len(change_list)): -# if change_list[i]['name'] == 'ts_server_ip': -# # static_path = cfg.static_path -# var_js = os.path.join(cfg.static_path, 'js', 'var.js') -# f = None -# try: -# f = open(var_js, 'w') -# # config_list = host.get_config_list() -# # ts_server = dict() -# # ts_server['ip'] = config_list['ts_server_ip'] -# # ts_server['ssh_port'] = config_list['ts_server_ssh_port'] -# # ts_server['rdp_port'] = config_list['ts_server_rdp_port'] -# # f.write("\"use strict\";\nvar teleport_ip = \"{}\";\n".format(ts_server['ip'])) -# f.write("\"use strict\";\nvar teleport_ip = \"{}\";\n".format(change_list[i]['value'])) -# break -# except Exception: -# return self.write(-1) -# finally: -# if f is not None: -# f.close() -# -# if reboot: -# restart_service() -# -# self.write_json(0) -# else: -# self.write_json(-1) -# except: -# self.write_json(-2) + if file_encode is None: + os.remove(sql_filename) + log.e('file `{}` unknown encode, neither GBK nor UTF8.\n'.format(sql_filename)) + ret['code'] = -2 + ret['message'] = 'upload sql file is not utf8 encode.' + return self.write(json.dumps(ret).encode('utf8')) -# class OsOperator(TPBaseUserAuthJsonHandler): -# def post(self): -# args = self.get_argument('args', None) -# if args is not None: -# args = json.loads(args) -# else: -# self.write_json(-1) -# return -# _OP = int(args['OP']) -# try: -# if _OP == 1: -# os.system('reboot') -# else: -# os.system('shutdown -h now') -# # 重新启动 -# self.write_json(0) -# except: -# self.write_json(-2) -# + with open(sql_filename, encoding=file_encode) as f: + lines = f.readlines() + for line in lines: + print(line) + pass + + ret['code'] = 0 + return self.write(json.dumps(ret).encode('utf8')) + except: + log.e('error\n') + ret['code'] = -6 + ret['message'] = '发生异常.' + return self.write(json.dumps(ret).encode('utf8')) + + finally: + if os.path.exists(sql_filename): + os.remove(sql_filename) diff --git a/server/www/teleport/static/js/ui/config/info.js b/server/www/teleport/static/js/ui/config/info.js index 8ff06da..8431334 100644 --- a/server/www/teleport/static/js/ui/config/info.js +++ b/server/www/teleport/static/js/ui/config/info.js @@ -4,8 +4,10 @@ ywl.on_init = function (cb_stack, cb_args) { console.log(ywl.page_options); var dom = { - info: $('#info-kv') + info: $('#info-kv'), // , btn_maintance: $('#btn_maintenance') + btn_db_export: $('#btn-db-export'), + btn_db_import: $('#btn-db-import'), }; var html = []; @@ -66,9 +68,79 @@ ywl.on_init = function (cb_stack, cb_args) { // }); // }); // + + dom.btn_db_export.click(function () { + alert('not implement.'); + window.location.href = '/config/export-database' + }); + dom.btn_db_import.click(function () { + alert('not implement.'); + + var _fn_sure = function (cb_stack, cb_args) { + var html = ''; + dom.btn_db_import.after($(html)); + var update_file = $("#upload-file"); + + update_file.change(function () { + var file_path = $(this).val(); + if (file_path === null || file_path === undefined || file_path === '') { + return; + } + ywl.do_upload_sql_file(); + }); + + update_file.trigger('click'); + }; + + var cb_stack = CALLBACK_STACK.create(); + ywl.dlg_confirm(cb_stack, { + msg: '

注意:操作不可恢复!!

您确定要清除所有现有数据,然后导入sql文件吗?

', + fn_yes: _fn_sure + }); + }); + cb_stack.exec(); }; +ywl.do_upload_sql_file = function () { + var param = {}; + $.ajaxFileUpload({ + url: "/config/import-database",// 需要链接到服务器地址 + secureuri: false, + fileElementId: "upload-file", // 文件选择框的id属性 + dataType: 'text', // 服务器返回的格式,可以是json + data: param, + success: function (data) { + $('#upload-file').remove(); + var ret = JSON.parse(data); + if (ret.code === TPE_OK) { +// g_host_table.reload(); + ywl.notify_success('导入sql成功!'); +// if (ret.data.msg.length > 0) { +// var html = []; +// html.push(''); +// +// // $('#batch_add_host_result').html(html.join('')); +//// $('#dialog_batch_add_host').modal({backdrop: 'static'}); +// } + } else { + ywl.notify_error('导入sql失败! 错误号:' + ret.code); + } + }, + error: function () { + $('#upload-file').remove(); + ywl.notify_error('网络故障,导入sql失败!'); + } + }); +}; + ywl._make_protocol_info = function (name, p) { if (_.isUndefined(p)) return ywl._make_info(name, '未能检测到'); diff --git a/server/www/teleport/static/js/ui/log.js b/server/www/teleport/static/js/ui/log.js index 078192c..407cd16 100644 --- a/server/www/teleport/static/js/ui/log.js +++ b/server/www/teleport/static/js/ui/log.js @@ -8,7 +8,7 @@ ywl.on_init = function (cb_stack, cb_args) { //=================================== // 表格数据 var disk_rate = 0; - if(0 == ywl.page_options.total_size) { + if(0 === ywl.page_options.total_size) { $('#disk-status').text('未能获取到录像文件所在磁盘空间信息'); } else { disk_rate = parseInt(ywl.page_options.free_size * 100 / ywl.page_options.total_size); diff --git a/server/www/teleport/view/config/index.mako b/server/www/teleport/view/config/index.mako index 810f7c8..b74ca37 100644 --- a/server/www/teleport/view/config/index.mako +++ b/server/www/teleport/view/config/index.mako @@ -19,6 +19,24 @@ <%block name="extend_css"> @@ -46,14 +77,27 @@
-

服务器配置信息

+

服务器配置信息

-##
-##

高级设置

-##

进入维护模式

-##
+
+
+

数据库管理

+ +
+

导出

+

将数据库中所有数据导出到sql文件,可用作备份。

+ +
+ +
+

导入

+

清空当前数据库中所有数据,然后从sql文件中导入数据到数据库中。

+

注意!导入操作将导致现有数据被清除且无法恢复,请谨慎使用!

+ +
+
From ef23397be8e70e957d87bf31b7218af9d7867672 Mon Sep 17 00:00:00 2001 From: Apex Liu Date: Sat, 27 May 2017 23:51:20 +0800 Subject: [PATCH 02/14] =?UTF-8?q?=E6=94=AF=E6=8C=81=E8=AE=B0=E5=BD=95SFTP?= =?UTF-8?q?=E6=93=8D=E4=BD=9C=E6=97=A5=E5=BF=97=E4=BA=86=EF=BC=8C=E8=83=BD?= =?UTF-8?q?=E8=AE=B0=E5=BD=95=E6=96=87=E4=BB=B6=E6=89=93=E5=BC=80=E3=80=81?= =?UTF-8?q?=E5=88=A0=E9=99=A4=EF=BC=8C=E7=9B=AE=E5=BD=95=E5=88=9B=E5=BB=BA?= =?UTF-8?q?=E3=80=81=E5=88=A0=E9=99=A4=EF=BC=8C=E6=94=B9=E5=90=8D=E4=BB=A5?= =?UTF-8?q?=E5=8F=8A=E5=88=9B=E5=BB=BA=E7=AC=A6=E5=8F=B7=E9=93=BE=E6=8E=A5?= =?UTF-8?q?=E7=AD=89=E6=93=8D=E4=BD=9C=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/tp_core/protocol/ssh/ssh_session.cpp | 2240 +++++++++-------- server/tp_core/protocol/ssh/ssh_session.h | 267 +- .../teleport/app/eom_app/controller/record.py | 28 +- .../teleport/view/log/record-sftp-cmd.mako | 125 + .../www/teleport/view/log/record-ssh-cmd.mako | 204 +- 5 files changed, 1558 insertions(+), 1306 deletions(-) create mode 100644 server/www/teleport/view/log/record-sftp-cmd.mako diff --git a/server/tp_core/protocol/ssh/ssh_session.cpp b/server/tp_core/protocol/ssh/ssh_session.cpp index a6763b5..09dbe4a 100644 --- a/server/tp_core/protocol/ssh/ssh_session.cpp +++ b/server/tp_core/protocol/ssh/ssh_session.cpp @@ -1,1071 +1,1169 @@ -#include "ssh_session.h" -#include "ssh_proxy.h" -#include "tpp_env.h" - -#include - -SshSession::SshSession(SshProxy *proxy, ssh_session sess_client) : - ExThreadBase("ssh-session-thread"), - m_proxy(proxy), - m_cli_session(sess_client), - m_srv_session(NULL) -{ - m_retcode = SESS_STAT_RUNNING; - m_db_id = 0; - - m_auth_mode = TS_AUTH_MODE_PASSWORD; - - m_is_first_server_data = true; - m_is_sftp = false; - - m_is_logon = false; - m_have_error = false; - m_recving_from_srv = false; - m_recving_from_cli = false; - - memset(&m_srv_cb, 0, sizeof(m_srv_cb)); - ssh_callbacks_init(&m_srv_cb); - m_srv_cb.userdata = this; - - memset(&m_cli_channel_cb, 0, sizeof(m_cli_channel_cb)); - ssh_callbacks_init(&m_cli_channel_cb); - m_cli_channel_cb.userdata = this; - - memset(&m_srv_channel_cb, 0, sizeof(m_srv_channel_cb)); - ssh_callbacks_init(&m_srv_channel_cb); - m_srv_channel_cb.userdata = this; - - m_command_flag = 0; - m_cmd_char_pos = m_cmd_char_list.begin(); -} - -SshSession::~SshSession() { - - _set_stop_flag(); - - if (m_is_sftp) { - m_proxy->remove_sftp_sid(m_sid); - } - - EXLOGD("[ssh] session destroy.\n"); -} - -void SshSession::_thread_loop(void) { - _run(); - _on_session_end(); - m_proxy->session_finished(this); -} - -void SshSession::_set_stop_flag(void) { - _close_channels(); - - if (NULL != m_cli_session) { - ssh_disconnect(m_cli_session); - ssh_free(m_cli_session); - m_cli_session = NULL; - } - if (NULL != m_srv_session) { - ssh_disconnect(m_srv_session); - ssh_free(m_srv_session); - m_srv_session = NULL; - } -} - -bool SshSession::_on_session_begin(const TPP_SESSION_INFO* info) -{ - if (!g_ssh_env.session_begin(info, &m_db_id)) - { - EXLOGD("[ssh] session_begin error. %d\n", m_db_id); - return false; - } - - m_rec.begin(g_ssh_env.replay_path.c_str(), L"tp-ssh", m_db_id, info); - - return true; -} - -bool SshSession::_on_session_end(void) -{ - if (m_db_id > 0) - { - EXLOGD("[ssh] session ret-code: %d\n", m_retcode); - - // Ựûз״̬Ϊ¼´ֵ - if (m_retcode == SESS_STAT_RUNNING) - m_retcode = SESS_STAT_END; - - g_ssh_env.session_end(m_db_id, m_retcode); - } - - return true; -} - -void SshSession::_close_channels(void) { - ExThreadSmartLock locker(m_lock); - - if (m_channel_cli_srv.size() > 0) - EXLOGW("[ssh] when close all channels, %d client channel need close.\n", m_channel_cli_srv.size()); - if (m_channel_srv_cli.size() > 0) - EXLOGW("[ssh] when close all channels, %d server channel need close.\n", m_channel_srv_cli.size()); - - ts_ssh_channel_map::iterator it = m_channel_cli_srv.begin(); - for (; it != m_channel_cli_srv.end(); ++it) { - if (!ssh_channel_is_eof(it->first)) - ssh_channel_send_eof(it->first); - if (!ssh_channel_is_closed(it->first)) - ssh_channel_close(it->first); - ssh_channel_free(it->first); - - if (NULL != it->second) { - if (!ssh_channel_is_eof(it->second->channel)) - ssh_channel_send_eof(it->second->channel); - if (!ssh_channel_is_closed(it->second->channel)) - ssh_channel_close(it->second->channel); - ssh_channel_free(it->second->channel); - - delete it->second; - } - } - - it = m_channel_srv_cli.begin(); - for (; it != m_channel_srv_cli.end(); ++it) { - if (NULL != it->second) { - delete it->second; - } - } - - m_channel_cli_srv.clear(); - m_channel_srv_cli.clear(); -} - -void SshSession::_run(void) { - m_srv_cb.auth_password_function = _on_auth_password_request; - m_srv_cb.channel_open_request_session_function = _on_new_channel_request; - - m_srv_channel_cb.channel_data_function = _on_server_channel_data; - m_srv_channel_cb.channel_close_function = _on_server_channel_close; - - m_cli_channel_cb.channel_data_function = _on_client_channel_data; - // channel_eof_function - m_cli_channel_cb.channel_close_function = _on_client_channel_close; - // channel_signal_function - // channel_exit_status_function - // channel_exit_signal_function - m_cli_channel_cb.channel_pty_request_function = _on_client_pty_request; - m_cli_channel_cb.channel_shell_request_function = _on_client_shell_request; - // channel_auth_agent_req_function - // channel_x11_req_function - m_cli_channel_cb.channel_pty_window_change_function = _on_client_pty_win_change; - m_cli_channel_cb.channel_exec_request_function = _on_client_channel_exec_request; - // channel_env_request_function - m_cli_channel_cb.channel_subsystem_request_function = _on_client_channel_subsystem_request; - - - ssh_set_server_callbacks(m_cli_session, &m_srv_cb); - - // ȫӣԿ - if (ssh_handle_key_exchange(m_cli_session)) { - EXLOGE("[ssh] key exchange with client failed: %s\n", ssh_get_error(m_cli_session)); - return; - } - - ssh_event event_loop = ssh_event_new(); - ssh_event_add_session(event_loop, m_cli_session); - - // ֤һͨ - int r = 0; - while (!(m_is_logon && m_channel_cli_srv.size() > 0)) { - if (m_have_error) - break; - r = ssh_event_dopoll(event_loop, -1); - if (r == SSH_ERROR) { - EXLOGE("[ssh] Error : %s\n", ssh_get_error(m_cli_session)); - ssh_disconnect(m_cli_session); - return; - } - } - - if (m_have_error) { - ssh_event_remove_session(event_loop, m_cli_session); - ssh_event_free(event_loop); - EXLOGE("[ssh] Error, exiting loop.\n"); - return; - } - - EXLOGW("[ssh] Authenticated and got a channel.\n"); - - // ˫Ѿˣʼת - ssh_event_add_session(event_loop, m_srv_session); - do { - r = ssh_event_dopoll(event_loop, -1); - if (r == SSH_ERROR) { - if (0 != ssh_get_error_code(m_cli_session)) - { - EXLOGE("[ssh] ssh_event_dopoll() [cli] %s\n", ssh_get_error(m_cli_session)); - //ssh_disconnect(m_cli_session); - } - else if (0 != ssh_get_error_code(m_srv_session)) - { - EXLOGE("[ssh] ssh_event_dopoll() [srv] %s\n", ssh_get_error(m_srv_session)); - //ssh_disconnect(m_srv_session); - } - - // EXLOGE("[ssh] ssh_event_dopoll() [cli] %s, [srv] %s\n", ssh_get_error(m_cli_session), ssh_get_error(m_srv_session)); - _close_channels(); - } - } while (m_channel_cli_srv.size() > 0); - - EXLOGV("[ssh] [%s:%d] all channel in this session are closed.\n", m_client_ip.c_str(), m_client_port); - - ssh_event_remove_session(event_loop, m_cli_session); - ssh_event_remove_session(event_loop, m_srv_session); - ssh_event_free(event_loop); -} - - -int SshSession::_on_auth_password_request(ssh_session session, const char *user, const char *password, void *userdata) { - // õuserҪsession-idpasswordticket䣬ΪжûǷʵݡ - SshSession *_this = (SshSession *)userdata; - _this->m_sid = user; - EXLOGV("[ssh] authenticating, session-id: %s\n", _this->m_sid.c_str()); - - //bool bRet = true; - int protocol = 0; - TPP_SESSION_INFO* sess_info = g_ssh_env.take_session(_this->m_sid.c_str()); - - if (NULL == sess_info) { - EXLOGW("[ssh] try to get login-info from ssh-sftp-session.\n"); - // ԴsftpӼ¼лȡϢһsshỰΪsftpỰڲὫϢ¼ã - TS_SFTP_SESSION_INFO sftp_info; - if (!_this->m_proxy->get_sftp_session_info(_this->m_sid, sftp_info)) { - EXLOGE("[ssh] no such session: %s\n", _this->m_sid.c_str()); - _this->m_have_error = true; - _this->m_retcode = SESS_STAT_ERR_AUTH_DENIED; - return SSH_AUTH_DENIED; - } - - _this->m_server_ip = sftp_info.host_ip; - _this->m_server_port = sftp_info.host_port; - _this->m_auth_mode = sftp_info.auth_mode; - _this->m_user_name = sftp_info.user_name; - _this->m_user_auth = sftp_info.user_auth; - -// sess_info.host_ip = sftp_info.host_ip; -// sess_info.host_port = sftp_info.host_port; -// sess_info.auth_mode = sftp_info.auth_mode; -// sess_info.user_name = sftp_info.user_name; -// sess_info.user_auth = sftp_info.user_auth; -// sess_info.protocol = TS_PROXY_PROTOCOL_SSH; - protocol = TS_PROXY_PROTOCOL_SSH; - - // ΪǴsftpỰĵ¼ݣƱỰֻsftpʹshellˡ - _this->_enter_sftp_mode(); - } else { - _this->m_server_ip = sess_info->host_ip; - _this->m_server_port = sess_info->host_port; - _this->m_auth_mode = sess_info->auth_mode; - _this->m_user_name = sess_info->user_name; - _this->m_user_auth = sess_info->user_auth; - protocol = sess_info->protocol; - } - - //EXLOGE("[ssh---------1] auth info [password:%s:%s:%d]\n", _this->m_user_name.c_str(),_this->m_user_auth.c_str(), _this->m_auth_mode); - if (protocol != TS_PROXY_PROTOCOL_SSH) { - g_ssh_env.free_session(sess_info); - EXLOGE("[ssh] session '%s' is not for SSH.\n", _this->m_sid.c_str()); - _this->m_have_error = true; - _this->m_retcode = SESS_STAT_ERR_AUTH_DENIED; - return SSH_AUTH_DENIED; - } - - if (!_this->_on_session_begin(sess_info)) - { - g_ssh_env.free_session(sess_info); - _this->m_have_error = true; - _this->m_retcode = SESS_STAT_ERR_AUTH_DENIED; - return SSH_AUTH_DENIED; - } - - g_ssh_env.free_session(sess_info); - sess_info = NULL; - - // ڳԸsession-idȡõϢӲ¼SSH - EXLOGV("[ssh] try to connect to real SSH server %s:%d\n", _this->m_server_ip.c_str(), _this->m_server_port); - _this->m_srv_session = ssh_new(); - ssh_options_set(_this->m_srv_session, SSH_OPTIONS_HOST, _this->m_server_ip.c_str()); - ssh_options_set(_this->m_srv_session, SSH_OPTIONS_PORT, &_this->m_server_port); - - if (_this->m_auth_mode != TS_AUTH_MODE_NONE) - ssh_options_set(_this->m_srv_session, SSH_OPTIONS_USER, _this->m_user_name.c_str()); - - int _timeout_us = 30000000; // 30 sec. - ssh_options_set(_this->m_srv_session, SSH_OPTIONS_TIMEOUT_USEC, &_timeout_us); - - int rc = 0; - rc = ssh_connect(_this->m_srv_session); - if (rc != SSH_OK) { - EXLOGE("[ssh] can not connect to real SSH server %s:%d.\n", _this->m_server_ip.c_str(), _this->m_server_port); - _this->m_have_error = true; - _this->m_retcode = SESS_STAT_ERR_CONNECT; - return SSH_AUTH_DENIED; - } - - if (_this->m_auth_mode == TS_AUTH_MODE_PASSWORD) { - rc = ssh_userauth_password(_this->m_srv_session, NULL, _this->m_user_auth.c_str()); - if (rc != SSH_OK) { - EXLOGE("[ssh] can not use user/name login to real SSH server %s:%d.\n", _this->m_server_ip.c_str(), _this->m_server_port); - _this->m_have_error = true; - _this->m_retcode = SESS_STAT_ERR_AUTH_DENIED; - return SSH_AUTH_DENIED; - } - } - else if (_this->m_auth_mode == TS_AUTH_MODE_PRIVATE_KEY) { - ssh_key key = NULL; - if (SSH_OK != ssh_pki_import_privkey_base64(_this->m_user_auth.c_str(), NULL, NULL, NULL, &key)) { - EXLOGE("[ssh] can not import private-key for auth.\n"); - _this->m_have_error = true; - _this->m_retcode = SESS_STAT_ERR_BAD_SSH_KEY; - return SSH_AUTH_DENIED; - } - - rc = ssh_userauth_publickey(_this->m_srv_session, NULL, key); - if (rc != SSH_OK) { - ssh_key_free(key); - EXLOGE("[ssh] can not use private-key login to real SSH server %s:%d.\n", _this->m_server_ip.c_str(), _this->m_server_port); - _this->m_have_error = true; - _this->m_retcode = SESS_STAT_ERR_AUTH_DENIED; - return SSH_AUTH_DENIED; - } - - ssh_key_free(key); - } - else if (_this->m_auth_mode == TS_AUTH_MODE_NONE) - { - // do nothing. - return SSH_AUTH_DENIED; - } - else { - EXLOGE("[ssh] invalid auth mode.\n"); - _this->m_have_error = true; - _this->m_retcode = SESS_STAT_ERR_AUTH_DENIED; - return SSH_AUTH_DENIED; - } - - _this->m_is_logon = true; - return SSH_AUTH_SUCCESS; -} - -ssh_channel SshSession::_on_new_channel_request(ssh_session session, void *userdata) { - // ͻ˳ԴһͨȻͨͨշݣ - EXLOGV("[ssh] allocated session channel\n"); - - SshSession *_this = (SshSession *)userdata; - - ssh_channel cli_channel = ssh_channel_new(session); - ssh_set_channel_callbacks(cli_channel, &_this->m_cli_channel_cb); - - // ҲҪķһͨת - ssh_channel srv_channel = ssh_channel_new(_this->m_srv_session); - if (ssh_channel_open_session(srv_channel)) { - EXLOGE("[ssh] error opening channel to real server: %s\n", ssh_get_error(session)); - ssh_channel_free(cli_channel); - return NULL; - } - ssh_set_channel_callbacks(srv_channel, &_this->m_srv_channel_cb); - - // ͻ˺ͷ˵ͨ - { - ExThreadSmartLock locker(_this->m_lock); - - TS_SSH_CHANNEL_INFO *srv_info = new TS_SSH_CHANNEL_INFO; - srv_info->channel = srv_channel; - srv_info->type = TS_SSH_CHANNEL_TYPE_UNKNOWN; - _this->m_channel_cli_srv.insert(std::make_pair(cli_channel, srv_info)); - - TS_SSH_CHANNEL_INFO *cli_info = new TS_SSH_CHANNEL_INFO; - cli_info->channel = cli_channel; - cli_info->type = TS_SSH_CHANNEL_TYPE_UNKNOWN; - _this->m_channel_srv_cli.insert(std::make_pair(srv_channel, cli_info)); - } - - return cli_channel; -} - -TS_SSH_CHANNEL_INFO *SshSession::_get_cli_channel(ssh_channel srv_channel) { - ExThreadSmartLock locker(m_lock); - ts_ssh_channel_map::iterator it = m_channel_srv_cli.find(srv_channel); - if (it == m_channel_srv_cli.end()) - return NULL; - else - return it->second; -} - -TS_SSH_CHANNEL_INFO *SshSession::_get_srv_channel(ssh_channel cli_channel) { - ExThreadSmartLock locker(m_lock); - ts_ssh_channel_map::iterator it = m_channel_cli_srv.find(cli_channel); - if (it == m_channel_cli_srv.end()) - return NULL; - else - return it->second; -} - -void SshSession::_process_command(int from, const ex_u8* data, int len) -{ - if (TS_SSH_DATA_FROM_CLIENT == from) - { - m_command_flag = 0; - - if (len == 3) - { - if ((data[0] == 0x1b && data[1] == 0x5b && data[2] == 0x41) // key-up - || (data[0] == 0x1b && data[1] == 0x5b && data[2] == 0x42) // key-down - ) - { - m_command_flag = 1; - return; - } - else if (data[0] == 0x1b && data[1] == 0x5b && data[2] == 0x43) // key-right - { - if (m_cmd_char_pos != m_cmd_char_list.end()) - m_cmd_char_pos++; - return; - } - else if (data[0] == 0x1b && data[1] == 0x5b && data[2] == 0x44) // key-left - { - if (m_cmd_char_pos != m_cmd_char_list.begin()) - m_cmd_char_pos--; - return; - } - else if ( - (data[0] == 0x1b && data[1] == 0x4f && data[2] == 0x41) - || (data[0] == 0x1b && data[1] == 0x4f && data[2] == 0x42) - || (data[0] == 0x1b && data[1] == 0x4f && data[2] == 0x43) - || (data[0] == 0x1b && data[1] == 0x4f && data[2] == 0x44) - ) - { - // ༭ģʽµҼ - return; - } - } - else if (len == 1) - { - if (data[0] == 0x09) - { - m_command_flag = 1; - return; - } - else if (data[0] == 0x7f) // Backspace (ɾһַ) - { - if (m_cmd_char_pos != m_cmd_char_list.begin()) - { - m_cmd_char_pos--; - m_cmd_char_pos = m_cmd_char_list.erase(m_cmd_char_pos); - } - return; - } - else if (data[0] == 0x1b) - { - // Esc - return; - } - - if (data[0] != 0x0d && !isprint(data[0])) - return; - } - else if (len > 3) - { - if (data[0] == 0x1b && data[1] == 0x5b) - { - // ϵңҲDZ༭ģʽµңôͺԣӦDZ༭ģʽµ룩 - m_cmd_char_list.clear(); - m_cmd_char_pos = m_cmd_char_list.begin(); - return; - } - } - - int processed = 0; - for (int i = 0; i < len; i++) - { - if (data[i] == 0x0d) - { - m_command_flag = 0; - - for (int j = processed; j < i; ++j) - { - m_cmd_char_pos = m_cmd_char_list.insert(m_cmd_char_pos, data[j]); - m_cmd_char_pos++; - } - - if (m_cmd_char_list.size() > 0) - { - m_cmd_char_list.push_back(0x0d); - m_cmd_char_list.push_back(0x0a); - ex_astr str(m_cmd_char_list.begin(), m_cmd_char_list.end()); - // EXLOGD("[ssh] save cmd: %s", str.c_str()); - m_rec.record_command(str); - } - m_cmd_char_list.clear(); - m_cmd_char_pos = m_cmd_char_list.begin(); - - processed = i + 1; - } - } - - if (processed < len) - { - for (int j = processed; j < len; ++j) - { - m_cmd_char_pos = m_cmd_char_list.insert(m_cmd_char_pos, data[j]); - m_cmd_char_pos++; - } - } - } - else if (TS_SSH_DATA_FROM_SERVER == from) - { - if (m_command_flag == 0) - return; - - bool esc_mode = false; - int esc_arg = 0; - - for (int i = 0; i < len; i++) - { - if (esc_mode) - { - switch (data[i]) - { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - esc_arg = esc_arg * 10 + (data[i] - '0'); - break; - - case 0x3f: - case ';': - case '>': - m_cmd_char_list.clear(); - m_cmd_char_pos = m_cmd_char_list.begin(); - return; - break; - - case 0x4b: // 'K' - { - if (0 == esc_arg) - { - // ɾ굽βַ - m_cmd_char_list.erase(m_cmd_char_pos, m_cmd_char_list.end()); - m_cmd_char_pos = m_cmd_char_list.end(); - } - else if (1 == esc_arg) - { - // ɾӿʼ괦ַ - m_cmd_char_list.erase(m_cmd_char_list.begin(), m_cmd_char_pos); - m_cmd_char_pos = m_cmd_char_list.end(); - } - else if (2 == esc_arg) - { - // ɾ - m_cmd_char_list.clear(); - m_cmd_char_pos = m_cmd_char_list.begin(); - } - - esc_mode = false; - break; - } - case 0x43: // 'C' - { - // - if (esc_arg == 0) - esc_arg = 1; - for (int j = 0; j < esc_arg; ++j) - { - if (m_cmd_char_pos != m_cmd_char_list.end()) - m_cmd_char_pos++; - } - esc_mode = false; - break; - } - - case 0x50: // 'P' ɾַָ - { - if (esc_arg == 0) - esc_arg = 1; - for (int j = 0; j < esc_arg; ++j) - { - if (m_cmd_char_pos != m_cmd_char_list.end()) - m_cmd_char_pos = m_cmd_char_list.erase(m_cmd_char_pos); - } - esc_mode = false; - break; - } - - case 0x40: // '@' ָĿհַ - { - if (esc_arg == 0) - esc_arg = 1; - for (int j = 0; j < esc_arg; ++j) - { - m_cmd_char_pos = m_cmd_char_list.insert(m_cmd_char_pos, ' '); - } - esc_mode = false; - break; - } - - default: - esc_mode = false; - break; - } - - continue; - } - - switch (data[i]) - { - case 0x07: - { - // - break; - } - case 0x08: - { - // - if (m_cmd_char_pos != m_cmd_char_list.begin()) - m_cmd_char_pos--; - - break; - } - case 0x1b: - { - if (i + 1 < len) - { - if (data[i + 1] == 0x5b) - { - esc_mode = true; - esc_arg = 0; - - i += 1; - break; - } - } - - break; - } - case 0x0d: - { - m_cmd_char_list.clear(); - m_cmd_char_pos = m_cmd_char_list.begin(); - - break; - } - default: - if (m_cmd_char_pos != m_cmd_char_list.end()) - { - m_cmd_char_pos = m_cmd_char_list.erase(m_cmd_char_pos); - m_cmd_char_pos = m_cmd_char_list.insert(m_cmd_char_pos, data[i]); - m_cmd_char_pos++; - } - else - { - m_cmd_char_list.push_back(data[i]); - m_cmd_char_pos = m_cmd_char_list.end(); - } - } - } - } - - return; -} - -int SshSession::_on_client_pty_request(ssh_session session, ssh_channel channel, const char *term, int x, int y, int px, - int py, void *userdata) { - SshSession *_this = (SshSession *)userdata; - - if (_this->m_is_sftp) { - EXLOGE("[ssh] try to request pty on a sftp-session.\n"); - return SSH_FATAL; - } - - EXLOGD("[ssh] client request terminal: %s, (%d, %d) / (%d, %d)\n", term, x, y, px, py); - _this->m_rec.record_win_size_startup(x, y); - TS_SSH_CHANNEL_INFO *info = _this->_get_srv_channel(channel); - if (NULL == info || NULL == info->channel) { - EXLOGE("[ssh] when client request pty, not found server channel.\n"); - return SSH_FATAL; - } - - return ssh_channel_request_pty_size(info->channel, term, x, y); -} - -int SshSession::_on_client_shell_request(ssh_session session, ssh_channel channel, void *userdata) { - SshSession *_this = (SshSession *)userdata; - char buf[2048] = { 0 }; - - if (_this->m_is_sftp) { - EXLOGE("[ssh] try to request shell on a sftp-session.\n"); - - snprintf(buf, sizeof(buf), - "\r\n\r\n"\ - "!! ERROR !!\r\n"\ - "Session-ID '%s' has been used for SFTP.\r\n"\ - "\r\n", _this->m_sid.c_str() - ); - ssh_channel_write(channel, buf, strlen(buf)); - - return SSH_FATAL; - } - - EXLOGD("[ssh] client request shell\n"); - - - TS_SSH_CHANNEL_INFO *srv_info = _this->_get_srv_channel(channel); - if (NULL == srv_info || NULL == srv_info->channel) { - EXLOGE("[ssh] when client request shell, not found server channel.\n"); - return SSH_FATAL; - } - srv_info->type = TS_SSH_CHANNEL_TYPE_SHELL; - - TS_SSH_CHANNEL_INFO *cli_info = _this->_get_cli_channel(srv_info->channel); - if (NULL == cli_info || NULL == cli_info->channel) { - EXLOGE("[ssh] when client request shell, not found client channel.\n"); - return SSH_FATAL; - } - cli_info->type = TS_SSH_CHANNEL_TYPE_SHELL; - - - return ssh_channel_request_shell(srv_info->channel); -} - -void SshSession::_on_client_channel_close(ssh_session session, ssh_channel channel, void *userdata) { - EXLOGD("[ssh] on_client_channel_close().\n"); - - SshSession *_this = (SshSession *)userdata; - TS_SSH_CHANNEL_INFO *info = _this->_get_srv_channel(channel); - if (NULL == info || NULL == info->channel) { - EXLOGW("[ssh] when close client channel, not found server channel, maybe it already closed.\n"); - return; - } - if (!ssh_channel_is_eof(channel)) - ssh_channel_send_eof(channel); - if (!ssh_channel_is_closed(channel)) - ssh_channel_close(channel); - //ssh_channel_free(channel); - - if (!ssh_channel_is_eof(info->channel)) - ssh_channel_send_eof(info->channel); - if (!ssh_channel_is_closed(info->channel)) - ssh_channel_close(info->channel); - //ssh_channel_free(info->channel); - - { - ExThreadSmartLock locker(_this->m_lock); - - ts_ssh_channel_map::iterator it = _this->m_channel_cli_srv.find(channel); - if (it != _this->m_channel_cli_srv.end()) { - delete it->second; - _this->m_channel_cli_srv.erase(it); - } - else { - EXLOGW("[ssh] when remove client channel, it not in charge.\n"); - } - - it = _this->m_channel_srv_cli.find(info->channel); - if (it != _this->m_channel_srv_cli.end()) { - delete it->second; - _this->m_channel_srv_cli.erase(it); - } - else { - EXLOGW("[ssh] when remove client channel, not found server channel.\n"); - } - } -} - -int SshSession::_on_client_channel_data(ssh_session session, ssh_channel channel, void *data, unsigned int len, int is_stderr, void *userdata) -{ - SshSession *_this = (SshSession *)userdata; - - // ǰ߳ڽշ˷صݣֱӷأŻٷʹݵ - if (_this->m_recving_from_srv) - return 0; - - if (_this->m_recving_from_cli) - return 0; - - TS_SSH_CHANNEL_INFO *info = _this->_get_srv_channel(channel); - if (NULL == info || NULL == info->channel) { - EXLOGE("[ssh] when receive client channel data, not found server channel.\n"); - return SSH_FATAL; - } - - _this->m_recving_from_cli = true; - - if (info->type == TS_SSH_CHANNEL_TYPE_SHELL) - { - try - { - _this->_process_command(TS_SSH_DATA_FROM_CLIENT, (ex_u8*)data, len); - } - catch (...) - { - } - } - - int ret = 0; - if (is_stderr) - ret = ssh_channel_write_stderr(info->channel, data, len); - else - ret = ssh_channel_write(info->channel, data, len); - if (ret <= 0) - EXLOGE("[ssh] send to server failed.\n"); - - _this->m_recving_from_cli = false; - - return ret; -} - -int SshSession::_on_client_pty_win_change(ssh_session session, ssh_channel channel, int width, int height, int pxwidth, int pwheight, void *userdata) { - EXLOGD("[ssh] client pty win size change to: (%d, %d)\n", width, height); - SshSession *_this = (SshSession *)userdata; - TS_SSH_CHANNEL_INFO *info = _this->_get_srv_channel(channel); - if (NULL == info || NULL == info->channel) { - EXLOGE("[ssh] when client pty win change, not found server channel.\n"); - return SSH_FATAL; - } - - _this->m_rec.record_win_size_change(width, height); - - return ssh_channel_change_pty_size(info->channel, width, height); -} - -int SshSession::_on_client_channel_subsystem_request(ssh_session session, ssh_channel channel, const char *subsystem, void *userdata) { - EXLOGD("[ssh] on_client_channel_subsystem_request(): %s\n", subsystem); - SshSession *_this = (SshSession *)userdata; - - // Ŀǰֻ֧SFTPϵͳ - if (strcmp(subsystem, "sftp") != 0) { - EXLOGE("[ssh] support `sftp` subsystem only, but got `%s`.\n", subsystem); - _this->m_retcode = SESS_STAT_ERR_UNSUPPORT_PROTOCOL; - return SSH_ERROR; - } - - TS_SSH_CHANNEL_INFO *srv_info = _this->_get_srv_channel(channel); - if (NULL == srv_info || NULL == srv_info->channel) { - EXLOGE("[ssh] when receive client channel subsystem request, not found server channel.\n"); - return SSH_FATAL; - } - srv_info->type = TS_SSH_CHANNEL_TYPE_SFTP; - - TS_SSH_CHANNEL_INFO *cli_info = _this->_get_cli_channel(srv_info->channel); - if (NULL == cli_info || NULL == cli_info->channel) { - EXLOGE("[ssh] when client request shell, not found client channel.\n"); - return SSH_FATAL; - } - cli_info->type = TS_SSH_CHANNEL_TYPE_SFTP; - - // һsshỰsftpͨͽϢ¼ãsession-idٴγʱӡ - _this->_enter_sftp_mode(); - - return ssh_channel_request_subsystem(srv_info->channel, subsystem); -} - -void SshSession::_enter_sftp_mode(void) { - if (!m_is_sftp) { - m_is_sftp = true; - m_proxy->add_sftp_session_info(m_sid, m_server_ip, m_server_port, m_user_name, m_user_auth, m_auth_mode); - } -} - -int SshSession::_on_client_channel_exec_request(ssh_session session, ssh_channel channel, const char *command, void *userdata) { - EXLOGD("[ssh] on_client_channel_exec_request(): %s\n", command); - return 0; -} - -int SshSession::_on_server_channel_data(ssh_session session, ssh_channel channel, void *data, unsigned int len, int is_stderr, void *userdata) -{ - SshSession *_this = (SshSession *)userdata; - - if (_this->m_recving_from_cli) - return 0; - if (_this->m_recving_from_srv) - return 0; - - TS_SSH_CHANNEL_INFO *info = _this->_get_cli_channel(channel); - if (NULL == info || NULL == info->channel) { - EXLOGE("[ssh] when receive server channel data, not found client channel.\n"); - _this->m_retcode = SESS_STAT_ERR_INTERNAL; - return SSH_FATAL; - } - - _this->m_recving_from_srv = true; - - if (info->type == TS_SSH_CHANNEL_TYPE_SHELL) - { - try - { - _this->_process_command(TS_SSH_DATA_FROM_SERVER, (ex_u8*)data, len); - _this->m_rec.record(TS_RECORD_TYPE_SSH_DATA, (unsigned char *)data, len); - } - catch (...) - { - } - } - - // յһ˷صʱ֮ǰʾһЩԶϢ - if (!is_stderr && _this->m_is_first_server_data) - { - _this->m_is_first_server_data = false; - - if (info->type != TS_SSH_CHANNEL_TYPE_SFTP) - { - char buf[256] = { 0 }; - - const char *auth_mode = NULL; - if (_this->m_auth_mode == TS_AUTH_MODE_PASSWORD) - auth_mode = "password"; - else if (_this->m_auth_mode == TS_AUTH_MODE_PRIVATE_KEY) - auth_mode = "private-key"; - else - auth_mode = "unknown"; - - snprintf(buf, sizeof(buf), - "\r\n\r\n"\ - "=============================================\r\n"\ - "Welcome to Teleport-SSH-Server...\r\n"\ - " - teleport to %s:%d\r\n"\ - " - authroized by %s\r\n"\ - "=============================================\r\n"\ - "\r\n"\ - "\033]0;tpssh://%s\007", - _this->m_server_ip.c_str(), - _this->m_server_port, auth_mode, - _this->m_server_ip.c_str() - ); - - // ע⣬ȻԸı䴰ڣ߱ǩҳı⣬ΪǷ˷صĵһ˿ܻᷢƵİһΣı - // ´ڱֱı䣬Ӧý˷صİʽģҪ滻һΡ - - ssh_channel_write(info->channel, buf, strlen(buf)); - } - } - - int ret = 0; - if (is_stderr) - { - ret = ssh_channel_write_stderr(info->channel, data, len); - } - else if(info->type != TS_SSH_CHANNEL_TYPE_SHELL) - { - ret = ssh_channel_write(info->channel, data, len); - } - else - { -// if (len > 5 && len < 256) -// { -// const ex_u8* _begin = ex_memmem((const ex_u8*)data, len, (const ex_u8*)"\033]0;", 4); -// if (NULL != _begin) -// { -// size_t len_before = _begin - (const ex_u8*)data; -// const ex_u8* _end = ex_memmem(_begin + 4, len - len_before, (const ex_u8*)"\007", 1); -// if (NULL != _end) -// { -// _end++; -// -// // киıݣ⻻ΪҪ -// size_t len_end = len - (_end - (const ex_u8*)data); -// MemBuffer mbuf; -// -// if (len_before > 0) -// mbuf.append((ex_u8*)data, len_before); -// -// mbuf.append((ex_u8*)"\033]0;tpssh://", 13); -// mbuf.append((ex_u8*)_this->m_server_ip.c_str(), _this->m_server_ip.length()); -// mbuf.append((ex_u8*)"\007", 1); -// -// if (len_end > 0) -// mbuf.append((ex_u8*)_end, len_end); -// -// if(mbuf.size() > 0) -// { -// ret = ssh_channel_write(info->channel, mbuf.data(), mbuf.size()); -// if (ret <= 0) -// EXLOGE("[ssh] send to client failed (1).\n"); -// else -// ret = len; -// } -// else -// { -// ret = ssh_channel_write(info->channel, data, len); -// } -// } -// else -// { -// ret = ssh_channel_write(info->channel, data, len); -// } -// } -// else -// { -// ret = ssh_channel_write(info->channel, data, len); -// } -// } -// else - { - ret = ssh_channel_write(info->channel, data, len); - } - } - _this->m_recving_from_srv = false; - if (ret <= 0) - EXLOGE("[ssh] send to client failed (2).\n"); - - return ret; -} - -void SshSession::_on_server_channel_close(ssh_session session, ssh_channel channel, void *userdata) { - EXLOGD("[ssh] on_server_channel_close().\n"); - - SshSession *_this = (SshSession *)userdata; - TS_SSH_CHANNEL_INFO *info = _this->_get_cli_channel(channel); - if (NULL == info || NULL == info->channel) { - EXLOGW("[ssh] when server channel close, not found client channel, maybe it already closed.\n"); - return; - } - - if (!ssh_channel_is_eof(channel)) - ssh_channel_send_eof(channel); - if (!ssh_channel_is_closed(channel)) - ssh_channel_close(channel); - //ssh_channel_free(channel); - - if (!ssh_channel_is_eof(info->channel)) - ssh_channel_send_eof(info->channel); - if (!ssh_channel_is_closed(info->channel)) - ssh_channel_close(info->channel); - //ssh_channel_free(info->channel); - - { - ExThreadSmartLock locker(_this->m_lock); - - ts_ssh_channel_map::iterator it = _this->m_channel_srv_cli.find(channel); - if (it != _this->m_channel_srv_cli.end()) { - delete it->second; - _this->m_channel_srv_cli.erase(it); - } - else { - EXLOGW("[ssh] when remove server channel, it not in charge..\n"); - } - - it = _this->m_channel_cli_srv.find(info->channel); - if (it != _this->m_channel_cli_srv.end()) { - delete it->second; - _this->m_channel_cli_srv.erase(it); - } - else { - EXLOGW("[ssh] when remove server channel, not found client channel.\n"); - } - } -} +#include "ssh_session.h" +#include "ssh_proxy.h" +#include "tpp_env.h" + +#include + +SshSession::SshSession(SshProxy *proxy, ssh_session sess_client) : + ExThreadBase("ssh-session-thread"), + m_proxy(proxy), + m_cli_session(sess_client), + m_srv_session(NULL) +{ + m_retcode = SESS_STAT_RUNNING; + m_db_id = 0; + + m_auth_mode = TS_AUTH_MODE_PASSWORD; + + m_is_first_server_data = true; + m_is_sftp = false; + + m_is_logon = false; + m_have_error = false; + m_recving_from_srv = false; + m_recving_from_cli = false; + + memset(&m_srv_cb, 0, sizeof(m_srv_cb)); + ssh_callbacks_init(&m_srv_cb); + m_srv_cb.userdata = this; + + memset(&m_cli_channel_cb, 0, sizeof(m_cli_channel_cb)); + ssh_callbacks_init(&m_cli_channel_cb); + m_cli_channel_cb.userdata = this; + + memset(&m_srv_channel_cb, 0, sizeof(m_srv_channel_cb)); + ssh_callbacks_init(&m_srv_channel_cb); + m_srv_channel_cb.userdata = this; + + m_command_flag = 0; + m_cmd_char_pos = m_cmd_char_list.begin(); +} + +SshSession::~SshSession() { + + _set_stop_flag(); + + if (m_is_sftp) { + m_proxy->remove_sftp_sid(m_sid); + } + + EXLOGD("[ssh] session destroy.\n"); +} + +void SshSession::_thread_loop(void) { + _run(); + _on_session_end(); + m_proxy->session_finished(this); +} + +void SshSession::_set_stop_flag(void) { + _close_channels(); + + if (NULL != m_cli_session) { + ssh_disconnect(m_cli_session); + ssh_free(m_cli_session); + m_cli_session = NULL; + } + if (NULL != m_srv_session) { + ssh_disconnect(m_srv_session); + ssh_free(m_srv_session); + m_srv_session = NULL; + } +} + +bool SshSession::_on_session_begin(const TPP_SESSION_INFO* info) +{ + if (!g_ssh_env.session_begin(info, &m_db_id)) + { + EXLOGD("[ssh] session_begin error. %d\n", m_db_id); + return false; + } + + m_rec.begin(g_ssh_env.replay_path.c_str(), L"tp-ssh", m_db_id, info); + + return true; +} + +bool SshSession::_on_session_end(void) +{ + if (m_db_id > 0) + { + EXLOGD("[ssh] session ret-code: %d\n", m_retcode); + + // Ựûз״̬Ϊ¼´ֵ + if (m_retcode == SESS_STAT_RUNNING) + m_retcode = SESS_STAT_END; + + g_ssh_env.session_end(m_db_id, m_retcode); + } + + return true; +} + +void SshSession::_close_channels(void) { + ExThreadSmartLock locker(m_lock); + + if (m_channel_cli_srv.size() > 0) + EXLOGW("[ssh] when close all channels, %d client channel need close.\n", m_channel_cli_srv.size()); + if (m_channel_srv_cli.size() > 0) + EXLOGW("[ssh] when close all channels, %d server channel need close.\n", m_channel_srv_cli.size()); + + ts_ssh_channel_map::iterator it = m_channel_cli_srv.begin(); + for (; it != m_channel_cli_srv.end(); ++it) { + if (!ssh_channel_is_eof(it->first)) + ssh_channel_send_eof(it->first); + if (!ssh_channel_is_closed(it->first)) + ssh_channel_close(it->first); + ssh_channel_free(it->first); + + if (NULL != it->second) { + if (!ssh_channel_is_eof(it->second->channel)) + ssh_channel_send_eof(it->second->channel); + if (!ssh_channel_is_closed(it->second->channel)) + ssh_channel_close(it->second->channel); + ssh_channel_free(it->second->channel); + + delete it->second; + } + } + + it = m_channel_srv_cli.begin(); + for (; it != m_channel_srv_cli.end(); ++it) { + if (NULL != it->second) { + delete it->second; + } + } + + m_channel_cli_srv.clear(); + m_channel_srv_cli.clear(); +} + +void SshSession::_run(void) { + m_srv_cb.auth_password_function = _on_auth_password_request; + m_srv_cb.channel_open_request_session_function = _on_new_channel_request; + + m_srv_channel_cb.channel_data_function = _on_server_channel_data; + m_srv_channel_cb.channel_close_function = _on_server_channel_close; + + m_cli_channel_cb.channel_data_function = _on_client_channel_data; + // channel_eof_function + m_cli_channel_cb.channel_close_function = _on_client_channel_close; + // channel_signal_function + // channel_exit_status_function + // channel_exit_signal_function + m_cli_channel_cb.channel_pty_request_function = _on_client_pty_request; + m_cli_channel_cb.channel_shell_request_function = _on_client_shell_request; + // channel_auth_agent_req_function + // channel_x11_req_function + m_cli_channel_cb.channel_pty_window_change_function = _on_client_pty_win_change; + m_cli_channel_cb.channel_exec_request_function = _on_client_channel_exec_request; + // channel_env_request_function + m_cli_channel_cb.channel_subsystem_request_function = _on_client_channel_subsystem_request; + + + ssh_set_server_callbacks(m_cli_session, &m_srv_cb); + + // ȫӣԿ + if (ssh_handle_key_exchange(m_cli_session)) { + EXLOGE("[ssh] key exchange with client failed: %s\n", ssh_get_error(m_cli_session)); + return; + } + + ssh_event event_loop = ssh_event_new(); + ssh_event_add_session(event_loop, m_cli_session); + + // ֤һͨ + int r = 0; + while (!(m_is_logon && m_channel_cli_srv.size() > 0)) { + if (m_have_error) + break; + r = ssh_event_dopoll(event_loop, -1); + if (r == SSH_ERROR) { + EXLOGE("[ssh] Error : %s\n", ssh_get_error(m_cli_session)); + ssh_disconnect(m_cli_session); + return; + } + } + + if (m_have_error) { + ssh_event_remove_session(event_loop, m_cli_session); + ssh_event_free(event_loop); + EXLOGE("[ssh] Error, exiting loop.\n"); + return; + } + + EXLOGW("[ssh] Authenticated and got a channel.\n"); + + // ˫Ѿˣʼת + ssh_event_add_session(event_loop, m_srv_session); + do { + r = ssh_event_dopoll(event_loop, -1); + if (r == SSH_ERROR) { + if (0 != ssh_get_error_code(m_cli_session)) + { + EXLOGE("[ssh] ssh_event_dopoll() [cli] %s\n", ssh_get_error(m_cli_session)); + //ssh_disconnect(m_cli_session); + } + else if (0 != ssh_get_error_code(m_srv_session)) + { + EXLOGE("[ssh] ssh_event_dopoll() [srv] %s\n", ssh_get_error(m_srv_session)); + //ssh_disconnect(m_srv_session); + } + + // EXLOGE("[ssh] ssh_event_dopoll() [cli] %s, [srv] %s\n", ssh_get_error(m_cli_session), ssh_get_error(m_srv_session)); + _close_channels(); + } + } while (m_channel_cli_srv.size() > 0); + + EXLOGV("[ssh] [%s:%d] all channel in this session are closed.\n", m_client_ip.c_str(), m_client_port); + + ssh_event_remove_session(event_loop, m_cli_session); + ssh_event_remove_session(event_loop, m_srv_session); + ssh_event_free(event_loop); +} + + +int SshSession::_on_auth_password_request(ssh_session session, const char *user, const char *password, void *userdata) { + // õuserҪsession-idpasswordticket䣬ΪжûǷʵݡ + SshSession *_this = (SshSession *)userdata; + _this->m_sid = user; + EXLOGV("[ssh] authenticating, session-id: %s\n", _this->m_sid.c_str()); + + //bool bRet = true; + int protocol = 0; + TPP_SESSION_INFO* sess_info = g_ssh_env.take_session(_this->m_sid.c_str()); + + if (NULL == sess_info) { + EXLOGW("[ssh] try to get login-info from ssh-sftp-session.\n"); + // ԴsftpӼ¼лȡϢһsshỰΪsftpỰڲὫϢ¼ã + TS_SFTP_SESSION_INFO sftp_info; + if (!_this->m_proxy->get_sftp_session_info(_this->m_sid, sftp_info)) { + EXLOGE("[ssh] no such session: %s\n", _this->m_sid.c_str()); + _this->m_have_error = true; + _this->m_retcode = SESS_STAT_ERR_AUTH_DENIED; + return SSH_AUTH_DENIED; + } + + _this->m_server_ip = sftp_info.host_ip; + _this->m_server_port = sftp_info.host_port; + _this->m_auth_mode = sftp_info.auth_mode; + _this->m_user_name = sftp_info.user_name; + _this->m_user_auth = sftp_info.user_auth; + +// sess_info.host_ip = sftp_info.host_ip; +// sess_info.host_port = sftp_info.host_port; +// sess_info.auth_mode = sftp_info.auth_mode; +// sess_info.user_name = sftp_info.user_name; +// sess_info.user_auth = sftp_info.user_auth; +// sess_info.protocol = TS_PROXY_PROTOCOL_SSH; + protocol = TS_PROXY_PROTOCOL_SSH; + + // ΪǴsftpỰĵ¼ݣƱỰֻsftpʹshellˡ + _this->_enter_sftp_mode(); + } else { + _this->m_server_ip = sess_info->host_ip; + _this->m_server_port = sess_info->host_port; + _this->m_auth_mode = sess_info->auth_mode; + _this->m_user_name = sess_info->user_name; + _this->m_user_auth = sess_info->user_auth; + protocol = sess_info->protocol; + } + + //EXLOGE("[ssh---------1] auth info [password:%s:%s:%d]\n", _this->m_user_name.c_str(),_this->m_user_auth.c_str(), _this->m_auth_mode); + if (protocol != TS_PROXY_PROTOCOL_SSH) { + g_ssh_env.free_session(sess_info); + EXLOGE("[ssh] session '%s' is not for SSH.\n", _this->m_sid.c_str()); + _this->m_have_error = true; + _this->m_retcode = SESS_STAT_ERR_AUTH_DENIED; + return SSH_AUTH_DENIED; + } + + if (!_this->_on_session_begin(sess_info)) + { + g_ssh_env.free_session(sess_info); + _this->m_have_error = true; + _this->m_retcode = SESS_STAT_ERR_AUTH_DENIED; + return SSH_AUTH_DENIED; + } + + g_ssh_env.free_session(sess_info); + sess_info = NULL; + + // ڳԸsession-idȡõϢӲ¼SSH + EXLOGV("[ssh] try to connect to real SSH server %s:%d\n", _this->m_server_ip.c_str(), _this->m_server_port); + _this->m_srv_session = ssh_new(); + ssh_options_set(_this->m_srv_session, SSH_OPTIONS_HOST, _this->m_server_ip.c_str()); + ssh_options_set(_this->m_srv_session, SSH_OPTIONS_PORT, &_this->m_server_port); + + if (_this->m_auth_mode != TS_AUTH_MODE_NONE) + ssh_options_set(_this->m_srv_session, SSH_OPTIONS_USER, _this->m_user_name.c_str()); + + int _timeout_us = 30000000; // 30 sec. + ssh_options_set(_this->m_srv_session, SSH_OPTIONS_TIMEOUT_USEC, &_timeout_us); + + int rc = 0; + rc = ssh_connect(_this->m_srv_session); + if (rc != SSH_OK) { + EXLOGE("[ssh] can not connect to real SSH server %s:%d.\n", _this->m_server_ip.c_str(), _this->m_server_port); + _this->m_have_error = true; + _this->m_retcode = SESS_STAT_ERR_CONNECT; + return SSH_AUTH_DENIED; + } + + if (_this->m_auth_mode == TS_AUTH_MODE_PASSWORD) { + rc = ssh_userauth_password(_this->m_srv_session, NULL, _this->m_user_auth.c_str()); + if (rc != SSH_OK) { + EXLOGE("[ssh] can not use user/name login to real SSH server %s:%d.\n", _this->m_server_ip.c_str(), _this->m_server_port); + _this->m_have_error = true; + _this->m_retcode = SESS_STAT_ERR_AUTH_DENIED; + return SSH_AUTH_DENIED; + } + } + else if (_this->m_auth_mode == TS_AUTH_MODE_PRIVATE_KEY) { + ssh_key key = NULL; + if (SSH_OK != ssh_pki_import_privkey_base64(_this->m_user_auth.c_str(), NULL, NULL, NULL, &key)) { + EXLOGE("[ssh] can not import private-key for auth.\n"); + _this->m_have_error = true; + _this->m_retcode = SESS_STAT_ERR_BAD_SSH_KEY; + return SSH_AUTH_DENIED; + } + + rc = ssh_userauth_publickey(_this->m_srv_session, NULL, key); + if (rc != SSH_OK) { + ssh_key_free(key); + EXLOGE("[ssh] can not use private-key login to real SSH server %s:%d.\n", _this->m_server_ip.c_str(), _this->m_server_port); + _this->m_have_error = true; + _this->m_retcode = SESS_STAT_ERR_AUTH_DENIED; + return SSH_AUTH_DENIED; + } + + ssh_key_free(key); + } + else if (_this->m_auth_mode == TS_AUTH_MODE_NONE) + { + // do nothing. + return SSH_AUTH_DENIED; + } + else { + EXLOGE("[ssh] invalid auth mode.\n"); + _this->m_have_error = true; + _this->m_retcode = SESS_STAT_ERR_AUTH_DENIED; + return SSH_AUTH_DENIED; + } + + _this->m_is_logon = true; + return SSH_AUTH_SUCCESS; +} + +ssh_channel SshSession::_on_new_channel_request(ssh_session session, void *userdata) { + // ͻ˳ԴһͨȻͨͨշݣ + EXLOGV("[ssh] allocated session channel\n"); + + SshSession *_this = (SshSession *)userdata; + + ssh_channel cli_channel = ssh_channel_new(session); + ssh_set_channel_callbacks(cli_channel, &_this->m_cli_channel_cb); + + // ҲҪķһͨת + ssh_channel srv_channel = ssh_channel_new(_this->m_srv_session); + if (ssh_channel_open_session(srv_channel)) { + EXLOGE("[ssh] error opening channel to real server: %s\n", ssh_get_error(session)); + ssh_channel_free(cli_channel); + return NULL; + } + ssh_set_channel_callbacks(srv_channel, &_this->m_srv_channel_cb); + + // ͻ˺ͷ˵ͨ + { + ExThreadSmartLock locker(_this->m_lock); + + TS_SSH_CHANNEL_INFO *srv_info = new TS_SSH_CHANNEL_INFO; + srv_info->channel = srv_channel; + srv_info->type = TS_SSH_CHANNEL_TYPE_UNKNOWN; + _this->m_channel_cli_srv.insert(std::make_pair(cli_channel, srv_info)); + + TS_SSH_CHANNEL_INFO *cli_info = new TS_SSH_CHANNEL_INFO; + cli_info->channel = cli_channel; + cli_info->type = TS_SSH_CHANNEL_TYPE_UNKNOWN; + _this->m_channel_srv_cli.insert(std::make_pair(srv_channel, cli_info)); + } + + return cli_channel; +} + +TS_SSH_CHANNEL_INFO *SshSession::_get_cli_channel(ssh_channel srv_channel) { + ExThreadSmartLock locker(m_lock); + ts_ssh_channel_map::iterator it = m_channel_srv_cli.find(srv_channel); + if (it == m_channel_srv_cli.end()) + return NULL; + else + return it->second; +} + +TS_SSH_CHANNEL_INFO *SshSession::_get_srv_channel(ssh_channel cli_channel) { + ExThreadSmartLock locker(m_lock); + ts_ssh_channel_map::iterator it = m_channel_cli_srv.find(cli_channel); + if (it == m_channel_cli_srv.end()) + return NULL; + else + return it->second; +} + +void SshSession::_process_ssh_command(int from, const ex_u8* data, int len) +{ + if (TS_SSH_DATA_FROM_CLIENT == from) + { + m_command_flag = 0; + + if (len == 3) + { + if ((data[0] == 0x1b && data[1] == 0x5b && data[2] == 0x41) // key-up + || (data[0] == 0x1b && data[1] == 0x5b && data[2] == 0x42) // key-down + ) + { + m_command_flag = 1; + return; + } + else if (data[0] == 0x1b && data[1] == 0x5b && data[2] == 0x43) // key-right + { + if (m_cmd_char_pos != m_cmd_char_list.end()) + m_cmd_char_pos++; + return; + } + else if (data[0] == 0x1b && data[1] == 0x5b && data[2] == 0x44) // key-left + { + if (m_cmd_char_pos != m_cmd_char_list.begin()) + m_cmd_char_pos--; + return; + } + else if ( + (data[0] == 0x1b && data[1] == 0x4f && data[2] == 0x41) + || (data[0] == 0x1b && data[1] == 0x4f && data[2] == 0x42) + || (data[0] == 0x1b && data[1] == 0x4f && data[2] == 0x43) + || (data[0] == 0x1b && data[1] == 0x4f && data[2] == 0x44) + ) + { + // ༭ģʽµҼ + return; + } + } + else if (len == 1) + { + if (data[0] == 0x09) + { + m_command_flag = 1; + return; + } + else if (data[0] == 0x7f) // Backspace (ɾһַ) + { + if (m_cmd_char_pos != m_cmd_char_list.begin()) + { + m_cmd_char_pos--; + m_cmd_char_pos = m_cmd_char_list.erase(m_cmd_char_pos); + } + return; + } + else if (data[0] == 0x1b) + { + // Esc + return; + } + + if (data[0] != 0x0d && !isprint(data[0])) + return; + } + else if (len > 3) + { + if (data[0] == 0x1b && data[1] == 0x5b) + { + // ϵңҲDZ༭ģʽµңôͺԣӦDZ༭ģʽµ룩 + m_cmd_char_list.clear(); + m_cmd_char_pos = m_cmd_char_list.begin(); + return; + } + } + + int processed = 0; + for (int i = 0; i < len; i++) + { + if (data[i] == 0x0d) + { + m_command_flag = 0; + + for (int j = processed; j < i; ++j) + { + m_cmd_char_pos = m_cmd_char_list.insert(m_cmd_char_pos, data[j]); + m_cmd_char_pos++; + } + + if (m_cmd_char_list.size() > 0) + { + m_cmd_char_list.push_back(0x0d); + m_cmd_char_list.push_back(0x0a); + ex_astr str(m_cmd_char_list.begin(), m_cmd_char_list.end()); + // EXLOGD("[ssh] save cmd: %s", str.c_str()); + m_rec.record_command(str); + } + m_cmd_char_list.clear(); + m_cmd_char_pos = m_cmd_char_list.begin(); + + processed = i + 1; + } + } + + if (processed < len) + { + for (int j = processed; j < len; ++j) + { + m_cmd_char_pos = m_cmd_char_list.insert(m_cmd_char_pos, data[j]); + m_cmd_char_pos++; + } + } + } + else if (TS_SSH_DATA_FROM_SERVER == from) + { + if (m_command_flag == 0) + return; + + bool esc_mode = false; + int esc_arg = 0; + + for (int i = 0; i < len; i++) + { + if (esc_mode) + { + switch (data[i]) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + esc_arg = esc_arg * 10 + (data[i] - '0'); + break; + + case 0x3f: + case ';': + case '>': + m_cmd_char_list.clear(); + m_cmd_char_pos = m_cmd_char_list.begin(); + return; + break; + + case 0x4b: // 'K' + { + if (0 == esc_arg) + { + // ɾ굽βַ + m_cmd_char_list.erase(m_cmd_char_pos, m_cmd_char_list.end()); + m_cmd_char_pos = m_cmd_char_list.end(); + } + else if (1 == esc_arg) + { + // ɾӿʼ괦ַ + m_cmd_char_list.erase(m_cmd_char_list.begin(), m_cmd_char_pos); + m_cmd_char_pos = m_cmd_char_list.end(); + } + else if (2 == esc_arg) + { + // ɾ + m_cmd_char_list.clear(); + m_cmd_char_pos = m_cmd_char_list.begin(); + } + + esc_mode = false; + break; + } + case 0x43: // 'C' + { + // + if (esc_arg == 0) + esc_arg = 1; + for (int j = 0; j < esc_arg; ++j) + { + if (m_cmd_char_pos != m_cmd_char_list.end()) + m_cmd_char_pos++; + } + esc_mode = false; + break; + } + + case 0x50: // 'P' ɾַָ + { + if (esc_arg == 0) + esc_arg = 1; + for (int j = 0; j < esc_arg; ++j) + { + if (m_cmd_char_pos != m_cmd_char_list.end()) + m_cmd_char_pos = m_cmd_char_list.erase(m_cmd_char_pos); + } + esc_mode = false; + break; + } + + case 0x40: // '@' ָĿհַ + { + if (esc_arg == 0) + esc_arg = 1; + for (int j = 0; j < esc_arg; ++j) + { + m_cmd_char_pos = m_cmd_char_list.insert(m_cmd_char_pos, ' '); + } + esc_mode = false; + break; + } + + default: + esc_mode = false; + break; + } + + continue; + } + + switch (data[i]) + { + case 0x07: + { + // + break; + } + case 0x08: + { + // + if (m_cmd_char_pos != m_cmd_char_list.begin()) + m_cmd_char_pos--; + + break; + } + case 0x1b: + { + if (i + 1 < len) + { + if (data[i + 1] == 0x5b) + { + esc_mode = true; + esc_arg = 0; + + i += 1; + break; + } + } + + break; + } + case 0x0d: + { + m_cmd_char_list.clear(); + m_cmd_char_pos = m_cmd_char_list.begin(); + + break; + } + default: + if (m_cmd_char_pos != m_cmd_char_list.end()) + { + m_cmd_char_pos = m_cmd_char_list.erase(m_cmd_char_pos); + m_cmd_char_pos = m_cmd_char_list.insert(m_cmd_char_pos, data[i]); + m_cmd_char_pos++; + } + else + { + m_cmd_char_list.push_back(data[i]); + m_cmd_char_pos = m_cmd_char_list.end(); + } + } + } + } + + return; +} + +void SshSession::_process_sftp_command(const ex_u8* data, int len) { + // SFTP protocol: https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13 + //EXLOG_BIN(data, len, "[sftp] client channel data"); + + if (len < 9) + return; + + int pkg_len = (int)((data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3]); + if (pkg_len + 4 != len) + return; + + ex_u8 sftp_cmd = data[4]; + + if (sftp_cmd == 0x01) { + // 0x01 = 1 = SSH_FXP_INIT + m_rec.record_command("SFTP INITIALIZE\r\n"); + return; + } + + // Ҫ14ֽ + // uint32 + byte + uint32 + (uint32 + char + ...) + // pkg_len + cmd + req_id + string( length + content...) + if (len < 14) + return; + + ex_u8* str1_ptr = (ex_u8*)data + 9; + int str1_len = (int)((str1_ptr[0] << 24) | (str1_ptr[1] << 16) | (str1_ptr[2] << 8) | str1_ptr[3]); +// if (str1_len + 9 != pkg_len) +// return; + ex_u8* str2_ptr = NULL;// (ex_u8*)data + 13; + int str2_len = 0;// (int)((data[9] << 24) | (data[10] << 16) | (data[11] << 8) | data[12]); + + + char* act = NULL; + switch (sftp_cmd) { + case 0x03: + // 0x03 = 3 = SSH_FXP_OPEN + act = "open file"; + break; +// case 0x0b: +// // 0x0b = 11 = SSH_FXP_OPENDIR +// act = "open dir"; +// break; + case 0x0d: + // 0x0d = 13 = SSH_FXP_REMOVE + act = "remove file"; + break; + case 0x0e: + // 0x0e = 14 = SSH_FXP_MKDIR + act = "create dir"; + break; + case 0x0f: + // 0x0f = 15 = SSH_FXP_RMDIR + act = "remove dir"; + break; + case 0x12: + // 0x12 = 18 = SSH_FXP_RENAME + // renameаַ + act = "rename"; + str2_ptr = str1_ptr + str1_len + 4; + str2_len = (int)((str2_ptr[0] << 24) | (str2_ptr[1] << 16) | (str2_ptr[2] << 8) | str2_ptr[3]); + break; + case 0x15: + // 0x15 = 21 = SSH_FXP_LINK + // linkаַǰµļбӵļ + act = "create link"; + str2_ptr = str1_ptr + str1_len + 4; + str2_len = (int)((str2_ptr[0] << 24) | (str2_ptr[1] << 16) | (str2_ptr[2] << 8) | str2_ptr[3]); + break; + default: + return; + } + + int total_len = 5 + str1_len + 4; + if (str2_len > 0) + total_len += str2_len + 4; + if (total_len > pkg_len) + return; + + char msg[2048] = { 0 }; + if (str2_len == 0) { + ex_astr str1((char*)((ex_u8*)data + 13), str1_len); + ex_strformat(msg, 2048, "%d:%s:%s:\r\n", sftp_cmd, act, str1.c_str()); + } + else { + ex_astr str1((char*)(str1_ptr + 4), str1_len); + ex_astr str2((char*)(str2_ptr + 4), str2_len); + ex_strformat(msg, 2048, "%d:%s:%s:%s\r\n", sftp_cmd, act, str1.c_str(), str2.c_str()); + } + + m_rec.record_command(msg); +} + + +int SshSession::_on_client_pty_request(ssh_session session, ssh_channel channel, const char *term, int x, int y, int px, + int py, void *userdata) { + SshSession *_this = (SshSession *)userdata; + + if (_this->m_is_sftp) { + EXLOGE("[ssh] try to request pty on a sftp-session.\n"); + return SSH_FATAL; + } + + EXLOGD("[ssh] client request terminal: %s, (%d, %d) / (%d, %d)\n", term, x, y, px, py); + _this->m_rec.record_win_size_startup(x, y); + TS_SSH_CHANNEL_INFO *info = _this->_get_srv_channel(channel); + if (NULL == info || NULL == info->channel) { + EXLOGE("[ssh] when client request pty, not found server channel.\n"); + return SSH_FATAL; + } + + return ssh_channel_request_pty_size(info->channel, term, x, y); +} + +int SshSession::_on_client_shell_request(ssh_session session, ssh_channel channel, void *userdata) { + SshSession *_this = (SshSession *)userdata; + char buf[2048] = { 0 }; + + if (_this->m_is_sftp) { + EXLOGE("[ssh] try to request shell on a sftp-session.\n"); + + snprintf(buf, sizeof(buf), + "\r\n\r\n"\ + "!! ERROR !!\r\n"\ + "Session-ID '%s' has been used for SFTP.\r\n"\ + "\r\n", _this->m_sid.c_str() + ); + ssh_channel_write(channel, buf, strlen(buf)); + + return SSH_FATAL; + } + + EXLOGD("[ssh] client request shell\n"); + + + TS_SSH_CHANNEL_INFO *srv_info = _this->_get_srv_channel(channel); + if (NULL == srv_info || NULL == srv_info->channel) { + EXLOGE("[ssh] when client request shell, not found server channel.\n"); + return SSH_FATAL; + } + srv_info->type = TS_SSH_CHANNEL_TYPE_SHELL; + + TS_SSH_CHANNEL_INFO *cli_info = _this->_get_cli_channel(srv_info->channel); + if (NULL == cli_info || NULL == cli_info->channel) { + EXLOGE("[ssh] when client request shell, not found client channel.\n"); + return SSH_FATAL; + } + cli_info->type = TS_SSH_CHANNEL_TYPE_SHELL; + + + return ssh_channel_request_shell(srv_info->channel); +} + +void SshSession::_on_client_channel_close(ssh_session session, ssh_channel channel, void *userdata) { + EXLOGD("[ssh] on_client_channel_close().\n"); + + SshSession *_this = (SshSession *)userdata; + TS_SSH_CHANNEL_INFO *info = _this->_get_srv_channel(channel); + if (NULL == info || NULL == info->channel) { + EXLOGW("[ssh] when close client channel, not found server channel, maybe it already closed.\n"); + return; + } + if (!ssh_channel_is_eof(channel)) + ssh_channel_send_eof(channel); + if (!ssh_channel_is_closed(channel)) + ssh_channel_close(channel); + //ssh_channel_free(channel); + + if (!ssh_channel_is_eof(info->channel)) + ssh_channel_send_eof(info->channel); + if (!ssh_channel_is_closed(info->channel)) + ssh_channel_close(info->channel); + //ssh_channel_free(info->channel); + + { + ExThreadSmartLock locker(_this->m_lock); + + ts_ssh_channel_map::iterator it = _this->m_channel_cli_srv.find(channel); + if (it != _this->m_channel_cli_srv.end()) { + delete it->second; + _this->m_channel_cli_srv.erase(it); + } + else { + EXLOGW("[ssh] when remove client channel, it not in charge.\n"); + } + + it = _this->m_channel_srv_cli.find(info->channel); + if (it != _this->m_channel_srv_cli.end()) { + delete it->second; + _this->m_channel_srv_cli.erase(it); + } + else { + EXLOGW("[ssh] when remove client channel, not found server channel.\n"); + } + } +} + +int SshSession::_on_client_channel_data(ssh_session session, ssh_channel channel, void *data, unsigned int len, int is_stderr, void *userdata) +{ + SshSession *_this = (SshSession *)userdata; + + // ǰ߳ڽշ˷صݣֱӷأŻٷʹݵ + if (_this->m_recving_from_srv) + return 0; + + if (_this->m_recving_from_cli) + return 0; + + TS_SSH_CHANNEL_INFO *info = _this->_get_srv_channel(channel); + if (NULL == info || NULL == info->channel) { + EXLOGE("[ssh] when receive client channel data, not found server channel.\n"); + return SSH_FATAL; + } + + _this->m_recving_from_cli = true; + + if (info->type == TS_SSH_CHANNEL_TYPE_SHELL) + { + try + { + _this->_process_ssh_command(TS_SSH_DATA_FROM_CLIENT, (ex_u8*)data, len); + } + catch (...) + { + } + } + else + { + _this->_process_sftp_command((ex_u8*)data, len); + } + + int ret = 0; + if (is_stderr) + ret = ssh_channel_write_stderr(info->channel, data, len); + else + ret = ssh_channel_write(info->channel, data, len); + if (ret <= 0) + EXLOGE("[ssh] send to server failed.\n"); + + _this->m_recving_from_cli = false; + + return ret; +} + +int SshSession::_on_client_pty_win_change(ssh_session session, ssh_channel channel, int width, int height, int pxwidth, int pwheight, void *userdata) { + EXLOGD("[ssh] client pty win size change to: (%d, %d)\n", width, height); + SshSession *_this = (SshSession *)userdata; + TS_SSH_CHANNEL_INFO *info = _this->_get_srv_channel(channel); + if (NULL == info || NULL == info->channel) { + EXLOGE("[ssh] when client pty win change, not found server channel.\n"); + return SSH_FATAL; + } + + _this->m_rec.record_win_size_change(width, height); + + return ssh_channel_change_pty_size(info->channel, width, height); +} + +int SshSession::_on_client_channel_subsystem_request(ssh_session session, ssh_channel channel, const char *subsystem, void *userdata) { + EXLOGD("[ssh] on_client_channel_subsystem_request(): %s\n", subsystem); + SshSession *_this = (SshSession *)userdata; + + // Ŀǰֻ֧SFTPϵͳ + if (strcmp(subsystem, "sftp") != 0) { + EXLOGE("[ssh] support `sftp` subsystem only, but got `%s`.\n", subsystem); + _this->m_retcode = SESS_STAT_ERR_UNSUPPORT_PROTOCOL; + return SSH_ERROR; + } + + TS_SSH_CHANNEL_INFO *srv_info = _this->_get_srv_channel(channel); + if (NULL == srv_info || NULL == srv_info->channel) { + EXLOGE("[ssh] when receive client channel subsystem request, not found server channel.\n"); + return SSH_FATAL; + } + srv_info->type = TS_SSH_CHANNEL_TYPE_SFTP; + + TS_SSH_CHANNEL_INFO *cli_info = _this->_get_cli_channel(srv_info->channel); + if (NULL == cli_info || NULL == cli_info->channel) { + EXLOGE("[ssh] when client request shell, not found client channel.\n"); + return SSH_FATAL; + } + cli_info->type = TS_SSH_CHANNEL_TYPE_SFTP; + + // һsshỰsftpͨͽϢ¼ãsession-idٴγʱӡ + _this->_enter_sftp_mode(); + + return ssh_channel_request_subsystem(srv_info->channel, subsystem); +} + +void SshSession::_enter_sftp_mode(void) { + if (!m_is_sftp) { + m_is_sftp = true; + m_proxy->add_sftp_session_info(m_sid, m_server_ip, m_server_port, m_user_name, m_user_auth, m_auth_mode); + } +} + +int SshSession::_on_client_channel_exec_request(ssh_session session, ssh_channel channel, const char *command, void *userdata) { + EXLOGD("[ssh] on_client_channel_exec_request(): %s\n", command); + return 0; +} + +int SshSession::_on_server_channel_data(ssh_session session, ssh_channel channel, void *data, unsigned int len, int is_stderr, void *userdata) +{ + SshSession *_this = (SshSession *)userdata; + + if (_this->m_recving_from_cli) + return 0; + if (_this->m_recving_from_srv) + return 0; + + TS_SSH_CHANNEL_INFO *info = _this->_get_cli_channel(channel); + if (NULL == info || NULL == info->channel) { + EXLOGE("[ssh] when receive server channel data, not found client channel.\n"); + _this->m_retcode = SESS_STAT_ERR_INTERNAL; + return SSH_FATAL; + } + + _this->m_recving_from_srv = true; + + if (info->type == TS_SSH_CHANNEL_TYPE_SHELL) + { + try + { + _this->_process_ssh_command(TS_SSH_DATA_FROM_SERVER, (ex_u8*)data, len); + _this->m_rec.record(TS_RECORD_TYPE_SSH_DATA, (unsigned char *)data, len); + } + catch (...) + { + } + } + + // յһ˷صʱ֮ǰʾһЩԶϢ + if (!is_stderr && _this->m_is_first_server_data) + { + _this->m_is_first_server_data = false; + + if (info->type != TS_SSH_CHANNEL_TYPE_SFTP) + { + char buf[256] = { 0 }; + + const char *auth_mode = NULL; + if (_this->m_auth_mode == TS_AUTH_MODE_PASSWORD) + auth_mode = "password"; + else if (_this->m_auth_mode == TS_AUTH_MODE_PRIVATE_KEY) + auth_mode = "private-key"; + else + auth_mode = "unknown"; + + snprintf(buf, sizeof(buf), + "\r\n\r\n"\ + "=============================================\r\n"\ + "Welcome to Teleport-SSH-Server...\r\n"\ + " - teleport to %s:%d\r\n"\ + " - authroized by %s\r\n"\ + "=============================================\r\n"\ + "\r\n"\ + "\033]0;tpssh://%s\007", + _this->m_server_ip.c_str(), + _this->m_server_port, auth_mode, + _this->m_server_ip.c_str() + ); + + // ע⣬ȻԸı䴰ڣ߱ǩҳı⣬ΪǷ˷صĵһ˿ܻᷢƵİһΣı + // ´ڱֱı䣬Ӧý˷صİʽģҪ滻һΡ + + ssh_channel_write(info->channel, buf, strlen(buf)); + } + } + + int ret = 0; + if (is_stderr) + { + ret = ssh_channel_write_stderr(info->channel, data, len); + } + else if(info->type != TS_SSH_CHANNEL_TYPE_SHELL) + { + ret = ssh_channel_write(info->channel, data, len); + } + else + { +// if (len > 5 && len < 256) +// { +// const ex_u8* _begin = ex_memmem((const ex_u8*)data, len, (const ex_u8*)"\033]0;", 4); +// if (NULL != _begin) +// { +// size_t len_before = _begin - (const ex_u8*)data; +// const ex_u8* _end = ex_memmem(_begin + 4, len - len_before, (const ex_u8*)"\007", 1); +// if (NULL != _end) +// { +// _end++; +// +// // киıݣ⻻ΪҪ +// size_t len_end = len - (_end - (const ex_u8*)data); +// MemBuffer mbuf; +// +// if (len_before > 0) +// mbuf.append((ex_u8*)data, len_before); +// +// mbuf.append((ex_u8*)"\033]0;tpssh://", 13); +// mbuf.append((ex_u8*)_this->m_server_ip.c_str(), _this->m_server_ip.length()); +// mbuf.append((ex_u8*)"\007", 1); +// +// if (len_end > 0) +// mbuf.append((ex_u8*)_end, len_end); +// +// if(mbuf.size() > 0) +// { +// ret = ssh_channel_write(info->channel, mbuf.data(), mbuf.size()); +// if (ret <= 0) +// EXLOGE("[ssh] send to client failed (1).\n"); +// else +// ret = len; +// } +// else +// { +// ret = ssh_channel_write(info->channel, data, len); +// } +// } +// else +// { +// ret = ssh_channel_write(info->channel, data, len); +// } +// } +// else +// { +// ret = ssh_channel_write(info->channel, data, len); +// } +// } +// else + { + ret = ssh_channel_write(info->channel, data, len); + } + } + _this->m_recving_from_srv = false; + if (ret <= 0) + EXLOGE("[ssh] send to client failed (2).\n"); + + return ret; +} + +void SshSession::_on_server_channel_close(ssh_session session, ssh_channel channel, void *userdata) { + EXLOGD("[ssh] on_server_channel_close().\n"); + + SshSession *_this = (SshSession *)userdata; + TS_SSH_CHANNEL_INFO *info = _this->_get_cli_channel(channel); + if (NULL == info || NULL == info->channel) { + EXLOGW("[ssh] when server channel close, not found client channel, maybe it already closed.\n"); + return; + } + + if (!ssh_channel_is_eof(channel)) + ssh_channel_send_eof(channel); + if (!ssh_channel_is_closed(channel)) + ssh_channel_close(channel); + //ssh_channel_free(channel); + + if (!ssh_channel_is_eof(info->channel)) + ssh_channel_send_eof(info->channel); + if (!ssh_channel_is_closed(info->channel)) + ssh_channel_close(info->channel); + //ssh_channel_free(info->channel); + + { + ExThreadSmartLock locker(_this->m_lock); + + ts_ssh_channel_map::iterator it = _this->m_channel_srv_cli.find(channel); + if (it != _this->m_channel_srv_cli.end()) { + delete it->second; + _this->m_channel_srv_cli.erase(it); + } + else { + EXLOGW("[ssh] when remove server channel, it not in charge..\n"); + } + + it = _this->m_channel_cli_srv.find(info->channel); + if (it != _this->m_channel_cli_srv.end()) { + delete it->second; + _this->m_channel_cli_srv.erase(it); + } + else { + EXLOGW("[ssh] when remove server channel, not found client channel.\n"); + } + } +} diff --git a/server/tp_core/protocol/ssh/ssh_session.h b/server/tp_core/protocol/ssh/ssh_session.h index c69f2a8..3ece32b 100644 --- a/server/tp_core/protocol/ssh/ssh_session.h +++ b/server/tp_core/protocol/ssh/ssh_session.h @@ -1,133 +1,134 @@ -#ifndef __SSH_SESSION_H__ -#define __SSH_SESSION_H__ - -#include "ssh_recorder.h" - -#include - -#include -#include -#include -#include - -#include -#include - -#define TS_SSH_CHANNEL_TYPE_UNKNOWN 0 -#define TS_SSH_CHANNEL_TYPE_SHELL 1 -#define TS_SSH_CHANNEL_TYPE_SFTP 2 - -#define TS_SSH_DATA_FROM_CLIENT 1 -#define TS_SSH_DATA_FROM_SERVER 2 - -typedef struct TS_SSH_CHANNEL_INFO -{ - int type; // TS_SSH_CHANNEL_TYPE_SHELL or TS_SSH_CHANNEL_TYPE_SFTP - ssh_channel channel; - - TS_SSH_CHANNEL_INFO() - { - type = TS_SSH_CHANNEL_TYPE_UNKNOWN; - channel = NULL; - } -}TS_SSH_CHANNEL_INFO; - -typedef std::map ts_ssh_channel_map; - -class SshProxy; - -class SshSession : public ExThreadBase -{ -public: - SshSession(SshProxy* proxy, ssh_session sess_client); - virtual ~SshSession(); - - SshProxy* get_proxy(void) { return m_proxy; } - - - TS_SSH_CHANNEL_INFO* _get_cli_channel(ssh_channel srv_channel); - TS_SSH_CHANNEL_INFO* _get_srv_channel(ssh_channel cli_channel); - - void client_ip(const char* ip) { m_client_ip = ip; } - const char* client_ip(void) const { return m_client_ip.c_str(); } - void client_port(ex_u16 port) { m_client_port = port; } - ex_u16 client_port(void) const { return m_client_port; } - -protected: - // ̳ TppSessionBase - bool _on_session_begin(const TPP_SESSION_INFO* info); - bool _on_session_end(void); - - - void _thread_loop(void); - void _set_stop_flag(void); - - void _process_command(int from, const ex_u8* data, int len); - -private: - void _run(void); - - void _close_channels(void); - - void _enter_sftp_mode(void); - - static int _on_auth_password_request(ssh_session session, const char *user, const char *password, void *userdata); - static ssh_channel _on_new_channel_request(ssh_session session, void *userdata); - static int _on_client_pty_request(ssh_session session, ssh_channel channel, const char *term, int x, int y, int px, int py, void *userdata); - static int _on_client_shell_request(ssh_session session, ssh_channel channel, void *userdata); - static void _on_client_channel_close(ssh_session session, ssh_channel channel, void* userdata); - static int _on_client_channel_data(ssh_session session, ssh_channel channel, void *data, unsigned int len, int is_stderr, void *userdata); - static int _on_client_pty_win_change(ssh_session session, ssh_channel channel, int width, int height, int pxwidth, int pwheight, void *userdata); - - static int _on_client_channel_subsystem_request(ssh_session session, ssh_channel channel, const char *subsystem, void *userdata); - static int _on_client_channel_exec_request(ssh_session session, ssh_channel channel, const char *command, void *userdata); - - static int _on_server_channel_data(ssh_session session, ssh_channel channel, void *data, unsigned int len, int is_stderr, void *userdata); - static void _on_server_channel_close(ssh_session session, ssh_channel channel, void* userdata); - -private: - int m_retcode; - int m_db_id; - - TppSshRec m_rec; - - SshProxy* m_proxy; - ssh_session m_cli_session; - ssh_session m_srv_session; - - ExThreadLock m_lock; - - ex_astr m_client_ip; - ex_u16 m_client_port; - - ex_astr m_sid; - ex_astr m_server_ip; - ex_u16 m_server_port; - ex_astr m_user_name; - ex_astr m_user_auth; - int m_auth_mode; - - bool m_is_first_server_data; - bool m_is_sftp; - - bool m_is_logon; - // һssh_sessionпԴ򿪶ssh_channel - ts_ssh_channel_map m_channel_cli_srv; // ͨͻͨҷͨ - ts_ssh_channel_map m_channel_srv_cli; // ͨͨҿͻͨ - - bool m_have_error; - - bool m_recving_from_srv; // Ƿڴӷݣ - bool m_recving_from_cli; // Ƿڴӿͻ˽ݣ - - struct ssh_server_callbacks_struct m_srv_cb; - struct ssh_channel_callbacks_struct m_cli_channel_cb; - struct ssh_channel_callbacks_struct m_srv_channel_cb; - - int m_command_flag; - - std::list m_cmd_char_list; - std::list::iterator m_cmd_char_pos; -}; - -#endif // __SSH_SESSION_H__ +#ifndef __SSH_SESSION_H__ +#define __SSH_SESSION_H__ + +#include "ssh_recorder.h" + +#include + +#include +#include +#include +#include + +#include +#include + +#define TS_SSH_CHANNEL_TYPE_UNKNOWN 0 +#define TS_SSH_CHANNEL_TYPE_SHELL 1 +#define TS_SSH_CHANNEL_TYPE_SFTP 2 + +#define TS_SSH_DATA_FROM_CLIENT 1 +#define TS_SSH_DATA_FROM_SERVER 2 + +typedef struct TS_SSH_CHANNEL_INFO +{ + int type; // TS_SSH_CHANNEL_TYPE_SHELL or TS_SSH_CHANNEL_TYPE_SFTP + ssh_channel channel; + + TS_SSH_CHANNEL_INFO() + { + type = TS_SSH_CHANNEL_TYPE_UNKNOWN; + channel = NULL; + } +}TS_SSH_CHANNEL_INFO; + +typedef std::map ts_ssh_channel_map; + +class SshProxy; + +class SshSession : public ExThreadBase +{ +public: + SshSession(SshProxy* proxy, ssh_session sess_client); + virtual ~SshSession(); + + SshProxy* get_proxy(void) { return m_proxy; } + + + TS_SSH_CHANNEL_INFO* _get_cli_channel(ssh_channel srv_channel); + TS_SSH_CHANNEL_INFO* _get_srv_channel(ssh_channel cli_channel); + + void client_ip(const char* ip) { m_client_ip = ip; } + const char* client_ip(void) const { return m_client_ip.c_str(); } + void client_port(ex_u16 port) { m_client_port = port; } + ex_u16 client_port(void) const { return m_client_port; } + +protected: + // ̳ TppSessionBase + bool _on_session_begin(const TPP_SESSION_INFO* info); + bool _on_session_end(void); + + + void _thread_loop(void); + void _set_stop_flag(void); + + void _process_ssh_command(int from, const ex_u8* data, int len); + void _process_sftp_command(const ex_u8* data, int len); + +private: + void _run(void); + + void _close_channels(void); + + void _enter_sftp_mode(void); + + static int _on_auth_password_request(ssh_session session, const char *user, const char *password, void *userdata); + static ssh_channel _on_new_channel_request(ssh_session session, void *userdata); + static int _on_client_pty_request(ssh_session session, ssh_channel channel, const char *term, int x, int y, int px, int py, void *userdata); + static int _on_client_shell_request(ssh_session session, ssh_channel channel, void *userdata); + static void _on_client_channel_close(ssh_session session, ssh_channel channel, void* userdata); + static int _on_client_channel_data(ssh_session session, ssh_channel channel, void *data, unsigned int len, int is_stderr, void *userdata); + static int _on_client_pty_win_change(ssh_session session, ssh_channel channel, int width, int height, int pxwidth, int pwheight, void *userdata); + + static int _on_client_channel_subsystem_request(ssh_session session, ssh_channel channel, const char *subsystem, void *userdata); + static int _on_client_channel_exec_request(ssh_session session, ssh_channel channel, const char *command, void *userdata); + + static int _on_server_channel_data(ssh_session session, ssh_channel channel, void *data, unsigned int len, int is_stderr, void *userdata); + static void _on_server_channel_close(ssh_session session, ssh_channel channel, void* userdata); + +private: + int m_retcode; + int m_db_id; + + TppSshRec m_rec; + + SshProxy* m_proxy; + ssh_session m_cli_session; + ssh_session m_srv_session; + + ExThreadLock m_lock; + + ex_astr m_client_ip; + ex_u16 m_client_port; + + ex_astr m_sid; + ex_astr m_server_ip; + ex_u16 m_server_port; + ex_astr m_user_name; + ex_astr m_user_auth; + int m_auth_mode; + + bool m_is_first_server_data; + bool m_is_sftp; + + bool m_is_logon; + // һssh_sessionпԴ򿪶ssh_channel + ts_ssh_channel_map m_channel_cli_srv; // ͨͻͨҷͨ + ts_ssh_channel_map m_channel_srv_cli; // ͨͨҿͻͨ + + bool m_have_error; + + bool m_recving_from_srv; // Ƿڴӷݣ + bool m_recving_from_cli; // Ƿڴӿͻ˽ݣ + + struct ssh_server_callbacks_struct m_srv_cb; + struct ssh_channel_callbacks_struct m_cli_channel_cb; + struct ssh_channel_callbacks_struct m_srv_channel_cb; + + int m_command_flag; + + std::list m_cmd_char_list; + std::list::iterator m_cmd_char_pos; +}; + +#endif // __SSH_SESSION_H__ diff --git a/server/www/teleport/app/eom_app/controller/record.py b/server/www/teleport/app/eom_app/controller/record.py index df01e33..f995fbd 100644 --- a/server/www/teleport/app/eom_app/controller/record.py +++ b/server/www/teleport/app/eom_app/controller/record.py @@ -80,10 +80,20 @@ class ReplayStaticFileHandler(tornado.web.StaticFileHandler): class ComandLogHandler(TPBaseAdminAuthHandler): def get(self, protocol, record_id): + header = record.read_record_head(record_id) + if header is None: + return self.write_json(-3, '操作失败') + + # ret = dict() + # ret['header'] = header + # return self.write_json(0, data=ret) + param = dict() + param['header'] = header param['count'] = 0 param['op'] = list() + cmd_type = 0 # 0 = ssh, 1 = sftp protocol = int(protocol) if protocol == 1: pass @@ -94,12 +104,26 @@ class ComandLogHandler(TPBaseAdminAuthHandler): file = open(file_info, 'r') data = file.readlines() for i in range(len(data)): - param['op'].append({'t': data[i][1:20], 'c': data[i][22:-1]}) + if 0 == i: + cmd = data[i][22:-1] + if 'SFTP INITIALIZE' == cmd: + cmd_type = 1 + continue + if cmd_type == 0: + param['op'].append({'t': data[i][1:20], 'c': data[i][22:-1]}) + else: + cmd_info = data[i][22:-1].split(':') + if len(cmd_info) != 4: + continue + param['op'].append({'t': data[i][1:20], 'c': cmd_info[0], 'p1': cmd_info[2], 'p2': cmd_info[3]}) except: pass param['count'] = len(param['op']) - self.render('log/record-ssh-cmd.mako', page_param=json.dumps(param)) + if cmd_type == 0: + self.render('log/record-ssh-cmd.mako', page_param=json.dumps(param)) + else: + self.render('log/record-sftp-cmd.mako', page_param=json.dumps(param)) class RecordGetHeader(TPBaseAdminAuthJsonHandler): diff --git a/server/www/teleport/view/log/record-sftp-cmd.mako b/server/www/teleport/view/log/record-sftp-cmd.mako new file mode 100644 index 0000000..7c8ad54 --- /dev/null +++ b/server/www/teleport/view/log/record-sftp-cmd.mako @@ -0,0 +1,125 @@ +<%! + page_title_ = 'SFTP操作记录' +%> + +<%inherit file="../page_no_sidebar_base.mako"/> +<%block name="extend_js"> + + +<%block name="breadcrumb"> + + + +<%block name="extend_css"> + + + +
+
+ 他悄悄地来,又悄悄地走,挥一挥衣袖,没有留下任何操作~~~~ +
+
+
+ +<%block name="embed_js"> + + \ No newline at end of file diff --git a/server/www/teleport/view/log/record-ssh-cmd.mako b/server/www/teleport/view/log/record-ssh-cmd.mako index 822974f..794cd24 100644 --- a/server/www/teleport/view/log/record-ssh-cmd.mako +++ b/server/www/teleport/view/log/record-ssh-cmd.mako @@ -1,101 +1,105 @@ -<%! - page_title_ = '操作记录' -%> - -<%inherit file="../page_no_sidebar_base.mako"/> -<%block name="extend_js"> - - -<%block name="breadcrumb"> - - - -<%block name="extend_css"> - - - -
-
- 他悄悄地来,又悄悄地走,挥一挥衣袖,没有留下任何操作~~~~ -
-
-
- -<%block name="embed_js"> - +<%! + page_title_ = 'SSH操作记录' +%> + +<%inherit file="../page_no_sidebar_base.mako"/> +<%block name="extend_js"> + + +<%block name="breadcrumb"> + + + +<%block name="extend_css"> + + + +
+
+ 他悄悄地来,又悄悄地走,挥一挥衣袖,没有留下任何操作~~~~ +
+
+
+ +<%block name="embed_js"> + \ No newline at end of file From 4ae6d4c5b5036bf023e6cbf1156213f4d30055cf Mon Sep 17 00:00:00 2001 From: Apex Liu Date: Sun, 28 May 2017 13:08:37 +0800 Subject: [PATCH 03/14] =?UTF-8?q?1.=20=E6=97=A0=E6=B3=95=E8=BF=9E=E6=8E=A5?= =?UTF-8?q?=E5=88=B0=E8=BF=9C=E7=A8=8B=E4=B8=BB=E6=9C=BA=E6=97=B6=EF=BC=8C?= =?UTF-8?q?=E8=BF=9E=E6=8E=A5=E8=B6=85=E6=97=B6=E8=AE=BE=E7=BD=AE=E4=B8=BA?= =?UTF-8?q?10=E7=A7=92=EF=BC=9B=202.=20=E8=A7=A3=E5=86=B3RDP=E8=BF=9E?= =?UTF-8?q?=E6=8E=A5=E5=87=BA=E9=94=99=E6=97=B6=EF=BC=8C=E4=BB=8D=E7=84=B6?= =?UTF-8?q?=E6=B1=87=E6=8A=A5=E8=BF=9E=E6=8E=A5=E6=88=90=E5=8A=9F=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/tp_core/common/ts_const.h | 2 ++ server/www/teleport/static/css/main.css | 2 +- server/www/teleport/static/js/ui/common.js | 18 ++++++++++-------- server/www/teleport/static/js/ui/log.js | 14 +++++++++++--- server/www/teleport/static/less/_base.less | 2 +- server/www/teleport/static/less/main.less | 5 +++++ 6 files changed, 30 insertions(+), 13 deletions(-) diff --git a/server/tp_core/common/ts_const.h b/server/tp_core/common/ts_const.h index db4e21e..d1ffa72 100644 --- a/server/tp_core/common/ts_const.h +++ b/server/tp_core/common/ts_const.h @@ -63,6 +63,8 @@ #define SESS_STAT_ERR_UNSUPPORT_PROTOCOL 5 // ỰΪЭ鲻֧(RDP) #define SESS_STAT_ERR_BAD_PKG 6 // ỰΪյı #define SESS_STAT_ERR_RESET 7 // ỰΪteleportķ +#define SESS_STAT_ERR_IO 8 // ỰΪж +#define SESS_STAT_ERR_SESSION 9 // ỰΪЧĻỰID #endif // __TS_ERRNO_H__ diff --git a/server/www/teleport/static/css/main.css b/server/www/teleport/static/css/main.css index 0a232d3..df5d315 100644 --- a/server/www/teleport/static/css/main.css +++ b/server/www/teleport/static/css/main.css @@ -1 +1 @@ -@charset "utf-8";body{font-family:"Open Sans","Helvetica Neue","Microsoft YaHei","微软雅黑",Helvetica,Arial,sans-serif;font-size:13px;background-color:#e9e9e9;color:#333}html,body{height:100%}#page-container{min-width:1260px}a{text-decoration:none}a:link{text-decoration:none}a:hover{text-decoration:none}a:active{text-decoration:none}a:visited{text-decoration:none}select{outline:none}label{font-weight:normal}.clear-float{clear:both}.bigger{font-size:120%}.normal-text{font-size:13px;color:#333}.mono{font-family:Consolas,Lucida Console,Monaco,Courier,'Courier New',monospace}hr.hr-sm{margin-top:5px;margin-bottom:5px}.btn-group-sm>.btn,.btn-sm{padding:2px 5px}.btn.btn-sm{padding:3px 8px}.btn.btn-icon{padding:3px 6px}.btn.btn-icon.btn-sm{padding:0;font-size:14px;height:24px;width:24px;line-height:24px;border-radius:0}.form-group-sm .input-group .input-group-btn>.btn{height:30px;padding:0 8px}.pop-menu-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040}.form-group{margin-bottom:5px}.badge{display:inline-block;min-width:8px;padding:5px 10px;border-radius:10px;text-align:center;white-space:nowrap;vertical-align:middle;font-size:13px;font-weight:400;line-height:1em;background-color:#888;color:#fff;text-shadow:1px 1px 0 #525252}.badge.badge-plain{text-shadow:none}.badge.badge-sm{font-size:11px;padding:3px 6px;margin-top:0;border-radius:8px;text-shadow:none}.badge.badge-sup{margin-left:-6px;margin-top:-16px}.badge.badge-ignore{background-color:#e5e5e5;color:#999;text-shadow:none}.badge.badge-info{background-color:#33b7d0}.badge.badge-primary{background-color:#348fe2}.badge.badge-success{background-color:#368142}.badge.badge-warning{background-color:#f57523}.badge.badge-danger{background-color:#d34242}.label{display:inline-block;min-width:8px;padding:5px 10px;border-radius:5px;text-align:center;white-space:nowrap;vertical-align:middle;font-size:13px;font-weight:400;line-height:1em;background-color:#888;color:#fff;text-shadow:1px 1px 0 #525252}.label.label-plain{text-shadow:none}.label.label-sm{font-size:11px;padding:3px 8px;margin-top:0;border-radius:5px;text-shadow:none}.label.label-ignore{background-color:#e5e5e5;color:#999;text-shadow:none}.label.label-info{background-color:#33b7d0}.label.label-primary{background-color:#348fe2}.label.label-success{background-color:#368142}.label.label-warning{background-color:#f57523}.label.label-danger{background-color:#d34242}.progress.progress-sm{height:18px;margin-bottom:2px;background-color:#aaa}.progress.progress-sm.button{cursor:pointer}.progress.progress-sm .progress-bar{display:block;font-size:11px;float:none}.alert-sm{padding:5px;margin-bottom:10px}.modal-dialog-sm .modal-header{padding:10px}.modal-dialog-sm .modal-body{padding:10px}.modal-dialog-sm .modal-footer{padding:10px}.modal-dialog-sm .form-horizontal .form-group{margin-right:-5px;margin-left:-5px}.modal-dialog-sm .col-sm-1,.modal-dialog-sm .col-sm-2,.modal-dialog-sm .col-sm-3,.modal-dialog-sm .col-sm-4,.modal-dialog-sm .col-sm-5,.modal-dialog-sm .col-sm-6,.modal-dialog-sm .col-sm-7,.modal-dialog-sm .col-sm-8,.modal-dialog-sm .col-sm-9,.modal-dialog-sm .col-sm-10,.modal-dialog-sm .col-sm-11{padding-right:5px;padding-left:5px}.btn-single-line{white-space:nowrap}.btn-single-line .btn:first-child{border-top-left-radius:3px;border-bottom-left-radius:3px}.btn-single-line .btn:last-child{border-top-right-radius:3px;border-bottom-right-radius:3px}.remote-action-group{margin-bottom:3px;height:28px;min-width:390px}.remote-action-group ul{display:block;height:28px;margin:0;padding:0}.remote-action-group ul li{float:left;position:relative;display:block;height:28px;padding:4px 5px;background-color:#eee;border-top:1px solid #ccc;border-right:1px solid #ccc;border-bottom:1px solid #ccc}.remote-action-group ul li.remote-action-btn{background:none;padding:0;border:none}.remote-action-group ul li.remote-action-input{background:none;padding:4px 0}.remote-action-group ul li.remote-action-input select{border:none}.remote-action-group ul li.remote-action-chk-protocol{width:86px}.remote-action-group ul li.remote-action-username,.remote-action-group ul li.remote-action-name,.remote-action-group ul li.remote-action-protocol{width:96px;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.remote-action-group ul li.remote-action-username{font-size:90%;color:#999}.remote-action-group ul li.remote-action-name,.remote-action-group ul li.remote-action-protocol,.remote-action-group ul li.remote-action-chk-protocol{color:#000}.remote-action-group ul li.remote-action-name,.remote-action-group ul li.remote-action-chk-protocol{font-weight:bold}.remote-action-group ul li.remote-action-password,.remote-action-group ul li.remote-action-sshkey,.remote-action-group ul li.remote-action-noauth{text-align:center;padding:4px 8px;width:45px}.remote-action-group ul li.remote-action-password{background-color:#e3ffe3;color:#999}.remote-action-group ul li.remote-action-sshkey{background-color:#fbe9c8;color:#666}.remote-action-group ul li.remote-action-noauth{background-color:#e0e0e0;color:#666}.remote-action-group ul li .btn{line-height:1.5;margin:0;padding:4px 8px;font-size:12px;border-radius:0}.remote-action-group ul li label{padding:0;display:block;float:left;margin-top:1px;cursor:pointer}.remote-action-group ul li input[type=checkbox]{display:block;float:left;margin:3px 5px 0 0}.remote-action-group ul li select{margin-top:-3px}.remote-action-group ul li:first-child{border-left:1px solid #ccc;border-top-left-radius:4px;border-bottom-left-radius:4px}.remote-action-group ul li:first-child .btn{border-top-left-radius:4px;border-bottom-left-radius:4px}.remote-action-group ul li:last-child{border-top-right-radius:4px;border-bottom-right-radius:4px}.remote-action-group ul li:last-child .btn{border-top-right-radius:4px;border-bottom-right-radius:4px}hr.small{margin:5px 0}.dlg-protocol-group{margin-bottom:3px}.dlg-protocol-group ul{display:block;height:28px;margin:0;padding:0}.dlg-protocol-group ul li{float:left;position:relative;display:block;height:28px;padding:4px 5px;background-color:#eee;border-top:1px solid #ccc;border-right:1px solid #ccc;border-bottom:1px solid #ccc}.dlg-protocol-group ul li.item-name{width:120px}.dlg-protocol-group ul li.item-btn{background:none;padding:0;border:none}.dlg-protocol-group ul li.item-input{background:none;border:none;padding:0}.dlg-protocol-group ul li .form-control{line-height:1.5;margin:0;padding:4px 5px;font-size:12px;height:28px;border-radius:0;border-left:none;width:100px}.dlg-protocol-group ul li label{padding:0;display:block;float:left;margin-top:1px;cursor:pointer}.dlg-protocol-group ul li input[type=checkbox]{display:block;float:left;margin:3px 5px 0 0}.dlg-protocol-group ul li:first-child{border-left:1px solid #ccc;border-top-left-radius:4px;border-bottom-left-radius:4px}.dlg-protocol-group ul li:first-child .btn,.dlg-protocol-group ul li:first-child .form-control{border-top-left-radius:4px;border-bottom-left-radius:4px}.dlg-protocol-group ul li:last-child{border-top-right-radius:4px;border-bottom-right-radius:4px}.dlg-protocol-group ul li:last-child .btn,.dlg-protocol-group ul li:last-child .form-control{border-top-right-radius:4px;border-bottom-right-radius:4px}.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{padding-right:5px;padding-left:5px}.form-group-sm .form-control-static{padding:6px 0}.table{margin-bottom:10px}.table>thead>tr>th{padding:5px 5px;outline:none;white-space:nowrap;font-weight:normal;text-align:center;background-color:#ededed}.table>tbody>tr>td{padding:5px;text-align:center;vertical-align:middle}.table>tbody>tr>td .nowrap{white-space:nowrap}.table.table-data thead .sorting,.table.table-data thead .sorting_asc,.table.table-data thead .sorting_desc{cursor:pointer;position:relative}.table.table-data thead .sorting>span:after,.table.table-data thead .sorting_asc>span:after,.table.table-data thead .sorting_desc>span:after{bottom:4px;padding-left:5px;display:inline-block;font-family:'FontAwesome';opacity:.8}.table.table-data thead .sorting>span:after{opacity:.2;content:"\f0dc"}.table.table-data thead .sorting_asc>span:after{content:"\f0de"}.table.table-data thead .sorting_desc>span:after{content:"\f0dd"}.host-id{display:block;font-size:16px;font-family:Consolas,Lucida Console,Monaco,Courier,'Courier New',monospace;color:#333}.host-id.not-active{font-size:14px;font-weight:400;color:#999}.host-desc{font-size:12px;color:#999;display:inline-block;white-space:nowrap;width:160px;overflow:hidden;text-overflow:ellipsis}a.host-desc:hover:before{display:inline-block;padding-right:3px;line-height:12px;content:"\f040";font-family:'FontAwesome'}.td-ip-list{padding-right:20px;padding-left:5px}.td-ip-show-more{font-size:14px;width:12px;float:right;display:block}.td-ip-item{min-width:12em;width:12em;height:18px;padding:2px 4px;margin:1px 0;color:#333;text-align:center;white-space:nowrap;border-radius:9px;line-height:11px;font-size:11px;background:#dfdfdf !important}.td-ip-item span{display:inline-block;font-family:Consolas,Lucida Console,Monaco,Courier,'Courier New',monospace;font-size:11px;font-weight:400}.td-ip-item a{display:inline-block;width:14px;float:right;font-size:14px}.admin{background-color:#930;color:#fff;padding:5px 15px;border-radius:5px}.page-header-fixed{padding-top:48px}.header{border:none;box-shadow:0 0 3px rgba(0,0,0,0.5)}.header .container-fluid{padding-left:0}.header .breadcrumb-container{display:inline-block;padding-top:6px}.header .breadcrumb{background-color:transparent;padding-left:20px;font-size:16px}.header.navbar{min-height:48px;height:48px;margin:0}.header.navbar .brand{display:inline-block;float:left;width:180px;height:48px;padding:12px 0 0;text-align:center;margin:0 auto;background-color:#3a3a3a}.header.navbar .brand .navbar-logo{display:inline-block;width:93px;height:30px;background:url(../img/site-logo-small.png) no-repeat}.header.navbar .breadcrumb>li+li:before{font-size:18px;padding:0 5px;color:#ccc;content:"\f105";font-family:'FontAwesome'}.page-sidebar-fixed .sidebar{position:fixed}.sidebar{top:0;bottom:0;left:0;width:180px;padding-top:48px;z-index:1010;background-color:#3a3a3a}.sidebar .nav-menu>li>a{padding:8px 0 8px 20px;line-height:24px;font-size:13px;color:#c2c2c2;border-left:5px solid #3a3a3a}.sidebar .nav-menu>li>a:focus{background-color:#3a3a3a;border-left:5px solid #3a3a3a}.sidebar .nav-menu>li>a:hover{background-color:#2d2d2d;border-left:5px solid #005c74}.sidebar .nav-menu>li>a.active{color:#fff;background-color:#0084a7;border-left:5px solid #0084a7}.sidebar .nav-menu>li>a.active:hover{border-left:5px solid #00acda}.sidebar .nav-menu>li>a.active:after{content:"\e251";font-family:'Glyphicons Halflings';position:relative;top:1px;display:inline-block;font-style:normal;font-weight:400;float:right;color:#e9e9e9;font-size:20px;line-height:24px;margin-right:-6px}.sidebar .nav-menu li .menu-caret:after{display:inline-block;width:12px;height:12px;margin-left:5px;top:1px;position:relative;border:none;font-family:'FontAwesome';font-style:normal}.sidebar .nav-menu li .menu-caret:after{content:'\f0da'}.sidebar .nav-menu li.expand .menu-caret:after{content:'\f0d7'}.sidebar .nav-menu>li.super-admin>a:hover{background-color:#620;border-left:5px solid #4d1a00}.sidebar .nav-menu>li.super-admin>a.active{background-color:#930;border-left:5px solid #930}.sidebar .nav-menu>li.super-admin>a.active:hover{border-left:5px solid #c40}.sidebar .nav-menu>li>a>i.icon{float:left;margin-top:1px;margin-right:15px;text-align:center;line-height:24px;font-size:14px}.sidebar .sub-menu{padding:0;margin:0;background-color:#292929;position:relative;list-style-type:none;border-top:1px solid #202020;border-bottom:1px solid #464646}.sidebar .sub-menu>li>a{padding:8px 0 8px 40px;line-height:20px;font-size:13px;display:block;position:relative;color:#889097;border-left:5px solid #292929}.sidebar .sub-menu>li>a:before{display:inline-block;padding-right:8px;line-height:20px;content:"\f105";font-family:'FontAwesome'}.sidebar .sub-menu>li>a:hover{color:#fff;border-left:5px solid #005c74}.sidebar .sub-menu>li>a.active{color:#fff;background-color:#0084a7;border-left:5px solid #0084a7}.sidebar .sub-menu>li>a.active:hover{border-left:5px solid #00acda}.sidebar .sub-menu>li>a.active:after{content:"\e251";font-family:'Glyphicons Halflings';position:relative;top:-2px;display:inline-block;font-style:normal;font-weight:400;float:right;color:#e9e9e9;font-size:20px;line-height:24px;margin-right:-6px}.sidebar .nav-profile{padding:15px 10px;color:#ccc;background-color:#333;border-bottom:1px solid #464646}.sidebar .nav-profile a.title{color:#ccc}.sidebar .nav-profile a.title:hover{color:#fff;background-color:transparent}.sidebar .nav-profile a.title:focus{background-color:transparent}.sidebar .nav-profile .image{float:left;margin-top:3px;font-size:24px;color:#69f;width:36px;height:36px;border-radius:5px;background-color:#eee;text-align:center;margin-right:10px;overflow:hidden}.sidebar .nav-profile .image img{margin-top:-3px}.sidebar .nav-profile .name{display:block;font-size:16px}.sidebar .nav-profile .role{display:block;font-size:12px;color:#999}.sidebar .nav-profile .dropdown-menu{font-size:13px}.sidebar .nav-profile .dropdown-menu>li>a{padding:5px 20px}.sidebar .nav-profile .dropdown-menu>li>a:hover{background-color:#ccc}.sidebar .nav-profile .dropdown-menu .divider{margin:5px 0}.sidebar .badge{margin-top:-10px;margin-left:5px}.content{margin-left:180px}.page-content{padding:15px}.page-content-dashboard{padding:20px 25px}.widget{overflow:hidden;border-radius:3px;padding:15px;margin-bottom:20px;color:#fff}.widget.widget-stats{position:relative}.widget .stats-icon{font-size:52px;top:12px;right:21px;width:56px;height:56px;text-align:center;line-height:56px;margin-left:15px;color:#fff;position:absolute;opacity:.2}.widget .stats-title{color:#fff;color:rgba(255,255,255,0.6)}.widget .stats-split{height:2px;margin:0 -15px 10px;background:rgba(0,0,0,0.2)}.widget .stats-content{font-size:24px;font-weight:300;margin-bottom:10px}.widget .stats-desc{display:inline-block;color:#fff;color:rgba(255,255,255,0.6)}.widget .stats-action{display:inline-block;float:right}.widget a{color:#eee;color:rgba(255,255,255,0.7)}.widget a:hover{color:#fff}.widget.widget-info{background-color:#33b7d0}.widget.widget-primary{background-color:#348fe2}.widget.widget-success{background-color:#368142}.widget.widget-warning{background-color:#f57523}.widget.widget-danger{background-color:#d34242}.panel{border:none;box-shadow:none;border-radius:3px}.panel .panel-heading{padding:6px 15px;color:#fff}.panel .panel-heading .panel-title{font-size:14px}.panel .panel-heading .panel-heading-btn{float:right}.panel .panel-heading .panel-heading-btn .btn{display:inline-block;padding:0;border:none;text-align:center}.panel .panel-heading .panel-heading-btn .btn.btn-xs{width:18px;height:18px;line-height:18px;font-size:12px}.panel .panel-heading .panel-heading-btn .btn.btn-circle{border-radius:50%}.place-holder-h200{width:100%;height:300px;background-color:#eee;border:1px solid #ccc;line-height:200px;text-align:center}.dashboard-panel2-holder{width:100%;height:1150px;background-color:#eee;border:1px solid #ccc;line-height:200px;text-align:center}.dashboard-panel-time{color:#cecece}.box{border:none;box-shadow:none;border-radius:3px;background-color:#fff;padding:15px;margin-bottom:15px}.box-fluid{border:none;box-shadow:none;border-radius:3px;margin-bottom:15px}.box.box-sm,.box-fluid.box-sm{padding:5px 15px}.box .box-title,.box-fluid .box-title{margin-bottom:10px}.box .box-title .title,.box-fluid .box-title .title{display:inline-block;font-size:18px;color:#333;height:30px;line-height:30px}.box .box-title .btn-sm,.box-fluid .box-title .btn-sm{padding:3px 8px;margin-top:-5px}.box .nav-tabs,.box-fluid .nav-tabs{font-size:14px;font-weight:bold}.box .nav-tabs>li:first-child,.box-fluid .nav-tabs>li:first-child{margin-left:50px}.box .tab-content>.tab-pane,.box-fluid .tab-content>.tab-pane{background-color:#fff;padding:20px;border:1px solid #ddd;border-top:none;border-bottom-left-radius:3px;border-bottom-right-radius:3px}.box-license{line-height:30px}.box-license .breadcrumb{padding:0;margin:0;border-radius:0;background-color:transparent}.box-btn-bar{line-height:30px}.box-btn-bar a.btn{margin-right:20px}.page-nav{height:30px;line-height:30px}.page-nav .breadcrumb{padding:0;margin:0;border-radius:0;background-color:transparent}.page-nav .pagination{margin:0 0}.page-nav .btn{margin-top:-3px}.page-filter{height:36px;line-height:36px;margin-bottom:10px}.page-filter .form-control{margin-top:5px;margin-right:4px}.btn.btn-sm .dropdown-menu li a{font-size:11px}.invite{text-align:center;padding-bottom:20px}.invite .code{color:#2f3991;font-size:36px;font-weight:700;font-family:Consolas,Lucida Console,Monaco,Courier,'Courier New',monospace}.invite .link{padding:5px;color:#2f3991;font-size:13px;font-weight:700;background-color:#eee;border-radius:5px}.invite-send-box{width:300px;margin:0 auto}.form-group .input-group{margin-bottom:5px}.op_box{display:block;padding:5px;border-radius:3px;text-align:center;margin-top:5px}.op_error{background:#fbb}.op_wait{background:#ccc}.table-data td.loading{text-align:left;padding:20px}.table-data .btn-group.open .dropdown-toggle{-webkit-box-shadow:none;box-shadow:none}.more-action{position:absolute !important}.more-action .dropdown-menu{background-color:rgba(60,60,60,0.9);color:#fff;font-size:13px}.more-action .dropdown-menu.dropdown-menu-left{margin-left:-120px}.more-action .dropdown-menu>li>a{padding:5px 20px;color:#fff}.more-action .dropdown-menu>li>a:hover,.more-action .dropdown-menu>li>a:active,.more-action .dropdown-menu>li>a:visited{background-color:#0084a7}.more-action .dropdown-menu .divider{margin:5px 0;background-color:#666}.popover-inline-edit input,.popover-inline-edit .btn{height:30px}.popover-inline-edit .popover-title{background-color:#ddd}.popover-inline-edit .popover-content{padding:20px 10px}.popover-inline-edit .popover{padding:0}.popover-inline-edit .popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#ddd}.user-info-table{font-size:14px}.user-info-table tbody>tr>td{padding:8px}.user-info-table .user-field{min-width:100px;width:100px;color:#999;text-align:right}.user-info-table .user-value{color:#333;font-weight:bold}.user-info-table .user-value a{font-weight:normal}.breadcrumb.breadcrumb-trans{background-color:transparent}.biz-box{display:inline-block;width:20%;max-width:20%}.biz-box .bb-inner{background-color:#368142;margin:3px;border-radius:4px}.biz-box .bb-name{color:#fff;padding:9px;margin-bottom:3px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:center;padding-top:1px;padding-bottom:1px}.biz-box .bb-ver{font-size:11px;height:16px;text-align:center;padding:0 5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;border-bottom-left-radius:4px;border-bottom-right-radius:4px;color:rgba(255,255,255,0.85);background-color:rgba(0,0,0,0.3)}.biz-box.mp-disabled .mp-inner{background-color:#e5e5e5}.biz-box.mp-disabled .mp-name{color:#999}.biz-box.mp-success .mp-inner{background-color:#368142}.biz-box.mp-success .mp-name{color:#fff}.biz-box.mp-danger .mp-inner{background-color:#d34242}.biz-box.mp-danger .mp-name{color:#fff}.biz-box.mp-warning .mp-inner{background-color:#f57523}.biz-box.mp-warning .mp-name{color:#fff}textarea.textarea-resize-y{resize:vertical}textarea.textarea-resize-none{resize:none}textarea.textarea-code{font-family:Consolas,Lucida Console,Monaco,Courier,'Courier New',monospace}textarea.cert_pub{width:100%;height:64px;border:1px solid #e2e2e2;background-color:#e4ffe5}.icon{display:inline-block}.icon16{width:16px;height:16px;line-height:16px;margin-right:3px;margin-top:0 !important;margin-bottom:-2px !important;vertical-align:top;background-image:url("img/desktop/mimetype-16.png") !important}.icon16.icon-disk{background-position:0 0 !important}.icon16.icon-folder{background-position:-16px 0 !important}.icon16.icon-file{background-position:0 -16px !important}.icon16.icon-txt{background-position:-16px -16px !important}.icon16.icon-help{background-position:-32px -16px !important}.icon16.icon-sys{background-position:-48px -16px !important}.icon16.icon-exe{background-position:-64px -16px !important}.icon16.icon-office{background-position:0 -32px !important}.icon16.icon-word{background-position:-16px -32px !important}.icon16.icon-excel{background-position:-32px -32px !important}.icon16.icon-ppt{background-position:-48px -32px !important}.icon16.icon-access{background-position:-64px -32px !important}.icon16.icon-visio{background-position:-80px -32px !important}.icon16.icon-audio{background-position:0 -48px !important}.icon16.icon-video{background-position:-16px -48px !important}.icon16.icon-pic{background-position:-32px -48px !important}.icon16.icon-pdf{background-position:-48px -48px !important}.icon16.icon-font{background-position:-64px -48px !important}.icon16.icon-script{background-position:0 -64px !important}.icon16.icon-html{background-position:-16px -64px !important}.icon16.icon-py{background-position:-32px -64px !important}.icon16.icon-h{background-position:-48px -64px !important}.icon16.icon-c{background-position:-64px -64px !important}.icon16.icon-cpp{background-position:-80px -64px !important}.icon16.icon-cs{background-position:-96px -64px !important}.icon16.icon-php{background-position:-112px -64px !important}.icon16.icon-ruby{background-position:-128px -64px !important}.icon16.icon-java{background-position:-144px -64px !important}.icon16.icon-vs{background-position:-160px -64px !important}.icon16.icon-js{background-position:-176px -64px !important}.icon16.icon-archive{background-position:0 -80px !important}.icon16.icon-rar{background-position:-16px -80px !important}.icon16.icon-zip{background-position:-32px -80px !important}.icon16.icon-7z{background-position:-48px -80px !important}.icon16.icon-tar{background-position:-64px -80px !important}.icon16.icon-gz{background-position:-80px -80px !important}.icon16.icon-jar{background-position:-96px -80px !important}.icon16.icon-bz2{background-position:-112px -80px !important}.icon24{width:24px;height:24px;line-height:24px;margin-right:3px;margin-top:0 !important;margin-bottom:-2px !important;vertical-align:top;background-image:url("img/desktop/icons-tree-24x24.png") !important}.icon24.icon-disk{background-position:0 0 !important}.icon24.icon-folder{background-position:-24px 0 !important}.icon24.icon-folder-open{background-position:-48px 0 !important}.os-icon-windows:after{color:#00bcf6;content:"\f17a";font-size:18px;width:24px;height:24px;line-height:24px;display:inline-block;font-family:'FontAwesome'}.os-icon-linux:after{color:#fff;content:"\f17c";font-size:18px;width:24px;height:24px;line-height:24px;background-color:#333;border-radius:50%;display:inline-block;font-family:'FontAwesome'}.os-icon-macos:after{color:#a7a7a7;content:"\f179";font-size:20px;width:24px;height:24px;line-height:24px;display:inline-block;font-family:'FontAwesome'}.os-icon-ubuntu:after,.os-icon-debian:after,.os-icon-centos:after,.os-icon-redhat:after{content:" ";width:24px;height:24px;line-height:24px;display:inline-block}.os-icon-ubuntu:after{background:url(../img/os-icon/ubuntu-24x24.png) no-repeat}.os-icon-debian:after{background:url(../img/os-icon/debian-24x24.png) no-repeat}.os-icon-centos:after{background:url(../img/os-icon/centos-24x24.png) no-repeat}.os-icon-redhat:after{background:url(../img/os-icon/redhat-24x24.png) no-repeat}#gritter-notice-wrapper{z-index:9999}.gritter-bottom,.gritter-item,.gritter-top{background:rgba(0,0,0,0.8) !important}.gritter-top{border-top-left-radius:3px;border-top-right-radius:3px}.gritter-bottom{border-bottom-left-radius:3px;border-bottom-right-radius:3px}.gritter-close,.gritter-light .gritter-close{left:auto !important;right:5px !important;top:5px !important;width:16px !important;height:16px !important;line-height:16px !important;display:block !important;border-radius:50%}.gritter-close:before,.gritter-light .gritter-close:before{content:'\f00d' !important;font-family:FontAwesome !important;font-size:9px !important;width:16px !important;height:16px !important;line-height:16px !important;color:#fff !important;text-indent:0 !important;position:absolute !important;text-align:center !important;right:0 !important;top:0 !important}.gritter-title{font-size:13px !important;line-height:16px !important;padding-bottom:5px !important;font-weight:400 !important;color:#fff !important;text-shadow:none !important}.gritter-item{color:#aaa !important;font-size:13px !important;padding:2px 15px 5px !important}.gritter-error .gritter-bottom,.gritter-error .gritter-item,.gritter-error .gritter-top{background:rgba(123,32,32,0.9) !important}.gritter-error .gritter-title{color:#fff !important}.gritter-error .gritter-item{color:#ddd !important}.gritter-error .gritter-close{left:auto !important;right:5px !important;top:5px !important;width:16px !important;height:16px !important;line-height:16px !important;display:block !important;border-radius:50%;background:#e33b3b !important}.gritter-success .gritter-bottom,.gritter-success .gritter-item,.gritter-success .gritter-top{background:rgba(1,65,16,0.9) !important}.gritter-success .gritter-title{color:#ddd !important}.gritter-success .gritter-item{color:#ccc !important}.gritter-success .gritter-close{background:#0eb320 !important} \ No newline at end of file +@charset "utf-8";body{font-family:"Open Sans","Helvetica Neue","Microsoft YaHei","微软雅黑",Helvetica,Arial,sans-serif;font-size:13px;background-color:#e9e9e9;color:#333}html,body{height:100%}#page-container{min-width:1260px}a{text-decoration:none}a:link{text-decoration:none}a:hover{text-decoration:none}a:active{text-decoration:none}a:visited{text-decoration:none}select{outline:none}label{font-weight:normal}.clear-float{clear:both}.bigger{font-size:120%}.normal-text{font-size:13px;color:#333}.mono{font-family:Consolas,Lucida Console,Monaco,Courier,'Courier New',monospace}hr.hr-sm{margin-top:5px;margin-bottom:5px}.btn-group-sm>.btn,.btn-sm{padding:2px 5px}.btn.btn-sm{padding:3px 8px}.btn.btn-icon{padding:3px 6px}.btn.btn-icon.btn-sm{padding:0;font-size:14px;height:24px;width:24px;line-height:24px;border-radius:0}.form-group-sm .input-group .input-group-btn>.btn{height:30px;padding:0 8px}.pop-menu-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040}.form-group{margin-bottom:5px}.badge{display:inline-block;min-width:8px;padding:5px 10px;border-radius:10px;text-align:center;white-space:nowrap;vertical-align:middle;font-size:13px;font-weight:400;line-height:1em;background-color:#888;color:#fff;text-shadow:1px 1px 0 #525252}.badge.badge-plain{text-shadow:none}.badge.badge-sm{font-size:11px;padding:3px 6px;margin-top:0;border-radius:8px;text-shadow:none}.badge.badge-sup{margin-left:-6px;margin-top:-16px}.badge.badge-ignore{background-color:#e5e5e5;color:#999;text-shadow:none}.badge.badge-info{background-color:#33b7d0}.badge.badge-primary{background-color:#348fe2}.badge.badge-success{background-color:#368142}.badge.badge-warning{background-color:#f57523}.badge.badge-danger{background-color:#d34242}.label{display:inline-block;min-width:8px;padding:5px 10px;border-radius:5px;text-align:center;white-space:nowrap;vertical-align:middle;font-size:13px;font-weight:400;line-height:1em;background-color:#888;color:#fff;text-shadow:1px 1px 0 #525252}.label.label-plain{text-shadow:none}.label.label-sm{font-size:11px;padding:3px 8px;margin-top:0;border-radius:5px;text-shadow:none}.label.label-ignore{background-color:#e5e5e5;color:#999;text-shadow:none}.label.label-info{background-color:#33b7d0}.label.label-primary{background-color:#348fe2}.label.label-success{background-color:#368142}.label.label-warning{background-color:#f57523}.label.label-danger{background-color:#d34242}.progress.progress-sm{height:18px;margin-bottom:2px;background-color:#aaa}.progress.progress-sm.button{cursor:pointer}.progress.progress-sm .progress-bar{display:block;font-size:11px;float:none}.alert-sm{padding:5px;margin-bottom:10px}.modal-dialog-sm .modal-header{padding:10px}.modal-dialog-sm .modal-body{padding:10px}.modal-dialog-sm .modal-footer{padding:10px}.modal-dialog-sm .form-horizontal .form-group{margin-right:-5px;margin-left:-5px}.modal-dialog-sm .col-sm-1,.modal-dialog-sm .col-sm-2,.modal-dialog-sm .col-sm-3,.modal-dialog-sm .col-sm-4,.modal-dialog-sm .col-sm-5,.modal-dialog-sm .col-sm-6,.modal-dialog-sm .col-sm-7,.modal-dialog-sm .col-sm-8,.modal-dialog-sm .col-sm-9,.modal-dialog-sm .col-sm-10,.modal-dialog-sm .col-sm-11{padding-right:5px;padding-left:5px}.btn-single-line{white-space:nowrap}.btn-single-line .btn:first-child{border-top-left-radius:3px;border-bottom-left-radius:3px}.btn-single-line .btn:last-child{border-top-right-radius:3px;border-bottom-right-radius:3px}.remote-action-group{margin-bottom:3px;height:28px;min-width:390px}.remote-action-group ul{display:block;height:28px;margin:0;padding:0}.remote-action-group ul li{float:left;position:relative;display:block;height:28px;padding:4px 5px;background-color:#eee;border-top:1px solid #ccc;border-right:1px solid #ccc;border-bottom:1px solid #ccc}.remote-action-group ul li.remote-action-btn{background:none;padding:0;border:none}.remote-action-group ul li.remote-action-input{background:none;padding:4px 0}.remote-action-group ul li.remote-action-input select{border:none}.remote-action-group ul li.remote-action-chk-protocol{width:86px}.remote-action-group ul li.remote-action-username,.remote-action-group ul li.remote-action-name,.remote-action-group ul li.remote-action-protocol{width:96px;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.remote-action-group ul li.remote-action-username{font-size:90%;color:#999}.remote-action-group ul li.remote-action-name,.remote-action-group ul li.remote-action-protocol,.remote-action-group ul li.remote-action-chk-protocol{color:#000}.remote-action-group ul li.remote-action-name,.remote-action-group ul li.remote-action-chk-protocol{font-weight:bold}.remote-action-group ul li.remote-action-password,.remote-action-group ul li.remote-action-sshkey,.remote-action-group ul li.remote-action-noauth{text-align:center;padding:4px 8px;width:45px}.remote-action-group ul li.remote-action-password{background-color:#e3ffe3;color:#999}.remote-action-group ul li.remote-action-sshkey{background-color:#fbe9c8;color:#666}.remote-action-group ul li.remote-action-noauth{background-color:#e0e0e0;color:#666}.remote-action-group ul li .btn{line-height:1.5;margin:0;padding:4px 8px;font-size:12px;border-radius:0}.remote-action-group ul li label{padding:0;display:block;float:left;margin-top:1px;cursor:pointer}.remote-action-group ul li input[type=checkbox]{display:block;float:left;margin:3px 5px 0 0}.remote-action-group ul li select{margin-top:-3px}.remote-action-group ul li:first-child{border-left:1px solid #ccc;border-top-left-radius:4px;border-bottom-left-radius:4px}.remote-action-group ul li:first-child .btn{border-top-left-radius:4px;border-bottom-left-radius:4px}.remote-action-group ul li:last-child{border-top-right-radius:4px;border-bottom-right-radius:4px}.remote-action-group ul li:last-child .btn{border-top-right-radius:4px;border-bottom-right-radius:4px}hr.small{margin:5px 0}.dlg-protocol-group{margin-bottom:3px}.dlg-protocol-group ul{display:block;height:28px;margin:0;padding:0}.dlg-protocol-group ul li{float:left;position:relative;display:block;height:28px;padding:4px 5px;background-color:#eee;border-top:1px solid #ccc;border-right:1px solid #ccc;border-bottom:1px solid #ccc}.dlg-protocol-group ul li.item-name{width:120px}.dlg-protocol-group ul li.item-btn{background:none;padding:0;border:none}.dlg-protocol-group ul li.item-input{background:none;border:none;padding:0}.dlg-protocol-group ul li .form-control{line-height:1.5;margin:0;padding:4px 5px;font-size:12px;height:28px;border-radius:0;border-left:none;width:100px}.dlg-protocol-group ul li label{padding:0;display:block;float:left;margin-top:1px;cursor:pointer}.dlg-protocol-group ul li input[type=checkbox]{display:block;float:left;margin:3px 5px 0 0}.dlg-protocol-group ul li:first-child{border-left:1px solid #ccc;border-top-left-radius:4px;border-bottom-left-radius:4px}.dlg-protocol-group ul li:first-child .btn,.dlg-protocol-group ul li:first-child .form-control{border-top-left-radius:4px;border-bottom-left-radius:4px}.dlg-protocol-group ul li:last-child{border-top-right-radius:4px;border-bottom-right-radius:4px}.dlg-protocol-group ul li:last-child .btn,.dlg-protocol-group ul li:last-child .form-control{border-top-right-radius:4px;border-bottom-right-radius:4px}.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{padding-right:5px;padding-left:5px}.form-group-sm .form-control-static{padding:6px 0}.table{margin-bottom:10px}.table>thead>tr>th{padding:5px 5px;outline:none;white-space:nowrap;font-weight:normal;text-align:center;background-color:#ededed}.table>tbody>tr>td{padding:5px;text-align:center;vertical-align:middle}.table>tbody>tr>td .nowrap{white-space:nowrap}.table.table-data thead .sorting,.table.table-data thead .sorting_asc,.table.table-data thead .sorting_desc{cursor:pointer;position:relative}.table.table-data thead .sorting>span:after,.table.table-data thead .sorting_asc>span:after,.table.table-data thead .sorting_desc>span:after{bottom:4px;padding-left:5px;display:inline-block;font-family:'FontAwesome';opacity:.8}.table.table-data thead .sorting>span:after{opacity:.2;content:"\f0dc"}.table.table-data thead .sorting_asc>span:after{content:"\f0de"}.table.table-data thead .sorting_desc>span:after{content:"\f0dd"}.host-id{display:block;font-size:16px;font-family:Consolas,Lucida Console,Monaco,Courier,'Courier New',monospace;color:#333}.host-id.not-active{font-size:14px;font-weight:400;color:#999}.host-desc{font-size:12px;color:#999;display:inline-block;white-space:nowrap;width:160px;overflow:hidden;text-overflow:ellipsis}a.host-desc:hover:before{display:inline-block;padding-right:3px;line-height:12px;content:"\f040";font-family:'FontAwesome'}.td-ip-list{padding-right:20px;padding-left:5px}.td-ip-show-more{font-size:14px;width:12px;float:right;display:block}.td-ip-item{min-width:12em;width:12em;height:18px;padding:2px 4px;margin:1px 0;color:#333;text-align:center;white-space:nowrap;border-radius:9px;line-height:11px;font-size:11px;background:#dfdfdf !important}.td-ip-item span{display:inline-block;font-family:Consolas,Lucida Console,Monaco,Courier,'Courier New',monospace;font-size:11px;font-weight:400}.td-ip-item a{display:inline-block;width:14px;float:right;font-size:14px}.admin{background-color:#930;color:#fff;padding:5px 15px;border-radius:5px}.page-header-fixed{padding-top:48px}.header{border:none;box-shadow:0 0 3px rgba(0,0,0,0.5)}.header .container-fluid{padding-left:0}.header .breadcrumb-container{display:inline-block;padding-top:6px}.header .breadcrumb{background-color:transparent;padding-left:20px;font-size:16px}.header.navbar{min-height:48px;height:48px;margin:0}.header.navbar .brand{display:inline-block;float:left;width:180px;height:48px;padding:12px 0 0;text-align:center;margin:0 auto;background-color:#3a3a3a}.header.navbar .brand .navbar-logo{display:inline-block;width:93px;height:30px;background:url(../img/site-logo-small.png) no-repeat}.header.navbar .breadcrumb>li+li:before{font-size:18px;padding:0 5px;color:#ccc;content:"\f105";font-family:'FontAwesome'}.page-sidebar-fixed .sidebar{position:fixed}.sidebar{top:0;bottom:0;left:0;width:180px;padding-top:48px;z-index:1010;background-color:#3a3a3a}.sidebar .nav-menu>li>a{padding:8px 0 8px 20px;line-height:24px;font-size:13px;color:#c2c2c2;border-left:5px solid #3a3a3a}.sidebar .nav-menu>li>a:focus{background-color:#3a3a3a;border-left:5px solid #3a3a3a}.sidebar .nav-menu>li>a:hover{background-color:#2d2d2d;border-left:5px solid #005c74}.sidebar .nav-menu>li>a.active{color:#fff;background-color:#0084a7;border-left:5px solid #0084a7}.sidebar .nav-menu>li>a.active:hover{border-left:5px solid #00acda}.sidebar .nav-menu>li>a.active:after{content:"\e251";font-family:'Glyphicons Halflings';position:relative;top:1px;display:inline-block;font-style:normal;font-weight:400;float:right;color:#e9e9e9;font-size:20px;line-height:24px;margin-right:-6px}.sidebar .nav-menu li .menu-caret:after{display:inline-block;width:12px;height:12px;margin-left:5px;top:1px;position:relative;border:none;font-family:'FontAwesome';font-style:normal}.sidebar .nav-menu li .menu-caret:after{content:'\f0da'}.sidebar .nav-menu li.expand .menu-caret:after{content:'\f0d7'}.sidebar .nav-menu>li.super-admin>a:hover{background-color:#620;border-left:5px solid #4d1a00}.sidebar .nav-menu>li.super-admin>a.active{background-color:#930;border-left:5px solid #930}.sidebar .nav-menu>li.super-admin>a.active:hover{border-left:5px solid #c40}.sidebar .nav-menu>li>a>i.icon{float:left;margin-top:1px;margin-right:15px;text-align:center;line-height:24px;font-size:14px}.sidebar .sub-menu{padding:0;margin:0;background-color:#292929;position:relative;list-style-type:none;border-top:1px solid #202020;border-bottom:1px solid #464646}.sidebar .sub-menu>li>a{padding:8px 0 8px 40px;line-height:20px;font-size:13px;display:block;position:relative;color:#889097;border-left:5px solid #292929}.sidebar .sub-menu>li>a:before{display:inline-block;padding-right:8px;line-height:20px;content:"\f105";font-family:'FontAwesome'}.sidebar .sub-menu>li>a:hover{color:#fff;border-left:5px solid #005c74}.sidebar .sub-menu>li>a.active{color:#fff;background-color:#0084a7;border-left:5px solid #0084a7}.sidebar .sub-menu>li>a.active:hover{border-left:5px solid #00acda}.sidebar .sub-menu>li>a.active:after{content:"\e251";font-family:'Glyphicons Halflings';position:relative;top:-2px;display:inline-block;font-style:normal;font-weight:400;float:right;color:#e9e9e9;font-size:20px;line-height:24px;margin-right:-6px}.sidebar .nav-profile{padding:15px 10px;color:#ccc;background-color:#333;border-bottom:1px solid #464646}.sidebar .nav-profile a.title{color:#ccc}.sidebar .nav-profile a.title:hover{color:#fff;background-color:transparent}.sidebar .nav-profile a.title:focus{background-color:transparent}.sidebar .nav-profile .image{float:left;margin-top:3px;font-size:24px;color:#69f;width:36px;height:36px;border-radius:5px;background-color:#eee;text-align:center;margin-right:10px;overflow:hidden}.sidebar .nav-profile .image img{margin-top:-3px}.sidebar .nav-profile .name{display:block;font-size:16px}.sidebar .nav-profile .role{display:block;font-size:12px;color:#999}.sidebar .nav-profile .dropdown-menu{font-size:13px}.sidebar .nav-profile .dropdown-menu>li>a{padding:5px 20px}.sidebar .nav-profile .dropdown-menu>li>a:hover{background-color:#ccc}.sidebar .nav-profile .dropdown-menu .divider{margin:5px 0}.sidebar .badge{margin-top:-10px;margin-left:5px}.content{margin-left:180px}.page-content{padding:15px}.page-content-dashboard{padding:20px 25px}.widget{overflow:hidden;border-radius:3px;padding:15px;margin-bottom:20px;color:#fff}.widget.widget-stats{position:relative}.widget .stats-icon{font-size:52px;top:12px;right:21px;width:56px;height:56px;text-align:center;line-height:56px;margin-left:15px;color:#fff;position:absolute;opacity:.2}.widget .stats-title{color:#fff;color:rgba(255,255,255,0.6)}.widget .stats-split{height:2px;margin:0 -15px 10px;background:rgba(0,0,0,0.2)}.widget .stats-content{font-size:24px;font-weight:300;margin-bottom:10px}.widget .stats-desc{display:inline-block;color:#fff;color:rgba(255,255,255,0.6)}.widget .stats-action{display:inline-block;float:right}.widget a{color:#eee;color:rgba(255,255,255,0.7)}.widget a:hover{color:#fff}.widget.widget-info{background-color:#33b7d0}.widget.widget-primary{background-color:#348fe2}.widget.widget-success{background-color:#368142}.widget.widget-warning{background-color:#f57523}.widget.widget-danger{background-color:#d34242}.panel{border:none;box-shadow:none;border-radius:3px}.panel .panel-heading{padding:6px 15px;color:#fff}.panel .panel-heading .panel-title{font-size:14px}.panel .panel-heading .panel-heading-btn{float:right}.panel .panel-heading .panel-heading-btn .btn{display:inline-block;padding:0;border:none;text-align:center}.panel .panel-heading .panel-heading-btn .btn.btn-xs{width:18px;height:18px;line-height:18px;font-size:12px}.panel .panel-heading .panel-heading-btn .btn.btn-circle{border-radius:50%}.place-holder-h200{width:100%;height:300px;background-color:#eee;border:1px solid #ccc;line-height:200px;text-align:center}.dashboard-panel2-holder{width:100%;height:1150px;background-color:#eee;border:1px solid #ccc;line-height:200px;text-align:center}.dashboard-panel-time{color:#cecece}.box{border:none;box-shadow:none;border-radius:3px;background-color:#fff;padding:15px;margin-bottom:15px}.box-fluid{border:none;box-shadow:none;border-radius:3px;margin-bottom:15px}.box.box-sm,.box-fluid.box-sm{padding:5px 15px}.box .box-title,.box-fluid .box-title{margin-bottom:10px}.box .box-title .title,.box-fluid .box-title .title{display:inline-block;font-size:18px;color:#333;height:30px;line-height:30px}.box .box-title .btn-sm,.box-fluid .box-title .btn-sm{padding:3px 8px;margin-top:-5px}.box .nav-tabs,.box-fluid .nav-tabs{font-size:14px;font-weight:bold}.box .nav-tabs>li:first-child,.box-fluid .nav-tabs>li:first-child{margin-left:50px}.box .tab-content>.tab-pane,.box-fluid .tab-content>.tab-pane{background-color:#fff;padding:20px;border:1px solid #ddd;border-top:none;border-bottom-left-radius:3px;border-bottom-right-radius:3px}.box-license{line-height:30px}.box-license .breadcrumb{padding:0;margin:0;border-radius:0;background-color:transparent}.box-btn-bar{line-height:30px}.box-btn-bar a.btn{margin-right:20px}.page-nav{height:30px;line-height:30px}.page-nav .breadcrumb{padding:0;margin:0;border-radius:0;background-color:transparent}.page-nav .pagination{margin:0 0}.page-nav .btn{margin-top:-3px}.page-filter{height:36px;line-height:36px;margin-bottom:10px}.page-filter .form-control{margin-top:5px;margin-right:4px}.btn.btn-sm .dropdown-menu li a{font-size:11px}.invite{text-align:center;padding-bottom:20px}.invite .code{color:#2f3991;font-size:36px;font-weight:700;font-family:Consolas,Lucida Console,Monaco,Courier,'Courier New',monospace}.invite .link{padding:5px;color:#2f3991;font-size:13px;font-weight:700;background-color:#eee;border-radius:5px}.invite-send-box{width:300px;margin:0 auto}.form-group .input-group{margin-bottom:5px}.op_box{display:block;padding:5px;border-radius:3px;text-align:center;margin-top:5px}.op_error{background:#fbb}.op_wait{background:#ccc}.table-data td.loading{text-align:left;padding:20px}.table-data .btn-group.open .dropdown-toggle{-webkit-box-shadow:none;box-shadow:none}.more-action{position:absolute !important}.more-action .dropdown-menu{background-color:rgba(60,60,60,0.9);color:#fff;font-size:13px}.more-action .dropdown-menu.dropdown-menu-left{margin-left:-120px}.more-action .dropdown-menu>li>a{padding:5px 20px;color:#fff}.more-action .dropdown-menu>li>a:hover,.more-action .dropdown-menu>li>a:active,.more-action .dropdown-menu>li>a:visited{background-color:#0084a7}.more-action .dropdown-menu .divider{margin:5px 0;background-color:#666}.popover-inline-edit input,.popover-inline-edit .btn{height:30px}.popover-inline-edit .popover-title{background-color:#ddd}.popover-inline-edit .popover-content{padding:20px 10px}.popover-inline-edit .popover{padding:0;max-width:500px}.popover-inline-edit .popover .popover-content{padding:10px 10px 20px 10px}.popover-inline-edit .popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#ddd}.user-info-table{font-size:14px}.user-info-table tbody>tr>td{padding:8px}.user-info-table .user-field{min-width:100px;width:100px;color:#999;text-align:right}.user-info-table .user-value{color:#333;font-weight:bold}.user-info-table .user-value a{font-weight:normal}.breadcrumb.breadcrumb-trans{background-color:transparent}.biz-box{display:inline-block;width:20%;max-width:20%}.biz-box .bb-inner{background-color:#368142;margin:3px;border-radius:4px}.biz-box .bb-name{color:#fff;padding:9px;margin-bottom:3px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:center;padding-top:1px;padding-bottom:1px}.biz-box .bb-ver{font-size:11px;height:16px;text-align:center;padding:0 5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;border-bottom-left-radius:4px;border-bottom-right-radius:4px;color:rgba(255,255,255,0.85);background-color:rgba(0,0,0,0.3)}.biz-box.mp-disabled .mp-inner{background-color:#e5e5e5}.biz-box.mp-disabled .mp-name{color:#999}.biz-box.mp-success .mp-inner{background-color:#368142}.biz-box.mp-success .mp-name{color:#fff}.biz-box.mp-danger .mp-inner{background-color:#d34242}.biz-box.mp-danger .mp-name{color:#fff}.biz-box.mp-warning .mp-inner{background-color:#f57523}.biz-box.mp-warning .mp-name{color:#fff}textarea.textarea-resize-y{resize:vertical}textarea.textarea-resize-none{resize:none}textarea.textarea-code{font-family:Consolas,Lucida Console,Monaco,Courier,'Courier New',monospace}textarea.cert_pub{width:100%;height:64px;border:1px solid #e2e2e2;background-color:#e4ffe5}.icon{display:inline-block}.icon16{width:16px;height:16px;line-height:16px;margin-right:3px;margin-top:0 !important;margin-bottom:-2px !important;vertical-align:top;background-image:url("img/desktop/mimetype-16.png") !important}.icon16.icon-disk{background-position:0 0 !important}.icon16.icon-folder{background-position:-16px 0 !important}.icon16.icon-file{background-position:0 -16px !important}.icon16.icon-txt{background-position:-16px -16px !important}.icon16.icon-help{background-position:-32px -16px !important}.icon16.icon-sys{background-position:-48px -16px !important}.icon16.icon-exe{background-position:-64px -16px !important}.icon16.icon-office{background-position:0 -32px !important}.icon16.icon-word{background-position:-16px -32px !important}.icon16.icon-excel{background-position:-32px -32px !important}.icon16.icon-ppt{background-position:-48px -32px !important}.icon16.icon-access{background-position:-64px -32px !important}.icon16.icon-visio{background-position:-80px -32px !important}.icon16.icon-audio{background-position:0 -48px !important}.icon16.icon-video{background-position:-16px -48px !important}.icon16.icon-pic{background-position:-32px -48px !important}.icon16.icon-pdf{background-position:-48px -48px !important}.icon16.icon-font{background-position:-64px -48px !important}.icon16.icon-script{background-position:0 -64px !important}.icon16.icon-html{background-position:-16px -64px !important}.icon16.icon-py{background-position:-32px -64px !important}.icon16.icon-h{background-position:-48px -64px !important}.icon16.icon-c{background-position:-64px -64px !important}.icon16.icon-cpp{background-position:-80px -64px !important}.icon16.icon-cs{background-position:-96px -64px !important}.icon16.icon-php{background-position:-112px -64px !important}.icon16.icon-ruby{background-position:-128px -64px !important}.icon16.icon-java{background-position:-144px -64px !important}.icon16.icon-vs{background-position:-160px -64px !important}.icon16.icon-js{background-position:-176px -64px !important}.icon16.icon-archive{background-position:0 -80px !important}.icon16.icon-rar{background-position:-16px -80px !important}.icon16.icon-zip{background-position:-32px -80px !important}.icon16.icon-7z{background-position:-48px -80px !important}.icon16.icon-tar{background-position:-64px -80px !important}.icon16.icon-gz{background-position:-80px -80px !important}.icon16.icon-jar{background-position:-96px -80px !important}.icon16.icon-bz2{background-position:-112px -80px !important}.icon24{width:24px;height:24px;line-height:24px;margin-right:3px;margin-top:0 !important;margin-bottom:-2px !important;vertical-align:top;background-image:url("img/desktop/icons-tree-24x24.png") !important}.icon24.icon-disk{background-position:0 0 !important}.icon24.icon-folder{background-position:-24px 0 !important}.icon24.icon-folder-open{background-position:-48px 0 !important}.os-icon-windows:after{color:#00bcf6;content:"\f17a";font-size:18px;width:24px;height:24px;line-height:24px;display:inline-block;font-family:'FontAwesome'}.os-icon-linux:after{color:#fff;content:"\f17c";font-size:18px;width:24px;height:24px;line-height:24px;background-color:#333;border-radius:50%;display:inline-block;font-family:'FontAwesome'}.os-icon-macos:after{color:#a7a7a7;content:"\f179";font-size:20px;width:24px;height:24px;line-height:24px;display:inline-block;font-family:'FontAwesome'}.os-icon-ubuntu:after,.os-icon-debian:after,.os-icon-centos:after,.os-icon-redhat:after{content:" ";width:24px;height:24px;line-height:24px;display:inline-block}.os-icon-ubuntu:after{background:url(../img/os-icon/ubuntu-24x24.png) no-repeat}.os-icon-debian:after{background:url(../img/os-icon/debian-24x24.png) no-repeat}.os-icon-centos:after{background:url(../img/os-icon/centos-24x24.png) no-repeat}.os-icon-redhat:after{background:url(../img/os-icon/redhat-24x24.png) no-repeat}#gritter-notice-wrapper{z-index:9999}.gritter-bottom,.gritter-item,.gritter-top{background:rgba(0,0,0,0.8) !important}.gritter-top{border-top-left-radius:3px;border-top-right-radius:3px}.gritter-bottom{border-bottom-left-radius:3px;border-bottom-right-radius:3px}.gritter-close,.gritter-light .gritter-close{left:auto !important;right:5px !important;top:5px !important;width:16px !important;height:16px !important;line-height:16px !important;display:block !important;border-radius:50%}.gritter-close:before,.gritter-light .gritter-close:before{content:'\f00d' !important;font-family:FontAwesome !important;font-size:9px !important;width:16px !important;height:16px !important;line-height:16px !important;color:#fff !important;text-indent:0 !important;position:absolute !important;text-align:center !important;right:0 !important;top:0 !important}.gritter-title{font-size:13px !important;line-height:16px !important;padding-bottom:5px !important;font-weight:400 !important;color:#fff !important;text-shadow:none !important}.gritter-item{color:#aaa !important;font-size:13px !important;padding:2px 15px 5px !important}.gritter-error .gritter-bottom,.gritter-error .gritter-item,.gritter-error .gritter-top{background:rgba(123,32,32,0.9) !important}.gritter-error .gritter-title{color:#fff !important}.gritter-error .gritter-item{color:#ddd !important}.gritter-error .gritter-close{left:auto !important;right:5px !important;top:5px !important;width:16px !important;height:16px !important;line-height:16px !important;display:block !important;border-radius:50%;background:#e33b3b !important}.gritter-success .gritter-bottom,.gritter-success .gritter-item,.gritter-success .gritter-top{background:rgba(1,65,16,0.9) !important}.gritter-success .gritter-title{color:#ddd !important}.gritter-success .gritter-item{color:#ccc !important}.gritter-success .gritter-close{background:#0eb320 !important} \ No newline at end of file diff --git a/server/www/teleport/static/js/ui/common.js b/server/www/teleport/static/js/ui/common.js index 995c965..e61e86e 100644 --- a/server/www/teleport/static/js/ui/common.js +++ b/server/www/teleport/static/js/ui/common.js @@ -251,15 +251,17 @@ ywl.create_dlg_modify_host_desc = function (tbl, row_id, host_id, host_ip, host_ self._make_dialog_box = function () { var _html = [ '
', - '