diff --git a/server/tp_core/common/protocol_interface.h b/server/tp_core/common/protocol_interface.h index c7a68ee..fc883f6 100644 --- a/server/tp_core/common/protocol_interface.h +++ b/server/tp_core/common/protocol_interface.h @@ -64,7 +64,9 @@ typedef struct TPP_INIT_ARGS TPP_SESSION_END_FUNC func_session_end; }TPP_INIT_ARGS; - +typedef struct TPP_SET_CFG_ARGS { + ex_u32 noop_timeout; // as second. +}TPP_SET_CFG_ARGS; #ifdef __cplusplus extern "C" @@ -75,6 +77,7 @@ extern "C" TPP_API ex_rv tpp_start(void); TPP_API ex_rv tpp_stop(void); TPP_API void tpp_timer(void); + TPP_API void tpp_set_cfg(TPP_SET_CFG_ARGS* cfg_args); #ifdef __cplusplus } @@ -84,5 +87,6 @@ typedef ex_rv (*TPP_INIT_FUNC)(TPP_INIT_ARGS* init_args); typedef ex_rv (*TPP_START_FUNC)(void); typedef ex_rv(*TPP_STOP_FUNC)(void); typedef void(*TPP_TIMER_FUNC)(void); +typedef void(*TPP_SET_CFG_FUNC)(TPP_SET_CFG_ARGS* cfg_args); #endif // __TP_PROTOCOL_INTERFACE_H__ diff --git a/server/tp_core/core/tp_core.vs2015.vcxproj b/server/tp_core/core/tp_core.vs2015.vcxproj index 68a04a7..c0b36b9 100644 --- a/server/tp_core/core/tp_core.vs2015.vcxproj +++ b/server/tp_core/core/tp_core.vs2015.vcxproj @@ -184,6 +184,7 @@ + @@ -210,6 +211,7 @@ + diff --git a/server/tp_core/core/tp_core.vs2015.vcxproj.filters b/server/tp_core/core/tp_core.vs2015.vcxproj.filters index d7da4d7..4014d60 100644 --- a/server/tp_core/core/tp_core.vs2015.vcxproj.filters +++ b/server/tp_core/core/tp_core.vs2015.vcxproj.filters @@ -106,6 +106,9 @@ main app + + main app + @@ -183,6 +186,9 @@ common + + main app + diff --git a/server/tp_core/core/tp_tpp_mgr.cpp b/server/tp_core/core/tp_tpp_mgr.cpp new file mode 100644 index 0000000..d3e1c64 --- /dev/null +++ b/server/tp_core/core/tp_tpp_mgr.cpp @@ -0,0 +1,120 @@ +#include "tp_tpp_mgr.h" +#include "ts_main.h" +// #include "ts_session.h" +// #include "ts_http_rpc.h" +// #include "ts_web_rpc.h" +#include "ts_env.h" + +// #include +// #include + +TppManager g_tpp_mgr; + +extern ExLogger g_ex_logger; + +bool TppManager::load_tpp(const ex_wstr& libname) +{ + ex_wstr filename; +#ifdef EX_OS_WIN32 + filename = libname + L".dll"; +#elif defined (EX_OS_LINUX) + filename = L"lib"; + filename += libname; + filename += L".so"; +#elif defined (EX_OS_MACOS) + filename = L"lib"; + filename += libname; + filename += L".dylib"; +#endif + + ex_wstr libfile = g_env.m_exec_path; + ex_path_join(libfile, false, filename.c_str(), NULL); + EXLOGV(L"[core] load protocol lib: %ls\n", libfile.c_str()); + + TPP_LIB* lib = new TPP_LIB; + + lib->dylib = ex_dlopen(libfile.c_str()); + if (NULL == lib->dylib) + { + EXLOGE(L"[core] load dylib `%ls` failed.\n", libfile.c_str()); + delete lib; + return false; + } + +#ifdef EX_OS_WIN32 + lib->init = (TPP_INIT_FUNC)GetProcAddress(lib->dylib, "tpp_init"); + lib->start = (TPP_START_FUNC)GetProcAddress(lib->dylib, "tpp_start"); + lib->stop = (TPP_STOP_FUNC)GetProcAddress(lib->dylib, "tpp_stop"); + lib->timer = (TPP_TIMER_FUNC)GetProcAddress(lib->dylib, "tpp_timer"); + lib->set_cfg = (TPP_SET_CFG_FUNC)GetProcAddress(lib->dylib, "tpp_set_cfg"); +#else + lib->init = (TPP_INIT_FUNC)dlsym(lib->dylib, "tpp_init"); + lib->start = (TPP_START_FUNC)dlsym(lib->dylib, "tpp_start"); + lib->stop = (TPP_STOP_FUNC)dlsym(lib->dylib, "tpp_stop"); + lib->timer = (TPP_TIMER_FUNC)dlsym(lib->dylib, "tpp_timer"); + lib->set_cfg = (TPP_SET_CFG_FUNC)dlsym(lib->dylib, "tpp_set_cfg"); +#endif + + if (lib->init == NULL || lib->start == NULL || lib->stop == NULL || lib->timer == NULL || lib->set_cfg == NULL) + { + EXLOGE(L"[core] load dylib `%ls` failed, can not locate all functions.\n", libfile.c_str()); + delete lib; + return false; + } + + TPP_INIT_ARGS init_args; + init_args.logger = &g_ex_logger; + init_args.exec_path = g_env.m_exec_path; + init_args.etc_path = g_env.m_etc_path; + init_args.replay_path = g_env.m_replay_path; + init_args.cfg = &g_env.get_ini(); + init_args.func_get_connect_info = tpp_get_connect_info; + init_args.func_free_connect_info = tpp_free_connect_info; + init_args.func_session_begin = tpp_session_begin; + init_args.func_session_update = tpp_session_update; + init_args.func_session_end = tpp_session_end; + + if (EXRV_OK != lib->init(&init_args)) + { + EXLOGE(L"[core] failed to init protocol `%ls`.\n", libname.c_str()); + delete lib; + return false; + } + if (EXRV_OK != lib->start()) + { + EXLOGE(L"[core] failed to start protocol `%ls`.\n", libname.c_str()); + delete lib; + return false; + } + + m_libs.push_back(lib); + return true; +} + +void TppManager::stop_all(void) { + tpp_libs::iterator it = m_libs.begin(); + for (; it != m_libs.end(); ++it) + { + (*it)->stop(); + } +} + +void TppManager::timer(void) { + tpp_libs::iterator it = m_libs.begin(); + for (; it != m_libs.end(); ++it) + { + (*it)->timer(); + } +} + +void TppManager::set_config(int noop_timeout) { + + TPP_SET_CFG_ARGS args; + args.noop_timeout = noop_timeout; + + tpp_libs::iterator it = m_libs.begin(); + for (; it != m_libs.end(); ++it) + { + (*it)->set_cfg(&args); + } +} diff --git a/server/tp_core/core/tp_tpp_mgr.h b/server/tp_core/core/tp_tpp_mgr.h new file mode 100644 index 0000000..9c88119 --- /dev/null +++ b/server/tp_core/core/tp_tpp_mgr.h @@ -0,0 +1,61 @@ +#ifndef __TP_TPP_MGR_H__ +#define __TP_TPP_MGR_H__ + +#include "../common/protocol_interface.h" + +#include + +typedef struct TPP_LIB +{ + TPP_LIB() + { + dylib = NULL; + init = NULL; + } + ~TPP_LIB() + { + if (NULL != dylib) + ex_dlclose(dylib); + dylib = NULL; + } + + EX_DYLIB_HANDLE dylib; + TPP_INIT_FUNC init; + TPP_START_FUNC start; + TPP_STOP_FUNC stop; + TPP_TIMER_FUNC timer; + TPP_SET_CFG_FUNC set_cfg; +}TPP_LIB; + +typedef std::list tpp_libs; + +class TppManager +{ +public: + TppManager() + { + } + ~TppManager() + { + tpp_libs::iterator it = m_libs.begin(); + for (; it != m_libs.end(); ++it) + { + delete (*it); + } + m_libs.clear(); + } + + bool load_tpp(const ex_wstr& libfile); + void stop_all(void); + void timer(void); // 大约1秒调用一次 + int count(void) { return m_libs.size(); } + + void set_config(int noop_timeout); + +private: + tpp_libs m_libs; +}; + +extern TppManager g_tpp_mgr; + +#endif // __TP_TPP_MGR_H__ diff --git a/server/tp_core/core/ts_http_rpc.cpp b/server/tp_core/core/ts_http_rpc.cpp index 241e10c..97e4340 100644 --- a/server/tp_core/core/ts_http_rpc.cpp +++ b/server/tp_core/core/ts_http_rpc.cpp @@ -4,6 +4,9 @@ #include "ts_session.h" #include "ts_crypto.h" #include "ts_web_rpc.h" +#include "tp_tpp_mgr.h" + +extern TppManager g_tpp_mgr; #include @@ -249,24 +252,22 @@ void TsHttpRpc::_create_json_ret(ex_astr& buf, int errcode, const char* message) void TsHttpRpc::_process_request(const ex_astr& func_cmd, const Json::Value& json_param, ex_astr& buf) { - if (func_cmd == "request_session") - { + if (func_cmd == "request_session") { _rpc_func_request_session(json_param, buf); } - else if (func_cmd == "get_config") - { + else if (func_cmd == "get_config") { _rpc_func_get_config(json_param, buf); } - else if (func_cmd == "enc") - { + else if (func_cmd == "set_config") { + _rpc_func_set_config(json_param, buf); + } + else if (func_cmd == "enc") { _rpc_func_enc(json_param, buf); } - else if (func_cmd == "exit") - { + else if (func_cmd == "exit") { _rpc_func_exit(json_param, buf); } - else - { + else { EXLOGE("[core] rpc got unknown command: %s\n", func_cmd.c_str()); _create_json_ret(buf, TPE_UNKNOWN_CMD); } @@ -422,6 +423,46 @@ void TsHttpRpc::_rpc_func_enc(const Json::Value& json_param, ex_astr& buf) _create_json_ret(buf, TPE_OK, jr_data); } +void TsHttpRpc::_rpc_func_set_config(const Json::Value& json_param, ex_astr& buf) +{ + // https://github.com/eomsoft/teleport/wiki/TELEPORT-CORE-JSON-RPC#set_config + /* + { + "noop-timeout": 900 # 900s = 15m + } + */ + + if (json_param.isArray()) + { + _create_json_ret(buf, TPE_PARAM); + return; + } + + if (json_param["noop_timeout"].isNull() || !json_param["noop_timeout"].isUInt()) + { + _create_json_ret(buf, TPE_PARAM); + return; + } + + int noop_timeout = json_param["noop_timeout"].asUInt(); + if (noop_timeout == 0) + { + _create_json_ret(buf, TPE_PARAM); + return; + } + + //static TppManager g_tpp_mgr; + EXLOGV("[core] no-op timeout set to %d minutes.\n", noop_timeout); + g_tpp_mgr.set_config(noop_timeout); + + +// Json::Value jr_data; +// jr_data["c"] = cipher_text; +// _create_json_ret(buf, TPE_OK, jr_data); + _create_json_ret(buf, TPE_OK); +} + + /* void TsHttpRpc::_rpc_func_enc(const Json::Value& json_param, ex_astr& buf) { diff --git a/server/tp_core/core/ts_http_rpc.h b/server/tp_core/core/ts_http_rpc.h index 3fe16de..206c80f 100644 --- a/server/tp_core/core/ts_http_rpc.h +++ b/server/tp_core/core/ts_http_rpc.h @@ -1,76 +1,54 @@ -#ifndef __TS_HTTP_RPC_H__ -#define __TS_HTTP_RPC_H__ - -#include "mongoose.h" - -#include -#include - - -/* -//================================================================= -接口使用说明: - -本程序启动后,监听 127.0.0.1:52080,接收http请求,请求格式要求如下: - -GET 方式 -http://127.0.0.1:52080/method/json_param -其中json_param是使用url_encode进行编码后的json格式字符串 - -POST 方式 -http://127.0.0.1:52080/method -post的数据区域是json_param - -其中,URI分为三个部分: -method 请求执行的任务方法。 -json_param 此任务方法的附加参数,如果没有附加参数,这部分可以省略。 - -返回格式:执行结束后,返回一个json格式的字符串给请求者,格式如下: - -{"code":0,"data":varb} - -其中,code是必有的,其值是一个错误编码,0表示成功。如果失败,则可能没有data域。操作成功时,data域就是 -操作的返回数据,其格式根据具体执行的任务方法不同而不同。 - -*/ - -class TsHttpRpc : public ExThreadBase -{ -public: - TsHttpRpc(); - ~TsHttpRpc(); - - bool init(void); - -protected: - void _thread_loop(void); - void _set_stop_flag(void); - -private: - ex_rv _parse_request(struct http_message* req, ex_astr& func_cmd, Json::Value& json_param); - void _process_request(const ex_astr& func_cmd, const Json::Value& json_param, ex_astr& buf); - - //void _create_json_ret(ex_astr& buf, Json::Value& jr_root); - void _create_json_ret(ex_astr& buf, int errcode, const Json::Value& jr_data); - void _create_json_ret(ex_astr& buf, int errcode); - void _create_json_ret(ex_astr& buf, int errcode, const char* message); - - // 获取core服务的配置信息(主要是支持的各个协议是否启用,以及其端口号等) - void _rpc_func_get_config(const Json::Value& json_param, ex_astr& buf); - // 请求一个会话ID - void _rpc_func_request_session(const Json::Value& json_param, ex_astr& buf); - // 加密一个字符串(返回的是密文的BASE64编码) - void _rpc_func_enc(const Json::Value& json_param, ex_astr& buf); - // 要求整个核心服务退出 - void _rpc_func_exit(const Json::Value& json_param, ex_astr& buf); - - static void _mg_event_handler(struct mg_connection *nc, int ev, void *ev_data); - -private: - ex_astr m_host_ip; - int m_host_port; - - struct mg_mgr m_mg_mgr; -}; - -#endif // __TS_HTTP_RPC_H__ +#ifndef __TS_HTTP_RPC_H__ +#define __TS_HTTP_RPC_H__ + +#include "mongoose.h" + +#include +#include + +// JSON-RPC documentation at: +// https://github.com/eomsoft/teleport/wiki/TELEPORT-CORE-JSON-RPC + + +class TsHttpRpc : public ExThreadBase +{ +public: + TsHttpRpc(); + ~TsHttpRpc(); + + bool init(void); + +protected: + void _thread_loop(void); + void _set_stop_flag(void); + +private: + ex_rv _parse_request(struct http_message* req, ex_astr& func_cmd, Json::Value& json_param); + void _process_request(const ex_astr& func_cmd, const Json::Value& json_param, ex_astr& buf); + + //void _create_json_ret(ex_astr& buf, Json::Value& jr_root); + void _create_json_ret(ex_astr& buf, int errcode, const Json::Value& jr_data); + void _create_json_ret(ex_astr& buf, int errcode); + void _create_json_ret(ex_astr& buf, int errcode, const char* message); + + // 获取core服务的配置信息(主要是支持的各个协议是否启用,以及其端口号等) + void _rpc_func_get_config(const Json::Value& json_param, ex_astr& buf); + // set run-time configuration, like no-op-timeout. + void _rpc_func_set_config(const Json::Value& json_param, ex_astr& buf); + // 请求一个会话ID + void _rpc_func_request_session(const Json::Value& json_param, ex_astr& buf); + // 加密一个字符串(返回的是密文的BASE64编码) + void _rpc_func_enc(const Json::Value& json_param, ex_astr& buf); + // 要求整个核心服务退出 + void _rpc_func_exit(const Json::Value& json_param, ex_astr& buf); + + static void _mg_event_handler(struct mg_connection *nc, int ev, void *ev_data); + +private: + ex_astr m_host_ip; + int m_host_port; + + struct mg_mgr m_mg_mgr; +}; + +#endif // __TS_HTTP_RPC_H__ diff --git a/server/tp_core/core/ts_main.cpp b/server/tp_core/core/ts_main.cpp index 65cdb82..e8dfb44 100644 --- a/server/tp_core/core/ts_main.cpp +++ b/server/tp_core/core/ts_main.cpp @@ -3,6 +3,7 @@ #include "ts_http_rpc.h" #include "ts_web_rpc.h" #include "ts_env.h" +#include "tp_tpp_mgr.h" #include #include @@ -97,154 +98,153 @@ bool tpp_session_update(int db_id, int protocol_sub_type, int state) { return ts_web_rpc_session_update(db_id, protocol_sub_type, state); } -bool tpp_session_end(const char* sid, int db_id, int ret) -{ +bool tpp_session_end(const char* sid, int db_id, int ret) { return ts_web_rpc_session_end(sid, db_id, ret); } -typedef struct TPP_LIB -{ - TPP_LIB() - { - dylib = NULL; - init = NULL; - } - ~TPP_LIB() - { - if (NULL != dylib) - ex_dlclose(dylib); - dylib = NULL; - } - - EX_DYLIB_HANDLE dylib; - TPP_INIT_FUNC init; - TPP_START_FUNC start; - TPP_STOP_FUNC stop; - TPP_TIMER_FUNC timer; -}TPP_LIB; - -typedef std::list tpp_libs; - -class TppManager -{ -public: - TppManager() - { - } - ~TppManager() - { - tpp_libs::iterator it = m_libs.begin(); - for (; it != m_libs.end(); ++it) - { - delete (*it); - } - m_libs.clear(); - } - - bool load_tpp(const ex_wstr& libfile); - void stop_all(void); - void timer(void); // 大约1秒调用一次 - int count(void) { return m_libs.size(); } - -private: - tpp_libs m_libs; -}; - -static TppManager g_tpp_mgr; -extern ExLogger g_ex_logger; - -bool TppManager::load_tpp(const ex_wstr& libname) -{ - ex_wstr filename; -#ifdef EX_OS_WIN32 - filename = libname + L".dll"; -#elif defined (EX_OS_LINUX) - filename = L"lib"; - filename += libname; - filename += L".so"; -#elif defined (EX_OS_MACOS) - filename = L"lib"; - filename += libname; - filename += L".dylib"; -#endif - - ex_wstr libfile = g_env.m_exec_path; - ex_path_join(libfile, false, filename.c_str(), NULL); - EXLOGV(L"[core] load protocol lib: %ls\n", libfile.c_str()); - - TPP_LIB* lib = new TPP_LIB; - - lib->dylib = ex_dlopen(libfile.c_str()); - if (NULL == lib->dylib) - { - EXLOGE(L"[core] load dylib `%ls` failed.\n", libfile.c_str()); - delete lib; - return false; - } - -#ifdef EX_OS_WIN32 - lib->init = (TPP_INIT_FUNC)GetProcAddress(lib->dylib, "tpp_init"); - lib->start = (TPP_START_FUNC)GetProcAddress(lib->dylib, "tpp_start"); - lib->stop = (TPP_STOP_FUNC)GetProcAddress(lib->dylib, "tpp_stop"); - lib->timer = (TPP_TIMER_FUNC)GetProcAddress(lib->dylib, "tpp_timer"); -#else - lib->init = (TPP_INIT_FUNC)dlsym(lib->dylib, "tpp_init"); - lib->start = (TPP_START_FUNC)dlsym(lib->dylib, "tpp_start"); - lib->stop = (TPP_STOP_FUNC)dlsym(lib->dylib, "tpp_stop"); - lib->timer = (TPP_TIMER_FUNC)dlsym(lib->dylib, "tpp_timer"); -#endif - - if (lib->init == NULL || lib->start == NULL || lib->stop == NULL || lib->timer == NULL) - { - EXLOGE(L"[core] load dylib `%ls` failed, can not locate all functions.\n", libfile.c_str()); - delete lib; - return false; - } - - TPP_INIT_ARGS init_args; - init_args.logger = &g_ex_logger; - init_args.exec_path = g_env.m_exec_path; - init_args.etc_path = g_env.m_etc_path; - init_args.replay_path = g_env.m_replay_path; - init_args.cfg = &g_env.get_ini(); - init_args.func_get_connect_info = tpp_get_connect_info; - init_args.func_free_connect_info = tpp_free_connect_info; - init_args.func_session_begin = tpp_session_begin; - init_args.func_session_update = tpp_session_update; - init_args.func_session_end = tpp_session_end; - - if (EXRV_OK != lib->init(&init_args)) - { - EXLOGE(L"[core] failed to init protocol `%ls`.\n", libname.c_str()); - delete lib; - return false; - } - if (EXRV_OK != lib->start()) - { - EXLOGE(L"[core] failed to start protocol `%ls`.\n", libname.c_str()); - delete lib; - return false; - } - - m_libs.push_back(lib); - return true; -} - -void TppManager::stop_all(void) { - tpp_libs::iterator it = m_libs.begin(); - for (; it != m_libs.end(); ++it) - { - (*it)->stop(); - } -} - -void TppManager::timer(void) { - tpp_libs::iterator it = m_libs.begin(); - for (; it != m_libs.end(); ++it) - { - (*it)->timer(); - } -} +// typedef struct TPP_LIB +// { +// TPP_LIB() +// { +// dylib = NULL; +// init = NULL; +// } +// ~TPP_LIB() +// { +// if (NULL != dylib) +// ex_dlclose(dylib); +// dylib = NULL; +// } +// +// EX_DYLIB_HANDLE dylib; +// TPP_INIT_FUNC init; +// TPP_START_FUNC start; +// TPP_STOP_FUNC stop; +// TPP_TIMER_FUNC timer; +// }TPP_LIB; +// +// typedef std::list tpp_libs; +// +// class TppManager +// { +// public: +// TppManager() +// { +// } +// ~TppManager() +// { +// tpp_libs::iterator it = m_libs.begin(); +// for (; it != m_libs.end(); ++it) +// { +// delete (*it); +// } +// m_libs.clear(); +// } +// +// bool load_tpp(const ex_wstr& libfile); +// void stop_all(void); +// void timer(void); // 大约1秒调用一次 +// int count(void) { return m_libs.size(); } +// +// private: +// tpp_libs m_libs; +// }; +// +// static TppManager g_tpp_mgr; +// extern ExLogger g_ex_logger; +// +// bool TppManager::load_tpp(const ex_wstr& libname) +// { +// ex_wstr filename; +// #ifdef EX_OS_WIN32 +// filename = libname + L".dll"; +// #elif defined (EX_OS_LINUX) +// filename = L"lib"; +// filename += libname; +// filename += L".so"; +// #elif defined (EX_OS_MACOS) +// filename = L"lib"; +// filename += libname; +// filename += L".dylib"; +// #endif +// +// ex_wstr libfile = g_env.m_exec_path; +// ex_path_join(libfile, false, filename.c_str(), NULL); +// EXLOGV(L"[core] load protocol lib: %ls\n", libfile.c_str()); +// +// TPP_LIB* lib = new TPP_LIB; +// +// lib->dylib = ex_dlopen(libfile.c_str()); +// if (NULL == lib->dylib) +// { +// EXLOGE(L"[core] load dylib `%ls` failed.\n", libfile.c_str()); +// delete lib; +// return false; +// } +// +// #ifdef EX_OS_WIN32 +// lib->init = (TPP_INIT_FUNC)GetProcAddress(lib->dylib, "tpp_init"); +// lib->start = (TPP_START_FUNC)GetProcAddress(lib->dylib, "tpp_start"); +// lib->stop = (TPP_STOP_FUNC)GetProcAddress(lib->dylib, "tpp_stop"); +// lib->timer = (TPP_TIMER_FUNC)GetProcAddress(lib->dylib, "tpp_timer"); +// #else +// lib->init = (TPP_INIT_FUNC)dlsym(lib->dylib, "tpp_init"); +// lib->start = (TPP_START_FUNC)dlsym(lib->dylib, "tpp_start"); +// lib->stop = (TPP_STOP_FUNC)dlsym(lib->dylib, "tpp_stop"); +// lib->timer = (TPP_TIMER_FUNC)dlsym(lib->dylib, "tpp_timer"); +// #endif +// +// if (lib->init == NULL || lib->start == NULL || lib->stop == NULL || lib->timer == NULL) +// { +// EXLOGE(L"[core] load dylib `%ls` failed, can not locate all functions.\n", libfile.c_str()); +// delete lib; +// return false; +// } +// +// TPP_INIT_ARGS init_args; +// init_args.logger = &g_ex_logger; +// init_args.exec_path = g_env.m_exec_path; +// init_args.etc_path = g_env.m_etc_path; +// init_args.replay_path = g_env.m_replay_path; +// init_args.cfg = &g_env.get_ini(); +// init_args.func_get_connect_info = tpp_get_connect_info; +// init_args.func_free_connect_info = tpp_free_connect_info; +// init_args.func_session_begin = tpp_session_begin; +// init_args.func_session_update = tpp_session_update; +// init_args.func_session_end = tpp_session_end; +// +// if (EXRV_OK != lib->init(&init_args)) +// { +// EXLOGE(L"[core] failed to init protocol `%ls`.\n", libname.c_str()); +// delete lib; +// return false; +// } +// if (EXRV_OK != lib->start()) +// { +// EXLOGE(L"[core] failed to start protocol `%ls`.\n", libname.c_str()); +// delete lib; +// return false; +// } +// +// m_libs.push_back(lib); +// return true; +// } +// +// void TppManager::stop_all(void) { +// tpp_libs::iterator it = m_libs.begin(); +// for (; it != m_libs.end(); ++it) +// { +// (*it)->stop(); +// } +// } +// +// void TppManager::timer(void) { +// tpp_libs::iterator it = m_libs.begin(); +// for (; it != m_libs.end(); ++it) +// { +// (*it)->timer(); +// } +// } int ts_main(void) { diff --git a/server/tp_core/core/ts_main.h b/server/tp_core/core/ts_main.h index 7b8ae3a..a5a6588 100644 --- a/server/tp_core/core/ts_main.h +++ b/server/tp_core/core/ts_main.h @@ -1,6 +1,15 @@ -#ifndef __TS_MAIN_H__ -#define __TS_MAIN_H__ - -int ts_main(void); - -#endif // __TS_MAIN_H__ +#ifndef __TS_MAIN_H__ +#define __TS_MAIN_H__ + +#include "../common/protocol_interface.h" + +int ts_main(void); + +TPP_CONNECT_INFO* tpp_get_connect_info(const char* sid); +void tpp_free_connect_info(TPP_CONNECT_INFO* info); +bool tpp_session_begin(const TPP_CONNECT_INFO* info, int* db_id); +bool tpp_session_update(int db_id, int protocol_sub_type, int state); +bool tpp_session_end(const char* sid, int db_id, int ret); + + +#endif // __TS_MAIN_H__ diff --git a/server/tp_core/protocol/ssh/ssh_proxy.cpp b/server/tp_core/protocol/ssh/ssh_proxy.cpp index d614fdc..6c4e89c 100644 --- a/server/tp_core/protocol/ssh/ssh_proxy.cpp +++ b/server/tp_core/protocol/ssh/ssh_proxy.cpp @@ -8,6 +8,8 @@ SshProxy::SshProxy() : m_bind(NULL) { m_timer_counter = 0; + + m_noop_timeout_sec = 900; // default to 15 minutes. } SshProxy::~SshProxy() @@ -71,13 +73,20 @@ void SshProxy::timer() { m_timer_counter = 0; ExThreadSmartLock locker(m_lock); + ex_u32 t_now = (ex_u32)time(NULL); ts_ssh_sessions::iterator it; for(it = m_sessions.begin(); it != m_sessions.end(); ++it) { it->first->save_record(); + if(0 != m_noop_timeout_sec) + it->first->check_noop_timeout(t_now, m_noop_timeout_sec); } } +void SshProxy::set_cfg(TPP_SET_CFG_ARGS* args) { + m_noop_timeout_sec = args->noop_timeout; +} + void SshProxy::_thread_loop() { EXLOGI("[ssh] TeleportServer-SSH ready on %s:%d\n", m_host_ip.c_str(), m_host_port); diff --git a/server/tp_core/protocol/ssh/ssh_proxy.h b/server/tp_core/protocol/ssh/ssh_proxy.h index 187293b..55c3259 100644 --- a/server/tp_core/protocol/ssh/ssh_proxy.h +++ b/server/tp_core/protocol/ssh/ssh_proxy.h @@ -15,6 +15,7 @@ public: bool init(); void timer(); + void set_cfg(TPP_SET_CFG_ARGS* args); void session_finished(SshSession* sess); @@ -34,6 +35,9 @@ private: ts_ssh_sessions m_sessions; ExThreadManager m_thread_mgr; + + // + ex_u32 m_noop_timeout_sec; }; extern SshProxy g_ssh_proxy; diff --git a/server/tp_core/protocol/ssh/ssh_session.cpp b/server/tp_core/protocol/ssh/ssh_session.cpp index 24fb875..d646a4f 100644 --- a/server/tp_core/protocol/ssh/ssh_session.cpp +++ b/server/tp_core/protocol/ssh/ssh_session.cpp @@ -1,1437 +1,1445 @@ -#include "ssh_session.h" -#include "ssh_proxy.h" -#include "tpp_env.h" - -#include -#include - -TP_SSH_CHANNEL_PAIR::TP_SSH_CHANNEL_PAIR() { - type = TS_SSH_CHANNEL_TYPE_UNKNOWN; - cli_channel = NULL; - srv_channel = NULL; - - state = TP_SESS_STAT_RUNNING; - db_id = 0; - channel_id = 0; - - win_width = 0; - is_first_server_data = true; - - server_ready = false; - maybe_cmd = false; - process_srv = false; - client_single_char = false; - - cmd_char_pos = cmd_char_list.begin(); -} - -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_conn_info(NULL) -{ - m_auth_type = TP_AUTH_TYPE_PASSWORD; - - m_ssh_ver = 2; // default to SSHv2 - - 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 (NULL != m_conn_info) { - g_ssh_env.free_connect_info(m_conn_info); - } - - EXLOGD("[ssh] session destroy: %s.\n", m_sid.c_str()); -} - -void SshSession::_thread_loop(void) { - _run(); - 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; - } -} - -void SshSession::_session_error(int err_code) { - int db_id = 0; - if (!g_ssh_env.session_begin(m_conn_info, &db_id) || db_id == 0) - { - EXLOGE("[ssh] can not write session error to database.\n"); - return; - } - - g_ssh_env.session_end(m_sid.c_str(), db_id, err_code); -} - -bool SshSession::_record_begin(TP_SSH_CHANNEL_PAIR* cp) -{ - if (!g_ssh_env.session_begin(m_conn_info, &(cp->db_id))) { - EXLOGE("[ssh] can not save to database, channel begin failed.\n"); - return false; - } - else { - cp->channel_id = cp->db_id; - //EXLOGD("[ssh] [channel:%d] channel begin\n", cp->channel_id); - } - - - if (!g_ssh_env.session_update(cp->db_id, m_conn_info->protocol_sub_type, TP_SESS_STAT_STARTED)) { - EXLOGE("[ssh] [channel:%d] can not update state, cannel begin failed.\n", cp->channel_id); - return false; - } - - - cp->rec.begin(g_ssh_env.replay_path.c_str(), L"tp-ssh", cp->db_id, m_conn_info); - - return true; -} - -void SshSession::_record_end(TP_SSH_CHANNEL_PAIR* cp) -{ - if (cp->db_id > 0) - { - //EXLOGD("[ssh] [channel:%d] channel end with code: %d\n", cp->channel_id, cp->state); - - // 如果会话过程中没有发生错误,则将其状态改为结束,否则记录下错误值 - if (cp->state == TP_SESS_STAT_RUNNING || cp->state == TP_SESS_STAT_STARTED) - cp->state = TP_SESS_STAT_END; - - g_ssh_env.session_end(m_sid.c_str(), cp->db_id, cp->state); - - cp->db_id = 0; - } - else { - //EXLOGD("[ssh] [channel:%d] when channel end, no db-id.\n", cp->channel_id); - } -} - -void SshSession::_close_channels(void) { - ExThreadSmartLock locker(m_lock); - - tp_channels::iterator it = m_channels.begin(); - for (; it != m_channels.end(); ++it) { - ssh_channel ch = (*it)->srv_channel; - if (ch != NULL) { - if (!ssh_channel_is_closed(ch)) { - ssh_channel_close(ch); - } - ssh_channel_free(ch); - } - - ch = (*it)->cli_channel; - if (ch != NULL) { - if (!ssh_channel_is_closed(ch)) { - ssh_channel_close(ch); - } - ssh_channel_free(ch); - } - - //EXLOGD("[ssh] [channel:%d] --- end by close all channel\n", (*it)->channel_id); - _record_end(*it); - - delete (*it); - } - - m_channels.clear(); -} - -void SshSession::_check_channels() { - //EXLOGD("[ssh] -- check channels\n"); - ExThreadSmartLock locker(m_lock); - - //EXLOGD("[ssh] -- check channels, have %d\n", m_channels.size()); - tp_channels::iterator it = m_channels.begin(); - for (; it != m_channels.end(); ) { - //EXLOGD("[ssh] -- channel id: %d\n", (*it)->channel_id); - bool closed = false; - ssh_channel cli = (*it)->cli_channel; - ssh_channel srv = (*it)->srv_channel; - if (cli != NULL) { - if (ssh_channel_is_closed(cli)) { - //EXLOGD("[ssh] [channel:%d] -- server channel already closed\n", (*it)->channel_id); - closed = true; - } - } - if (srv != NULL) { - if (ssh_channel_is_closed(srv)) { - //EXLOGD("[ssh] [channel:%d] -- client channel already closed\n", (*it)->channel_id); - closed = true; - } - } - - if (closed) { - //EXLOGD("[ssh] [channel:%d] --- end by check channel\n", (*it)->channel_id); - _record_end((*it)); - - if (!ssh_channel_is_closed(cli)) { - ssh_channel_close(cli); - } - ssh_channel_free(cli); - - if (!ssh_channel_is_closed(srv)) { - ssh_channel_close(srv); - } - ssh_channel_free(srv); - - delete (*it); - - m_channels.erase(it++); - } - else { - ++it; - } - } -} - -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); - - int err = SSH_OK; - - // 安全连接(密钥交换) - err = ssh_handle_key_exchange(m_cli_session); - if (err != SSH_OK) { - EXLOGE("[ssh] key exchange with client failed: %s\n", ssh_get_error(m_cli_session)); - return; - } - - ssh_event event_loop = ssh_event_new(); - if (NULL == event_loop) { - EXLOGE("[ssh] can not create event loop.\n"); - return; - } - err = ssh_event_add_session(event_loop, m_cli_session); - if (err != SSH_OK) { - EXLOGE("[ssh] can not add client-session into event loop.\n"); - return; - } - - // 认证,并打开一个通道 - while (!(m_is_logon && m_channels.size() > 0)) { - if (m_have_error) - break; - err = ssh_event_dopoll(event_loop, -1); - if (err != SSH_OK) { - EXLOGE("[ssh] error when event poll: %s\n", ssh_get_error(m_cli_session)); - m_have_error = true; - break; - } - } - - 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"); - - // // 现在双方的连接已经建立好了,开始转发 - // err = ssh_event_add_session(event_loop, m_srv_session); - // if (err != SSH_OK) { - // EXLOGE("[ssh] can not add server-session into event loop.\n"); - // ssh_event_remove_session(event_loop, m_cli_session); - // return; - // } - // - // do { - // err = ssh_event_dopoll(event_loop, 5000); - // if (err == 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)); - // } - // else if (0 != ssh_get_error_code(m_srv_session)) - // { - // EXLOGE("[ssh] ssh_event_dopoll() [srv] %s\n", ssh_get_error(m_srv_session)); - // } - // - // _close_channels(); - // } - // else if (err == SSH_AGAIN) { - // // timeout. - // _check_channels(); - // } - // } while (m_channels.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); - // - - - - - - // 现在双方的连接已经建立好了,开始转发 - ssh_event_add_session(event_loop, m_srv_session); - do { - err = ssh_event_dopoll(event_loop, 5000); - if (err == 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)); - } - else if (0 != ssh_get_error_code(m_srv_session)) - { - EXLOGE("[ssh] ssh_event_dopoll() [srv] %s\n", ssh_get_error(m_srv_session)); - } - - _close_channels(); - } - - if (m_ssh_ver == 1) { - tp_channels::iterator it = m_channels.begin(); - if ((*it)->type == TS_SSH_CHANNEL_TYPE_SHELL || (*it)->type == TS_SSH_CHANNEL_TYPE_SFTP) - break; - } - - if (err == SSH_AGAIN) { - // timeout. - _check_channels(); - } - } while (m_channels.size() > 0); - - if (m_channels.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); - - - // 如果一边是走SSHv1,另一边是SSHv2,放在同一个event_loop时,SSHv1会收不到数据,放到循环中时,SSHv2得不到数据 - // 所以,当SSHv1的远程主机连接后,到建立好shell环境之后,就进入另一种读取数据的循环,不再使用ssh_event_dopoll()了。 - - if (m_ssh_ver == 1) - { - tp_channels::iterator it = m_channels.begin(); // SSHv1只能打开一个channel - ssh_channel cli = (*it)->cli_channel; - ssh_channel srv = (*it)->srv_channel; - - bool ok = true; - do { - ex_u8 buf[4096] = { 0 }; - int len = 0; - - if (ok) { - len = ssh_channel_read_nonblocking(cli, buf, 4096, 0); - if (len < 0) - ok = false; - else if (len > 0) - _on_client_channel_data(m_cli_session, cli, buf, len, 0, this); - - len = ssh_channel_read_nonblocking(cli, buf, 4096, 1); - if (len < 0) - ok = false; - else if (len > 0) - _on_client_channel_data(m_cli_session, cli, buf, len, 1, this); - - len = ssh_channel_read_nonblocking(srv, buf, 4096, 0); - if (len < 0) - ok = false; - else if (len > 0) - _on_server_channel_data(m_srv_session, srv, buf, len, 0, this); - - len = ssh_channel_read_nonblocking(srv, buf, 4096, 1); - if (len < 0) - ok = false; - else if (len > 0) - _on_server_channel_data(m_srv_session, srv, buf, len, 1, this); - - if (!ok) { - _close_channels(); - } - } - - if (!ok) { - _check_channels(); - ex_sleep_ms(1000); - continue; - } - - ex_sleep_ms(30); - } while (m_channels.size() > 0); - - EXLOGV("[ssh] [%s:%d] all channel in this session are closed.\n", m_client_ip.c_str(), m_client_port); - } -} - -void SshSession::save_record() { - ExThreadSmartLock locker(m_lock); - - tp_channels::iterator it = m_channels.begin(); - for (; it != m_channels.end(); ++it) { - (*it)->rec.save_record(); - } -} - -int SshSession::_on_auth_password_request(ssh_session session, const char *user, const char *password, void *userdata) { - // 这里拿到的user就是我们要的session-id。 - SshSession *_this = (SshSession *)userdata; - _this->m_sid = user; - EXLOGV("[ssh] authenticating, session-id: %s\n", _this->m_sid.c_str()); - - // int protocol = 0; - //TPP_CONNECT_INFO* sess_info = g_ssh_env.get_connect_info(_this->m_sid.c_str()); - _this->m_conn_info = g_ssh_env.get_connect_info(_this->m_sid.c_str()); - - if (NULL == _this->m_conn_info) { - EXLOGE("[ssh] no such session: %s\n", _this->m_sid.c_str()); - _this->m_have_error = true; - _this->_session_error(TP_SESS_STAT_ERR_SESSION); - return SSH_AUTH_DENIED; - } - else { - _this->m_conn_ip = _this->m_conn_info->conn_ip; - _this->m_conn_port = _this->m_conn_info->conn_port; - _this->m_auth_type = _this->m_conn_info->auth_type; - _this->m_acc_name = _this->m_conn_info->acc_username; - _this->m_acc_secret = _this->m_conn_info->acc_secret; - //protocol = _this->m_conn_info->protocol_type; - if (_this->m_conn_info->protocol_type != TP_PROTOCOL_TYPE_SSH) { - EXLOGE("[ssh] session '%s' is not for SSH.\n", _this->m_sid.c_str()); - _this->m_have_error = true; - _this->_session_error(TP_SESS_STAT_ERR_INTERNAL); - return SSH_AUTH_DENIED; - } - } - - // 现在尝试根据session-id获取得到的信息,连接并登录真正的SSH服务器 - EXLOGV("[ssh] try to connect to real SSH server %s:%d\n", _this->m_conn_ip.c_str(), _this->m_conn_port); - _this->m_srv_session = ssh_new(); - // int verbosity = 4; - // ssh_options_set(_this->m_srv_session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); - ssh_set_blocking(_this->m_srv_session, 1); - ssh_options_set(_this->m_srv_session, SSH_OPTIONS_HOST, _this->m_conn_ip.c_str()); - int port = (int)_this->m_conn_port; - ssh_options_set(_this->m_srv_session, SSH_OPTIONS_PORT, &port); -#ifdef EX_DEBUG - // int flag = SSH_LOG_FUNCTIONS; - // ssh_options_set(_this->m_srv_session, SSH_OPTIONS_LOG_VERBOSITY, &flag); -#endif - // int val = 0; - // ssh_options_set(_this->m_srv_session, SSH_OPTIONS_STRICTHOSTKEYCHECK, &val); - - - if (_this->m_auth_type != TP_AUTH_TYPE_NONE) - ssh_options_set(_this->m_srv_session, SSH_OPTIONS_USER, _this->m_acc_name.c_str()); - - // default timeout is 10 seconds. - int _timeout = 30; // 30 sec. - ssh_options_set(_this->m_srv_session, SSH_OPTIONS_TIMEOUT, &_timeout); - - 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. [%d] %s\n", _this->m_conn_ip.c_str(), _this->m_conn_port, rc, ssh_get_error(_this->m_srv_session)); - _this->m_have_error = true; - _this->_session_error(TP_SESS_STAT_ERR_CONNECT); - return SSH_AUTH_ERROR; - } - - _timeout = 10; // 10 sec. - ssh_options_set(_this->m_srv_session, SSH_OPTIONS_TIMEOUT, &_timeout); - - // 获取服务端ssh版本,是v1还是v2 - _this->m_ssh_ver = ssh_get_version(_this->m_srv_session); - EXLOGW("[ssh] real host is SSHv%d\n", _this->m_ssh_ver); - - // // 检查服务端支持的认证协议 - //ssh_userauth_none(_this->m_srv_session, _this->m_acc_name.c_str()); - // rc = ssh_userauth_none(_this->m_srv_session, NULL); - // if (rc == SSH_AUTH_ERROR) { - // EXLOGE("[ssh] invalid password for password mode to 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_ERROR; - // } - // // int auth_methods = ssh_userauth_list(_this->m_srv_session, NULL); - // const char* banner = ssh_get_issue_banner(_this->m_srv_session); - // if (NULL != banner) { - // EXLOGE("[ssh] issue banner: %s\n", banner); - // } - - - if (_this->m_auth_type == TP_AUTH_TYPE_PASSWORD) { - int retry_count = 0; - - if (_this->m_ssh_ver == 1) { - // 远程主机是SSHv1,则优先尝试密码登录 - rc = ssh_userauth_password(_this->m_srv_session, _this->m_acc_name.c_str(), _this->m_acc_secret.c_str()); - for (;;) { - if (rc == SSH_AUTH_AGAIN) { - retry_count += 1; - if (retry_count >= 3) - break; - ex_sleep_ms(100); - rc = ssh_userauth_password(_this->m_srv_session, _this->m_acc_name.c_str(), _this->m_acc_secret.c_str()); - continue; - } - if (rc == SSH_AUTH_SUCCESS) { - EXLOGW("[ssh] logon with password mode.\n"); - _this->m_is_logon = true; - return SSH_AUTH_SUCCESS; - } - else { - EXLOGW("[ssh] failed to login with password mode, got %d.\n", rc); - } - } - } - - // SSHv2则优先尝试交互式登录(SSHv2推荐) - retry_count = 0; - rc = ssh_userauth_kbdint(_this->m_srv_session, NULL, NULL); - for (;;) { - if (rc == SSH_AUTH_AGAIN) { - retry_count += 1; - if (retry_count >= 5) - break; - ex_sleep_ms(500); - rc = ssh_userauth_kbdint(_this->m_srv_session, NULL, NULL); - continue; - } - - if (rc != SSH_AUTH_INFO) - break; - - int nprompts = ssh_userauth_kbdint_getnprompts(_this->m_srv_session); - if (0 == nprompts) { - rc = ssh_userauth_kbdint(_this->m_srv_session, NULL, NULL); - continue; - } - - for (int iprompt = 0; iprompt < nprompts; ++iprompt) { - char echo = 0; - const char* prompt = ssh_userauth_kbdint_getprompt(_this->m_srv_session, iprompt, &echo); - EXLOGV("[ssh] interactive login prompt: %s\n", prompt); - - rc = ssh_userauth_kbdint_setanswer(_this->m_srv_session, iprompt, _this->m_acc_secret.c_str()); - if (rc < 0) { - EXLOGE("[ssh] invalid password for interactive mode to login to real SSH server %s:%d.\n", _this->m_conn_ip.c_str(), _this->m_conn_port); - _this->m_have_error = true; - _this->_session_error(TP_SESS_STAT_ERR_AUTH_DENIED); - return SSH_AUTH_ERROR; - } - } - - rc = ssh_userauth_kbdint(_this->m_srv_session, NULL, NULL); - } - - if (rc == SSH_AUTH_SUCCESS) { - EXLOGW("[ssh] logon with keyboard interactive mode.\n"); - _this->m_is_logon = true; - return SSH_AUTH_SUCCESS; - } - else { - EXLOGW("[ssh] failed to login with keyboard interactive mode, got %d, try password mode.\n", rc); - } - - if (_this->m_ssh_ver != 1) { - // 不支持交互式登录,则尝试密码方式 - rc = ssh_userauth_password(_this->m_srv_session, _this->m_acc_name.c_str(), _this->m_acc_secret.c_str()); - if (rc == SSH_AUTH_SUCCESS) { - EXLOGW("[ssh] logon with password mode.\n"); - _this->m_is_logon = true; - return SSH_AUTH_SUCCESS; - } - else { - EXLOGW("[ssh] failed to login with password mode, got %d.\n", rc); - } - } - - EXLOGE("[ssh] can not use password mode or interactive mode to login to real SSH server %s:%d.\n", _this->m_conn_ip.c_str(), _this->m_conn_port); - _this->m_have_error = true; - _this->_session_error(TP_SESS_STAT_ERR_AUTH_DENIED); - return SSH_AUTH_ERROR; - } - else if (_this->m_auth_type == TP_AUTH_TYPE_PRIVATE_KEY) { - ssh_key key = NULL; - if (SSH_OK != ssh_pki_import_privkey_base64(_this->m_acc_secret.c_str(), NULL, NULL, NULL, &key)) { - EXLOGE("[ssh] can not import private-key for auth.\n"); - _this->m_have_error = true; - _this->_session_error(TP_SESS_STAT_ERR_BAD_SSH_KEY); - return SSH_AUTH_ERROR; - } - - rc = ssh_userauth_publickey(_this->m_srv_session, NULL, key); - ssh_key_free(key); - - if (rc == SSH_AUTH_SUCCESS) { - EXLOGW("[ssh] logon with public-key mode.\n"); - _this->m_is_logon = true; - return SSH_AUTH_SUCCESS; - } - else { - EXLOGE("[ssh] failed to use private-key to login to real SSH server %s:%d.\n", _this->m_conn_ip.c_str(), _this->m_conn_port); - _this->m_have_error = true; - _this->_session_error(TP_SESS_STAT_ERR_AUTH_DENIED); - return SSH_AUTH_ERROR; - } - } - else if (_this->m_auth_type == TP_AUTH_TYPE_NONE) { - _this->_session_error(TP_SESS_STAT_ERR_AUTH_DENIED); - return SSH_AUTH_ERROR; - } - else { - EXLOGE("[ssh] invalid auth mode.\n"); - _this->m_have_error = true; - _this->_session_error(TP_SESS_STAT_ERR_AUTH_DENIED); - return SSH_AUTH_ERROR; - } -} - -ssh_channel SshSession::_on_new_channel_request(ssh_session session, void *userdata) { - // 客户端尝试打开一个通道(然后才能通过这个通道发控制命令或者收发数据) - EXLOGV("[ssh] client open channel\n"); - - SshSession *_this = (SshSession *)userdata; - - // TODO: 客户端与TP连接使用的总是SSHv2协议,因为最开始连接时还不知道真正的远程主机是不是SSHv1。 - // 因此此处行为与客户端直连远程主机有些不一样。直连时,SecureCRT的克隆会话功能会因为以为连接的是SSHv1而自动重新连接,而不是打开新通道。 - if (_this->m_ssh_ver == 1 && _this->m_channels.size() != 0) { - EXLOGE("[ssh] SSH1 supports only one execution channel. One has already been opened.\n"); - return NULL; - } - - ssh_channel cli_channel = ssh_channel_new(session); - if (cli_channel == NULL) { - EXLOGE("[ssh] can not create channel for client.\n"); - return NULL; - } - ssh_set_channel_callbacks(cli_channel, &_this->m_cli_channel_cb); - - // 我们也要向真正的服务器申请打开一个通道,来进行转发 - ssh_channel srv_channel = ssh_channel_new(_this->m_srv_session); - if (srv_channel == NULL) { - EXLOGE("[ssh] can not create channel for server.\n"); - return NULL; - } - if (SSH_OK != ssh_channel_open_session(srv_channel)) { - EXLOGE("[ssh] error opening channel to real server: %s\n", ssh_get_error(_this->m_srv_session)); - ssh_channel_free(cli_channel); - ssh_channel_free(srv_channel); - return NULL; - } - ssh_set_channel_callbacks(srv_channel, &_this->m_srv_channel_cb); - - TP_SSH_CHANNEL_PAIR* cp = new TP_SSH_CHANNEL_PAIR; - cp->type = TS_SSH_CHANNEL_TYPE_UNKNOWN; - cp->cli_channel = cli_channel; - cp->srv_channel = srv_channel; - - if (!_this->_record_begin(cp)) { - ssh_channel_close(cli_channel); - ssh_channel_free(cli_channel); - ssh_channel_close(srv_channel); - ssh_channel_free(srv_channel); - delete cp; - return NULL; - } - - // 将客户端和服务端的通道关联起来 - { - ExThreadSmartLock locker(_this->m_lock); - _this->m_channels.push_back(cp); - } - - EXLOGD("[ssh] channel for client and server created.\n"); - return cli_channel; -} - -TP_SSH_CHANNEL_PAIR* SshSession::_get_channel_pair(int channel_side, ssh_channel channel) { - ExThreadSmartLock locker(m_lock); - - tp_channels::iterator it = m_channels.begin(); - for (; it != m_channels.end(); ++it) { - if (channel_side == TP_SSH_CLIENT_SIDE) { - if ((*it)->cli_channel == channel) - return (*it); - } - else { - if ((*it)->srv_channel == channel) - return (*it); - } - } - - return NULL; -} - -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; - - EXLOGD("[ssh] client request pty: %s, (%d, %d) / (%d, %d)\n", term, x, y, px, py); - - TP_SSH_CHANNEL_PAIR* cp = _this->_get_channel_pair(TP_SSH_CLIENT_SIDE, channel); - if (NULL == cp) { - EXLOGE("[ssh] when client request pty, not found channel pair.\n"); - return SSH_ERROR; - } - - cp->win_width = x; - cp->rec.record_win_size_startup(x, y); - - int err = ssh_channel_request_pty_size(cp->srv_channel, term, x, y); - if (err != SSH_OK) - EXLOGE("[ssh] pty request from server got %d\n", err); - return err; -} - -int SshSession::_on_client_shell_request(ssh_session session, ssh_channel channel, void *userdata) { - SshSession *_this = (SshSession *)userdata; - - EXLOGD("[ssh] client request shell\n"); - - TP_SSH_CHANNEL_PAIR* cp = _this->_get_channel_pair(TP_SSH_CLIENT_SIDE, channel); - if (NULL == cp) { - EXLOGE("[ssh] when client request shell, not found channel pair.\n"); - return SSH_ERROR; - } - - cp->type = TS_SSH_CHANNEL_TYPE_SHELL; - if (_this->m_ssh_ver == 1) - cp->server_ready = true; - g_ssh_env.session_update(cp->db_id, TP_PROTOCOL_TYPE_SSH_SHELL, TP_SESS_STAT_STARTED); - - - // FIXME: sometimes it will block here. the following function will never return. - // at this time, can not write data to this channel. read from this channel with timeout, got 0 byte. - // I have no idea how to fix it... :( - int err = ssh_channel_request_shell(cp->srv_channel); - if (err != SSH_OK) { - EXLOGE("[ssh] shell request from server got %d\n", err); - } - return err; -} - -void SshSession::_on_client_channel_close(ssh_session session, ssh_channel channel, void *userdata) { - - SshSession *_this = (SshSession *)userdata; - - TP_SSH_CHANNEL_PAIR* cp = _this->_get_channel_pair(TP_SSH_CLIENT_SIDE, channel); - if (NULL == cp) { - EXLOGE("[ssh] when client channel close, not found channel pair.\n"); - return; - } - - //EXLOGD("[ssh] [channel:%d] -- end by client channel close\n", cp->channel_id); - _this->_record_end(cp); - - if (cp->srv_channel == NULL) { - EXLOGW("[ssh] when client channel close, server-channel not exists.\n"); - } - else { - if (!ssh_channel_is_closed(cp->srv_channel)) { - ssh_channel_close(cp->srv_channel); - } - } -} - -int SshSession::_on_client_channel_data(ssh_session session, ssh_channel channel, void *data, unsigned int len, int is_stderr, void *userdata) -{ - //EXLOG_BIN((ex_u8*)data, len, " ---> on_client_channel_data [is_stderr=%d]:", is_stderr); - - SshSession *_this = (SshSession *)userdata; - - // 当前线程正在接收服务端返回的数据,因此我们直接返回,这样紧跟着会重新再发送此数据的 - if (_this->m_recving_from_srv) - return 0; - if (_this->m_recving_from_cli) - return 0; - - TP_SSH_CHANNEL_PAIR* cp = _this->_get_channel_pair(TP_SSH_CLIENT_SIDE, channel); - if (NULL == cp) { - EXLOGE("[ssh] when receive client channel data, not found channel pair.\n"); - return SSH_ERROR; - } - - _this->m_recving_from_cli = true; - - int _len = len; - if (cp->type == TS_SSH_CHANNEL_TYPE_SHELL) { - // 在收取服务端数据直到显示命令行提示符之前,不允许发送客户端数据到服务端,避免日志记录混乱。 - if (!cp->server_ready) { - _this->m_recving_from_cli = false; - return 0; - } - - // 如果用户复制粘贴多行文本,我们将其拆分为每一行发送一次数据包 - for (unsigned int i = 0; i < len; ++i) { - if (((ex_u8*)data)[i] == 0x0d) { - _len = i + 1; - break; - } - } - - _this->_process_ssh_command(cp, TP_SSH_CLIENT_SIDE, (ex_u8*)data, _len); - - // ex_astr str(cp->cmd_char_list.begin(), cp->cmd_char_list.end()); - // ex_replace_all(str, "\r", ""); - // ex_replace_all(str, "\n", ""); - // EXLOGD("[ssh] -- [%s]\n", str.c_str()); - } - else { - _this->_process_sftp_command(cp, (ex_u8*)data, _len); - } - - int ret = 0; - if (is_stderr) - ret = ssh_channel_write_stderr(cp->srv_channel, data, _len); - else - ret = ssh_channel_write(cp->srv_channel, data, _len); - - if (ret == SSH_ERROR) { - EXLOGE("[ssh] send data(%dB) to server failed. [%d][cli:%s][srv:%s]\n", _len, ret, ssh_get_error(_this->m_cli_session), ssh_get_error(_this->m_srv_session)); - - ssh_channel_close(channel); - } - - _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; - - TP_SSH_CHANNEL_PAIR* cp = _this->_get_channel_pair(TP_SSH_CLIENT_SIDE, channel); - if (NULL == cp) { - EXLOGE("[ssh] when client pty win change, not found channel pair.\n"); - return SSH_ERROR; - } - - cp->win_width = width; - cp->rec.record_win_size_change(width, height); - - return ssh_channel_change_pty_size(cp->srv_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; - - if (_this->m_ssh_ver == 1) { - // SSHv1 not support subsystem, so some client like WinSCP will use shell-mode instead. - EXLOGE("[ssh] real host running on SSHv1, does not support subsystem `%s`.\n", subsystem); - return SSH_ERROR; - } - - TP_SSH_CHANNEL_PAIR* cp = _this->_get_channel_pair(TP_SSH_CLIENT_SIDE, channel); - if (NULL == cp) { - EXLOGE("[ssh] when request channel subsystem, not found channel pair.\n"); - return SSH_ERROR; - } - - // 目前只支持SFTP子系统 - if (strcmp(subsystem, "sftp") != 0) { - EXLOGE("[ssh] support `sftp` subsystem only, but got `%s`.\n", subsystem); - cp->state = TP_SESS_STAT_ERR_UNSUPPORT_PROTOCOL; - return SSH_ERROR; - } - - cp->type = TS_SSH_CHANNEL_TYPE_SFTP; - g_ssh_env.session_update(cp->db_id, TP_PROTOCOL_TYPE_SSH_SFTP, TP_SESS_STAT_STARTED); - - //EXLOGD("[ssh] ---> request channel subsystem from server\n"); - int err = ssh_channel_request_subsystem(cp->srv_channel, subsystem); - //EXLOGD("[ssh] <--- request channel subsystem from server\n"); - if (err != SSH_OK) - EXLOGE("[ssh] request channel subsystem from server got %d\n", err); - return err; -} - -int SshSession::_on_client_channel_exec_request(ssh_session session, ssh_channel channel, const char *command, void *userdata) { - EXLOGW("[ssh] not-impl: client_channel_exec_request(): %s\n", command); - return SSH_ERROR; -} - -int SshSession::_on_server_channel_data(ssh_session session, ssh_channel channel, void *data, unsigned int len, int is_stderr, void *userdata) { - //EXLOG_BIN((ex_u8*)data, len, " <--- on_server_channel_data [is_stderr=%d]:", is_stderr); - - SshSession *_this = (SshSession *)userdata; - - // return 0 means data not processed, so this function will be called with this data again. - if (_this->m_recving_from_cli) - return 0; - if (_this->m_recving_from_srv) - return 0; - - TP_SSH_CHANNEL_PAIR* cp = _this->_get_channel_pair(TP_SSH_SERVER_SIDE, channel); - if (NULL == cp) { - EXLOGE("[ssh] when receive server channel data, not found channel pair.\n"); - return SSH_ERROR; - } - -// #ifdef EX_OS_WIN32 -// // TODO: hard code not good... :( -// // 偶尔,某次操作会导致ssh_session->session_state为SSH_SESSION_STATE_ERROR -// // 但是将其强制改为SSH_SESSION_STATE_AUTHENTICATED,后续操作仍然能成功(主要在向客户端发送第一包数据时) -// ex_u8* _t = (ex_u8*)(ssh_channel_get_session(cp->cli_channel)); -// if (_t[1116] == 9) // SSH_SESSION_STATE_AUTHENTICATED = 8, SSH_SESSION_STATE_ERROR = 9 -// { -// EXLOGW(" --- [ssh] hard code to fix client connect session error state.\n"); -// _t[1116] = 8; -// } -// #endif - - _this->m_recving_from_srv = true; - - if (cp->type == TS_SSH_CHANNEL_TYPE_SHELL && !is_stderr) - { - if (!cp->server_ready) { - if (len >= 2 && (((ex_u8*)data)[len - 2] != 0x0d && ((ex_u8*)data)[len - 1] != 0x0a)) { - cp->server_ready = true; - } - } - - _this->_process_ssh_command(cp, TP_SSH_SERVER_SIDE, (ex_u8*)data, len); - // ex_astr str(cp->cmd_char_list.begin(), cp->cmd_char_list.end()); - // ex_replace_all(str, "\r", ""); - // ex_replace_all(str, "\n", ""); - // EXLOGD("[ssh] -- [%s]\n", str.c_str()); - - cp->rec.record(TS_RECORD_TYPE_SSH_DATA, (unsigned char *)data, len); - } - - int ret = 0; - - // 收到第一包服务端返回的数据时,在输出数据之前显示一些自定义的信息 -#if 1 - if (!is_stderr && cp->is_first_server_data) - { - cp->is_first_server_data = false; - - if (cp->type != TS_SSH_CHANNEL_TYPE_SFTP) - { - char buf[512] = { 0 }; - - const char *auth_mode = NULL; - if (_this->m_auth_type == TP_AUTH_TYPE_PASSWORD) - auth_mode = "password"; - else if (_this->m_auth_type == TP_AUTH_TYPE_PRIVATE_KEY) - auth_mode = "private-key"; - else - auth_mode = "unknown"; - -#ifdef EX_OS_WIN32 - int w = min(cp->win_width, 128); -#else - int w = std::min(cp->win_width, 128); -#endif - ex_astr line(w, '='); - - snprintf(buf, sizeof(buf), - "\r\n"\ - "%s\r\n"\ - "Teleport SSH Bastion Server...\r\n"\ - " - teleport to %s:%d\r\n"\ - " - authroized by %s\r\n"\ - "%s\r\n"\ - "\r\n\r\n", - line.c_str(), - _this->m_conn_ip.c_str(), - _this->m_conn_port, auth_mode, - line.c_str() - ); - - int buf_len = strlen(buf); - ex_bin _data; - _data.resize(buf_len + len); - memcpy(&_data[0], buf, buf_len); - memcpy(&_data[buf_len], data, len); - - ret = ssh_channel_write(cp->cli_channel, &_data[0], _data.size()); - - _this->m_recving_from_srv = false; - return len; - } - } -#endif - -#if 1 - // 直接转发数据到客户端 - if (is_stderr) - ret = ssh_channel_write_stderr(cp->cli_channel, data, len); - else - ret = ssh_channel_write(cp->cli_channel, data, len); -#else - // 分析收到的服务端数据包,如果包含类似 \033]0;AABB\007 这样的数据,客户端会根据此改变窗口标题 - // 我们需要替换这部分数据,使之显示类似 \033]0;TP#ssh://remote-ip\007 这样的标题。 - // 但是这样会降低一些性能,因此目前不启用,保留此部分代码备用。 - if (is_stderr) { - ret = ssh_channel_write_stderr(cp->cli_channel, data, len); - } - else if (cp->type != TS_SSH_CHANNEL_TYPE_SHELL) { - ret = ssh_channel_write(cp->cli_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++; - - // 这个包中含有改变标题的数据,将标题换为我们想要的 - EXLOGD("-- found title\n"); - 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;TP#ssh://", 13); - mbuf.append((ex_u8*)_this->m_conn_ip.c_str(), _this->m_conn_ip.length()); - mbuf.append((ex_u8*)"\007", 1); - - if (len_end > 0) - mbuf.append((ex_u8*)_end, len_end); - - if (mbuf.size() > 0) - { - for (;;) { - ret = ssh_channel_write(cp->cli_channel, mbuf.data(), mbuf.size()); - if (ret == SSH_ERROR) - break; - if (ret == mbuf.size()) { - ret = len; // 表示我们已经处理了所有的数据了。 - break; - } - else { - mbuf.pop(ret); - ex_sleep_ms(100); - } - } - // if (ret <= 0) - // EXLOGE("[ssh] send to client failed (1).\n"); - // else - // ret = len; - } - else - { - ret = ssh_channel_write(cp->cli_channel, data, len); - } - } - else - { - ret = ssh_channel_write(cp->cli_channel, data, len); - } - } - else { - ret = ssh_channel_write(cp->cli_channel, data, len); - } - } - else { - ret = ssh_channel_write(cp->cli_channel, data, len); - } - } -#endif - - if (ret == SSH_ERROR) { - EXLOGE("[ssh] send data(%dB) to client failed. [%d][cli:%s][srv:%s]\n", len, ret, ssh_get_error(_this->m_cli_session), ssh_get_error(_this->m_srv_session)); - ssh_channel_close(channel); - } - else if (ret != len) { - EXLOGW("[ssh] received server data, got %dB, processed %dB.\n", len, ret); - } - - _this->m_recving_from_srv = false; - return ret; -} - -void SshSession::_on_server_channel_close(ssh_session session, ssh_channel channel, void *userdata) { - SshSession *_this = (SshSession *)userdata; - TP_SSH_CHANNEL_PAIR* cp = _this->_get_channel_pair(TP_SSH_SERVER_SIDE, channel); - if (NULL == cp) { - EXLOGE("[ssh] when server channel close, not found channel pair.\n"); - return; - } - - //EXLOGD("[ssh] [channel:%d] --- end by server channel close\n", cp->channel_id); - _this->_record_end(cp); - - // will the server-channel exist, the client-channel must exist too. - if (cp->cli_channel == NULL) { - EXLOGE("[ssh] when server channel close, client-channel not exists.\n"); - } - else { - if (!ssh_channel_is_closed(cp->cli_channel)) { - ssh_channel_close(cp->cli_channel); - } - } -} - -void SshSession::_process_ssh_command(TP_SSH_CHANNEL_PAIR* cp, int from, const ex_u8* data, int len) -{ - if (len == 0) - return; - - if (TP_SSH_CLIENT_SIDE == from) { - if (len >= 2) { - if (((ex_u8*)data)[len - 1] == 0x0d) { - // 疑似复制粘贴多行命令一次性执行,将其记录到日志文件中 - ex_astr str((const char*)data, len - 1); - cp->rec.record_command(1, str); - - cp->process_srv = false; - return; - } - } - - // 客户端输入回车时,可能时执行了一条命令,需要根据服务端返回的数据进行进一步判断 - cp->maybe_cmd = (data[len - 1] == 0x0d); - // if (cp->maybe_cmd) - // EXLOGD("[ssh] maybe cmd.\n"); - - // 有时在执行类似top命令的情况下,输入一个字母'q'就退出程序,没有输入回车,可能会导致后续记录命令时将返回的命令行提示符作为命令 - // 记录下来了,要避免这种情况,排除的方式是:客户端单个字母,后续服务端如果收到的是控制序列 1b 5b xx xx,就不计做命令。 - cp->client_single_char = (len == 1 && isprint(data[0])); - - cp->process_srv = true; - } - else if (TP_SSH_SERVER_SIDE == from) { - if (!cp->process_srv) - return; - - int offset = 0; - bool esc_mode = false; - int esc_arg = 0; - for (; offset < len;) { - ex_u8 ch = data[offset]; - - if (esc_mode) { - switch (ch) { - 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 + (ch - '0'); - break; - - case 0x3f: - case ';': - case '>': - cp->cmd_char_list.clear(); - cp->cmd_char_pos = cp->cmd_char_list.begin(); - return; - break; - - case 0x4b: { // 'K' - if (0 == esc_arg) { - // 删除光标到行尾的字符串 - cp->cmd_char_list.erase(cp->cmd_char_pos, cp->cmd_char_list.end()); - cp->cmd_char_pos = cp->cmd_char_list.end(); - } - else if (1 == esc_arg) { - // 删除从开始到光标处的字符串 - cp->cmd_char_list.erase(cp->cmd_char_list.begin(), cp->cmd_char_pos); - cp->cmd_char_pos = cp->cmd_char_list.end(); - } - else if (2 == esc_arg) { - // 删除整行 - cp->cmd_char_list.clear(); - cp->cmd_char_pos = cp->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 (cp->cmd_char_pos != cp->cmd_char_list.end()) - cp->cmd_char_pos++; - } - esc_mode = false; - break; - } - case 0x44: { // ^[D - // 光标左移 - if (esc_arg == 0) - esc_arg = 1; - for (int j = 0; j < esc_arg; ++j) { - - if (cp->cmd_char_pos != cp->cmd_char_list.begin()) - cp->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 (cp->cmd_char_pos != cp->cmd_char_list.end()) - cp->cmd_char_pos = cp->cmd_char_list.erase(cp->cmd_char_pos); - } - esc_mode = false; - break; - } - - case 0x40: { // '@' 插入指定数量的空白字符 - if (esc_arg == 0) - esc_arg = 1; - for (int j = 0; j < esc_arg; ++j) - cp->cmd_char_pos = cp->cmd_char_list.insert(cp->cmd_char_pos, ' '); - esc_mode = false; - break; - } - - default: - esc_mode = false; - break; - } - - //d += 1; - //l -= 1; - offset++; - continue; - } - - switch (ch) { - case 0x07: - // 响铃 - break; - case 0x08: { - // 光标左移 - if (cp->cmd_char_pos != cp->cmd_char_list.begin()) - cp->cmd_char_pos--; - break; - } - case 0x1b: - { - if (offset + 1 < len) - { - if (data[offset + 1] == 0x5b || data[offset + 1] == 0x5d) { - if (offset == 0 && cp->client_single_char) { - cp->cmd_char_list.clear(); - cp->cmd_char_pos = cp->cmd_char_list.begin(); - cp->maybe_cmd = false; - cp->process_srv = false; - cp->client_single_char = false; - return; - } - } - - if (data[offset + 1] == 0x5b) { - esc_mode = true; - esc_arg = 0; - - offset += 1; - } - } - - break; - } - case 0x0d: - { - if (offset + 1 < len && data[offset + 1] == 0x0a) { - // if (cp->maybe_cmd) - // EXLOGD("[ssh] maybe cmd.\n"); - if (cp->maybe_cmd) { - if (cp->cmd_char_list.size() > 0) - { - ex_astr str(cp->cmd_char_list.begin(), cp->cmd_char_list.end()); - // EXLOGD("[ssh] --==--==-- save cmd: [%s]\n", str.c_str()); - cp->rec.record_command(0, str); - } - - cp->cmd_char_list.clear(); - cp->cmd_char_pos = cp->cmd_char_list.begin(); - cp->maybe_cmd = false; - } - } - else { - cp->cmd_char_list.clear(); - cp->cmd_char_pos = cp->cmd_char_list.begin(); - } - cp->process_srv = false; - return; - break; - } - default: - if (cp->cmd_char_pos != cp->cmd_char_list.end()) - { - cp->cmd_char_pos = cp->cmd_char_list.erase(cp->cmd_char_pos); - cp->cmd_char_pos = cp->cmd_char_list.insert(cp->cmd_char_pos, ch); - cp->cmd_char_pos++; - } - else - { - cp->cmd_char_list.push_back(ch); - cp->cmd_char_pos = cp->cmd_char_list.end(); - } - } - - offset++; - } - } - - return; -} - -void SshSession::_process_sftp_command(TP_SSH_CHANNEL_PAIR* cp, 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"); - - // TODO: 根据客户端的请求和服务端的返回,可以进一步判断用户是如何操作文件的,比如读、写等等,以及操作的结果是成功还是失败。 - // 记录格式: time-offset,flag,action,result,file-path,[file-path] - // 其中,flag目前总是为0,可以忽略(为保证与ssh-cmd格式一致),time-offset/action/result 都是数字 - // file-path是被操作的对象,规格为 长度:实际内容,例如, 13:/root/abc.txt - - - 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 - cp->rec.record_command(0, "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]); - - - switch (sftp_cmd) { - case 0x03: - // 0x03 = 3 = SSH_FXP_OPEN - break; - // case 0x0b: - // // 0x0b = 11 = SSH_FXP_OPENDIR - // act = "open dir"; - // break; - case 0x0d: - // 0x0d = 13 = SSH_FXP_REMOVE - break; - case 0x0e: - // 0x0e = 14 = SSH_FXP_MKDIR - break; - case 0x0f: - // 0x0f = 15 = SSH_FXP_RMDIR - break; - case 0x12: - // 0x12 = 18 = SSH_FXP_RENAME - // 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操作数据中包含两个字符串,前者是新的链接文件名,后者是现有被链接的文件名 - 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,%d,%s", sftp_cmd, 0, 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,%d,%s:%s", sftp_cmd, 0, str1.c_str(), str2.c_str()); - } - - cp->rec.record_command(0, msg); -} +#include "ssh_session.h" +#include "ssh_proxy.h" +#include "tpp_env.h" + +#include +#include + +TP_SSH_CHANNEL_PAIR::TP_SSH_CHANNEL_PAIR() { + type = TS_SSH_CHANNEL_TYPE_UNKNOWN; + cli_channel = NULL; + srv_channel = NULL; + last_access_timestamp = (ex_u32)time(NULL); + + state = TP_SESS_STAT_RUNNING; + db_id = 0; + channel_id = 0; + + win_width = 0; + is_first_server_data = true; + need_close = false; + + server_ready = false; + maybe_cmd = false; + process_srv = false; + client_single_char = false; + + cmd_char_pos = cmd_char_list.begin(); +} + +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_conn_info(NULL) +{ + m_auth_type = TP_AUTH_TYPE_PASSWORD; + + m_ssh_ver = 2; // default to SSHv2 + + 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 (NULL != m_conn_info) { + g_ssh_env.free_connect_info(m_conn_info); + } + + EXLOGD("[ssh] session destroy: %s.\n", m_sid.c_str()); +} + +void SshSession::_thread_loop(void) { + _run(); + 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; + } +} + +void SshSession::_session_error(int err_code) { + int db_id = 0; + if (!g_ssh_env.session_begin(m_conn_info, &db_id) || db_id == 0) + { + EXLOGE("[ssh] can not write session error to database.\n"); + return; + } + + g_ssh_env.session_end(m_sid.c_str(), db_id, err_code); +} + +bool SshSession::_record_begin(TP_SSH_CHANNEL_PAIR* cp) +{ + if (!g_ssh_env.session_begin(m_conn_info, &(cp->db_id))) { + EXLOGE("[ssh] can not save to database, channel begin failed.\n"); + return false; + } + else { + cp->channel_id = cp->db_id; + //EXLOGD("[ssh] [channel:%d] channel begin\n", cp->channel_id); + } + + + if (!g_ssh_env.session_update(cp->db_id, m_conn_info->protocol_sub_type, TP_SESS_STAT_STARTED)) { + EXLOGE("[ssh] [channel:%d] can not update state, cannel begin failed.\n", cp->channel_id); + return false; + } + + + cp->rec.begin(g_ssh_env.replay_path.c_str(), L"tp-ssh", cp->db_id, m_conn_info); + + return true; +} + +void SshSession::_record_end(TP_SSH_CHANNEL_PAIR* cp) +{ + if (cp->db_id > 0) + { + //EXLOGD("[ssh] [channel:%d] channel end with code: %d\n", cp->channel_id, cp->state); + + // 如果会话过程中没有发生错误,则将其状态改为结束,否则记录下错误值 + if (cp->state == TP_SESS_STAT_RUNNING || cp->state == TP_SESS_STAT_STARTED) + cp->state = TP_SESS_STAT_END; + + g_ssh_env.session_end(m_sid.c_str(), cp->db_id, cp->state); + + cp->db_id = 0; + } + else { + //EXLOGD("[ssh] [channel:%d] when channel end, no db-id.\n", cp->channel_id); + } +} + +void SshSession::_close_channels(void) { + ExThreadSmartLock locker(m_lock); + + tp_channels::iterator it = m_channels.begin(); + for (; it != m_channels.end(); ++it) { +// ssh_channel ch = (*it)->srv_channel; +// if (ch != NULL) { +// if (!ssh_channel_is_closed(ch)) { +// ssh_channel_close(ch); +// } +// ssh_channel_free(ch); +// } +// +// ch = (*it)->cli_channel; +// if (ch != NULL) { +// if (!ssh_channel_is_closed(ch)) { +// ssh_channel_close(ch); +// } +// ssh_channel_free(ch); +// } +// +// //EXLOGD("[ssh] [channel:%d] --- end by close all channel\n", (*it)->channel_id); +// _record_end(*it); +// +// delete (*it); + + (*it)->need_close = true; + m_have_error = true; + } + +// m_channels.clear(); +} + +void SshSession::_check_channels() { + ExThreadSmartLock locker(m_lock); + + tp_channels::iterator it = m_channels.begin(); + for (; it != m_channels.end(); ) { + ssh_channel cli = (*it)->cli_channel; + ssh_channel srv = (*it)->srv_channel; + + // of both cli-channel and srv-channel closed, free and erase. + if ( + (cli != NULL && ssh_channel_is_closed(cli) && srv != NULL && ssh_channel_is_closed(srv)) + || (cli == NULL && srv == NULL) + || (cli == NULL && srv != NULL && ssh_channel_is_closed(srv)) + || (srv == NULL && cli != NULL && ssh_channel_is_closed(cli)) + ) { + if (cli) + ssh_channel_free(cli); + if (srv) + ssh_channel_free(srv); + + _record_end((*it)); + + delete(*it); + m_channels.erase(it++); + + continue; + } + + // check if channel need close + bool need_close = (*it)->need_close; + if (!need_close) { + if (cli != NULL && ssh_channel_is_closed(cli)) { + need_close = true; + } + if (srv != NULL && ssh_channel_is_closed(srv)) { + need_close = true; + } + } + + if (need_close) { + if (cli != NULL && !ssh_channel_is_closed(cli)) { + ssh_channel_close(cli); + } + + if (srv != NULL && !ssh_channel_is_closed(srv)) { + ssh_channel_close(srv); + } + } + + ++it; + } +} + +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); + + int err = SSH_OK; + + // 安全连接(密钥交换) + err = ssh_handle_key_exchange(m_cli_session); + if (err != SSH_OK) { + EXLOGE("[ssh] key exchange with client failed: %s\n", ssh_get_error(m_cli_session)); + return; + } + + ssh_event event_loop = ssh_event_new(); + if (NULL == event_loop) { + EXLOGE("[ssh] can not create event loop.\n"); + return; + } + err = ssh_event_add_session(event_loop, m_cli_session); + if (err != SSH_OK) { + EXLOGE("[ssh] can not add client-session into event loop.\n"); + return; + } + + // 认证,并打开一个通道 + while (!(m_is_logon && m_channels.size() > 0)) { + if (m_have_error) + break; + err = ssh_event_dopoll(event_loop, -1); + if (err != SSH_OK) { + EXLOGE("[ssh] error when event poll: %s\n", ssh_get_error(m_cli_session)); + m_have_error = true; + break; + } + } + + 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 { + //err = ssh_event_dopoll(event_loop, 5000); + err = ssh_event_dopoll(event_loop, 1000); + if (err == 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)); + } + else if (0 != ssh_get_error_code(m_srv_session)) + { + EXLOGE("[ssh] ssh_event_dopoll() [srv] %s\n", ssh_get_error(m_srv_session)); + } + + //_close_channels(); + m_have_error = true; + } + + if (m_ssh_ver == 1) { + tp_channels::iterator it = m_channels.begin(); + if ((*it)->type == TS_SSH_CHANNEL_TYPE_SHELL || (*it)->type == TS_SSH_CHANNEL_TYPE_SFTP) + break; + } + + if (m_have_error || err == SSH_AGAIN) { + m_have_error = false; + // timeout. + _check_channels(); + } + } while (m_channels.size() > 0); + + if (m_channels.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); + + + // 如果一边是走SSHv1,另一边是SSHv2,放在同一个event_loop时,SSHv1会收不到数据,放到循环中时,SSHv2得不到数据 + // 所以,当SSHv1的远程主机连接后,到建立好shell环境之后,就进入另一种读取数据的循环,不再使用ssh_event_dopoll()了。 + + if (m_ssh_ver == 1) + { + tp_channels::iterator it = m_channels.begin(); // SSHv1只能打开一个channel + ssh_channel cli = (*it)->cli_channel; + ssh_channel srv = (*it)->srv_channel; + + bool ok = true; + do { + ex_u8 buf[4096] = { 0 }; + int len = 0; + + if (ok) { + len = ssh_channel_read_nonblocking(cli, buf, 4096, 0); + if (len < 0) + ok = false; + else if (len > 0) + _on_client_channel_data(m_cli_session, cli, buf, len, 0, this); + + len = ssh_channel_read_nonblocking(cli, buf, 4096, 1); + if (len < 0) + ok = false; + else if (len > 0) + _on_client_channel_data(m_cli_session, cli, buf, len, 1, this); + + len = ssh_channel_read_nonblocking(srv, buf, 4096, 0); + if (len < 0) + ok = false; + else if (len > 0) + _on_server_channel_data(m_srv_session, srv, buf, len, 0, this); + + len = ssh_channel_read_nonblocking(srv, buf, 4096, 1); + if (len < 0) + ok = false; + else if (len > 0) + _on_server_channel_data(m_srv_session, srv, buf, len, 1, this); + + if (!ok) { + _close_channels(); + } + } + + if (!ok) { + _check_channels(); + ex_sleep_ms(1000); + continue; + } + + ex_sleep_ms(30); + } while (m_channels.size() > 0); + + EXLOGV("[ssh] [%s:%d] all channel in this session are closed.\n", m_client_ip.c_str(), m_client_port); + } +} + +void SshSession::save_record() { + ExThreadSmartLock locker(m_lock); + + tp_channels::iterator it = m_channels.begin(); + for (; it != m_channels.end(); ++it) { + (*it)->rec.save_record(); + } +} + +void SshSession::check_noop_timeout(ex_u32 t_now, ex_u32 timeout) { + ExThreadSmartLock locker(m_lock); + tp_channels::iterator it = m_channels.begin(); + for (; it != m_channels.end(); ++it) { + if ((*it)->need_close) + continue; + if (t_now - (*it)->last_access_timestamp > timeout) { + EXLOGW("[ssh] need close channel by timeout.\n"); + (*it)->need_close = true; + m_have_error = true; + } + } +} + +int SshSession::_on_auth_password_request(ssh_session session, const char *user, const char *password, void *userdata) { + // 这里拿到的user就是我们要的session-id。 + SshSession *_this = (SshSession *)userdata; + _this->m_sid = user; + EXLOGV("[ssh] authenticating, session-id: %s\n", _this->m_sid.c_str()); + + _this->m_conn_info = g_ssh_env.get_connect_info(_this->m_sid.c_str()); + + if (NULL == _this->m_conn_info) { + EXLOGE("[ssh] no such session: %s\n", _this->m_sid.c_str()); + _this->m_have_error = true; + _this->_session_error(TP_SESS_STAT_ERR_SESSION); + return SSH_AUTH_DENIED; + } + else { + _this->m_conn_ip = _this->m_conn_info->conn_ip; + _this->m_conn_port = _this->m_conn_info->conn_port; + _this->m_auth_type = _this->m_conn_info->auth_type; + _this->m_acc_name = _this->m_conn_info->acc_username; + _this->m_acc_secret = _this->m_conn_info->acc_secret; + if (_this->m_conn_info->protocol_type != TP_PROTOCOL_TYPE_SSH) { + EXLOGE("[ssh] session '%s' is not for SSH.\n", _this->m_sid.c_str()); + _this->m_have_error = true; + _this->_session_error(TP_SESS_STAT_ERR_INTERNAL); + return SSH_AUTH_DENIED; + } + } + + // 现在尝试根据session-id获取得到的信息,连接并登录真正的SSH服务器 + EXLOGV("[ssh] try to connect to real SSH server %s:%d\n", _this->m_conn_ip.c_str(), _this->m_conn_port); + _this->m_srv_session = ssh_new(); + // int verbosity = 4; + // ssh_options_set(_this->m_srv_session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_set_blocking(_this->m_srv_session, 1); + ssh_options_set(_this->m_srv_session, SSH_OPTIONS_HOST, _this->m_conn_ip.c_str()); + int port = (int)_this->m_conn_port; + ssh_options_set(_this->m_srv_session, SSH_OPTIONS_PORT, &port); +#ifdef EX_DEBUG + // int flag = SSH_LOG_FUNCTIONS; + // ssh_options_set(_this->m_srv_session, SSH_OPTIONS_LOG_VERBOSITY, &flag); +#endif + // int val = 0; + // ssh_options_set(_this->m_srv_session, SSH_OPTIONS_STRICTHOSTKEYCHECK, &val); + + + if (_this->m_auth_type != TP_AUTH_TYPE_NONE) + ssh_options_set(_this->m_srv_session, SSH_OPTIONS_USER, _this->m_acc_name.c_str()); + + // default timeout is 10 seconds, it is too short for connect progress, so set it to 60 sec. + int _timeout = 60; // 60 sec. + ssh_options_set(_this->m_srv_session, SSH_OPTIONS_TIMEOUT, &_timeout); + + 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. [%d] %s\n", _this->m_conn_ip.c_str(), _this->m_conn_port, rc, ssh_get_error(_this->m_srv_session)); + _this->m_have_error = true; + _this->_session_error(TP_SESS_STAT_ERR_CONNECT); + return SSH_AUTH_ERROR; + } + + // once the server are connected, change the timeout back to default. + _timeout = 10; // 10 sec. + ssh_options_set(_this->m_srv_session, SSH_OPTIONS_TIMEOUT, &_timeout); + + // 获取服务端ssh版本,是v1还是v2 + _this->m_ssh_ver = ssh_get_version(_this->m_srv_session); + EXLOGW("[ssh] real host is SSHv%d\n", _this->m_ssh_ver); + + // // 检查服务端支持的认证协议 + //ssh_userauth_none(_this->m_srv_session, _this->m_acc_name.c_str()); + // rc = ssh_userauth_none(_this->m_srv_session, NULL); + // if (rc == SSH_AUTH_ERROR) { + // EXLOGE("[ssh] invalid password for password mode to 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_ERROR; + // } + // // int auth_methods = ssh_userauth_list(_this->m_srv_session, NULL); + // const char* banner = ssh_get_issue_banner(_this->m_srv_session); + // if (NULL != banner) { + // EXLOGE("[ssh] issue banner: %s\n", banner); + // } + + + if (_this->m_auth_type == TP_AUTH_TYPE_PASSWORD) { + int retry_count = 0; + + if (_this->m_ssh_ver == 1) { + // 远程主机是SSHv1,则优先尝试密码登录 + rc = ssh_userauth_password(_this->m_srv_session, _this->m_acc_name.c_str(), _this->m_acc_secret.c_str()); + for (;;) { + if (rc == SSH_AUTH_AGAIN) { + retry_count += 1; + if (retry_count >= 3) + break; + ex_sleep_ms(100); + rc = ssh_userauth_password(_this->m_srv_session, _this->m_acc_name.c_str(), _this->m_acc_secret.c_str()); + continue; + } + if (rc == SSH_AUTH_SUCCESS) { + EXLOGW("[ssh] logon with password mode.\n"); + _this->m_is_logon = true; + return SSH_AUTH_SUCCESS; + } + else { + EXLOGW("[ssh] failed to login with password mode, got %d.\n", rc); + } + } + } + + // SSHv2则优先尝试交互式登录(SSHv2推荐) + retry_count = 0; + rc = ssh_userauth_kbdint(_this->m_srv_session, NULL, NULL); + for (;;) { + if (rc == SSH_AUTH_AGAIN) { + retry_count += 1; + if (retry_count >= 5) + break; + ex_sleep_ms(500); + rc = ssh_userauth_kbdint(_this->m_srv_session, NULL, NULL); + continue; + } + + if (rc != SSH_AUTH_INFO) + break; + + int nprompts = ssh_userauth_kbdint_getnprompts(_this->m_srv_session); + if (0 == nprompts) { + rc = ssh_userauth_kbdint(_this->m_srv_session, NULL, NULL); + continue; + } + + for (int iprompt = 0; iprompt < nprompts; ++iprompt) { + char echo = 0; + const char* prompt = ssh_userauth_kbdint_getprompt(_this->m_srv_session, iprompt, &echo); + EXLOGV("[ssh] interactive login prompt: %s\n", prompt); + + rc = ssh_userauth_kbdint_setanswer(_this->m_srv_session, iprompt, _this->m_acc_secret.c_str()); + if (rc < 0) { + EXLOGE("[ssh] invalid password for interactive mode to login to real SSH server %s:%d.\n", _this->m_conn_ip.c_str(), _this->m_conn_port); + _this->m_have_error = true; + _this->_session_error(TP_SESS_STAT_ERR_AUTH_DENIED); + return SSH_AUTH_ERROR; + } + } + + rc = ssh_userauth_kbdint(_this->m_srv_session, NULL, NULL); + } + + if (rc == SSH_AUTH_SUCCESS) { + EXLOGW("[ssh] logon with keyboard interactive mode.\n"); + _this->m_is_logon = true; + return SSH_AUTH_SUCCESS; + } + else { + EXLOGW("[ssh] failed to login with keyboard interactive mode, got %d, try password mode.\n", rc); + } + + if (_this->m_ssh_ver != 1) { + // 如果SSHv2的主机不支持交互式登录,则尝试密码方式 + rc = ssh_userauth_password(_this->m_srv_session, _this->m_acc_name.c_str(), _this->m_acc_secret.c_str()); + if (rc == SSH_AUTH_SUCCESS) { + EXLOGW("[ssh] logon with password mode.\n"); + _this->m_is_logon = true; + return SSH_AUTH_SUCCESS; + } + else { + EXLOGW("[ssh] failed to login with password mode, got %d.\n", rc); + } + } + + EXLOGE("[ssh] can not use password mode or interactive mode to login to real SSH server %s:%d.\n", _this->m_conn_ip.c_str(), _this->m_conn_port); + _this->m_have_error = true; + _this->_session_error(TP_SESS_STAT_ERR_AUTH_DENIED); + return SSH_AUTH_ERROR; + } + else if (_this->m_auth_type == TP_AUTH_TYPE_PRIVATE_KEY) { + ssh_key key = NULL; + if (SSH_OK != ssh_pki_import_privkey_base64(_this->m_acc_secret.c_str(), NULL, NULL, NULL, &key)) { + EXLOGE("[ssh] can not import private-key for auth.\n"); + _this->m_have_error = true; + _this->_session_error(TP_SESS_STAT_ERR_BAD_SSH_KEY); + return SSH_AUTH_ERROR; + } + + rc = ssh_userauth_publickey(_this->m_srv_session, NULL, key); + ssh_key_free(key); + + if (rc == SSH_AUTH_SUCCESS) { + EXLOGW("[ssh] logon with public-key mode.\n"); + _this->m_is_logon = true; + return SSH_AUTH_SUCCESS; + } + else { + EXLOGE("[ssh] failed to use private-key to login to real SSH server %s:%d.\n", _this->m_conn_ip.c_str(), _this->m_conn_port); + _this->m_have_error = true; + _this->_session_error(TP_SESS_STAT_ERR_AUTH_DENIED); + return SSH_AUTH_ERROR; + } + } + else if (_this->m_auth_type == TP_AUTH_TYPE_NONE) { + _this->_session_error(TP_SESS_STAT_ERR_AUTH_DENIED); + return SSH_AUTH_ERROR; + } + else { + EXLOGE("[ssh] invalid auth mode.\n"); + _this->m_have_error = true; + _this->_session_error(TP_SESS_STAT_ERR_AUTH_DENIED); + return SSH_AUTH_ERROR; + } +} + +ssh_channel SshSession::_on_new_channel_request(ssh_session session, void *userdata) { + // 客户端尝试打开一个通道(然后才能通过这个通道发控制命令或者收发数据) + EXLOGV("[ssh] client open channel\n"); + + SshSession *_this = (SshSession *)userdata; + + // TODO: 客户端与TP连接使用的总是SSHv2协议,因为最开始连接时还不知道真正的远程主机是不是SSHv1。 + // 因此此处行为与客户端直连远程主机有些不一样。直连时,SecureCRT的克隆会话功能会因为以为连接的是SSHv1而自动重新连接,而不是打开新通道。 + if (_this->m_ssh_ver == 1 && _this->m_channels.size() != 0) { + EXLOGE("[ssh] SSH1 supports only one execution channel. One has already been opened.\n"); + return NULL; + } + + ssh_channel cli_channel = ssh_channel_new(session); + if (cli_channel == NULL) { + EXLOGE("[ssh] can not create channel for client.\n"); + return NULL; + } + ssh_set_channel_callbacks(cli_channel, &_this->m_cli_channel_cb); + + // 我们也要向真正的服务器申请打开一个通道,来进行转发 + ssh_channel srv_channel = ssh_channel_new(_this->m_srv_session); + if (srv_channel == NULL) { + EXLOGE("[ssh] can not create channel for server.\n"); + return NULL; + } + if (SSH_OK != ssh_channel_open_session(srv_channel)) { + EXLOGE("[ssh] error opening channel to real server: %s\n", ssh_get_error(_this->m_srv_session)); + ssh_channel_free(cli_channel); + ssh_channel_free(srv_channel); + return NULL; + } + ssh_set_channel_callbacks(srv_channel, &_this->m_srv_channel_cb); + + TP_SSH_CHANNEL_PAIR* cp = new TP_SSH_CHANNEL_PAIR; + cp->type = TS_SSH_CHANNEL_TYPE_UNKNOWN; + cp->cli_channel = cli_channel; + cp->srv_channel = srv_channel; + cp->last_access_timestamp = (ex_u32)time(NULL); + + if (!_this->_record_begin(cp)) { + ssh_channel_close(cli_channel); + ssh_channel_free(cli_channel); + ssh_channel_close(srv_channel); + ssh_channel_free(srv_channel); + delete cp; + return NULL; + } + + // 将客户端和服务端的通道关联起来 + { + ExThreadSmartLock locker(_this->m_lock); + _this->m_channels.push_back(cp); + } + + EXLOGD("[ssh] channel for client and server created.\n"); + return cli_channel; +} + +TP_SSH_CHANNEL_PAIR* SshSession::_get_channel_pair(int channel_side, ssh_channel channel) { + ExThreadSmartLock locker(m_lock); + + tp_channels::iterator it = m_channels.begin(); + for (; it != m_channels.end(); ++it) { + if (channel_side == TP_SSH_CLIENT_SIDE) { + if ((*it)->cli_channel == channel) + return (*it); + } + else { + if ((*it)->srv_channel == channel) + return (*it); + } + } + + return NULL; +} + +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; + + EXLOGD("[ssh] client request pty: %s, (%d, %d) / (%d, %d)\n", term, x, y, px, py); + + TP_SSH_CHANNEL_PAIR* cp = _this->_get_channel_pair(TP_SSH_CLIENT_SIDE, channel); + if (NULL == cp) { + EXLOGE("[ssh] when client request pty, not found channel pair.\n"); + return SSH_ERROR; + } + + cp->win_width = x; + cp->rec.record_win_size_startup(x, y); + cp->last_access_timestamp = (ex_u32)time(NULL); + + int err = ssh_channel_request_pty_size(cp->srv_channel, term, x, y); + if (err != SSH_OK) + EXLOGE("[ssh] pty request from server got %d\n", err); + return err; +} + +int SshSession::_on_client_shell_request(ssh_session session, ssh_channel channel, void *userdata) { + SshSession *_this = (SshSession *)userdata; + + EXLOGD("[ssh] client request shell\n"); + + TP_SSH_CHANNEL_PAIR* cp = _this->_get_channel_pair(TP_SSH_CLIENT_SIDE, channel); + if (NULL == cp) { + EXLOGE("[ssh] when client request shell, not found channel pair.\n"); + return SSH_ERROR; + } + + cp->type = TS_SSH_CHANNEL_TYPE_SHELL; + if (_this->m_ssh_ver == 1) + cp->server_ready = true; + g_ssh_env.session_update(cp->db_id, TP_PROTOCOL_TYPE_SSH_SHELL, TP_SESS_STAT_STARTED); + cp->last_access_timestamp = (ex_u32)time(NULL); + + + // FIXME: sometimes it will block here. the following function will never return. + // at this time, can not write data to this channel. read from this channel with timeout, got 0 byte. + // I have no idea how to fix it... :( + int err = ssh_channel_request_shell(cp->srv_channel); + if (err != SSH_OK) { + EXLOGE("[ssh] shell request from server got %d\n", err); + } + return err; +} + +void SshSession::_on_client_channel_close(ssh_session session, ssh_channel channel, void *userdata) { + EXLOGV("---client channel closed.\n"); + SshSession *_this = (SshSession *)userdata; + + TP_SSH_CHANNEL_PAIR* cp = _this->_get_channel_pair(TP_SSH_CLIENT_SIDE, channel); + if (NULL == cp) { + EXLOGE("[ssh] when client channel close, not found channel pair.\n"); + return; + } +// cp->need_close = true; + _this->m_have_error = true; + + //EXLOGD("[ssh] [channel:%d] -- end by client channel close\n", cp->channel_id); + //_this->_record_end(cp); + + if (cp->srv_channel == NULL) { + EXLOGW("[ssh] when client channel close, server-channel not exists.\n"); + } + else { + if (!ssh_channel_is_closed(cp->srv_channel)) { + // ssh_channel_close(cp->srv_channel); + //cp->need_close = true; + //_this->m_have_error = true; + } + } +} + +int SshSession::_on_client_channel_data(ssh_session session, ssh_channel channel, void *data, unsigned int len, int is_stderr, void *userdata) +{ + //EXLOG_BIN((ex_u8*)data, len, " ---> on_client_channel_data [is_stderr=%d]:", is_stderr); + + SshSession *_this = (SshSession *)userdata; + + // 当前线程正在接收服务端返回的数据,因此我们直接返回,这样紧跟着会重新再发送此数据的 + if (_this->m_recving_from_srv) + return 0; + if (_this->m_recving_from_cli) + return 0; + + TP_SSH_CHANNEL_PAIR* cp = _this->_get_channel_pair(TP_SSH_CLIENT_SIDE, channel); + if (NULL == cp) { + EXLOGE("[ssh] when receive client channel data, not found channel pair.\n"); + return SSH_ERROR; + } + cp->last_access_timestamp = (ex_u32)time(NULL); + + _this->m_recving_from_cli = true; + + int _len = len; + if (cp->type == TS_SSH_CHANNEL_TYPE_SHELL) { + // 在收取服务端数据直到显示命令行提示符之前,不允许发送客户端数据到服务端,避免日志记录混乱。 + if (!cp->server_ready) { + _this->m_recving_from_cli = false; + return 0; + } + + // 如果用户复制粘贴多行文本,我们将其拆分为每一行发送一次数据包 + for (unsigned int i = 0; i < len; ++i) { + if (((ex_u8*)data)[i] == 0x0d) { + _len = i + 1; + break; + } + } + + _this->_process_ssh_command(cp, TP_SSH_CLIENT_SIDE, (ex_u8*)data, _len); + + // ex_astr str(cp->cmd_char_list.begin(), cp->cmd_char_list.end()); + // ex_replace_all(str, "\r", ""); + // ex_replace_all(str, "\n", ""); + // EXLOGD("[ssh] -- [%s]\n", str.c_str()); + } + else { + _this->_process_sftp_command(cp, (ex_u8*)data, _len); + } + + int ret = 0; + if (is_stderr) + ret = ssh_channel_write_stderr(cp->srv_channel, data, _len); + else + ret = ssh_channel_write(cp->srv_channel, data, _len); + + if (ret == SSH_ERROR) { + EXLOGE("[ssh] send data(%dB) to server failed. [%d][cli:%s][srv:%s]\n", _len, ret, ssh_get_error(_this->m_cli_session), ssh_get_error(_this->m_srv_session)); + + //ssh_channel_close(channel); + cp->need_close = true; + _this->m_have_error = true; + } + + _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; + + TP_SSH_CHANNEL_PAIR* cp = _this->_get_channel_pair(TP_SSH_CLIENT_SIDE, channel); + if (NULL == cp) { + EXLOGE("[ssh] when client pty win change, not found channel pair.\n"); + return SSH_ERROR; + } + + cp->win_width = width; + cp->rec.record_win_size_change(width, height); + cp->last_access_timestamp = (ex_u32)time(NULL); + + return ssh_channel_change_pty_size(cp->srv_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; + + if (_this->m_ssh_ver == 1) { + // SSHv1 not support subsystem, so some client like WinSCP will use shell-mode instead. + EXLOGE("[ssh] real host running on SSHv1, does not support subsystem `%s`.\n", subsystem); + return SSH_ERROR; + } + + TP_SSH_CHANNEL_PAIR* cp = _this->_get_channel_pair(TP_SSH_CLIENT_SIDE, channel); + if (NULL == cp) { + EXLOGE("[ssh] when request channel subsystem, not found channel pair.\n"); + return SSH_ERROR; + } + cp->last_access_timestamp = (ex_u32)time(NULL); + + + // 目前只支持SFTP子系统 + if (strcmp(subsystem, "sftp") != 0) { + EXLOGE("[ssh] support `sftp` subsystem only, but got `%s`.\n", subsystem); + cp->state = TP_SESS_STAT_ERR_UNSUPPORT_PROTOCOL; + return SSH_ERROR; + } + + cp->type = TS_SSH_CHANNEL_TYPE_SFTP; + g_ssh_env.session_update(cp->db_id, TP_PROTOCOL_TYPE_SSH_SFTP, TP_SESS_STAT_STARTED); + + //EXLOGD("[ssh] ---> request channel subsystem from server\n"); + int err = ssh_channel_request_subsystem(cp->srv_channel, subsystem); + //EXLOGD("[ssh] <--- request channel subsystem from server\n"); + if (err != SSH_OK) + EXLOGE("[ssh] request channel subsystem from server got %d\n", err); + return err; +} + +int SshSession::_on_client_channel_exec_request(ssh_session session, ssh_channel channel, const char *command, void *userdata) { + EXLOGW("[ssh] not-impl: client_channel_exec_request(): %s\n", command); + return SSH_ERROR; +} + +int SshSession::_on_server_channel_data(ssh_session session, ssh_channel channel, void *data, unsigned int len, int is_stderr, void *userdata) { + //EXLOG_BIN((ex_u8*)data, len, " <--- on_server_channel_data [is_stderr=%d]:", is_stderr); + + SshSession *_this = (SshSession *)userdata; + + // return 0 means data not processed, so this function will be called with this data again. + if (_this->m_recving_from_cli) + return 0; + if (_this->m_recving_from_srv) + return 0; + + TP_SSH_CHANNEL_PAIR* cp = _this->_get_channel_pair(TP_SSH_SERVER_SIDE, channel); + if (NULL == cp) { + EXLOGE("[ssh] when receive server channel data, not found channel pair.\n"); + return SSH_ERROR; + } + cp->last_access_timestamp = (ex_u32)time(NULL); + +// #ifdef EX_OS_WIN32 +// // TODO: hard code not good... :( +// // 偶尔,某次操作会导致ssh_session->session_state为SSH_SESSION_STATE_ERROR +// // 但是将其强制改为SSH_SESSION_STATE_AUTHENTICATED,后续操作仍然能成功(主要在向客户端发送第一包数据时) +// ex_u8* _t = (ex_u8*)(ssh_channel_get_session(cp->cli_channel)); +// if (_t[1116] == 9) // SSH_SESSION_STATE_AUTHENTICATED = 8, SSH_SESSION_STATE_ERROR = 9 +// { +// EXLOGW(" --- [ssh] hard code to fix client connect session error state.\n"); +// _t[1116] = 8; +// } +// #endif + + _this->m_recving_from_srv = true; + + if (cp->type == TS_SSH_CHANNEL_TYPE_SHELL && !is_stderr) + { + if (!cp->server_ready) { + if (len >= 2 && (((ex_u8*)data)[len - 2] != 0x0d && ((ex_u8*)data)[len - 1] != 0x0a)) { + cp->server_ready = true; + } + } + + _this->_process_ssh_command(cp, TP_SSH_SERVER_SIDE, (ex_u8*)data, len); + // ex_astr str(cp->cmd_char_list.begin(), cp->cmd_char_list.end()); + // ex_replace_all(str, "\r", ""); + // ex_replace_all(str, "\n", ""); + // EXLOGD("[ssh] -- [%s]\n", str.c_str()); + + cp->rec.record(TS_RECORD_TYPE_SSH_DATA, (unsigned char *)data, len); + } + + int ret = 0; + + // 收到第一包服务端返回的数据时,在输出数据之前显示一些自定义的信息 +#if 1 + if (!is_stderr && cp->is_first_server_data) + { + cp->is_first_server_data = false; + + if (cp->type != TS_SSH_CHANNEL_TYPE_SFTP) + { + char buf[512] = { 0 }; + + const char *auth_mode = NULL; + if (_this->m_auth_type == TP_AUTH_TYPE_PASSWORD) + auth_mode = "password"; + else if (_this->m_auth_type == TP_AUTH_TYPE_PRIVATE_KEY) + auth_mode = "private-key"; + else + auth_mode = "unknown"; + +#ifdef EX_OS_WIN32 + int w = min(cp->win_width, 128); +#else + int w = std::min(cp->win_width, 128); +#endif + ex_astr line(w, '='); + + snprintf(buf, sizeof(buf), + "\r\n"\ + "%s\r\n"\ + "Teleport SSH Bastion Server...\r\n"\ + " - teleport to %s:%d\r\n"\ + " - authroized by %s\r\n"\ + "%s\r\n"\ + "\r\n\r\n", + line.c_str(), + _this->m_conn_ip.c_str(), + _this->m_conn_port, auth_mode, + line.c_str() + ); + + int buf_len = strlen(buf); + ex_bin _data; + _data.resize(buf_len + len); + memcpy(&_data[0], buf, buf_len); + memcpy(&_data[buf_len], data, len); + + ret = ssh_channel_write(cp->cli_channel, &_data[0], _data.size()); + + _this->m_recving_from_srv = false; + return len; + } + } +#endif + +#if 1 + // 直接转发数据到客户端 + if (is_stderr) + ret = ssh_channel_write_stderr(cp->cli_channel, data, len); + else + ret = ssh_channel_write(cp->cli_channel, data, len); +#else + // 分析收到的服务端数据包,如果包含类似 \033]0;AABB\007 这样的数据,客户端会根据此改变窗口标题 + // 我们需要替换这部分数据,使之显示类似 \033]0;TP#ssh://remote-ip\007 这样的标题。 + // 但是这样会降低一些性能,因此目前不启用,保留此部分代码备用。 + if (is_stderr) { + ret = ssh_channel_write_stderr(cp->cli_channel, data, len); + } + else if (cp->type != TS_SSH_CHANNEL_TYPE_SHELL) { + ret = ssh_channel_write(cp->cli_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++; + + // 这个包中含有改变标题的数据,将标题换为我们想要的 + EXLOGD("-- found title\n"); + 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;TP#ssh://", 13); + mbuf.append((ex_u8*)_this->m_conn_ip.c_str(), _this->m_conn_ip.length()); + mbuf.append((ex_u8*)"\007", 1); + + if (len_end > 0) + mbuf.append((ex_u8*)_end, len_end); + + if (mbuf.size() > 0) + { + for (;;) { + ret = ssh_channel_write(cp->cli_channel, mbuf.data(), mbuf.size()); + if (ret == SSH_ERROR) + break; + if (ret == mbuf.size()) { + ret = len; // 表示我们已经处理了所有的数据了。 + break; + } + else { + mbuf.pop(ret); + ex_sleep_ms(100); + } + } + // if (ret <= 0) + // EXLOGE("[ssh] send to client failed (1).\n"); + // else + // ret = len; + } + else + { + ret = ssh_channel_write(cp->cli_channel, data, len); + } + } + else + { + ret = ssh_channel_write(cp->cli_channel, data, len); + } + } + else { + ret = ssh_channel_write(cp->cli_channel, data, len); + } + } + else { + ret = ssh_channel_write(cp->cli_channel, data, len); + } + } +#endif + + if (ret == SSH_ERROR) { + EXLOGE("[ssh] send data(%dB) to client failed. [%d][cli:%s][srv:%s]\n", len, ret, ssh_get_error(_this->m_cli_session), ssh_get_error(_this->m_srv_session)); + //ssh_channel_close(channel); + cp->need_close = true; + _this->m_have_error = true; + } + else if (ret != len) { + EXLOGW("[ssh] received server data, got %dB, processed %dB.\n", len, ret); + } + + _this->m_recving_from_srv = false; + return ret; +} + +void SshSession::_on_server_channel_close(ssh_session session, ssh_channel channel, void *userdata) { + EXLOGV("---server channel closed.\n"); + SshSession *_this = (SshSession *)userdata; + TP_SSH_CHANNEL_PAIR* cp = _this->_get_channel_pair(TP_SSH_SERVER_SIDE, channel); + if (NULL == cp) { + EXLOGE("[ssh] when server channel close, not found channel pair.\n"); + return; + } + //cp->last_access_timestamp = (ex_u32)time(NULL); + //cp->need_close = true; + _this->m_have_error = true; + + //EXLOGD("[ssh] [channel:%d] --- end by server channel close\n", cp->channel_id); + //_this->_record_end(cp); + + // will the server-channel exist, the client-channel must exist too. + if (cp->cli_channel == NULL) { + EXLOGE("[ssh] when server channel close, client-channel not exists.\n"); + } + else { + if (!ssh_channel_is_closed(cp->cli_channel)) { + //ssh_channel_close(cp->cli_channel); + //cp->need_close = true; + //_this->m_have_error = true; + } + } +} + +void SshSession::_process_ssh_command(TP_SSH_CHANNEL_PAIR* cp, int from, const ex_u8* data, int len) +{ + if (len == 0) + return; + + if (TP_SSH_CLIENT_SIDE == from) { + if (len >= 2) { + if (((ex_u8*)data)[len - 1] == 0x0d) { + // 疑似复制粘贴多行命令一次性执行,将其记录到日志文件中 + ex_astr str((const char*)data, len - 1); + cp->rec.record_command(1, str); + + cp->process_srv = false; + return; + } + } + + // 客户端输入回车时,可能时执行了一条命令,需要根据服务端返回的数据进行进一步判断 + cp->maybe_cmd = (data[len - 1] == 0x0d); + // if (cp->maybe_cmd) + // EXLOGD("[ssh] maybe cmd.\n"); + + // 有时在执行类似top命令的情况下,输入一个字母'q'就退出程序,没有输入回车,可能会导致后续记录命令时将返回的命令行提示符作为命令 + // 记录下来了,要避免这种情况,排除的方式是:客户端单个字母,后续服务端如果收到的是控制序列 1b 5b xx xx,就不计做命令。 + cp->client_single_char = (len == 1 && isprint(data[0])); + + cp->process_srv = true; + } + else if (TP_SSH_SERVER_SIDE == from) { + if (!cp->process_srv) + return; + + int offset = 0; + bool esc_mode = false; + int esc_arg = 0; + for (; offset < len;) { + ex_u8 ch = data[offset]; + + if (esc_mode) { + switch (ch) { + 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 + (ch - '0'); + break; + + case 0x3f: + case ';': + case '>': + cp->cmd_char_list.clear(); + cp->cmd_char_pos = cp->cmd_char_list.begin(); + return; + break; + + case 0x4b: { // 'K' + if (0 == esc_arg) { + // 删除光标到行尾的字符串 + cp->cmd_char_list.erase(cp->cmd_char_pos, cp->cmd_char_list.end()); + cp->cmd_char_pos = cp->cmd_char_list.end(); + } + else if (1 == esc_arg) { + // 删除从开始到光标处的字符串 + cp->cmd_char_list.erase(cp->cmd_char_list.begin(), cp->cmd_char_pos); + cp->cmd_char_pos = cp->cmd_char_list.end(); + } + else if (2 == esc_arg) { + // 删除整行 + cp->cmd_char_list.clear(); + cp->cmd_char_pos = cp->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 (cp->cmd_char_pos != cp->cmd_char_list.end()) + cp->cmd_char_pos++; + } + esc_mode = false; + break; + } + case 0x44: { // ^[D + // 光标左移 + if (esc_arg == 0) + esc_arg = 1; + for (int j = 0; j < esc_arg; ++j) { + + if (cp->cmd_char_pos != cp->cmd_char_list.begin()) + cp->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 (cp->cmd_char_pos != cp->cmd_char_list.end()) + cp->cmd_char_pos = cp->cmd_char_list.erase(cp->cmd_char_pos); + } + esc_mode = false; + break; + } + + case 0x40: { // '@' 插入指定数量的空白字符 + if (esc_arg == 0) + esc_arg = 1; + for (int j = 0; j < esc_arg; ++j) + cp->cmd_char_pos = cp->cmd_char_list.insert(cp->cmd_char_pos, ' '); + esc_mode = false; + break; + } + + default: + esc_mode = false; + break; + } + + //d += 1; + //l -= 1; + offset++; + continue; + } + + switch (ch) { + case 0x07: + // 响铃 + break; + case 0x08: { + // 光标左移 + if (cp->cmd_char_pos != cp->cmd_char_list.begin()) + cp->cmd_char_pos--; + break; + } + case 0x1b: + { + if (offset + 1 < len) + { + if (data[offset + 1] == 0x5b || data[offset + 1] == 0x5d) { + if (offset == 0 && cp->client_single_char) { + cp->cmd_char_list.clear(); + cp->cmd_char_pos = cp->cmd_char_list.begin(); + cp->maybe_cmd = false; + cp->process_srv = false; + cp->client_single_char = false; + return; + } + } + + if (data[offset + 1] == 0x5b) { + esc_mode = true; + esc_arg = 0; + + offset += 1; + } + } + + break; + } + case 0x0d: + { + if (offset + 1 < len && data[offset + 1] == 0x0a) { + // if (cp->maybe_cmd) + // EXLOGD("[ssh] maybe cmd.\n"); + if (cp->maybe_cmd) { + if (cp->cmd_char_list.size() > 0) + { + ex_astr str(cp->cmd_char_list.begin(), cp->cmd_char_list.end()); + // EXLOGD("[ssh] --==--==-- save cmd: [%s]\n", str.c_str()); + cp->rec.record_command(0, str); + } + + cp->cmd_char_list.clear(); + cp->cmd_char_pos = cp->cmd_char_list.begin(); + cp->maybe_cmd = false; + } + } + else { + cp->cmd_char_list.clear(); + cp->cmd_char_pos = cp->cmd_char_list.begin(); + } + cp->process_srv = false; + return; + break; + } + default: + if (cp->cmd_char_pos != cp->cmd_char_list.end()) + { + cp->cmd_char_pos = cp->cmd_char_list.erase(cp->cmd_char_pos); + cp->cmd_char_pos = cp->cmd_char_list.insert(cp->cmd_char_pos, ch); + cp->cmd_char_pos++; + } + else + { + cp->cmd_char_list.push_back(ch); + cp->cmd_char_pos = cp->cmd_char_list.end(); + } + } + + offset++; + } + } + + return; +} + +void SshSession::_process_sftp_command(TP_SSH_CHANNEL_PAIR* cp, 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"); + + // TODO: 根据客户端的请求和服务端的返回,可以进一步判断用户是如何操作文件的,比如读、写等等,以及操作的结果是成功还是失败。 + // 记录格式: time-offset,flag,action,result,file-path,[file-path] + // 其中,flag目前总是为0,可以忽略(为保证与ssh-cmd格式一致),time-offset/action/result 都是数字 + // file-path是被操作的对象,规格为 长度:实际内容,例如, 13:/root/abc.txt + + + 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 + cp->rec.record_command(0, "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]); + + + switch (sftp_cmd) { + case 0x03: + // 0x03 = 3 = SSH_FXP_OPEN + break; + // case 0x0b: + // // 0x0b = 11 = SSH_FXP_OPENDIR + // act = "open dir"; + // break; + case 0x0d: + // 0x0d = 13 = SSH_FXP_REMOVE + break; + case 0x0e: + // 0x0e = 14 = SSH_FXP_MKDIR + break; + case 0x0f: + // 0x0f = 15 = SSH_FXP_RMDIR + break; + case 0x12: + // 0x12 = 18 = SSH_FXP_RENAME + // 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操作数据中包含两个字符串,前者是新的链接文件名,后者是现有被链接的文件名 + 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,%d,%s", sftp_cmd, 0, 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,%d,%s:%s", sftp_cmd, 0, str1.c_str(), str2.c_str()); + } + + cp->rec.record_command(0, msg); +} diff --git a/server/tp_core/protocol/ssh/ssh_session.h b/server/tp_core/protocol/ssh/ssh_session.h index ab320c2..2a1e98f 100644 --- a/server/tp_core/protocol/ssh/ssh_session.h +++ b/server/tp_core/protocol/ssh/ssh_session.h @@ -37,6 +37,7 @@ private: ssh_channel srv_channel; TppSshRec rec; + ex_u32 last_access_timestamp; int state; int db_id; @@ -45,6 +46,7 @@ private: int win_width; // window width, in char count. bool is_first_server_data; + bool need_close; // for ssh command record cache. bool server_ready; @@ -74,6 +76,8 @@ public: // save record cache into file. be called per 5 seconds. void save_record(); + // + void check_noop_timeout(ex_u32 t_now, ex_u32 timeout); protected: void _thread_loop(void); diff --git a/server/tp_core/protocol/ssh/tpssh.cpp b/server/tp_core/protocol/ssh/tpssh.cpp index 2df77ab..fb0ff5d 100644 --- a/server/tp_core/protocol/ssh/tpssh.cpp +++ b/server/tp_core/protocol/ssh/tpssh.cpp @@ -38,3 +38,7 @@ TPP_API void tpp_timer(void) { // be called per one second. g_ssh_proxy.timer(); } + +TPP_API void tpp_set_cfg(TPP_SET_CFG_ARGS* cfg_args) { + g_ssh_proxy.set_cfg(cfg_args); +} diff --git a/server/tp_core/protocol/telnet/telnet_proxy.cpp b/server/tp_core/protocol/telnet/telnet_proxy.cpp index af066dc..b180f88 100644 --- a/server/tp_core/protocol/telnet/telnet_proxy.cpp +++ b/server/tp_core/protocol/telnet/telnet_proxy.cpp @@ -8,6 +8,7 @@ TelnetProxy::TelnetProxy() : ExThreadBase("telnet-proxy-thread") { memset(&m_loop, 0, sizeof(uv_loop_t)); m_timer_counter = 0; + m_noop_timeout_sec = 900; } TelnetProxy::~TelnetProxy() @@ -44,13 +45,20 @@ void TelnetProxy::timer() { m_timer_counter = 0; ExThreadSmartLock locker(m_lock); + ex_u32 t_now = (ex_u32)time(NULL); ts_telnet_sessions::iterator it = m_sessions.begin(); for (; it != m_sessions.end(); ++it) { it->first->save_record(); + if(0 != m_noop_timeout_sec) + it->first->check_noop_timeout(t_now, m_noop_timeout_sec); } } +void TelnetProxy::set_cfg(TPP_SET_CFG_ARGS* args) { + m_noop_timeout_sec = args->noop_timeout; +} + void TelnetProxy::_thread_loop(void) { struct sockaddr_in addr; diff --git a/server/tp_core/protocol/telnet/telnet_proxy.h b/server/tp_core/protocol/telnet/telnet_proxy.h index 1538082..9ec8f33 100644 --- a/server/tp_core/protocol/telnet/telnet_proxy.h +++ b/server/tp_core/protocol/telnet/telnet_proxy.h @@ -16,6 +16,8 @@ public: bool init(); void timer(); + void set_cfg(TPP_SET_CFG_ARGS* args); + uv_loop_t* get_loop() { return &m_loop; } void clean_session(); @@ -37,6 +39,8 @@ private: private: bool m_stop_flag; int m_timer_counter; + // + ex_u32 m_noop_timeout_sec; uv_loop_t m_loop; uv_tcp_t m_handle; diff --git a/server/tp_core/protocol/telnet/telnet_session.cpp b/server/tp_core/protocol/telnet/telnet_session.cpp index c6fb11e..53bb008 100644 --- a/server/tp_core/protocol/telnet/telnet_session.cpp +++ b/server/tp_core/protocol/telnet/telnet_session.cpp @@ -22,6 +22,7 @@ TelnetSession::TelnetSession(TelnetProxy *proxy) : m_is_relay = false; m_is_closed = false; m_first_client_pkg = true; + m_last_access_timestamp = (ex_u32)time(NULL); m_win_width = 0; m_win_height = 0; @@ -54,6 +55,13 @@ void TelnetSession::save_record() { m_rec.save_record(); } +void TelnetSession::check_noop_timeout(ex_u32 t_now, ex_u32 timeout) { + if (t_now - m_last_access_timestamp > timeout) { + EXLOGW("[telnet] need close session by timeout.\n"); + _do_close(TP_SESS_STAT_END); + } +} + void TelnetSession::_session_error(int err_code) { int db_id = 0; if (!g_telnet_env.session_begin(m_conn_info, &db_id) || db_id == 0) @@ -465,41 +473,41 @@ sess_state TelnetSession::_do_server_connected() { int w = 50; if (m_win_width != 0) { -#ifdef EX_OS_WIN32 - int w = min(m_win_width, 128); -#else - int w = std::min(m_win_width, 128); -#endif +#ifdef EX_OS_WIN32 + int w = min(m_win_width, 128); +#else + int w = std::min(m_win_width, 128); +#endif m_startup_win_size_recorded = true; m_rec.record_win_size_startup(m_win_width, m_win_height); } - char buf[512] = { 0 }; - - const char *auth_mode = NULL; - if (m_conn_info->auth_type == TP_AUTH_TYPE_PASSWORD) - auth_mode = "password"; - else if (m_conn_info->auth_type == TP_AUTH_TYPE_NONE) - auth_mode = "nothing"; - else - auth_mode = "unknown"; - - ex_astr line(w, '='); - - snprintf(buf, sizeof(buf), - "\r\n"\ - "%s\r\n"\ - "Teleport TELNET Bastion Server...\r\n"\ - " - teleport to %s:%d\r\n"\ - " - authroized by %s\r\n"\ - "%s\r\n"\ - "\r\n\r\n", - line.c_str(), - m_conn_ip.c_str(), - m_conn_port, auth_mode, - line.c_str() - ); - + char buf[512] = { 0 }; + + const char *auth_mode = NULL; + if (m_conn_info->auth_type == TP_AUTH_TYPE_PASSWORD) + auth_mode = "password"; + else if (m_conn_info->auth_type == TP_AUTH_TYPE_NONE) + auth_mode = "nothing"; + else + auth_mode = "unknown"; + + ex_astr line(w, '='); + + snprintf(buf, sizeof(buf), + "\r\n"\ + "%s\r\n"\ + "Teleport TELNET Bastion Server...\r\n"\ + " - teleport to %s:%d\r\n"\ + " - authroized by %s\r\n"\ + "%s\r\n"\ + "\r\n\r\n", + line.c_str(), + m_conn_ip.c_str(), + m_conn_port, auth_mode, + line.c_str() + ); + m_conn_client->send((ex_u8*)buf, strlen(buf)); if (m_is_putty_mode) @@ -520,6 +528,8 @@ sess_state TelnetSession::_do_server_connected() { } sess_state TelnetSession::_do_relay(TelnetConn *conn) { + m_last_access_timestamp = (ex_u32)time(NULL); + TelnetSession* _this = conn->session(); bool is_processed = false; diff --git a/server/tp_core/protocol/telnet/telnet_session.h b/server/tp_core/protocol/telnet/telnet_session.h index 2f59715..7047021 100644 --- a/server/tp_core/protocol/telnet/telnet_session.h +++ b/server/tp_core/protocol/telnet/telnet_session.h @@ -49,6 +49,8 @@ public: void record(ex_u8 type, const ex_u8* data, size_t size) { m_rec.record(type, data, size); } + // + void check_noop_timeout(ex_u32 t_now, ex_u32 timeout); void client_addr(const char* addr) { m_client_addr = addr; } const char* client_addr() const { return m_client_addr.c_str(); } @@ -85,6 +87,7 @@ private: bool m_first_client_pkg; bool m_is_relay; // 是否进入relay模式了(只有进入relay模式才会有录像存在) bool m_is_closed; + ex_u32 m_last_access_timestamp; TppTelnetRec m_rec; int m_win_width; diff --git a/server/tp_core/protocol/telnet/tptelnet.cpp b/server/tp_core/protocol/telnet/tptelnet.cpp index ef1913b..3c0e4cb 100644 --- a/server/tp_core/protocol/telnet/tptelnet.cpp +++ b/server/tp_core/protocol/telnet/tptelnet.cpp @@ -30,3 +30,8 @@ TPP_API void tpp_timer(void) { // be called per one second. g_telnet_proxy.timer(); } + + +TPP_API void tpp_set_cfg(TPP_SET_CFG_ARGS* cfg_args) { + g_telnet_proxy.set_cfg(cfg_args); +} diff --git a/server/www/teleport/view/system/config.mako b/server/www/teleport/view/system/config.mako index 7d1af1e..69b29f9 100644 --- a/server/www/teleport/view/system/config.mako +++ b/server/www/teleport/view/system/config.mako @@ -88,8 +88,8 @@ 璁よ瘉鏂瑰紡 - - 娉ㄦ剰锛氬彲浠ヤ负姣忎釜鐢ㄦ埛鎸囧畾鐗瑰畾鐨勮璇佹柟寮忋 + 璁剧疆绯荤粺鍚敤鐨勭櫥褰曡璇佹柟寮 + 杩樺彲浠ヤ负姣忎釜鐢ㄦ埛鎸囧畾鐗瑰畾鐨勭櫥褰曡璇佹柟寮忋 ## @@ -167,7 +167,7 @@ ##
- 鍏ㄥ眬RDP閫夐」 + 鍏ㄥ眬RDP閫夐」锛堟敞锛氬皻鏈疄鐜帮級 @@ -196,7 +196,7 @@
- 鍏ㄥ眬SSH閫夐」 + 鍏ㄥ眬SSH閫夐」锛堟敞锛氬皻鏈疄鐜帮級 diff --git a/server/www/teleport/webroot/app/base/webapp.py b/server/www/teleport/webroot/app/base/webapp.py index ad17f0f..30bc983 100644 --- a/server/www/teleport/webroot/app/base/webapp.py +++ b/server/www/teleport/webroot/app/base/webapp.py @@ -56,10 +56,13 @@ class WebApp: rep = urllib.request.urlopen(req, timeout=3) body = rep.read().decode() x = json.loads(body) - log.d('connect core server and get config info succeeded.\n') - cfg.update_core(x['data']) + if 'code' not in x or x['code'] != 0: + log.e('connect core-server for get config info failed.\n') + else: + cfg.update_core(x['data']) + log.d('get config info of core-server succeeded.\n') except: - log.w('can not connect to core server to get config, maybe it not start yet, ignore.\n') + log.w('can not connect to core-server to get config, maybe it not start yet, ignore.\n') def run(self): log.i('\n') @@ -93,6 +96,23 @@ class WebApp: cfg.app_mode = APP_MODE_NORMAL _db.load_system_config() + try: + # 灏嗚繍琛屾椂閰嶇疆鍙戦佺粰鏍稿績鏈嶅姟 + req = {'method': 'set_config', 'param': {'noop_timeout': tp_cfg().sys.session.noop_timeout}} + req_data = json.dumps(req) + data = urllib.parse.quote(req_data).encode('utf-8') + req = urllib.request.Request(url=cfg.common.core_server_rpc, data=data) + rep = urllib.request.urlopen(req, timeout=3) + body = rep.read().decode() + x = json.loads(body) + if 'code' not in x or x['code'] != 0: + print(x) + log.e('connect core-server for set runtime-config failed.\n') + else: + log.d('set runtime-config for core-server succeeded.\n') + except: + log.w('can not connect to core-server to set runtime-config, maybe it not start yet, ignore.\n') + if not tp_session().init(): log.e('can not initialize session manager.\n') return 0 diff --git a/server/www/teleport/webroot/app/controller/rpc.py b/server/www/teleport/webroot/app/controller/rpc.py index 9d0133d..d98e1d5 100644 --- a/server/www/teleport/webroot/app/controller/rpc.py +++ b/server/www/teleport/webroot/app/controller/rpc.py @@ -103,7 +103,7 @@ class RpcHandler(TPBaseJsonHandler): code = param['code'] except: return self.write_json(TPE_PARAM) - if 'rid' not in param or 'code' not in param : + if 'rid' not in param or 'code' not in param: return self.write_json(TPE_PARAM) if not record.session_update(rid, protocol_sub_type, code): @@ -140,6 +140,13 @@ class RpcHandler(TPBaseJsonHandler): log.d('update base server config info.\n') tp_cfg().update_core(ret_data) + # 灏嗚繍琛屾椂閰嶇疆鍙戦佺粰鏍稿績鏈嶅姟 + req = {'method': 'set_config', 'param': {'noop_timeout': tp_cfg().sys.session.noop_timeout}} + _yr = core_service_async_post_http(req) + code, ret_data = yield _yr + if code != TPE_OK: + return self.write_json(code, 'set runtime-config to core-service failed.') + return self.write_json(TPE_OK) def _exit(self): diff --git a/server/www/teleport/webroot/app/controller/system.py b/server/www/teleport/webroot/app/controller/system.py index 1ea20c8..acc3d4f 100644 --- a/server/www/teleport/webroot/app/controller/system.py +++ b/server/www/teleport/webroot/app/controller/system.py @@ -216,6 +216,7 @@ class DoGetLogsHandler(TPBaseJsonHandler): class DoSaveCfgHandler(TPBaseJsonHandler): + @tornado.gen.coroutine def post(self): ret = self.check_privilege(TP_PRIVILEGE_SYS_CONFIG) if ret != TPE_OK: @@ -287,11 +288,20 @@ class DoSaveCfgHandler(TPBaseJsonHandler): _flag_ssh = _cfg['flag_ssh'] err = system_model.save_config(self, '鏇存柊杩炴帴鎺у埗璁剧疆', 'session', _cfg) if err == TPE_OK: + try: + req = {'method': 'set_config', 'param': {'noop_timeout': _noop_timeout}} + _yr = core_service_async_post_http(req) + code, ret_data = yield _yr + if code != TPE_OK: + log.e('can not set runtime-config to core-server.\n') + return self.write_json(code) + except: + pass + tp_cfg().sys.session.noop_timeout = _noop_timeout tp_cfg().sys.session.flag_record = _flag_record tp_cfg().sys.session.flag_rdp = _flag_rdp tp_cfg().sys.session.flag_ssh = _flag_ssh - tp_session().update_default_expire() else: return self.write_json(err)