diff --git a/.idea/encodings.xml b/.idea/encodings.xml index 8f8d6ca..7611e82 100644 --- a/.idea/encodings.xml +++ b/.idea/encodings.xml @@ -1,6 +1,6 @@ - + @@ -30,18 +30,15 @@ - - - - - - - - - + + + + + + diff --git a/CMakeCfg.txt b/CMakeCfg.txt index b80f03b..58cf131 100644 --- a/CMakeCfg.txt +++ b/CMakeCfg.txt @@ -17,9 +17,9 @@ if ("${CMAKE_SYSTEM_NAME}" STREQUAL "Darwin") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") set(TP_EXTERNAL_RELEASE_DIR "${PROJECT_SOURCE_DIR}/external/macos/release") elseif ("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux") + MESSAGE(STATUS "build on Linux...") set(OS_LINUX 1) set(OS_POSIX 1) - MESSAGE(STATUS "build on Linux...") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") set(TP_EXTERNAL_RELEASE_DIR "${PROJECT_SOURCE_DIR}/external/linux/release") elseif ("${CMAKE_SYSTEM_NAME}" STREQUAL "Windows") diff --git a/CMakeLists.txt b/CMakeLists.txt index 47ae499..09ec489 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,32 +3,6 @@ project(teleport) include(CMakeCfg.txt) -#MESSAGE(STATUS "operation system is ${CMAKE_SYSTEM}") -#MESSAGE(STATUS "current source directory is ${CMAKE_CURRENT_SOURCE_DIR}") -# -#set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/out/server/x64/bin") -#set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/out/server/x64/bin") -# -#set(CMAKE_CONFIGURATION_TYPES Debug Release) -# -## Determine the platform. -#if ("${CMAKE_SYSTEM_NAME}" STREQUAL "Darwin") -# MESSAGE(STATUS "build on macOS...") -# set(OS_MACOS 1) -# set(OS_POSIX 1) -# set(TP_EXTERNAL_RELEASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/macos/release") -#elseif ("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux") -# set(OS_LINUX 1) -# set(OS_POSIX 1) -# MESSAGE(STATUS "build on Linux...") -# add_subdirectory(server/tp_web/src) -# set(TP_EXTERNAL_RELEASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/linux/release") -#elseif ("${CMAKE_SYSTEM_NAME}" STREQUAL "Windows") -# # MESSAGE(FATAL_ERROR "unsupported platform: Windows") -#else () -# MESSAGE(FATAL_ERROR "unsupported platform: ${CMAKE_SYSTEM_NAME}") -#endif () - if (OS_LINUX) add_subdirectory(server/tp_web/src) endif() diff --git a/common/libex/include/ex/ex_str.h b/common/libex/include/ex/ex_str.h index ca9cea9..b85ca63 100644 --- a/common/libex/include/ex/ex_str.h +++ b/common/libex/include/ex/ex_str.h @@ -81,6 +81,8 @@ void ex_remove_white_space(ex_wstr& str_fix, int ulFlag = EX_RSC_ALL); ex_astr& ex_replace_all(ex_astr& str, const ex_astr& old_value, const ex_astr& new_value); ex_wstr& ex_replace_all(ex_wstr& str, const ex_wstr& old_value, const ex_wstr& new_value); +bool ex_strformat(ex_astr& out, size_t max_size, const char* fmt, ...); + class ex_str_utf16le { public: ex_str_utf16le(); diff --git a/common/libex/include/ex/ex_thread.h b/common/libex/include/ex/ex_thread.h index 3948749..354408b 100644 --- a/common/libex/include/ex/ex_thread.h +++ b/common/libex/include/ex/ex_thread.h @@ -21,22 +21,22 @@ public: ExThreadBase(const char* thread_name); virtual ~ExThreadBase(); - bool is_running(void) { return m_is_running; } + bool is_running() { return m_is_running; } // 创建并启动线程(执行被重载了的run()函数) - bool start(void); + bool start(); // 结束线程(等待wait_timeout_ms毫秒,如果wait_timeout_ms为0,则无限等待) - bool stop(void); + bool stop(); // 直接结束线程(强杀,不建议使用) - bool terminate(void); + bool terminate(); protected: // main loop of this thread. - virtual void _thread_loop(void) = 0; + virtual void _thread_loop() = 0; // called by another thread when thread ready to stop. - virtual void _on_stop(void) {}; + virtual void _on_stop() {}; // called inside thread when thread fully stopped. - virtual void _on_stopped(void) {}; + virtual void _on_stopped() {}; #ifdef EX_OS_WIN32 static unsigned int WINAPI _thread_func(LPVOID lpParam); @@ -59,8 +59,8 @@ public: ExThreadLock(); virtual ~ExThreadLock(); - void lock(void); - void unlock(void); + void lock(); + void unlock(); private: #ifdef EX_OS_WIN32 @@ -97,7 +97,7 @@ public: ExThreadManager(); virtual ~ExThreadManager(); - void stop_all(void); + void stop_all(); //private: void add(ExThreadBase* tb); @@ -115,6 +115,6 @@ int ex_atomic_inc(volatile int* pt); int ex_atomic_dec(volatile int* pt); // 线程相关操作 -ex_u64 ex_get_thread_id(void); +ex_u64 ex_get_thread_id(); #endif // __EX_THREAD_H__ diff --git a/common/libex/src/ex_str.cpp b/common/libex/src/ex_str.cpp index b88fabc..9408448 100644 --- a/common/libex/src/ex_str.cpp +++ b/common/libex/src/ex_str.cpp @@ -377,6 +377,29 @@ ex_wstr& ex_replace_all(ex_wstr& str, const ex_wstr& old_value, const ex_wstr& n return str; } +bool ex_strformat(ex_astr& out, size_t max_size, const char* fmt, ...) { + auto tmp = (char*)calloc(max_size, 1); + if (!tmp) + return false; + + int ret = 0; + va_list valist; + va_start(valist, fmt); + //_ts_printf_a(level, EX_COLOR_BLACK, fmt, valist); +#ifdef EX_OS_WIN32 + ret = vsnprintf(tmp, max_size-1, fmt, valist); +#else + ret = vsnprintf(tmp, max_size-1, fmt, valist); +#endif + va_end(valist); + + if (ret >= max_size) + return false; + + out = tmp; + free(tmp); + return true; +} #ifndef EX_OS_WIN32 @@ -869,13 +892,13 @@ const uint16_t* ex_str_utf16le::c_str() const { // int iSize = MultiByteToWideChar(CP_UTF8, 0, from.c_str(), -1, NULL, 0); // if (iSize <= 0) // return false; -// +// // //++iSize; // to.resize(iSize); // memset(&to[0], 0, sizeof(ex_utf16)); -// +// // MultiByteToWideChar(CP_UTF8, 0, from.c_str(), -1, &to[0], iSize); -// +// // return true; // } diff --git a/common/libex/src/ex_thread.cpp b/common/libex/src/ex_thread.cpp index fb28d41..8233628 100644 --- a/common/libex/src/ex_thread.cpp +++ b/common/libex/src/ex_thread.cpp @@ -38,7 +38,7 @@ ExThreadBase::~ExThreadBase() { } } -bool ExThreadBase::start(void) { +bool ExThreadBase::start() { m_need_stop = false; EXLOGV("[thread] + `%s` starting.\n", m_thread_name.c_str()); #ifdef WIN32 @@ -62,7 +62,7 @@ bool ExThreadBase::start(void) { return true; } -bool ExThreadBase::stop(void) { +bool ExThreadBase::stop() { if (m_handle == 0) { EXLOGW("[thread] `%s` already stopped before stop() call.\n", m_thread_name.c_str()); return true; @@ -91,7 +91,7 @@ bool ExThreadBase::stop(void) { return true; } -bool ExThreadBase::terminate(void) { +bool ExThreadBase::terminate() { #ifdef EX_OS_WIN32 return (TerminateThread(m_handle, 1) == TRUE); #else @@ -112,7 +112,7 @@ ExThreadManager::~ExThreadManager() { } } -void ExThreadManager::stop_all(void) { +void ExThreadManager::stop_all() { ExThreadSmartLock locker(m_lock); ex_threads::iterator it = m_threads.begin(); @@ -173,7 +173,7 @@ ExThreadLock::~ExThreadLock() { #endif } -void ExThreadLock::lock(void) { +void ExThreadLock::lock() { #ifdef EX_OS_WIN32 EnterCriticalSection(&m_locker); #else @@ -181,7 +181,7 @@ void ExThreadLock::lock(void) { #endif } -void ExThreadLock::unlock(void) { +void ExThreadLock::unlock() { #ifdef EX_OS_WIN32 LeaveCriticalSection(&m_locker); #else @@ -218,7 +218,7 @@ int ex_atomic_dec(volatile int *pt) { } -ex_u64 ex_get_thread_id(void) { +ex_u64 ex_get_thread_id() { #ifdef EX_OS_WIN32 return GetCurrentThreadId(); #else diff --git a/server/tp_core/protocol/ssh/ssh_session.cpp b/server/tp_core/protocol/ssh/ssh_session.cpp deleted file mode 100644 index fc10a02..0000000 --- a/server/tp_core/protocol/ssh/ssh_session.cpp +++ /dev/null @@ -1,1497 +0,0 @@ -#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_need_send_keepalive = 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() { - - _on_stop(); - - 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(); -} - -void SshSession::_on_stop(void) { - ExThreadBase::_on_stop(); - - _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::_on_stopped() { - m_proxy->session_finished(this); -} - -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); - cli = NULL; - } - if (srv) { - ssh_channel_free(srv); - srv = NULL; - } - _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.empty())) { - 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(); - } - - if (m_need_send_keepalive) { - m_need_send_keepalive = false; - EXLOGD("[ssh] send keep-alive.\n"); - if (m_srv_session) - //ssh_send_keepalive(m_srv_session); - ssh_send_ignore(m_srv_session, "keepalive@openssh.com"); - if (m_cli_session) - //ssh_send_keepalive(m_cli_session); - ssh_send_ignore(m_cli_session, "keepalive@openssh.com"); - } - - } while (!m_channels.empty()); - - if (m_channels.empty()) - 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 == 0) - EXLOGW("[ssh] try close channel by kill.\n"); - else if (t_now - (*it)->last_access_timestamp > timeout) - EXLOGW("[ssh] try close channel by timeout.\n"); - if (t_now == 0 || t_now - (*it)->last_access_timestamp > timeout) { - (*it)->need_close = true; - m_have_error = true; - } - } -} - -void SshSession::keep_alive() { - m_need_send_keepalive = true; -// EXLOGD("[ssh] keep-alive.\n"); -// if(m_srv_session) -// ssh_send_keepalive(m_srv_session); -// if (m_cli_session) -// ssh_send_keepalive(m_cli_session); -} - - -int SshSession::_on_auth_password_request(ssh_session session, const char *user, const char *password, void *userdata) { - // here, `user` is the session-id we need. - 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; - _this->m_flags = _this->m_conn_info->protocol_flag; - 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; - } - } - - // config and try to connect to real SSH host. - 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(); - -// 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); - int val = 0; - ssh_options_set(_this->m_srv_session, SSH_OPTIONS_STRICTHOSTKEYCHECK, &val); - -//#ifdef EX_DEBUG -// int flag = SSH_LOG_FUNCTIONS; -// ssh_options_set(_this->m_srv_session, SSH_OPTIONS_LOG_VERBOSITY, &flag); -//#endif - - int _timeout_cli = 120; // 120 sec. - ssh_options_set(_this->m_cli_session, SSH_OPTIONS_TIMEOUT, &_timeout_cli); - - - 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 120 sec. - // usually when sshd config to UseDNS. - int _timeout = 120; // 120 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; - } - - if(ssh_is_blocking(_this->m_cli_session)) - EXLOGD("[ssh] client session is blocking.\n"); - if(ssh_is_blocking(_this->m_srv_session)) - EXLOGD("[ssh] server session is blocking.\n"); - - // once the server are connected, change the timeout back to default. - _timeout = 120; // in seconds. - ssh_options_set(_this->m_srv_session, SSH_OPTIONS_TIMEOUT, &_timeout); - - // get ssh version of host, v1 or v2 - // TODO: libssh-0.8.5 does not support sshv1 anymore. - _this->m_ssh_ver = ssh_get_version(_this->m_srv_session); - //EXLOGW("[ssh] real host is SSHv%d\n", _this->m_ssh_ver); - -#if 0 - // check supported auth type by host - 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] can not got auth type supported by 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_SESSION); - return SSH_AUTH_ERROR; - } - - int auth_methods = ssh_userauth_list(_this->m_srv_session, _this->m_acc_name.c_str()); - - const char* banner = ssh_get_issue_banner(_this->m_srv_session); - if (NULL != banner) { - EXLOGE("[ssh] issue banner: %s\n", banner); - } -#endif - - int auth_methods = 0; - if (SSH_AUTH_ERROR != ssh_userauth_none(_this->m_srv_session, NULL)) { - auth_methods = ssh_userauth_list(_this->m_srv_session, NULL); - EXLOGV("[ssh] allowed auth method: 0x%08x\n", auth_methods); - } else { - EXLOGW("[ssh] can not get allowed auth method, try each method we can.\n"); - } - - // some host does not give me the auth methods list, so we need try each one. - if(auth_methods == 0) - auth_methods = SSH_AUTH_METHOD_INTERACTIVE | SSH_AUTH_METHOD_PASSWORD | SSH_AUTH_METHOD_PUBLICKEY; - - if (_this->m_auth_type == TP_AUTH_TYPE_PASSWORD) { - if (!(((auth_methods & SSH_AUTH_METHOD_INTERACTIVE) == SSH_AUTH_METHOD_INTERACTIVE) || ((auth_methods & SSH_AUTH_METHOD_PASSWORD) == SSH_AUTH_METHOD_PASSWORD))) { - _this->m_have_error = true; - _this->_session_error(TP_SESS_STAT_ERR_AUTH_TYPE); - return SSH_AUTH_ERROR; - } - - int retry_count = 0; - - // first try interactive login mode if server allow. - if ((auth_methods & SSH_AUTH_METHOD_INTERACTIVE) == SSH_AUTH_METHOD_INTERACTIVE) { - 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); - //ssh_send_debug(_this->m_cli_session, "TTTTTESTTTTTTTTTTT", 1); - _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); - } - } - - // and then try password login mode if server allow. - if ((auth_methods & SSH_AUTH_METHOD_PASSWORD) == SSH_AUTH_METHOD_PASSWORD) { - retry_count = 0; - rc = ssh_userauth_password(_this->m_srv_session, NULL, _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, NULL, _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 { - EXLOGE("[ssh] failed to login with password mode, got %d.\n", rc); - break; - } - } - } - - 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) { - if ((auth_methods & SSH_AUTH_METHOD_PUBLICKEY) != SSH_AUTH_METHOD_PUBLICKEY) { - _this->m_have_error = true; - _this->_session_error(TP_SESS_STAT_ERR_AUTH_TYPE); - return SSH_AUTH_ERROR; - } - - 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; - } - - 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"); - if ((_this->m_flags & TP_FLAG_SSH_SHELL) != TP_FLAG_SSH_SHELL) { - EXLOGE("[ssh] ssh-shell disabled by ops-policy.\n"); - return SSH_ERROR; - } - - 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); - - // sometimes it will block here. the following function will never return. - // Fixed at 20190104: - // at libssh ssh_handle_packets_termination(), set timeout always to SSH_TIMEOUT_USER. - // and at ssh_handle_packets(), when call ssh_poll_add_events() should use POLLIN|POLLOUT. - 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("[ssh] ---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; - } - - _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); - //EXLOGD(" ---> recv from client %d B\n", len); - - SshSession *_this = (SshSession *) userdata; - - // 当前线程正在接收服务端返回的数据,因此我们直接返回,这样紧跟着会重新再发送此数据的 - if (_this->m_recving_from_srv) { - // EXLOGD("recving from srv...try again later...\n"); - return 0; - } - if (_this->m_recving_from_cli) { - // EXLOGD("recving from cli...try again later...\n"); - 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; - } - - // 不可以拆分!!否则执行 rz 命令会出错! - // xxxx 如果用户复制粘贴多行文本,我们将其拆分为每一行发送一次数据包 -// 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); - } 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; - } - - if ((_this->m_flags & TP_FLAG_SSH_SFTP) != TP_FLAG_SSH_SFTP) { - EXLOGE("[ssh] ssh-sftp disabled by ops-policy.\n"); - 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); - //EXLOGD(" <--- send to server %d B\n", len); - - 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) { - // EXLOGD("recving from cli...try again later...\n"); - return 0; - } - if (_this->m_recving_from_srv) { - // EXLOGD("recving from srv...try again later...\n"); - 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); - - _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 [%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, - cp->db_id, - 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 - //static int idx = 0; - - ssh_set_blocking(_this->m_cli_session, 0); - - int xx = 0; - for(xx = 0; xx < 10; ++xx) { - -// idx++; -// EXLOGD(">>>>> %d . %d\n", cp->db_id, idx); - - // 直接转发数据到客户端 - if (is_stderr) - ret = ssh_channel_write_stderr(cp->cli_channel, data, len); - else - ret = ssh_channel_write(cp->cli_channel, data, len); - -// EXLOGD("<<<<< %d . %d\n", cp->db_id, idx); - - if(ret == SSH_OK) { -// EXLOGD("ssh_channel_write() ok.\n"); - break; - } - else if(ret == SSH_AGAIN) { -// EXLOGD("ssh_channel_write() need again, %d.\n", xx); - ex_sleep_ms(500); - continue; - } - else { -// EXLOGD("ssh_channel_write() failed.\n"); - break; - } - } - - ssh_set_blocking(_this->m_cli_session, 1); - -#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)); - 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("[ssh] ---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 deleted file mode 100644 index 66b11fc..0000000 --- a/server/tp_core/protocol/ssh/ssh_session.h +++ /dev/null @@ -1,159 +0,0 @@ -#ifndef __SSH_SESSION_H__ -#define __SSH_SESSION_H__ - -#include "ssh_recorder.h" - -#include - -#include -#include -#include -#include - -#include -#include - -#define TS_SSH_CHANNEL_TYPE_UNKNOWN 0 -#define TS_SSH_CHANNEL_TYPE_SHELL 1 -#define TS_SSH_CHANNEL_TYPE_SFTP 2 - -#define TP_SSH_CLIENT_SIDE 1 -#define TP_SSH_SERVER_SIDE 2 - -class SshProxy; -class SshSession; - -class TP_SSH_CHANNEL_PAIR { - - friend class SshSession; - -public: - TP_SSH_CHANNEL_PAIR(); - -private: - int type; // TS_SSH_CHANNEL_TYPE_SHELL or TS_SSH_CHANNEL_TYPE_SFTP - - ssh_channel cli_channel; - ssh_channel srv_channel; - - TppSshRec rec; - ex_u32 last_access_timestamp; - - int state; - int db_id; - int channel_id; // for debug only. - - int win_width; // window width, in char count. - - bool is_first_server_data; - bool need_close; - - // for ssh command record cache. - bool server_ready; - bool maybe_cmd; - bool process_srv; - bool client_single_char; - std::list cmd_char_list; - std::list::iterator cmd_char_pos; -}; - -typedef std::list tp_channels; - -class SshSession : public ExThreadBase -{ -public: - SshSession(SshProxy* proxy, ssh_session sess_client); - virtual ~SshSession(); - - SshProxy* get_proxy(void) { return m_proxy; } - - TP_SSH_CHANNEL_PAIR* _get_channel_pair(int channel_side, ssh_channel channel); - - void client_ip(const char* ip) { m_client_ip = ip; } - const char* client_ip(void) const { return m_client_ip.c_str(); } - void client_port(ex_u16 port) { m_client_port = port; } - ex_u16 client_port(void) const { return m_client_port; } - - // save record cache into file. be called per 5 seconds. - void save_record(); - // - void check_noop_timeout(ex_u32 t_now, ex_u32 timeout); - void keep_alive(); - - const ex_astr& sid() { return m_sid; } - -protected: - void _thread_loop(); - void _on_stop(); - void _on_stopped(); - - // record an error when session connecting or auth-ing. - void _session_error(int err_code); - // when client<->server channel created, start to record. - bool _record_begin(TP_SSH_CHANNEL_PAIR* cp); - // stop record because channel closed. - void _record_end(TP_SSH_CHANNEL_PAIR* cp); - - void _process_ssh_command(TP_SSH_CHANNEL_PAIR* cp, int from, const ex_u8* data, int len); - void _process_sftp_command(TP_SSH_CHANNEL_PAIR* cp, const ex_u8* data, int len); - -private: - void _run(void); - - void _close_channels(void); - void _check_channels(void); - - static int _on_auth_password_request(ssh_session session, const char *user, const char *password, void *userdata); - static ssh_channel _on_new_channel_request(ssh_session session, void *userdata); - static int _on_client_pty_request(ssh_session session, ssh_channel channel, const char *term, int x, int y, int px, int py, void *userdata); - static int _on_client_shell_request(ssh_session session, ssh_channel channel, void *userdata); - static void _on_client_channel_close(ssh_session session, ssh_channel channel, void* userdata); - static int _on_client_channel_data(ssh_session session, ssh_channel channel, void *data, unsigned int len, int is_stderr, void *userdata); - static int _on_client_pty_win_change(ssh_session session, ssh_channel channel, int width, int height, int pxwidth, int pwheight, void *userdata); - - static int _on_client_channel_subsystem_request(ssh_session session, ssh_channel channel, const char *subsystem, void *userdata); - static int _on_client_channel_exec_request(ssh_session session, ssh_channel channel, const char *command, void *userdata); - - static int _on_server_channel_data(ssh_session session, ssh_channel channel, void *data, unsigned int len, int is_stderr, void *userdata); - static void _on_server_channel_close(ssh_session session, ssh_channel channel, void* userdata); - -private: - SshProxy* m_proxy; - ssh_session m_cli_session; - ssh_session m_srv_session; - - ExThreadLock m_lock; - - ex_astr m_client_ip; - ex_u16 m_client_port; - - TPP_CONNECT_INFO* m_conn_info; - - ex_astr m_sid; - ex_astr m_conn_ip; - ex_u16 m_conn_port; - ex_astr m_acc_name; - ex_astr m_acc_secret; - ex_u32 m_flags; - int m_auth_type; - - bool m_is_logon; - - int m_ssh_ver; - - // 一个ssh_session中可以打开多个ssh_channel - tp_channels m_channels; - - bool m_have_error; - - bool m_need_send_keepalive; - - bool m_recving_from_srv; // 是否正在从服务器接收数据? - bool m_recving_from_cli; // 是否正在从客户端接收数据? - - struct ssh_server_callbacks_struct m_srv_cb; - struct ssh_channel_callbacks_struct m_cli_channel_cb; - struct ssh_channel_callbacks_struct m_srv_channel_cb; -}; - -#endif // __SSH_SESSION_H__ diff --git a/server/tp_core/protocol/ssh/tpssh.cpp b/server/tp_core/protocol/ssh/tpssh.cpp index d102b45..9feba93 100644 --- a/server/tp_core/protocol/ssh/tpssh.cpp +++ b/server/tp_core/protocol/ssh/tpssh.cpp @@ -1,4 +1,4 @@ -#include "ssh_proxy.h" +#include "tpssh_proxy.h" #include "tpp_env.h" #include diff --git a/server/tp_core/protocol/ssh/tpssh_channel.cpp b/server/tp_core/protocol/ssh/tpssh_channel.cpp new file mode 100644 index 0000000..e210bed --- /dev/null +++ b/server/tp_core/protocol/ssh/tpssh_channel.cpp @@ -0,0 +1,401 @@ +#include "tpssh_channel.h" +#include "tpssh_session.h" +#include "tpp_env.h" +#include + +//=============================================================== +// SshChannelPair +//=============================================================== +SshChannelPair::SshChannelPair(SshSession *owner, ssh_channel channel_tp2cli, ssh_channel channel_tp2srv) : + m_owner(owner), + channel_tp2cli(channel_tp2cli), + channel_tp2srv(channel_tp2srv) +{ + last_access_timestamp = (ex_u32) time(nullptr); + + 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(); +} + +SshChannelPair::~SshChannelPair() { + +} + +bool SshChannelPair::record_begin() { +#ifndef TEST_SSH_SESSION_000000 + if (!g_ssh_env.session_begin(m_conn_info, &(db_id))) { + EXLOGE("[ssh] can not save to database, channel begin failed.\n"); + return false; + } + else { + channel_id = db_id; + //EXLOGD("[ssh] [channel:%d] channel begin\n", cp->channel_id); + } + + + if (!g_ssh_env.session_update(db_id, m_conn_info->protocol_sub_type, TP_SESS_STAT_STARTED)) { + EXLOGE("[ssh] [channel:%d] can not update state, channel begin failed.\n", channel_id); + return false; + } + + + rec.begin(g_ssh_env.replay_path.c_str(), L"tp-ssh", db_id, m_conn_info); +#endif + return true; +} + +void SshChannelPair::record_end() { +#ifndef TEST_SSH_SESSION_000000 + if (db_id > 0) { + //EXLOGD("[ssh] [channel:%d] channel end with code: %d\n", channel_id, state); + + // 如果会话过程中没有发生错误,则将其状态改为结束,否则记录下错误值 + if (state == TP_SESS_STAT_RUNNING || state == TP_SESS_STAT_STARTED) + state = TP_SESS_STAT_END; + + g_ssh_env.session_end(m_sid.c_str(), db_id, state); + + db_id = 0; + } + else { + //EXLOGD("[ssh] [channel:%d] when channel end, no db-id.\n", channel_id); + } +#endif +} + +void SshChannelPair::update_session_state(int protocol_sub_type, int state) { + // g_ssh_env.session_update(db_id, protocol_sub_type, state); +} + +void SshChannelPair::close() { + need_close = true; + + if (channel_tp2cli) { + if (!ssh_channel_is_closed(channel_tp2cli)) + ssh_channel_close(channel_tp2cli); + channel_tp2cli = nullptr; + } + + if (channel_tp2srv) { + if (!ssh_channel_is_closed(channel_tp2srv)) + ssh_channel_close(channel_tp2srv); + channel_tp2srv = nullptr; + } +} + +void SshChannelPair::process_ssh_command(ssh_channel ch, const ex_u8 *data, uint32_t len) { + + EXLOGD("[ssh] -- cp 1.\n"); + + if (len == 0) + return; + + if (ch == channel_tp2cli) { + if (len >= 2) { + if (((ex_u8 *) data)[len - 1] == 0x0d) { + // 疑似复制粘贴多行命令一次性执行,将其记录到日志文件中 + ex_astr str((const char *) data, len - 1); + rec.record_command(1, str); + + // cp->process_srv = false; + return; + } + } + + // 客户端输入回车时,可能时执行了一条命令,需要根据服务端返回的数据进行进一步判断 + maybe_cmd = (data[len - 1] == 0x0d); + // if (cp->maybe_cmd) + // EXLOGD("[ssh] maybe cmd.\n"); + + // 有时在执行类似top命令的情况下,输入一个字母'q'就退出程序,没有输入回车,可能会导致后续记录命令时将返回的命令行提示符作为命令 + // 记录下来了,要避免这种情况,排除的方式是:客户端单个字母,后续服务端如果收到的是控制序列 1b 5b xx xx,就不计做命令。 + client_single_char = (len == 1 && isprint(data[0])); + + // cp->process_srv = true; + } + else if (ch == channel_tp2srv) { +// 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 '>': + cmd_char_list.clear(); + cmd_char_pos = cmd_char_list.begin(); + return; + break; + + case 0x4b: { // 'K' + if (0 == esc_arg) { + // 删除光标到行尾的字符串 + cmd_char_list.erase(cmd_char_pos, cmd_char_list.end()); + cmd_char_pos = cmd_char_list.end(); + } + else if (1 == esc_arg) { + // 删除从开始到光标处的字符串 + cmd_char_list.erase(cmd_char_list.begin(), cmd_char_pos); + cmd_char_pos = cmd_char_list.end(); + } + else if (2 == esc_arg) { + // 删除整行 + cmd_char_list.clear(); + cmd_char_pos = 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 (cmd_char_pos != cmd_char_list.end()) + 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 (cmd_char_pos != cmd_char_list.begin()) + 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 (cmd_char_pos != cmd_char_list.end()) + cmd_char_pos = cmd_char_list.erase(cmd_char_pos); + } + esc_mode = false; + break; + } + + case 0x40: { // '@' 插入指定数量的空白字符 + if (esc_arg == 0) + esc_arg = 1; + for (int j = 0; j < esc_arg; ++j) + cmd_char_pos = cmd_char_list.insert(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 (cmd_char_pos != cmd_char_list.begin()) + cmd_char_pos--; + break; + } + case 0x1b: { + if (offset + 1 < len) { + if (data[offset + 1] == 0x5b || data[offset + 1] == 0x5d) { + if (offset == 0 && client_single_char) { + cmd_char_list.clear(); + cmd_char_pos = cmd_char_list.begin(); + maybe_cmd = false; + process_srv = false; + 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 (maybe_cmd) { + if (!cmd_char_list.empty()) { + ex_astr str(cmd_char_list.begin(), cmd_char_list.end()); + // EXLOGD("[ssh] --==--==-- save cmd: [%s]\n", str.c_str()); + rec.record_command(0, str); + } + + cmd_char_list.clear(); + cmd_char_pos = cmd_char_list.begin(); + maybe_cmd = false; + } + } + else { + cmd_char_list.clear(); + cmd_char_pos = cmd_char_list.begin(); + } + process_srv = false; + return; + break; + } + default: + if (cmd_char_pos != cmd_char_list.end()) { + cmd_char_pos = cmd_char_list.erase(cmd_char_pos); + cmd_char_pos = cmd_char_list.insert(cmd_char_pos, ch); + cmd_char_pos++; + } + else { + cmd_char_list.push_back(ch); + cmd_char_pos = cmd_char_list.end(); + } + } + + offset++; + } + } +} + +void SshChannelPair::process_sftp_command(ssh_channel ch, const ex_u8 *data, uint32_t 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 + 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 = nullptr;// (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()); + } + + rec.record_command(0, msg); +} diff --git a/server/tp_core/protocol/ssh/tpssh_channel.h b/server/tp_core/protocol/ssh/tpssh_channel.h new file mode 100644 index 0000000..001ca91 --- /dev/null +++ b/server/tp_core/protocol/ssh/tpssh_channel.h @@ -0,0 +1,83 @@ +#ifndef __TPSSH__CHANNEL_H__ +#define __TPSSH__CHANNEL_H__ + +#include +#include + +#include +#include + +#include "tpssh_rec.h" + +class SshServerSide; + +class SshClientSide; + +class SshSession; + +class SshChannelPair { + friend class SshServerSide; + + friend class SshClientSide; + + friend class SshSession; + +public: + SshChannelPair( + SshSession *owner, + //ssh_session session_tp2cli, + ssh_channel channel_tp2cli, + //ssh_session session_tp2srv, + ssh_channel channel_tp2srv + ); + + virtual ~SshChannelPair(); + + // when client<->server channel created, start to record. + bool record_begin(); + + // stop record because channel closed. + void record_end(); + + void update_session_state(int protocol_sub_type, int state); + + void close(); + + void process_ssh_command(ssh_channel ch, const ex_u8 *data, uint32_t len); + + void process_sftp_command(ssh_channel ch, const ex_u8 *data, uint32_t len); + +protected: + SshSession *m_owner; + int type; // TS_SSH_CHANNEL_TYPE_SHELL or TS_SSH_CHANNEL_TYPE_SFTP + + //ssh_session session_tp2cli; + ssh_channel channel_tp2cli; + //ssh_session session_tp2srv; + ssh_channel channel_tp2srv; + + TppSshRec rec; + ex_u32 last_access_timestamp; + + int state; + int db_id; + int channel_id; // for debug only. + + int win_width; // window width, in char count. + + bool is_first_server_data; + bool need_close; + + // for ssh command record cache. + bool server_ready; + bool maybe_cmd; + bool process_srv; + bool client_single_char; + std::list cmd_char_list; + std::list::iterator cmd_char_pos; +}; + +typedef std::list TPChannelPairs; +typedef std::map channel_map; + +#endif // __TPSSH__CHANNEL_H__ diff --git a/server/tp_core/protocol/ssh/tpssh_cli.cpp b/server/tp_core/protocol/ssh/tpssh_cli.cpp new file mode 100644 index 0000000..a5a6a4b --- /dev/null +++ b/server/tp_core/protocol/ssh/tpssh_cli.cpp @@ -0,0 +1,651 @@ +#include "tpssh_cli.h" +#include "tpssh_session.h" +#include + +SshClientSide::SshClientSide( + SshSession *owner, + const std::string &host_ip, + uint16_t host_port, + const std::string &acc_name, + const std::string &thread_name +) : + ExThreadBase(thread_name.c_str()), + m_owner(owner), + m_host_ip(host_ip), + m_host_port(host_port), + m_acc_name(acc_name) { + + ex_strformat(m_dbg_name, 128, "S%s:%d", host_ip.c_str(), host_port); + + memset(&m_channel_cb, 0, sizeof(m_channel_cb)); + ssh_callbacks_init(&m_channel_cb); + m_channel_cb.userdata = this; + + m_channel_cb.channel_data_function = _on_server_channel_data; + m_channel_cb.channel_close_function = _on_server_channel_close; +} + +SshClientSide::~SshClientSide() { + _on_stop(); + EXLOGD("[ssh] session tp<->server destroy.\n"); +} + +void SshClientSide::channel_closed(ssh_channel ch) { + ExThreadSmartLock locker(m_lock); + auto it = m_channels.begin(); + for(; it != m_channels.end(); ++it) { + if ((*it) == ch) { + m_channels.erase(it); + break; + } + } +} + +void SshClientSide::_thread_loop() { + int err = 0; + + ssh_event event_loop = ssh_event_new(); + if (nullptr == event_loop) { + EXLOGE("[ssh] can not create event loop.\n"); + return; + } + + err = ssh_event_add_session(event_loop, m_session); + if (err != SSH_OK) { + EXLOGE("[ssh] can not add tp2srv session into event loop.\n"); + return; + } + +// int timer_for_keepalive = 0; + do { + err = ssh_event_dopoll(event_loop, 1000); +// EXLOGV("-- tp2srv dopool return %d.\n", err); +// if (m_need_stop_poll) { +// EXLOGE("[ssh] exit loop because need stop pool flag set.\n"); +// break; +// } + if (err == SSH_OK) + continue; + + if (err == SSH_AGAIN) { + // _check_channels(); + +// if (m_need_send_keepalive) { +// timer_for_keepalive++; +// if (timer_for_keepalive >= 60) { +// timer_for_keepalive = 0; +// EXLOGD("[ssh] send keep-alive.\n"); +// ssh_send_ignore(m_session, "keepalive@openssh.com"); +// } +// } + } + else if (err == SSH_ERROR) { + EXLOGE("[ssh] poll event failed, %s\n", ssh_get_error(m_session)); + m_need_stop_poll = true; + break; + } + } while (!m_channels.empty()); + + EXLOGV("[ssh] session tp<->server [%s:%d] are closed.\n", m_host_ip.c_str(), m_host_port); + if (!m_channels.empty()) { + EXLOGW("[ssh] [%s:%d] not all tp<->client channels in this session are closed, close them later.\n", m_host_ip.c_str(), m_host_port); + } + + ssh_event_remove_session(event_loop, m_session); + ssh_event_free(event_loop); +} + +void SshClientSide::_on_stop() {} + +void SshClientSide::_on_stopped() { + // _close_channels(); + { + ExThreadSmartLock locker(m_lock); + + if (m_channels.empty()) + return; + + EXLOGV("[ssh] close all channels.\n"); + + auto it = m_channels.begin(); + for (; it != m_channels.end(); ++it) { +// // 先从通道管理器中摘掉这个通道(因为马上就要关闭它了) +// m_owner->remove_channel(*it); +// if (!ssh_channel_is_closed(*it)) +// ssh_channel_close(*it); +// ssh_channel_free(*it); +// *it = nullptr; + + if (!ssh_channel_is_closed(*it)) + ssh_channel_close(*it); + } + + m_channels.clear(); + } + + if (nullptr != m_session) { + ssh_disconnect(m_session); + ssh_free(m_session); + m_session = nullptr; + } + // m_proxy->session_finished(this); + //m_owner->tp2srv_end(this); +} + +uint32_t SshClientSide::connect() { + // config and try to connect to real SSH host. + EXLOGV("[ssh-%s] try to connect to real SSH server %s:%d\n", m_dbg_name.c_str(), m_host_ip.c_str(), m_host_port); + EXLOGD("[ssh-%s] account=%s\n", m_dbg_name.c_str(), m_acc_name.c_str()); + m_session = ssh_new(); + // ssh_set_blocking(m_session, 0); + + ssh_options_set(m_session, SSH_OPTIONS_HOST, m_host_ip.c_str()); + int port = (int) m_host_port; + ssh_options_set(m_session, SSH_OPTIONS_PORT, &port); + int val = 0; + ssh_options_set(m_session, SSH_OPTIONS_STRICTHOSTKEYCHECK, &val); + +//#ifdef EX_DEBUG +// int flag = SSH_LOG_FUNCTIONS; +// ssh_options_set(m_session, SSH_OPTIONS_LOG_VERBOSITY, &flag); +//#endif + +// int _timeout_cli = 120; // 120 sec. +// ssh_options_set(_this->m_cli_session, SSH_OPTIONS_TIMEOUT, &_timeout_cli); + + +// if (auth_type != TP_AUTH_TYPE_NONE) +// ssh_options_set(m_session, SSH_OPTIONS_USER, acc_name.c_str()); + + if (!m_acc_name.empty()) + ssh_options_set(m_session, SSH_OPTIONS_USER, m_acc_name.c_str()); + + // default timeout is 10 seconds, it is too short for connect progress, so set it to 120 sec. + // usually when sshd config to UseDNS. + int _timeout = 120; // 120 sec. + ssh_options_set(m_session, SSH_OPTIONS_TIMEOUT, &_timeout); + + int rc = 0; + rc = ssh_connect(m_session); + if (rc != SSH_OK) { + EXLOGE("[ssh-%s] can not connect to real SSH server. [%d] %s\n", m_dbg_name.c_str(), rc, ssh_get_error(m_session)); + //_this->m_need_stop_poll = true; + //_this->_session_error(TP_SESS_STAT_ERR_CONNECT); + //return SSH_AUTH_ERROR; + return TP_SESS_STAT_ERR_CONNECT; + } + +// if (ssh_is_blocking(_this->m_cli_session)) +// EXLOGD("[ssh] client session is blocking.\n"); +// if (ssh_is_blocking(m_session)) +// EXLOGD("[ssh] server session is blocking.\n"); + + // once the server are connected, change the timeout back to default. + _timeout = 10; // in seconds. + ssh_options_set(m_session, SSH_OPTIONS_TIMEOUT, &_timeout); + + // get ssh version of host, v1 or v2 + // TODO: from v0.8.5, libssh does not support SSHv1 anymore. + //m_ssh_ver = ssh_get_version(m_session); + //EXLOGW("[ssh] real host is SSHv%d\n", _this->m_ssh_ver); + +#if 0 + const char* banner = ssh_get_issue_banner(m_session); + if (NULL != banner) { + EXLOGE("[ssh] issue banner: %s\n", banner); + } +#endif + + return TP_SESS_STAT_RUNNING; +} + +uint32_t SshClientSide::auth(int auth_type, const std::string &acc_secret) { + EXLOGD("[ssh-%s] auth with type=%d.\n", m_dbg_name.c_str(), auth_type); + m_auth_type = auth_type; + + int rc = 0; + int auth_methods = 0; + if (SSH_AUTH_ERROR != ssh_userauth_none(m_session, nullptr)) { + auth_methods = ssh_userauth_list(m_session, nullptr); + EXLOGV("[ssh-%s] allowed auth method: 0x%08x\n", m_dbg_name.c_str(), auth_methods); + } + + // some host does not give me the auth methods list, so we need try each one. + if (auth_methods == 0) { + EXLOGW("[ssh] can not get allowed auth method, try each method we can.\n"); + auth_methods = SSH_AUTH_METHOD_INTERACTIVE | SSH_AUTH_METHOD_PASSWORD | SSH_AUTH_METHOD_PUBLICKEY; + } + + if (auth_type == TP_AUTH_TYPE_PASSWORD) { + if (!(((auth_methods & SSH_AUTH_METHOD_INTERACTIVE) == SSH_AUTH_METHOD_INTERACTIVE) || ((auth_methods & SSH_AUTH_METHOD_PASSWORD) == SSH_AUTH_METHOD_PASSWORD))) { +// _this->m_need_stop_poll = true; +// _this->_session_error(TP_SESS_STAT_ERR_AUTH_TYPE); +// return SSH_AUTH_ERROR; + return TP_SESS_STAT_ERR_AUTH_TYPE; + } + + int retry_count = 0; + + // first try interactive login mode if server allow. + if ((auth_methods & SSH_AUTH_METHOD_INTERACTIVE) == SSH_AUTH_METHOD_INTERACTIVE) { + retry_count = 0; + rc = ssh_userauth_kbdint(m_session, nullptr, nullptr); + for (;;) { + if (rc == SSH_AUTH_SUCCESS) { + EXLOGW("[ssh] login with interactive mode succeeded.\n"); + return TP_SESS_STAT_RUNNING; + } + + if (rc == SSH_AUTH_AGAIN) { + retry_count += 1; + if (retry_count >= 5) + break; + ex_sleep_ms(500); + rc = ssh_userauth_kbdint(m_session, nullptr, nullptr); + continue; + } + + if (rc != SSH_AUTH_INFO) + break; + + int nprompts = ssh_userauth_kbdint_getnprompts(m_session); + if (nprompts < 0) + break; + if (0 == nprompts) { + rc = ssh_userauth_kbdint(m_session, nullptr, nullptr); + continue; + } + + for (unsigned int iprompt = 0; iprompt < nprompts; ++iprompt) { + char echo = 0; + const char *prompt = ssh_userauth_kbdint_getprompt(m_session, iprompt, &echo); + EXLOGV("[ssh] interactive login prompt: %s\n", prompt); + + rc = ssh_userauth_kbdint_setanswer(m_session, iprompt, acc_secret.c_str()); + if (rc < 0) { + EXLOGE("[ssh] can not set answer for prompt '%s' of '%s:%d', error: [%d] %s\n", prompt, m_host_ip.c_str(), m_host_port, rc, ssh_get_error(m_session)); +// _this->m_need_stop_poll = true; +// _this->_session_error(TP_SESS_STAT_ERR_AUTH_DENIED); +// return SSH_AUTH_ERROR; + return TP_SESS_STAT_ERR_AUTH_DENIED; + } + } + + rc = ssh_userauth_kbdint(m_session, nullptr, nullptr); + } + } + + // and then try password login mode if server allow. + if ((auth_methods & SSH_AUTH_METHOD_PASSWORD) == SSH_AUTH_METHOD_PASSWORD) { + retry_count = 0; + rc = ssh_userauth_password(m_session, nullptr, 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(m_session, nullptr, acc_secret.c_str()); + continue; + } + if (rc == SSH_AUTH_SUCCESS) { + EXLOGW("[ssh] login with password mode succeeded.\n"); +// _this->m_is_logon = true; +// return SSH_AUTH_SUCCESS; + return TP_SESS_STAT_RUNNING; + } + else { + EXLOGE("[ssh] failed to login with password mode, error: [%d] %s\n", rc, ssh_get_error(m_session)); + break; + } + } + } + + EXLOGE("[ssh] failed to login to real SSH server %s:%d with password/interactive mode.\n", m_host_ip.c_str(), m_host_port); + return TP_SESS_STAT_ERR_AUTH_DENIED; + } + else if (auth_type == TP_AUTH_TYPE_PRIVATE_KEY) { + if ((auth_methods & SSH_AUTH_METHOD_PUBLICKEY) != SSH_AUTH_METHOD_PUBLICKEY) { +// _this->m_need_stop_poll = true; +// _this->_session_error(TP_SESS_STAT_ERR_AUTH_TYPE); +// return SSH_AUTH_ERROR; + return TP_SESS_STAT_ERR_AUTH_TYPE; + } + + ssh_key key = nullptr; + if (SSH_OK != ssh_pki_import_privkey_base64(acc_secret.c_str(), nullptr, nullptr, nullptr, &key)) { + EXLOGE("[ssh] can not import private-key for auth.\n"); +// _this->m_need_stop_poll = true; +// _this->_session_error(TP_SESS_STAT_ERR_BAD_SSH_KEY); +// return SSH_AUTH_ERROR; + return TP_SESS_STAT_ERR_BAD_SSH_KEY; + } + + rc = ssh_userauth_publickey(m_session, nullptr, key); + ssh_key_free(key); + + if (rc == SSH_AUTH_SUCCESS) { + EXLOGW("[ssh] login with public-key mode succeeded.\n"); +// _this->m_is_logon = true; +// return SSH_AUTH_SUCCESS; + return TP_SESS_STAT_RUNNING; + } + + EXLOGE("[ssh] failed to login to real SSH server %s:%d with private-key.\n", m_host_ip.c_str(), m_host_port); +// _this->m_need_stop_poll = true; +// _this->_session_error(TP_SESS_STAT_ERR_AUTH_DENIED); +// return SSH_AUTH_ERROR; + return TP_SESS_STAT_ERR_AUTH_DENIED; + } + else if (auth_type == TP_AUTH_TYPE_NONE) { +// _this->_session_error(TP_SESS_STAT_ERR_AUTH_DENIED); +// return SSH_AUTH_ERROR; +// return TP_SESS_STAT_ERR_AUTH_DENIED; + return TP_SESS_STAT_ERR_AUTH_TYPE; + } + else { + EXLOGE("[ssh] invalid auth type: %d.\n", auth_type); +// _this->m_need_stop_poll = true; +// _this->_session_error(TP_SESS_STAT_ERR_AUTH_DENIED); +// return SSH_AUTH_ERROR; + return TP_SESS_STAT_ERR_AUTH_TYPE; + } +} + +ssh_channel SshClientSide::request_new_channel() { + EXLOGV("-- cp x1\n"); + ssh_channel ch = ssh_channel_new(m_session); + if (ch == nullptr) { + EXLOGE("[ssh] can not create channel for communicate with server.\n"); + return nullptr; + } + EXLOGV("-- cp x2\n"); + + if (SSH_OK != ssh_channel_open_session(ch)) { + EXLOGE("[ssh] error opening channel to real server: %s\n", ssh_get_error(m_session)); + ssh_channel_free(ch); + return nullptr; + } + EXLOGV("-- cp x3\n"); + + ssh_set_channel_callbacks(ch, &m_channel_cb); + m_channels.push_back(ch); + + return ch; +} + +//void SshClientSide::close_channel(ssh_channel ch) { +// // 先判断一下这个通道是不是自己管理的 +// +// // 然后关闭此通道 +// if (!ssh_channel_is_closed(ch)) +// ssh_channel_close(ch); +// ssh_channel_free(ch); +//} + +int SshClientSide::_on_server_channel_data(ssh_session session, ssh_channel channel, void *data, unsigned int len, int is_stderr, void *userdata) { + //EXLOG_BIN(static_cast(data), len, " <--- on_server_channel_data [is_stderr=%d]:", is_stderr); + //EXLOGD(" <--- send to server %d B\n", len); + + auto *_this = (SshClientSide *) userdata; + _this->m_owner->update_last_access_time(); + EXLOGD("[ssh] -- cp 1.\n"); + +// // return 0 means data not processed, so this function will be called with this data again. +// if (_this->m_recving_from_cli) { +// // EXLOGD("recving from cli...try again later...\n"); +// return 0; +// } +// if (_this->m_recving_from_srv) { +// // EXLOGD("recving from srv...try again later...\n"); +// return 0; +// } + + auto cp = _this->m_owner->get_channel_pair(channel); + if (nullptr == cp) { + EXLOGE("[ssh] when receive server channel data, not found channel pair.\n"); + return SSH_ERROR; + } + +// _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; + } + } + + //cp->process_ssh_command(channel, (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); + } + EXLOGD("[ssh] -- cp 2.\n"); + + int ret = 0; + ex_bin ext_data; + const uint8_t *_send_data = static_cast(data); + unsigned int _send_len = len; + + + // 收到第一包服务端返回的数据时,在输出数据之前显示一些自定义的信息 +#if 1 + if (cp->is_first_server_data && !is_stderr && cp->type == TS_SSH_CHANNEL_TYPE_SHELL) { + cp->is_first_server_data = false; + + char buf[512] = {0}; + + const char *auth_mode = nullptr; + 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, 64); +#else + int w = std::min(cp->win_width, 64); +#endif + ex_astr line(w, '='); + + snprintf(buf, sizeof(buf), + "\r\n"\ + "%s\r\n"\ + "Teleport SSH Bastion Server...\r\n"\ + " - teleport to %s:%d [%d]\r\n"\ + " - authroized by %s\r\n"\ + "%s\r\n"\ + "\r\n\r\n", + line.c_str(), + _this->m_host_ip.c_str(), + _this->m_host_port, + cp->db_id, + auth_mode, + line.c_str() + ); + + auto buf_len = static_cast(strlen(buf)); + _send_len = len + buf_len; + ext_data.resize(_send_len); + memset(&ext_data[0], 0, _send_len); + memcpy(&ext_data[0], buf, buf_len); + memcpy(&ext_data[buf_len], data, len); + + EXLOGV("[ssh] title:\n%s\n", &ext_data[0]); + + _send_data = &ext_data[0]; + +// ret = ssh_channel_write(cp->channel_tp2cli, &_data[0], _data.size()); +// +// if (ret == SSH_ERROR) { +// EXLOGE("[ssh] send data(%dB) to client failed. [%d] %s\n", len, ret, ssh_get_error(_this->m_owner->tp2cli())); +// 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 len; + + } +#endif + +#if 1 + //static int idx = 0; + + // ssh_set_blocking(_this->m_session, 0); + ssh_channel_set_blocking(cp->channel_tp2cli, 0); + EXLOGD("[ssh] -- cp 3.\n"); + + unsigned int sent_len = 0; + do { + // 直接转发数据到客户端 + if (is_stderr) + ret = ssh_channel_write_stderr(cp->channel_tp2cli, _send_data + sent_len, _send_len - sent_len); + else + ret = ssh_channel_write(cp->channel_tp2cli, _send_data + sent_len, _send_len - sent_len); + + if (ret > 0) { + sent_len += ret; + } + else if (ret == SSH_AGAIN) { + EXLOGD("ssh_channel_write() need send again.\n"); + ex_sleep_ms(50); + continue; + } + else { + break; + } + }while(sent_len >= _send_len); + + // ssh_set_blocking(_this->m_session, 1); + ssh_channel_set_blocking(cp->channel_tp2cli, 1); + EXLOGD("[ssh] -- cp 4.\n"); + +#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 (nullptr != _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 (nullptr != _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] %s\n", _send_len, ret, ssh_get_error(_this->m_owner->tp2cli())); + ssh_channel_close(channel); + //cp->need_close = true; + //_this->m_need_stop_poll = true; + } + else if (sent_len != _send_len) { + EXLOGW("[ssh] received server data, got %dB, processed %dB.\n", _send_len, sent_len); + } + +// _this->m_recving_from_srv = false; + return sent_len > len ? len : sent_len; +} + +void SshClientSide::_on_server_channel_close(ssh_session session, ssh_channel channel, void *userdata) { + // 注意,此回调会在通道已经被关闭之后调用 + // 无需做额外操作,关闭的通道会由 SshSession 实例定期清理(包括关闭对端通道) + EXLOGV("[ssh] channel tp<->server closed.\n"); + +// auto *_this = (SshClientSide *) userdata; +// +// auto cp = _this->m_owner->get_channel_pair(channel); +// if (nullptr == cp) { +// EXLOGE("[ssh] when server channel close, not found channel pair.\n"); +// return; +// } +// //cp->last_access_timestamp = (ex_u32)time(nullptr); +// //cp->need_close = true; +// //_this->m_need_stop_poll = 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 == nullptr) { +// 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_need_stop_poll = true; +// } +// } +} diff --git a/server/tp_core/protocol/ssh/tpssh_cli.h b/server/tp_core/protocol/ssh/tpssh_cli.h new file mode 100644 index 0000000..cd71b58 --- /dev/null +++ b/server/tp_core/protocol/ssh/tpssh_cli.h @@ -0,0 +1,60 @@ +#ifndef __SSH_CLIENT_SIDE_H__ +#define __SSH_CLIENT_SIDE_H__ + +#include +#include +#include +#include + +class SshSession; +class SshClientSide : public ExThreadBase { +public: + SshClientSide(SshSession* owner, const std::string &host_ip, uint16_t host_port, const std::string &acc_name, const std::string &thread_name); + virtual ~SshClientSide(); + + uint32_t connect(); + uint32_t auth(int auth_type, const std::string &acc_secret); + + ssh_channel request_new_channel(); + + void channel_closed(ssh_channel ch); + +protected: + void _thread_loop(); + + void _on_stop(); + + void _on_stopped(); + + static int _on_server_channel_data(ssh_session session, ssh_channel channel, void *data, unsigned int len, int is_stderr, void *userdata); + + static void _on_server_channel_close(ssh_session session, ssh_channel channel, void *userdata); + + +private: + SshSession* m_owner; + std::string m_host_ip; + uint16_t m_host_port; + ex_astr m_acc_name; + + ssh_session m_session; + struct ssh_channel_callbacks_struct m_channel_cb; + + std::list m_channels; + + ExThreadLock m_lock; + + std::string m_dbg_name; + + // 是否需要停止ssh事件处理循环 + bool m_need_stop_poll; + + bool m_need_send_keepalive; + + + ex_astr m_acc_secret; + int m_auth_type; +}; + + +#endif // __SSH_CLIENT_SIDE_H__ diff --git a/server/tp_core/protocol/ssh/ssh_proxy.cpp b/server/tp_core/protocol/ssh/tpssh_proxy.cpp similarity index 58% rename from server/tp_core/protocol/ssh/ssh_proxy.cpp rename to server/tp_core/protocol/ssh/tpssh_proxy.cpp index ba3a75e..a078235 100644 --- a/server/tp_core/protocol/ssh/ssh_proxy.cpp +++ b/server/tp_core/protocol/ssh/tpssh_proxy.cpp @@ -1,20 +1,23 @@ -#include "ssh_proxy.h" +#include "tpssh_proxy.h" #include "tpp_env.h" SshProxy g_ssh_proxy; SshProxy::SshProxy() : ExThreadBase("ssh-proxy-thread"), - m_bind(NULL) { + m_bind(nullptr) { + // m_session_dbg_base_id = 0; m_timer_counter_check_noop = 0; - m_timer_counter_keep_alive= 0; - m_noop_timeout_sec = 900; // default to 15 minutes. + m_timer_counter_keep_alive = 0; + m_cfg_noop_timeout_sec = 900; // default to 15 minutes. m_listener_running = false; } SshProxy::~SshProxy() { - if (NULL != m_bind) + if (!m_bind) { ssh_bind_free(m_bind); + m_bind = nullptr; + } //ssh_finalize(); } @@ -24,7 +27,7 @@ bool SshProxy::init() { m_host_port = g_ssh_env.bind_port; m_bind = ssh_bind_new(); - if (NULL == m_bind) { + if (!m_bind) { EXLOGE("[ssh] can not create bind.\n"); return false; } @@ -64,44 +67,58 @@ void SshProxy::timer() { m_timer_counter_check_noop++; m_timer_counter_keep_alive++; + // clean closed session. + _clean_closed_session(); + // check no-op per 5 seconds. + // save record per 5 seconds. if (m_timer_counter_check_noop >= 5) { m_timer_counter_check_noop = 0; ExThreadSmartLock locker(m_lock); - ex_u32 t_now = (ex_u32)time(NULL); + auto t_now = (uint32_t) time(nullptr); - 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); + auto it = m_sessions.begin(); + for (; it != m_sessions.end(); ++it) { + (*it)->save_record(); + if (0 != m_cfg_noop_timeout_sec) + (*it)->check_noop_timeout(t_now, m_cfg_noop_timeout_sec); } } - // send keep-alive every 60 seconds - if (m_timer_counter_keep_alive >= 60) { - m_timer_counter_keep_alive = 0; +// // send keep-alive every 60 seconds +// if (m_timer_counter_keep_alive >= 60) { +// m_timer_counter_keep_alive = 0; +// ExThreadSmartLock locker(m_lock); +// auto it = m_sessions.begin(); +// for (; it != m_sessions.end(); ++it) { +// (*it)->keep_alive(); +// } +// } + + { ExThreadSmartLock locker(m_lock); - ts_ssh_sessions::iterator it; - for (it = m_sessions.begin(); it != m_sessions.end(); ++it) { - it->first->keep_alive(); + + auto it = m_sessions.begin(); + for (; it != m_sessions.end(); ++it) { + (*it)->check_channels(); } } } void SshProxy::set_cfg(ex_u32 noop_timeout) { - m_noop_timeout_sec = noop_timeout; + m_cfg_noop_timeout_sec = noop_timeout; } void SshProxy::kill_sessions(const ex_astrs &sessions) { ExThreadSmartLock locker(m_lock); - ts_ssh_sessions::iterator it = m_sessions.begin(); + auto it = m_sessions.begin(); for (; it != m_sessions.end(); ++it) { - for (size_t i = 0; i < sessions.size(); ++i) { - if (it->first->sid() == sessions[i]) { - EXLOGW("[ssh] try to kill %s\n", sessions[i].c_str()); - it->first->check_noop_timeout(0, 0); // 立即结束 + //for (size_t i = 0; i < sessions.size(); ++i) { + for (const auto &session : sessions) { + if ((*it)->sid() == session) { + EXLOGW("[ssh] try to kill %s\n", session.c_str()); + (*it)->check_noop_timeout(0, 0); // 立即结束 } } } @@ -112,74 +129,74 @@ void SshProxy::_thread_loop() { for (;;) { // 注意,ssh_new()出来的指针,如果遇到停止标志,本函数内部就释放了,否则这个指针交给了SshSession类实例管理,其析构时会释放。 - ssh_session sess_to_client = ssh_new(); + ssh_session sess_tp2cli = ssh_new(); // #ifdef EX_DEBUG // int flag = SSH_LOG_FUNCTIONS; -// ssh_options_set(sess_to_client, SSH_OPTIONS_LOG_VERBOSITY, &flag); +// ssh_options_set(sess_tp2cli, SSH_OPTIONS_LOG_VERBOSITY, &flag); // #endif - //ssh_set_blocking(sess_to_client, 1); + // ssh_set_blocking(sess_tp2cli, 0); - struct sockaddr_storage sock_client; - char ip[32] = {0}; - int len = sizeof(ip); - - if (ssh_bind_accept(m_bind, sess_to_client) != SSH_OK) { + struct sockaddr_storage sock_client{}; + if (ssh_bind_accept(m_bind, sess_tp2cli) != SSH_OK) { EXLOGE("[ssh] accepting a connection failed: %s.\n", ssh_get_error(m_bind)); continue; } EXLOGD("[ssh] ssh_bind_accept() returned...\n"); if (m_need_stop) { - ssh_free(sess_to_client); + ssh_free(sess_tp2cli); break; } - SshSession *sess = new SshSession(this, sess_to_client); - + char ip[32] = {0}; + int len = sizeof(ip); #ifdef EX_OS_WIN32 getpeername(ssh_get_fd(sess_to_client), (struct sockaddr *) &sock_client, &len); #else - getpeername(ssh_get_fd(sess_to_client), (struct sockaddr*)&sock_client, (unsigned int*)&len); + getpeername(ssh_get_fd(sess_tp2cli), (struct sockaddr *) &sock_client, (unsigned int *) &len); #endif - sockaddr_in *addrin = (sockaddr_in *) &sock_client; + auto *addrin = (sockaddr_in *) &sock_client; - if (0 == ex_ip4_name(addrin, ip, sizeof(ip))) { - sess->client_ip(ip); - sess->client_port(addrin->sin_port); + if (0 != ex_ip4_name(addrin, ip, sizeof(ip))) { + ssh_free(sess_tp2cli); + EXLOGE("[ssh] can not parse address of client.\n"); + continue; } - - EXLOGV("[ssh] ------ NEW SSH CLIENT [%s:%d] ------\n", sess->client_ip(), sess->client_port()); - + // todo: add debug id for session and channel. + auto s = new SshSession(this, ip, addrin->sin_port); + EXLOGV("[ssh] ------ NEW SSH SESSION [%s] ------\n", s->dbg_name().c_str()); { ExThreadSmartLock locker(m_lock); - m_sessions.insert(std::make_pair(sess, 0)); + m_sessions.push_back(s); } - sess->start(); + if (!s->start(sess_tp2cli)) { + EXLOGE("[ssh] can not start session [%s]\n", s->dbg_name().c_str()); + continue; + } } - // 等待所有工作线程退出 - //m_thread_mgr.stop_all(); - + // listen stoped, ask all sessions stop too. { ExThreadSmartLock locker(m_lock); - ts_ssh_sessions::iterator it = m_sessions.begin(); + auto it = m_sessions.begin(); for (; it != m_sessions.end(); ++it) { - it->first->check_noop_timeout(0, 0); // 立即结束 + (*it)->check_noop_timeout(0, 0); // 立即结束 } } - for(;;) - { + // and wait for all sessions stop. + for (;;) { { ExThreadSmartLock locker(m_lock); + timer(); if (m_sessions.empty()) break; - ex_sleep_ms(100); + ex_sleep_ms(500); } } @@ -217,17 +234,17 @@ void SshProxy::_on_stop() { // m_thread_mgr.stop_all(); } -void SshProxy::session_finished(SshSession *sess) { - // TODO: 向核心模块汇报此会话终止,以减少对应连接信息的引用计数 - +void SshProxy::_clean_closed_session() { ExThreadSmartLock locker(m_lock); - ts_ssh_sessions::iterator it = m_sessions.find(sess); - if (it != m_sessions.end()) { - m_sessions.erase(it); - EXLOGV("[ssh] client %s:%d session removed.\n", sess->client_ip(), sess->client_port()); - } else { - EXLOGW("[ssh] when session %s:%d end, it not in charge.\n", sess->client_ip(), sess->client_port()); + auto it = m_sessions.begin(); + for (; it != m_sessions.end(); ) { + if (!(*it)->is_running()) { + EXLOGV("[ssh-%s] session removed.\n", (*it)->dbg_name().c_str()); + delete (*it); + m_sessions.erase(it++); + } + else { + ++it; + } } - - delete sess; } diff --git a/server/tp_core/protocol/ssh/ssh_proxy.h b/server/tp_core/protocol/ssh/tpssh_proxy.h similarity index 71% rename from server/tp_core/protocol/ssh/ssh_proxy.h rename to server/tp_core/protocol/ssh/tpssh_proxy.h index ffcb26e..77576d2 100644 --- a/server/tp_core/protocol/ssh/ssh_proxy.h +++ b/server/tp_core/protocol/ssh/tpssh_proxy.h @@ -1,11 +1,11 @@ #ifndef __SSH_PROXY_H__ #define __SSH_PROXY_H__ -#include "ssh_session.h" +#include "tpssh_session.h" #include -typedef std::map ts_ssh_sessions; +typedef std::list tp_ssh_sessions; class SshProxy : public ExThreadBase { @@ -18,12 +18,13 @@ public: void set_cfg(ex_u32 noop_timeout); void kill_sessions(const ex_astrs& sessions); - void session_finished(SshSession* sess); - protected: void _thread_loop(); void _on_stop(); + // 异步方式清理已经结束的会话实例 + void _clean_closed_session(); + private: ssh_bind m_bind; int m_timer_counter_check_noop; @@ -35,12 +36,10 @@ private: ex_astr m_host_ip; int m_host_port; - ts_ssh_sessions m_sessions; + tp_ssh_sessions m_sessions; -// ExThreadManager m_thread_mgr; - - // - ex_u32 m_noop_timeout_sec; + // + ex_u32 m_cfg_noop_timeout_sec; }; extern SshProxy g_ssh_proxy; diff --git a/server/tp_core/protocol/ssh/ssh_recorder.cpp b/server/tp_core/protocol/ssh/tpssh_rec.cpp similarity index 99% rename from server/tp_core/protocol/ssh/ssh_recorder.cpp rename to server/tp_core/protocol/ssh/tpssh_rec.cpp index 0190493..c761bc7 100644 --- a/server/tp_core/protocol/ssh/ssh_recorder.cpp +++ b/server/tp_core/protocol/ssh/tpssh_rec.cpp @@ -1,4 +1,4 @@ -#include "ssh_recorder.h" +#include "tpssh_rec.h" //#include static ex_u8 TPP_RECORD_MAGIC[4] = {'T', 'P', 'P', 'R'}; diff --git a/server/tp_core/protocol/ssh/ssh_recorder.h b/server/tp_core/protocol/ssh/tpssh_rec.h similarity index 100% rename from server/tp_core/protocol/ssh/ssh_recorder.h rename to server/tp_core/protocol/ssh/tpssh_rec.h diff --git a/server/tp_core/protocol/ssh/tpssh_session.cpp b/server/tp_core/protocol/ssh/tpssh_session.cpp new file mode 100644 index 0000000..4f6df8b --- /dev/null +++ b/server/tp_core/protocol/ssh/tpssh_session.cpp @@ -0,0 +1,223 @@ +#include "tpssh_session.h" +#include "tpssh_proxy.h" +#include + +SshSession::SshSession(SshProxy *owner, const char *client_ip, uint16_t client_port) : + m_owner(owner), + m_client_ip(client_ip), + m_client_port(client_port), + m_running(true), + m_tp2cli(nullptr), + m_tp2srv(nullptr) { + ex_strformat(m_dbg_name, 128, "C%s:%d", client_ip, client_port); + m_last_access_timestamp = 0; +} + +SshSession::~SshSession() { + if (m_tp2cli) { + EXLOGE("[ssh-%s] when destroy, tp<->client not finished yet.\n", m_dbg_name.c_str()); + } + if (m_tp2srv) { + EXLOGE("[ssh-%s] when destroy, tp<->server not finished yet.\n", m_dbg_name.c_str()); + } +} + +bool SshSession::start(ssh_session sess_tp2cli) { + if (m_dbg_name.empty()) + return false; + + std::string thread_name; + ex_strformat(thread_name, 128, "ssh-thread-tp2cli-%s", m_dbg_name.c_str()); + m_tp2cli = new SshServerSide(this, sess_tp2cli, thread_name); + if (!m_tp2cli) { + m_running = false; + return false; + } + + return m_tp2cli->start(); +} + +SshClientSide *SshSession::connect_to_host( + const std::string &host_ip, + uint16_t host_port, + const std::string &acc_name, + uint32_t &rv +) { + if (m_tp2srv) { + delete m_tp2srv; + } + + std::string thread_name; + ex_strformat(thread_name, 128, "ssh-thread-tp2srv-S%s:%d", host_ip.c_str(), host_port); + m_tp2srv = new SshClientSide(this, host_ip, host_port, acc_name, thread_name); + if (!m_tp2srv) { + EXLOGE("[ssh-%s] create tp2srv instance failed.\n", m_dbg_name.c_str()); + // TODO: set last error. + on_error(TP_SESS_STAT_ERR_INTERNAL); + return nullptr; + } + + rv = m_tp2srv->connect(); + if (rv != TP_SESS_STAT_RUNNING) { + delete m_tp2srv; + m_tp2srv = nullptr; + } + + return m_tp2srv; +} + +void SshSession::on_error(int error_code) { +#ifndef TEST_SSH_SESSION_000000 + int db_id = 0; + if (!g_ssh_env.session_begin(m_conn_info, &db_id) || db_id == 0) { + EXLOGE("[ssh-%s] can not write session error to database.\n", m_dbg_name.c_str()); + return; + } + + g_ssh_env.session_end(m_sid.c_str(), db_id, err_code); +#endif +} + +void SshSession::save_record() { + ExThreadSmartLock locker(m_lock); + + auto it = m_pairs.begin(); + for (; it != m_pairs.end(); ++it) { + (*it)->rec.save_record(); + } +} + +void SshSession::keep_alive() { +// m_need_send_keepalive = true; +// EXLOGD("[ssh] keep-alive.\n"); +// if(m_srv_session) +// ssh_send_keepalive(m_srv_session); +// if (m_session) +// ssh_send_keepalive(m_session); +} + +void SshSession::check_noop_timeout(ex_u32 t_now, ex_u32 timeout) { + ExThreadSmartLock locker(m_lock); + + bool need_stop = false; + + if (t_now == 0) { + EXLOGW("[ssh-%s] try close channel by kill.\n", m_dbg_name.c_str()); + need_stop = true; + } + else if (m_last_access_timestamp != 0 && t_now - m_last_access_timestamp > timeout) { + if (!m_channel_map.empty()) { + EXLOGW("[ssh-%s] try close channel by timeout.\n", m_dbg_name.c_str()); + need_stop = true; + } + } + + if (need_stop) { + if (m_tp2cli) + m_tp2cli->stop(); + if (m_tp2srv) + m_tp2srv->stop(); + } +} + +void SshSession::check_channels() { + // 尚未准备好,不用进行检查 + if (m_last_access_timestamp == 0) + return; + + ExThreadSmartLock locker(m_lock); + + auto it = m_pairs.begin(); + for (; it != m_pairs.end();) { + ssh_channel ch_tp2cli = (*it)->channel_tp2cli; + ssh_channel ch_tp2srv = (*it)->channel_tp2srv; + + // 判断是否需要关闭通道: + // 如果通道一侧打开过,但现在已经关闭了,则需要关闭另外一侧。 + bool need_close = (*it)->need_close; + if (!need_close) { + if (ch_tp2cli != nullptr && ssh_channel_is_closed(ch_tp2cli)) + need_close = true; + if (ch_tp2srv != nullptr && ssh_channel_is_closed(ch_tp2srv)) + need_close = true; + } + + if (need_close) { + if (ch_tp2cli != nullptr) { + if (!ssh_channel_is_closed(ch_tp2cli)) + ssh_channel_close(ch_tp2cli); + m_tp2cli->channel_closed(ch_tp2cli); + ssh_channel_free(ch_tp2cli); + auto it_map = m_channel_map.find(ch_tp2cli); + if (it_map != m_channel_map.end()) + m_channel_map.erase(it_map); + ch_tp2cli = nullptr; + } + + if (ch_tp2srv != nullptr) { + if (!ssh_channel_is_closed(ch_tp2srv)) + ssh_channel_close(ch_tp2srv); + m_tp2srv->channel_closed(ch_tp2srv); + ssh_channel_free(ch_tp2srv); + auto it_map = m_channel_map.find(ch_tp2srv); + if (it_map != m_channel_map.end()) + m_channel_map.erase(it_map); + ch_tp2srv = nullptr; + } + } + + if (ch_tp2cli == nullptr && ch_tp2srv == nullptr) { + (*it)->record_end(); + delete(*it); + m_pairs.erase(it++); + } + else { + ++it; + } + } + + // 是否要关闭会话(当两端的通道均关闭时,会话也就结束了,由SshProxy中的定时任务来释放此会话实例) + if (m_pairs.empty()) { + EXLOGV("[ssh-%s] all channels closed, close this session.\n", m_dbg_name.c_str()); + + // todo: 不要直接delete,而是先判断一个线程是否在运行的标记,当线程结束,才删除,否则会导致崩溃。 + delete m_tp2srv; + delete m_tp2cli; + m_tp2srv = nullptr; + m_tp2cli = nullptr; + m_running = false; + } +} + +// -------------------------- +// 通道管理 +// -------------------------- +bool SshSession::make_channel_pair(ssh_channel ch_tp2cli, ssh_channel ch_tp2srv) { + ExThreadSmartLock locker(m_lock); + + auto it = m_channel_map.find(ch_tp2cli); + if (it != m_channel_map.end()) + return false; + it = m_channel_map.find(ch_tp2srv); + if (it != m_channel_map.end()) + return false; + + auto _pair = new SshChannelPair(this, ch_tp2cli, ch_tp2srv); + m_pairs.push_back(_pair); + + m_channel_map.insert(std::make_pair(ch_tp2cli, _pair)); + m_channel_map.insert(std::make_pair(ch_tp2srv, _pair)); + + return true; +} + +SshChannelPair *SshSession::get_channel_pair(ssh_channel ch) { + ExThreadSmartLock locker(m_lock); + + auto it = m_channel_map.find(ch); + if (it == m_channel_map.end()) + return nullptr; + + it->second->last_access_timestamp = static_cast(time(nullptr)); + return it->second; +} diff --git a/server/tp_core/protocol/ssh/tpssh_session.h b/server/tp_core/protocol/ssh/tpssh_session.h new file mode 100644 index 0000000..bc4ae9f --- /dev/null +++ b/server/tp_core/protocol/ssh/tpssh_session.h @@ -0,0 +1,95 @@ +#ifndef __TPSSH_SESSION_H__ +#define __TPSSH_SESSION_H__ + +#include +#include +#include + +#include "tpssh_srv.h" +#include "tpssh_cli.h" +#include "tpssh_channel.h" + +#define TEST_SSH_SESSION_000000 + + +class SshProxy; + +class SshSession { +public: + SshSession(SshProxy *owner, const char *client_ip, uint16_t client_port); + + virtual ~SshSession(); + + bool start(ssh_session sess_tp2cli); + + // 返回会话是否还在运行中,当会话中与客户端和服务端的连接都断开了,则视作会话结束了。 + // SshProxy实例每一秒钟会检查一次会话,如果已经结束了,则删除本会话实例。 + bool is_running() { return m_running; } + + void on_error(int error_code); + + SshClientSide *connect_to_host( + const std::string &host_ip, + uint16_t host_port, + const std::string &acc_name, + uint32_t &rv + ); + + SshClientSide *tp2srv() { return m_tp2srv; } + + SshServerSide *tp2cli() { return m_tp2cli; } + +// void tp2cli_end(SshServerSide *tp2cli); +// +// void tp2srv_end(SshClientSide *tp2srv); + + void check_noop_timeout(ex_u32 t_now, ex_u32 timeout); + + void check_channels(); + + // save record cache into file. be called per 5 seconds. + void save_record(); + + void keep_alive(); + + void update_last_access_time() { m_last_access_timestamp = static_cast(time(nullptr)); } + // void update_last_access_time(uint32_t last_time) { m_last_access_timestamp = last_time; } + + const std::string &sid() { return m_sid; }; + + const std::string &dbg_name() { return m_dbg_name; } + + // -------------------------- + // 通道管理 + // -------------------------- + bool make_channel_pair(ssh_channel ch_tp2cli, ssh_channel ch_tp2srv); + + SshChannelPair *get_channel_pair(ssh_channel ch); + +// void remove_channel(ssh_channel ch); + + +private: + SshProxy *m_owner; + std::string m_dbg_name; + std::string m_sid; + + std::string m_client_ip; + uint16_t m_client_port; + + ExThreadLock m_lock; + + bool m_running; + SshServerSide *m_tp2cli; + SshClientSide *m_tp2srv; + + // 此会话的最后数据通过时间 + uint32_t m_last_access_timestamp; + + TPChannelPairs m_pairs; + // 用于快速查找 + channel_map m_channel_map; +}; + + +#endif // __TPSSH_SESSION_H__ diff --git a/server/tp_core/protocol/ssh/tpssh_srv.cpp b/server/tp_core/protocol/ssh/tpssh_srv.cpp new file mode 100644 index 0000000..76cb918 --- /dev/null +++ b/server/tp_core/protocol/ssh/tpssh_srv.cpp @@ -0,0 +1,878 @@ +#include "tpssh_srv.h" +#include "tpssh_session.h" +// #include "tpp_env.h" + +#include +#include + +#if 0 +TP_SSH_CHANNEL_PAIR::TP_SSH_CHANNEL_PAIR() { + type = TS_SSH_CHANNEL_TYPE_UNKNOWN; + cli_channel = nullptr; + srv_channel = nullptr; + last_access_timestamp = (ex_u32) time(nullptr); + + 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(); +} +#endif + +SshServerSide::SshServerSide(SshSession *s, ssh_session sess_tp2cli, const std::string &thread_name) : + ExThreadBase(thread_name.c_str()), + m_owner(s), + m_session(sess_tp2cli), + m_conn_info(nullptr) { + + m_auth_error = TPE_FAILED; + m_first_auth = true; + m_allow_user_input_password = false; + m_auth_fail_count = 0; + + m_auth_type = TP_AUTH_TYPE_PASSWORD; + + m_ssh_ver = 2; // default to SSHv2 + + m_is_logon = false; + m_need_stop_poll = false; + m_need_send_keepalive = 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_channel_with_cli_cb, 0, sizeof(m_channel_with_cli_cb)); + ssh_callbacks_init(&m_channel_with_cli_cb); + m_channel_with_cli_cb.userdata = this; + + m_srv_cb.auth_password_function = _on_auth_password_request; + m_srv_cb.channel_open_request_session_function = _on_new_channel_request; + + m_channel_with_cli_cb.channel_data_function = _on_client_channel_data; + // channel_eof_function + m_channel_with_cli_cb.channel_close_function = _on_client_channel_close; + // channel_signal_function + // channel_exit_status_function + // channel_exit_signal_function + m_channel_with_cli_cb.channel_pty_request_function = _on_client_pty_request; + m_channel_with_cli_cb.channel_shell_request_function = _on_client_shell_request; + // channel_auth_agent_req_function + // channel_x11_req_function + m_channel_with_cli_cb.channel_pty_window_change_function = _on_client_pty_win_change; + m_channel_with_cli_cb.channel_exec_request_function = _on_client_channel_exec_request; + // channel_env_request_function + m_channel_with_cli_cb.channel_subsystem_request_function = _on_client_channel_subsystem_request; + + ssh_set_server_callbacks(m_session, &m_srv_cb); +} + +SshServerSide::~SshServerSide() { + _on_stop(); + + if (m_conn_info) { +#ifdef TEST_SSH_SESSION_000000 + delete m_conn_info; +#else + g_ssh_env.free_connect_info(m_conn_info); +#endif + } + m_conn_info = nullptr; + + EXLOGD("[ssh] session tp<->client destroy: %s.\n", m_sid.c_str()); +} + +bool SshServerSide::init() { + return true; +} + +void SshServerSide::_on_stop() { + // ExThreadBase::_on_stop(); +} + +void SshServerSide::_on_stopped() { + _close_channels(); + + if (nullptr != m_session) { + ssh_disconnect(m_session); + ssh_free(m_session); + m_session = nullptr; + } + // m_proxy->session_finished(this); + //m_owner->tp2cli_end(this); +} + +#if 0 +void SshServerSide::_session_error(int err_code) { +#ifndef TEST_SSH_SESSION_000000 + 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); +#endif +} +#endif + + +void SshServerSide::_close_channels() { + ExThreadSmartLock locker(m_lock); + + if (m_channels.empty()) + return; + + EXLOGV("[ssh] close all channels.\n"); + + auto it = m_channels.begin(); + for (; it != m_channels.end(); ++it) { +// // 先从通道管理器中摘掉这个通道(因为马上就要关闭它了) +// m_owner->remove_channel(*it); +// if (!ssh_channel_is_closed(*it)) +// ssh_channel_close(*it); +// ssh_channel_free(*it); +// *it = nullptr; + + if (!ssh_channel_is_closed(*it)) { + ssh_channel_close(*it); + ssh_channel_free(*it); + } + } + + m_channels.clear(); +} +#if 0 +void SshServerSide::_check_channels() { + ExThreadSmartLock locker(m_lock); + + auto it = m_channel_mgr.begin(); + for (; it != m_channel_mgr.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 != nullptr && ssh_channel_is_closed(cli) && srv != nullptr && ssh_channel_is_closed(srv)) + || (cli == nullptr && srv == nullptr) + || (cli == nullptr && srv != nullptr && ssh_channel_is_closed(srv)) + || (srv == nullptr && cli != nullptr && ssh_channel_is_closed(cli)) + ) { + if (cli) { + ssh_channel_free(cli); + cli = nullptr; + } + if (srv) { + ssh_channel_free(srv); + srv = nullptr; + } + _record_end((*it)); + + delete (*it); + m_channel_mgr.erase(it++); + + continue; + } + + // check if channel need close + bool need_close = (*it)->need_close; + if (!need_close) { + if (cli != nullptr && ssh_channel_is_closed(cli)) { + need_close = true; + } + if (srv != nullptr && ssh_channel_is_closed(srv)) { + need_close = true; + } + } + + if (need_close) { + if (cli != nullptr && !ssh_channel_is_closed(cli)) { + ssh_channel_close(cli); + } + + if (srv != nullptr && !ssh_channel_is_closed(srv)) { + ssh_channel_close(srv); + } + } + + ++it; + } +} +#endif + +void SshServerSide::channel_closed(ssh_channel ch) { + ExThreadSmartLock locker(m_lock); + auto it = m_channels.begin(); + for(; it != m_channels.end(); ++it) { + if ((*it) == ch) { + m_channels.erase(it); + break; + } + } +} + + +void SshServerSide::_thread_loop() { + // 安全连接(密钥交换) + int err = ssh_handle_key_exchange(m_session); + if (err != SSH_OK) { + EXLOGE("[ssh] key exchange with client failed: %s\n", ssh_get_error(m_session)); + return; + } + + ssh_event event_loop = ssh_event_new(); + if (nullptr == event_loop) { + EXLOGE("[ssh] can not create event loop.\n"); + return; + } + err = ssh_event_add_session(event_loop, m_session); + if (err != SSH_OK) { + EXLOGE("[ssh] can not add tp2client session into event loop.\n"); + return; + } + + //int timer_for_auth = 0; + + // 认证,并打开一个通道 + while (!(m_is_logon && !m_channels.empty())) { + if (m_need_stop_poll) { + EXLOGE("[ssh] error when connect and auth.\n"); + break; + } + err = ssh_event_dopoll(event_loop, 1000); + if (err == SSH_AGAIN) { + //timer_for_auth++; + //if (timer_for_auth >= 60) { + // EXLOGE("[ssh] can not auth and open channel in 60 seconds, session end.\n"); + // m_need_stop_poll = true; + // break; + //} + } + else if (err != SSH_OK) { + EXLOGE("[ssh] error when event poll: %s\n", ssh_get_error(m_session)); + m_need_stop_poll = true; + break; + } + } + + if (m_need_stop_poll) { + ssh_event_remove_session(event_loop, m_session); + ssh_event_free(event_loop); + return; + } + + EXLOGW("[ssh] authenticated and got a channel.\n"); + + // 更新会话的最后访问时间(变为非0,开始接受通道和会话状态检查,必要时会被关闭、清理掉) + m_owner->update_last_access_time(); + + int timer_for_keepalive = 0; + do { + err = ssh_event_dopoll(event_loop, 1000); +// EXLOGV("-- tp2cli dopool return %d.\n", err); + + if (m_need_stop_poll) + break; + if (err == SSH_OK) + continue; + + if (err == SSH_AGAIN) { + // _check_channels(); + + if (m_need_send_keepalive) { + timer_for_keepalive++; + if (timer_for_keepalive >= 60) { + timer_for_keepalive = 0; + EXLOGD("[ssh] send keep-alive.\n"); + ssh_send_ignore(m_session, "keepalive@openssh.com"); + } + } + } + else if (err == SSH_ERROR) { + if (0 != ssh_get_error_code(m_session)) { + EXLOGW("[ssh] %s\n", ssh_get_error(m_session)); + } + m_need_stop_poll = true; + break; + } + } while (!m_channels.empty()); + + EXLOGV("[ssh] session of [%s:%d] are closed.\n", m_client_ip.c_str(), m_client_port); + if (!m_channels.empty()) { + EXLOGW("[ssh] [%s:%d] not all tp<->client channels in this session are closed, close them later.\n", m_client_ip.c_str(), m_client_port); + } + + ssh_event_remove_session(event_loop, m_session); + ssh_event_free(event_loop); +} + +int SshServerSide::_auth(const char *user, const char *password) { + // v3.6.0 + // 场景 + // 1. 标准方式:在web界面上登记远程账号的用户名和密码,远程连接时由TP负责填写; + // 2. 手动输入密码:web界面上仅登记远程账号的用户名,不填写密码,每次远程连接时由操作者输入密码; + // 3. 手动输入用户名和密码:web界面上选择连接时输入用户名密码,每次远程时,web界面上先提示操作者输入远程用户名,然后开始连接, + // 在连接过程中要求输入密码; + // 注意,这种方式,在数据库中的远程账号用户名字段,填写的是"INTERACTIVE_USER"。 + // 4. 脱离web进行远程连接:这种方式类似于手动输入用户名和密码,需要配合授权码使用,操作者需要使用 远程用户名--授权码 组合形式 + // 作为ssh连接的用户名来连接到TP的SSH核心服务。 + // 用户名的格式: + // username--CODE 用两个减号分隔第一部分和第二部分。第一部分为用户名,第二部分为会话ID或者授权码 + // 范例: + // TP--03ad57 // 最简单的形式,省略了远程用户名(用大写TP代替),核心服务从会话的连接信息中取得远程用户名。 + // apex.liu--25c308 // 总是从最后两个减号开始分解,这是操作者手动输入用户名的情况 + // apex-liu--7769b2e3 // 8字节为授权码,可以脱离web页面和助手使用 + // + // 如果第一部分为大写 TP,则从连接信息中获取远程用户名,如果此时连接信息中的远程用户名是大写的 INTERACTIVE_USER,则报错。 + // 如果第二部分是授权码,这可能是脱离web使用,核心服务需要通过web接口获取响应的连接信息,并临时生成一个会话ID来使用。 + // 授权码具有有效期,在此有效期期间可以进行任意数量的连接,可用于如下场景: + // 1. 操作者将 远程用户名--授权码 和对应的密码添加到ssh客户端如SecureCRT或者xShell中,以后可以脱离web使用; + // 2. 邀请第三方运维人员临时参与某个运维工作,可以生成一个相对短有效期的授权码发给第三方运维人员。 + // 授权码到期时,已经连接的会话会被强行中断。 + // 授权码可以绑定到主机,也可绑定到具体远程用户。前者需要操作者自己填写真实的远程用户名,后者则无需填写,可以直接登录。 + // 为增加安全性,内部人员使用时可以配合动态密码使用,而对于临时的授权码,可以生成一个配套的临时密码来进行认证。 + // 此外,还可以利用QQ机器人或微信小程序,操作者向QQ机器人发送特定指令,QQ机器人则发送一个新的临时密码。 + // + // Linux系统中,用户名通常长度不超过8个字符,并且由大小写字母和/或数字组成。登录名中不能有冒号(,因为冒号在这里是分隔符。为了兼容 + // 起见,登录名中最好不要包含点字符(.),并且不使用连字符(-)和加号(+)打头。 + + if (m_first_auth) { + std::string tmp(user); + std::string::size_type tmp_pos = tmp.rfind("--"); + if (tmp_pos == std::string::npos) { + m_need_stop_poll = true; + m_owner->on_error(TP_SESS_STAT_ERR_SESSION); + return SSH_AUTH_SUCCESS; + } + + std::string _name; + std::string _sid; + _name.assign(tmp, 0, tmp_pos); + m_sid.assign(tmp, tmp_pos + 2, tmp.length() - tmp_pos - 2); + + if (_name != "TP") + m_acc_name = _name; + + //EXLOGV("[ssh] authenticating, session-id: %s\n", _this->m_sid.c_str()); + EXLOGD("[ssh] first auth, user: %s, password: %s\n", user, password); + +#ifdef TEST_SSH_SESSION_000000 + m_conn_info = new TPP_CONNECT_INFO; + + m_conn_info->sid = "000000"; + m_conn_info->user_id = 1; + m_conn_info->host_id = 1; + m_conn_info->acc_id = 1; + m_conn_info->user_username = "apex.liu"; // 申请本次连接的用户名 + m_conn_info->client_ip = "127.0.0.1"; + m_conn_info->username_prompt = ""; // for telnet + m_conn_info->password_prompt = ""; // for telnet + m_conn_info->protocol_type = TP_PROTOCOL_TYPE_SSH; + m_conn_info->protocol_sub_type = TP_PROTOCOL_TYPE_SSH_SHELL; + m_conn_info->protocol_flag = TP_FLAG_ALL; + m_conn_info->record_flag = TP_FLAG_ALL; + m_conn_info->auth_type = TP_AUTH_TYPE_PASSWORD; + + m_conn_info->host_ip = "39.97.125.170"; + m_conn_info->conn_ip = "39.97.125.170"; + m_conn_info->conn_port = 22; +// m_conn_info->acc_username = "root"; + m_conn_info->acc_secret = "Mc7b5We8"; + m_conn_info->acc_username = "INTERACTIVE_USER"; +// m_conn_info->acc_secret = ""; +#else + _this->m_conn_info = g_ssh_env.get_connect_info(_this->m_sid.c_str()); +#endif + + if (!m_conn_info) { + EXLOGE("[ssh] no such session: %s\n", m_sid.c_str()); + m_need_stop_poll = true; + m_owner->on_error(TP_SESS_STAT_ERR_SESSION); + return SSH_AUTH_SUCCESS; + } + + m_conn_ip = m_conn_info->conn_ip; + m_conn_port = m_conn_info->conn_port; + m_auth_type = m_conn_info->auth_type; + // m_acc_name = m_conn_info->acc_username; + m_acc_secret = m_conn_info->acc_secret; + m_flags = m_conn_info->protocol_flag; + if (m_conn_info->protocol_type != TP_PROTOCOL_TYPE_SSH) { + EXLOGE("[ssh] session '%s' is not for SSH.\n", m_sid.c_str()); + m_need_stop_poll = true; + m_owner->on_error(TP_SESS_STAT_ERR_INTERNAL); + return SSH_AUTH_SUCCESS; + } + + _name = m_conn_info->acc_username; + if ((m_acc_name.empty() && _name == "INTERACTIVE_USER") + || (!m_acc_name.empty() && _name != "INTERACTIVE_USER") + ) { + EXLOGE("[ssh] conflict account info.\n"); + m_need_stop_poll = true; + m_owner->on_error(TP_SESS_STAT_ERR_SESSION); + return SSH_AUTH_SUCCESS; + } + if (m_acc_name.empty()) + m_acc_name = _name; + + if (m_acc_secret.empty()) { + // 如果TP中未设置远程账号密码,表示允许用户自行输入密码 + m_allow_user_input_password = true; + + // 如果传入的password为特定值,应该是由助手调用客户端,填写的密码 + // 直接返回认证失败,这样客户端会让用户输入密码 + if (0 == strcmp(password, "INTERACTIVE_USER")) + return SSH_AUTH_DENIED; + + // 用户脱离TP-WEB,直接使用客户端输入的密码 + m_acc_secret = password; + } + } + else { + // 允许用户自行输入密码的情况下,第二次认证,参数password就是用户自己输入的密码了。 + m_acc_secret = password; + } + + // 连接到远程主机并进行认证 + uint32_t rv = TP_SESS_STAT_RUNNING; + auto tp2srv = m_owner->connect_to_host(m_conn_ip, m_conn_port, m_acc_name, rv); + if (!tp2srv || rv != TP_SESS_STAT_RUNNING) { + m_need_stop_poll = true; + m_owner->on_error(rv); + return SSH_AUTH_SUCCESS; + } + + EXLOGV("[ssh] connected, now auth, type=%d, secret=%s\n", m_auth_type, m_acc_secret.c_str()); + rv = tp2srv->auth(m_auth_type, m_acc_secret); + if (rv == TP_SESS_STAT_RUNNING) { + // 操作成功!! + //m_need_stop_poll = false; + m_auth_error = TPE_OK; + m_is_logon = true; + return SSH_AUTH_SUCCESS; + } + else if (rv == TP_SESS_STAT_ERR_AUTH_DENIED) { + // 连接成功,但是认证失败 + if (m_auth_type == TP_AUTH_TYPE_PRIVATE_KEY || !m_allow_user_input_password || m_auth_fail_count >= 3) { + m_need_stop_poll = true; + m_owner->on_error(TP_SESS_STAT_ERR_AUTH_DENIED); + return SSH_AUTH_SUCCESS; + } + + m_auth_fail_count++; + return SSH_AUTH_DENIED; + } + else { + m_need_stop_poll = true; + m_owner->on_error(rv); + return SSH_AUTH_SUCCESS; + } +} + +int SshServerSide::_on_auth_password_request(ssh_session session, const char *user, const char *password, void *userdata) { + auto *_this = (SshServerSide *) userdata; + int ret = _this->_auth(user, password); + if (_this->m_first_auth) + _this->m_first_auth = false; + // _this->m_owner->update_last_access_time(); + return ret; +} + +ssh_channel SshServerSide::_on_new_channel_request(ssh_session session, void *userdata) { + // 客户端尝试打开一个通道(然后才能通过这个通道发控制命令或者收发数据) + EXLOGV("[ssh] client open channel\n"); + + auto _this = (SshServerSide *) userdata; + auto tp2srv = _this->m_owner->tp2srv(); + // _this->m_owner->update_last_access_time(); + + // 如果认证过程中已经发生了不可继续的错误,这里返回null,中断连接 + if (_this->m_auth_error != TPE_OK || !tp2srv) { + _this->m_need_stop_poll = true; + EXLOGE("[ssh] before request shell, already failed.\n"); + return nullptr; + } + + EXLOGV("-- cp a\n"); + + ssh_channel ch_tp2cli = ssh_channel_new(session); + if (ch_tp2cli == nullptr) { + EXLOGE("[ssh] can not create channel for communicate client.\n"); + return nullptr; + } + ssh_set_channel_callbacks(ch_tp2cli, &_this->m_channel_with_cli_cb); + _this->m_channels.push_back(ch_tp2cli); + + EXLOGV("-- cp b\n"); + + // 我们也要向真正的服务器申请打开一个通道,来进行转发 + ssh_channel ch_tp2srv = tp2srv->request_new_channel(); + if (ch_tp2srv == nullptr) { + EXLOGE("[ssh] can not create channel for server.\n"); + return nullptr; + } + + EXLOGV("-- cp c\n"); + + // start the thread. + tp2srv->start(); + + if (!_this->m_owner->make_channel_pair(ch_tp2cli, ch_tp2srv)) { + EXLOGE("[ssh] can not make channel pair.\n"); + return nullptr; + } + +#if 0 + 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(channel_to_cli); + ssh_channel_free(srv_channel); + return nullptr; + } + 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 = channel_to_cli; + cp->srv_channel = srv_channel; + cp->last_access_timestamp = (ex_u32) time(nullptr); + + if (!_this->_record_begin(cp)) { + ssh_channel_close(channel_to_cli); + ssh_channel_free(channel_to_cli); + ssh_channel_close(srv_channel); + ssh_channel_free(srv_channel); + delete cp; + return nullptr; + } + + // 将客户端和服务端的通道关联起来 + { + ExThreadSmartLock locker(_this->m_lock); + _this->m_channel_mgr.push_back(cp); + } +#endif // 0 + + EXLOGV("-- cp d\n"); + + EXLOGD("[ssh] channel for client and server created.\n"); + return ch_tp2cli; +} + +#if 0 +TP_SSH_CHANNEL_PAIR *SshServerSide::_get_channel_pair(int channel_side, ssh_channel channel) { + ExThreadSmartLock locker(m_lock); + + auto it = m_channel_mgr.begin(); + for (; it != m_channel_mgr.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 nullptr; +} +#endif + +int SshServerSide::_on_client_pty_request(ssh_session session, ssh_channel channel, const char *term, int x, int y, int px, int py, void *userdata) { + EXLOGD("[ssh] client request pty: %s, (%d, %d) / (%d, %d)\n", term, x, y, px, py); + + auto _this = (SshServerSide *) userdata; + _this->m_owner->update_last_access_time(); + + auto cp = _this->m_owner->get_channel_pair(channel); + if (nullptr == 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->channel_tp2srv, term, x, y); + if (err != SSH_OK) { + EXLOGE("[ssh] pty request from server got %d\n", err); + } + + return err; +} + +int SshServerSide::_on_client_shell_request(ssh_session session, ssh_channel channel, void *userdata) { + EXLOGD("[ssh] client request shell\n"); + + auto _this = (SshServerSide *) userdata; + _this->m_owner->update_last_access_time(); + + if ((_this->m_flags & TP_FLAG_SSH_SHELL) != TP_FLAG_SSH_SHELL) { + EXLOGE("[ssh] ssh-shell disabled by ops-policy.\n"); + return SSH_ERROR; + } + EXLOGV("-- cp y1\n"); + + auto cp = _this->m_owner->get_channel_pair(channel); + if (!cp) { + EXLOGE("[ssh] when client request shell, not found channel pair.\n"); + return SSH_ERROR; + } + + cp->type = TS_SSH_CHANNEL_TYPE_SHELL; + cp->update_session_state(TP_PROTOCOL_TYPE_SSH_SHELL, TP_SESS_STAT_STARTED); + + EXLOGV("-- cp y2\n"); +// std::string msg("Hello world.\r\n"); +// ssh_channel_write(cp->channel_tp2cli, msg.c_str(), msg.length()); + EXLOGV("-- cp y3\n"); + + // sometimes it will block here. the following function will never return. + // Fixed at 20190104: + // at libssh ssh_handle_packets_termination(), set timeout always to SSH_TIMEOUT_USER. + // and at ssh_handle_packets(), when call ssh_poll_add_events() should use POLLIN|POLLOUT. + // 注意,此处有嵌套死锁的问题。 + // 在本回调函数中,向另一侧要求打开shell,另一侧会收到第一包数据(一般是 welcome 信息),需要转发给本侧, + // 但是本侧尚在回调函数中,处于阻塞状态,因此发送会阻塞,从而形成死锁。 + int err = ssh_channel_request_shell(cp->channel_tp2srv); + if (err != SSH_OK) { + EXLOGE("[ssh] shell request from server got %d\n", err); + } + EXLOGV("-- cp y4\n"); + + return err; +} + +void SshServerSide::_on_client_channel_close(ssh_session session, ssh_channel channel, void *userdata) { + // 注意,此回调会在通道已经被关闭之后调用 + // 无需做额外操作,关闭的通道会由 SshSession 实例定期清理(包括关闭对端通道) + EXLOGV("[ssh] channel client<->tp closed.\n"); + + +// auto _this = (SshServerSide *) userdata; +// auto tp2srv = _this->m_owner->tp2srv(); +// auto cp = _this->m_owner->get_channel_pair(channel); +// +// if (nullptr == cp) { +// EXLOGE("[ssh] when client channel close, not found channel pair.\n"); +// return; +// } + +// _this->m_owner->remove_channel(channel); +//// if (!ssh_channel_is_closed(channel)) +//// ssh_channel_close(channel); +// ssh_channel_free(channel); +// +// // 与客户端的通道关闭了,同时关闭与远程主机的对应通道 +// cp->channel_tp2cli = nullptr; +// cp->need_close = true; +//// tp2srv->close_channel(cp->channel_tp2srv); +//// cp->close(); +// + + +// _this->m_need_stop_poll = true; + +// //EXLOGD("[ssh] [channel:%d] -- end by client channel close\n", cp->channel_id); +// //_this->_record_end(cp); +// +// if (cp->srv_channel == nullptr) { +// 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_need_stop_poll = true; +// } +// } +} + +int SshServerSide::_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); + + auto _this = (SshServerSide *) userdata; + _this->m_owner->update_last_access_time(); + + auto cp = _this->m_owner->get_channel_pair(channel); + if (nullptr == 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->channel_tp2srv, width, height); +// return _this->m_cli->change_pty_size(width, height); +} + +int SshServerSide::_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); + + auto _this = (SshServerSide *) userdata; + _this->m_owner->update_last_access_time(); + +// 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; +// } + + auto cp = _this->m_owner->get_channel_pair(channel); + if (nullptr == 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; + } + + if ((_this->m_flags & TP_FLAG_SSH_SFTP) != TP_FLAG_SSH_SFTP) { + EXLOGE("[ssh] ssh-sftp disabled by ops-policy.\n"); + return SSH_ERROR; + } + + //EXLOGD("[ssh] ---> request channel subsystem from server\n"); + int err = ssh_channel_request_subsystem(cp->channel_tp2srv, 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; + } + + cp->type = TS_SSH_CHANNEL_TYPE_SFTP; + cp->update_session_state(TP_PROTOCOL_TYPE_SSH_SFTP, TP_SESS_STAT_STARTED); + + return SSH_OK; + +// return _this->m_cli->request_subsystem(subsystem); +} + +int SshServerSide::_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 SshServerSide::_on_client_channel_data(ssh_session session, ssh_channel channel, void *data, unsigned int len, int is_stderr, void *userdata) { + //EXLOG_BIN(static_cast(data), len, " ---> on_client_channel_data [is_stderr=%d]:", is_stderr); + //EXLOGD(" ---> recv from client %d B\n", len); + + auto _this = (SshServerSide *) userdata; + _this->m_owner->update_last_access_time(); + +// // 当前线程正在接收服务端返回的数据,因此我们直接返回,这样紧跟着会重新再发送此数据的 +// if (_this->m_recving_from_srv) { +// // EXLOGD("recving from srv...try again later...\n"); +// return 0; +// } +// if (_this->m_recving_from_cli) { +// // EXLOGD("recving from cli...try again later...\n"); +// return 0; +// } + + auto cp = _this->m_owner->get_channel_pair(channel); + if (nullptr == cp) { + EXLOGE("[ssh] when receive client channel data, not found channel pair.\n"); + return SSH_ERROR; + } + +// _this->m_recving_from_cli = true; + + if (cp->type == TS_SSH_CHANNEL_TYPE_SHELL) { + // 在收取服务端数据直到显示命令行提示符之前,不允许发送客户端数据到服务端,避免日志记录混乱。 +// if (!cp->server_ready) { +// _this->m_recving_from_cli = false; +// return 0; +// } + + // 不可以拆分!!否则执行 rz 命令会出错! + // xxxx 如果用户复制粘贴多行文本,我们将其拆分为每一行发送一次数据包 +// for (unsigned int i = 0; i < len; ++i) { +// if (((ex_u8 *) data)[i] == 0x0d) { +// _len = i + 1; +// break; +// } +// } + + //cp->process_ssh_command(channel, (ex_u8 *) data, len); + // _this->_process_ssh_command(cp, TP_SSH_CLIENT_SIDE, (ex_u8 *) data, _len); + } + else { + cp->process_sftp_command(channel, (ex_u8 *) data, len); + // _this->_process_sftp_command(cp, (ex_u8 *) data, _len); + } + + int ret = 0; +// if (is_stderr) +// ret = ssh_channel_write_stderr(cp->channel_tp2srv, data, len); +// else +// ret = ssh_channel_write(cp->channel_tp2srv, data, len); + + + ssh_set_blocking(_this->m_session, 0); + EXLOGD("[ssh] -- cp a3.\n"); + + int xx = 0; + for (xx = 0; xx < 1000; ++xx) { + +// idx++; +// EXLOGD(">>>>> %d . %d\n", cp->db_id, idx); + + // 直接转发数据到客户端 + if (is_stderr) + ret = ssh_channel_write_stderr(cp->channel_tp2srv, data, len); + else + ret = ssh_channel_write(cp->channel_tp2srv, data, len); + +// EXLOGD("<<<<< %d . %d\n", cp->db_id, idx); + + if (ret == SSH_OK) { +// EXLOGD("ssh_channel_write() ok.\n"); + break; + } + else if (ret == SSH_AGAIN) { + EXLOGD("ssh_channel_write() need again, %d.\n", xx); + ex_sleep_ms(50); + continue; + } + else { +// EXLOGD("ssh_channel_write() failed.\n"); + break; + } + } + + ssh_set_blocking(_this->m_session, 1); + EXLOGD("[ssh] -- cp a4.\n"); + + + + if (ret == SSH_ERROR) { + EXLOGE("[ssh] send data(%dB) to server failed. [%d] %s\n", len, ret, ssh_get_error(_this->m_owner->tp2srv())); + ssh_channel_close(channel); + // cp->need_close = true; + } + +// _this->m_recving_from_cli = false; + + return ret; +} diff --git a/server/tp_core/protocol/ssh/tpssh_srv.h b/server/tp_core/protocol/ssh/tpssh_srv.h new file mode 100644 index 0000000..d6de294 --- /dev/null +++ b/server/tp_core/protocol/ssh/tpssh_srv.h @@ -0,0 +1,192 @@ +#ifndef __SSH_SESSION_H__ +#define __SSH_SESSION_H__ + +#include + +#include +#include +#include +#include + +#include +#include + +#include "tpssh_rec.h" +#include "tpssh_cli.h" +#include "tpssh_channel.h" + + +#define TS_SSH_CHANNEL_TYPE_UNKNOWN 0 +#define TS_SSH_CHANNEL_TYPE_SHELL 1 +#define TS_SSH_CHANNEL_TYPE_SFTP 2 + +#define TP_SSH_CLIENT_SIDE 1 +#define TP_SSH_SERVER_SIDE 2 + +class SshProxy; +class SshSession; + +#if 0 +class SshServerSide; + +class SshClientSide; + +class TP_SSH_CHANNEL_PAIR { + + friend class SshServerSide; + + friend class SshClientSide; + +public: + TP_SSH_CHANNEL_PAIR(); + +private: + int type; // TS_SSH_CHANNEL_TYPE_SHELL or TS_SSH_CHANNEL_TYPE_SFTP + + ssh_channel cli_channel; + ssh_channel srv_channel; + + TppSshRec rec; + ex_u32 last_access_timestamp; + + int state; + int db_id; + int channel_id; // for debug only. + + int win_width; // window width, in char count. + + bool is_first_server_data; + bool need_close; + + // for ssh command record cache. + bool server_ready; + bool maybe_cmd; + bool process_srv; + bool client_single_char; + std::list cmd_char_list; + std::list::iterator cmd_char_pos; +}; + +typedef std::list tp_channels; +#endif + +class SshServerSide : public ExThreadBase { +public: + SshServerSide(SshSession *s, ssh_session sess_tp2cli, const std::string &thread_name); + + virtual ~SshServerSide(); + + // SshProxy *get_proxy() { return m_proxy; } + + //TP_SSH_CHANNEL_PAIR *_get_channel_pair(int channel_side, ssh_channel channel); + + //void client_ip(const char* ip) { m_client_ip = ip; } + const char *client_ip() const { return m_client_ip.c_str(); } + + //void client_port(ex_u16 port) { m_client_port = port; } + ex_u16 client_port() const { return m_client_port; } + + bool init(); + + const ex_astr &sid() { return m_sid; } + + void channel_closed(ssh_channel ch); +protected: + void _thread_loop(); + + void _on_stop(); + + void _on_stopped(); + + // record an error when session connecting or auth-ing. +// void _session_error(int err_code); + +// // when client<->server channel created, start to record. +// bool _record_begin(TP_SSH_CHANNEL_PAIR *cp); +// +// // stop record because channel closed. +// void _record_end(TP_SSH_CHANNEL_PAIR *cp); +// +// void _process_ssh_command(TP_SSH_CHANNEL_PAIR *cp, int from, const ex_u8 *data, int len); +// +// void _process_sftp_command(TP_SSH_CHANNEL_PAIR *cp, const ex_u8 *data, int len); + +private: + + void _close_channels(); + + // void _check_channels(); + + int _auth(const char *user, const char *password); + + static int _on_auth_password_request(ssh_session session, const char *user, const char *password, void *userdata); + + static ssh_channel _on_new_channel_request(ssh_session session, void *userdata); + + static int _on_client_pty_request(ssh_session session, ssh_channel channel, const char *term, int x, int y, int px, int py, void *userdata); + + static int _on_client_shell_request(ssh_session session, ssh_channel channel, void *userdata); + + static void _on_client_channel_close(ssh_session session, ssh_channel channel, void *userdata); + + static int _on_client_channel_data(ssh_session session, ssh_channel channel, void *data, unsigned int len, int is_stderr, void *userdata); + + static int _on_client_pty_win_change(ssh_session session, ssh_channel channel, int width, int height, int pxwidth, int pwheight, void *userdata); + + static int _on_client_channel_subsystem_request(ssh_session session, ssh_channel channel, const char *subsystem, void *userdata); + + static int _on_client_channel_exec_request(ssh_session session, ssh_channel channel, const char *command, void *userdata); + +// static int _on_server_channel_data(ssh_session session, ssh_channel channel, void *data, unsigned int len, int is_stderr, void *userdata); +// +// static void _on_server_channel_close(ssh_session session, ssh_channel channel, void *userdata); + +private: + SshSession *m_owner; + + // 认证阶段发生的错误。认证阶段出错时并不返回错误,避免客户端反复提示用户输入密码,而是返回认证成功,但记录错误值, + // 下一步在申请开启通道时,先检查记录的错误值,如果认证过程中已经出错,则不打开通道,而是断开连接。 + uint32_t m_auth_error; + // 是否是首次认证(仅用于允许用户自行输入密码的情况,即,未在TP中设置远程账号密码,否则首次认证失败,就结束会话) + bool m_first_auth; + // 是否允许用户自行输入密码 + bool m_allow_user_input_password; + // 用户自行输入密码的错误计数器。超过3次中断会话,不允许继续尝试 + int m_auth_fail_count; + + ssh_session m_session; + std::list m_channels; + + ExThreadLock m_lock; + + ex_astr m_client_ip; + ex_u16 m_client_port; + + TPP_CONNECT_INFO *m_conn_info; + + ex_astr m_sid; + ex_astr m_conn_ip; + ex_u16 m_conn_port; + ex_astr m_acc_name; + ex_astr m_acc_secret; + ex_u32 m_flags; + int m_auth_type; + + bool m_is_logon; + + int m_ssh_ver; + + // 是否需要停止ssh事件处理循环 + bool m_need_stop_poll; + + bool m_need_send_keepalive; + + bool m_recving_from_srv; // 是否正在从服务器接收数据? + bool m_recving_from_cli; // 是否正在从客户端接收数据? + + struct ssh_server_callbacks_struct m_srv_cb; + struct ssh_channel_callbacks_struct m_channel_with_cli_cb; +// struct ssh_channel_callbacks_struct m_srv_channel_cb; +}; + +#endif // __SSH_SESSION_H__