mirror of https://github.com/tp4a/teleport
大幅度改进SSH模块,增加稳定性。
parent
85e6f4dd28
commit
b7d9f6f0d8
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding" addBOMForNewFiles="with NO BOM">
|
||||
<component name="Encoding">
|
||||
<file url="file://$PROJECT_DIR$/common/libex/include/ex/ex_log.h" charset="GBK" />
|
||||
<file url="file://$PROJECT_DIR$/common/libex/include/ex/ex_path.h" charset="GBK" />
|
||||
<file url="file://$PROJECT_DIR$/common/libex/include/ex/ex_thread.h" charset="GBK" />
|
||||
|
@ -33,6 +33,7 @@
|
|||
<file url="file://$PROJECT_DIR$/server/tp_core/protocol/rdp/rdp_proxy.cpp" charset="GBK" />
|
||||
<file url="file://$PROJECT_DIR$/server/tp_core/protocol/rdp/rdp_session.cpp" charset="GBK" />
|
||||
<file url="file://$PROJECT_DIR$/server/tp_core/protocol/rdp/rdp_session.h" charset="GBK" />
|
||||
<file url="file://$PROJECT_DIR$/server/tp_core/protocol/ssh/ssh_recorder.h" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/server/tp_core/protocol/ssh/tpssh_proxy.cpp" charset="GBK" />
|
||||
<file url="file://$PROJECT_DIR$/server/tp_core/protocol/ssh/tpssh_proxy.h" charset="GBK" />
|
||||
<file url="file://$PROJECT_DIR$/server/tp_core/protocol/ssh/tpssh_rec.cpp" charset="GBK" />
|
||||
|
|
|
@ -815,11 +815,20 @@ class BuilderMacOS(BuilderBase):
|
|||
return
|
||||
cc.v('')
|
||||
|
||||
cc.n('fix libssh source code... ', end='')
|
||||
s_name = 'libssh-{}'.format(env.ver_libssh)
|
||||
utils.ensure_file_exists(os.path.join(PATH_EXTERNAL, 'fix-external', 'libssh', s_name, 'src', 'session.c'))
|
||||
# ## utils.ensure_file_exists(os.path.join(PATH_EXTERNAL, 'fix-external', 'libssh', s_name, 'src', 'libcrypto.c'))
|
||||
# # utils.ensure_file_exists(os.path.join(PATH_EXTERNAL, 'fix-external', 'libssh', s_name, 'src', 'libcrypto-compat.c'))
|
||||
utils.copy_file(os.path.join(PATH_EXTERNAL, 'fix-external', 'libssh', s_name, 'src'), os.path.join(self.LIBSSH_PATH_SRC, 'src'), 'session.c')
|
||||
# ## utils.copy_file(os.path.join(PATH_EXTERNAL, 'fix-external', 'libssh', s_name, 'src'), os.path.join(self.LIBSSH_PATH_SRC, 'src'), 'libcrypto.c')
|
||||
# # utils.copy_file(os.path.join(PATH_EXTERNAL, 'fix-external', 'libssh', s_name, 'src'), os.path.join(self.LIBSSH_PATH_SRC, 'src'), 'libcrypto-compat.c')
|
||||
|
||||
build_path = os.path.join(self.LIBSSH_PATH_SRC, 'build')
|
||||
|
||||
# because on MacOS, I install openssl 1.1.1g by homebrew, it localted at /usr/local/opt/openssl
|
||||
cmake_define = ' -DCMAKE_INSTALL_PREFIX={path_release}' \
|
||||
' -DOPENSSL_INCLUDE_DIR=/usr/local/opt/openssl/include' \
|
||||
' -DOPENSSL_LIBRARIES=/usr/local/opt/openssl/lib' \
|
||||
' -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl' \
|
||||
' -DWITH_GCRYPT=OFF' \
|
||||
' -DWITH_GEX=OFF' \
|
||||
' -DWITH_SFTP=ON' \
|
||||
|
@ -832,9 +841,12 @@ class BuilderMacOS(BuilderBase):
|
|||
' -DWITH_EXAMPLES=OFF' \
|
||||
' -DWITH_BENCHMARKS=OFF' \
|
||||
' -DWITH_NACL=OFF' \
|
||||
' -DWITH_STATIC_LIB=ON' \
|
||||
''.format(path_release=self.PATH_RELEASE)
|
||||
|
||||
# ' -DWITH_STATIC_LIB=ON'
|
||||
# ' -DOPENSSL_INCLUDE_DIR=/usr/local/opt/openssl/include'
|
||||
# ' -DOPENSSL_LIBRARIES=/usr/local/opt/openssl/lib'
|
||||
|
||||
try:
|
||||
utils.cmake(build_path, 'Release', False, cmake_define)
|
||||
|
|
|
@ -9,19 +9,23 @@
|
|||
# include <process.h>
|
||||
typedef HANDLE EX_THREAD_HANDLE;
|
||||
#else
|
||||
|
||||
# include <pthread.h>
|
||||
# include <sys/time.h>
|
||||
|
||||
typedef pthread_t EX_THREAD_HANDLE;
|
||||
#endif
|
||||
|
||||
class ExThreadManager;
|
||||
|
||||
class ExThreadBase
|
||||
{
|
||||
public:
|
||||
ExThreadBase(const char* thread_name);
|
||||
explicit ExThreadBase(const char* thread_name);
|
||||
virtual ~ExThreadBase();
|
||||
|
||||
bool is_running() { return m_is_running; }
|
||||
bool is_running() const
|
||||
{
|
||||
return m_is_running;
|
||||
}
|
||||
|
||||
// 创建并启动线程(执行被重载了的run()函数)
|
||||
bool start();
|
||||
|
@ -33,15 +37,21 @@ public:
|
|||
protected:
|
||||
// main loop of this thread.
|
||||
virtual void _thread_loop() = 0;
|
||||
|
||||
// called by another thread when thread ready to stop.
|
||||
virtual void _on_stop() {};
|
||||
virtual void _on_stop()
|
||||
{
|
||||
};
|
||||
|
||||
// called inside thread when thread fully stopped.
|
||||
virtual void _on_stopped() {};
|
||||
virtual void _on_stopped()
|
||||
{
|
||||
};
|
||||
|
||||
#ifdef EX_OS_WIN32
|
||||
static unsigned int WINAPI _thread_func(LPVOID lpParam);
|
||||
#else
|
||||
static void* _thread_func(void * pParam);
|
||||
static void* _thread_func(void* pParam);
|
||||
#endif
|
||||
|
||||
protected:
|
||||
|
@ -74,10 +84,12 @@ private:
|
|||
class ExThreadSmartLock
|
||||
{
|
||||
public:
|
||||
ExThreadSmartLock(ExThreadLock& lock) : m_lock(lock)
|
||||
explicit ExThreadSmartLock(ExThreadLock& lock) :
|
||||
m_lock(lock)
|
||||
{
|
||||
m_lock.lock();
|
||||
}
|
||||
|
||||
~ExThreadSmartLock()
|
||||
{
|
||||
m_lock.unlock();
|
||||
|
@ -99,7 +111,7 @@ public:
|
|||
|
||||
void stop_all();
|
||||
|
||||
//private:
|
||||
//private:
|
||||
void add(ExThreadBase* tb);
|
||||
void remove(ExThreadBase* tb);
|
||||
|
||||
|
@ -108,6 +120,85 @@ private:
|
|||
ex_threads m_threads;
|
||||
};
|
||||
|
||||
// Event
|
||||
class ExEventHelper;
|
||||
|
||||
class ExEvent
|
||||
{
|
||||
friend class ExEventHelper;
|
||||
|
||||
public:
|
||||
ExEvent()
|
||||
{
|
||||
#ifdef EX_OS_WIN32
|
||||
#else
|
||||
pthread_mutex_init(&m_mutex, nullptr);
|
||||
pthread_cond_init(&m_cond, nullptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
~ExEvent()
|
||||
{
|
||||
pthread_mutex_destroy(&m_mutex);
|
||||
pthread_cond_destroy(&m_cond);
|
||||
}
|
||||
|
||||
void wait()
|
||||
{
|
||||
pthread_cond_wait(&m_cond, &m_mutex);
|
||||
}
|
||||
|
||||
void wait_timeout_ms(int timeout_ms)
|
||||
{
|
||||
// timeval.tv_usec ==== ms
|
||||
// timespec.tv_nsec === nano-second
|
||||
struct timeval now = { 0 };
|
||||
struct timespec out_time = { 0 };
|
||||
gettimeofday(&now, nullptr);
|
||||
|
||||
uint64_t abs_time_ms = now.tv_sec * 1000ll + now.tv_usec + timeout_ms;
|
||||
out_time.tv_sec = abs_time_ms / 1000ll;
|
||||
out_time.tv_nsec = (long)((abs_time_ms % 1000ll) * 1000ll);
|
||||
|
||||
pthread_cond_timedwait(&m_cond, &m_mutex, &out_time);
|
||||
}
|
||||
|
||||
void signal()
|
||||
{
|
||||
pthread_cond_signal(&m_cond);
|
||||
}
|
||||
|
||||
private:
|
||||
#ifdef EX_OS_WIN32
|
||||
#else
|
||||
pthread_mutex_t m_mutex;
|
||||
pthread_cond_t m_cond;
|
||||
#endif
|
||||
};
|
||||
|
||||
class ExEventHelper
|
||||
{
|
||||
public:
|
||||
explicit ExEventHelper(ExEvent& event) :
|
||||
m_event(event)
|
||||
{
|
||||
#ifdef EX_OS_WIN32
|
||||
#else
|
||||
pthread_mutex_lock(&m_event.m_mutex);
|
||||
#endif
|
||||
}
|
||||
|
||||
~ExEventHelper()
|
||||
{
|
||||
#ifdef EX_OS_WIN32
|
||||
#else
|
||||
pthread_mutex_unlock(&m_event.m_mutex);
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
ExEvent& m_event;
|
||||
};
|
||||
|
||||
// 原子操作
|
||||
int ex_atomic_add(volatile int* pt, int t);
|
||||
|
|
|
@ -52,4 +52,12 @@ int ex_ip4_name(const struct sockaddr_in* src, char* dst, size_t size);
|
|||
#define EX_IPV6_NAME_LEN 46
|
||||
const char* ex_inet_ntop(int af, const void *src, char *dst, size_t size);
|
||||
|
||||
#ifndef MIN
|
||||
# ifdef EX_OS_WIN32
|
||||
# define MIN(x, y) min((x), (y))
|
||||
# else
|
||||
# define MIN(x, y) std::min((x), (y))
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#endif // __LIB_EX_UTIL_H__
|
||||
|
|
|
@ -13,16 +13,16 @@ unsigned int WINAPI ExThreadBase::_thread_func(LPVOID pParam)
|
|||
void *ExThreadBase::_thread_func(void *pParam)
|
||||
#endif
|
||||
{
|
||||
ExThreadBase *_this = (ExThreadBase *) pParam;
|
||||
auto _this = (ExThreadBase *) pParam;
|
||||
|
||||
_this->m_is_running = true;
|
||||
_this->_thread_loop();
|
||||
_this->m_is_running = false;
|
||||
_this->m_handle = 0;
|
||||
_this->m_handle = nullptr;
|
||||
|
||||
EXLOGV("[thread] - `%s` exit.\n", _this->m_thread_name.c_str());
|
||||
_this->_on_stopped();
|
||||
return 0;
|
||||
EXLOGV("[thread] - `%s` exit.\n", _this->m_thread_name.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ExThreadBase::ExThreadBase(const char *thread_name) :
|
||||
|
@ -50,12 +50,12 @@ bool ExThreadBase::start() {
|
|||
}
|
||||
m_handle = h;
|
||||
#else
|
||||
pthread_t ptid = 0;
|
||||
int ret = pthread_create(&ptid, NULL, _thread_func, (void *) this);
|
||||
pthread_t tid = nullptr;
|
||||
int ret = pthread_create(&tid, nullptr, _thread_func, (void *) this);
|
||||
if (ret != 0) {
|
||||
return false;
|
||||
}
|
||||
m_handle = ptid;
|
||||
m_handle = tid;
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -82,7 +82,7 @@ bool ExThreadBase::stop() {
|
|||
}
|
||||
#else
|
||||
if(m_handle != 0) {
|
||||
if (pthread_join(m_handle, NULL) != 0) {
|
||||
if (pthread_join(m_handle, nullptr) != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -115,9 +115,8 @@ ExThreadManager::~ExThreadManager() {
|
|||
void ExThreadManager::stop_all() {
|
||||
ExThreadSmartLock locker(m_lock);
|
||||
|
||||
ex_threads::iterator it = m_threads.begin();
|
||||
for (; it != m_threads.end(); ++it) {
|
||||
(*it)->stop();
|
||||
for (auto & t : m_threads) {
|
||||
t->stop();
|
||||
}
|
||||
m_threads.clear();
|
||||
}
|
||||
|
@ -125,9 +124,8 @@ void ExThreadManager::stop_all() {
|
|||
void ExThreadManager::add(ExThreadBase *tb) {
|
||||
ExThreadSmartLock locker(m_lock);
|
||||
|
||||
ex_threads::iterator it = m_threads.begin();
|
||||
for (; it != m_threads.end(); ++it) {
|
||||
if ((*it) == tb) {
|
||||
for (auto & t : m_threads) {
|
||||
if (t == tb) {
|
||||
EXLOGE("[thread] when add thread to manager, it already exist.\n");
|
||||
return;
|
||||
}
|
||||
|
@ -139,8 +137,7 @@ void ExThreadManager::add(ExThreadBase *tb) {
|
|||
void ExThreadManager::remove(ExThreadBase *tb) {
|
||||
ExThreadSmartLock locker(m_lock);
|
||||
|
||||
ex_threads::iterator it = m_threads.begin();
|
||||
for (; it != m_threads.end(); ++it) {
|
||||
for (auto it = m_threads.begin(); it != m_threads.end(); ++it) {
|
||||
if ((*it) == tb) {
|
||||
m_threads.erase(it);
|
||||
return;
|
||||
|
|
|
@ -0,0 +1,652 @@
|
|||
#include "ssh_channel_pair.h"
|
||||
#include "ssh_session.h"
|
||||
#include "tpp_env.h"
|
||||
|
||||
#include <teleport_const.h>
|
||||
|
||||
SshChannelPair::SshChannelPair(SshSession *_owner, ssh_channel _rsc_tp2cli, ssh_channel _rsc_tp2srv) :
|
||||
m_owner(_owner),
|
||||
rsc_tp2cli(_rsc_tp2cli),
|
||||
rsc_tp2srv(_rsc_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;
|
||||
|
||||
m_is_cmd_mode = false;
|
||||
m_recv_prompt = false;
|
||||
m_client_last_char = 0;
|
||||
|
||||
m_pty_stat = PTY_STAT_NORMAL_WAIT_PROMPT;
|
||||
}
|
||||
|
||||
SshChannelPair::~SshChannelPair()
|
||||
{
|
||||
}
|
||||
|
||||
void SshChannelPair::process_pty_data_from_client(const uint8_t *data, uint32_t len)
|
||||
{
|
||||
if (data == nullptr || len == 0)
|
||||
return;
|
||||
|
||||
if (len == 1)
|
||||
{
|
||||
if (data[0] == 0x0d)
|
||||
{
|
||||
// 0x0d 回车键
|
||||
if (!m_cmd.empty())
|
||||
EXLOGD("[%s] CMD=[%s]\n", m_owner->dbg_name().c_str(), m_cmd.str().c_str());
|
||||
m_cmd.reset();
|
||||
m_pty_stat = PTY_STAT_NORMAL_WAIT_PROMPT;
|
||||
// EXLOGD("------ turn to PTY_STAT_NORMAL_WAIT_PROMPT, input single RETURN.\n");
|
||||
return;
|
||||
}
|
||||
else if (data[0] == 0x03)
|
||||
{
|
||||
// 0x03 Ctrl-C
|
||||
m_pty_stat = PTY_STAT_NORMAL_WAIT_PROMPT;
|
||||
// EXLOGD("------ turn to PTY_STAT_NORMAL_WAIT_PROMPT, input Ctrl-C.\n");
|
||||
return;
|
||||
}
|
||||
else if (data[0] == 0x09)
|
||||
{
|
||||
// 0x09 TAB键
|
||||
if (m_pty_stat == PTY_STAT_WAIT_CLIENT_INPUT || m_pty_stat == PTY_STAT_TAB_WAIT_PROMPT || m_pty_stat == PTY_STAT_TAB_PRESSED)
|
||||
{
|
||||
m_pty_stat = PTY_STAT_TAB_PRESSED;
|
||||
// EXLOGD("------ turn to PTY_STAT_TAB_PRESSED, input TAB.\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (data[0] == 0x7f)
|
||||
{
|
||||
// 7f backspace 回删键
|
||||
m_pty_stat = PTY_STAT_WAIT_SERVER_ECHO;
|
||||
// EXLOGD("------ turn to PTY_STAT_WAIT_SERVER_ECHO, input BACKSPACE.\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (len == 3)
|
||||
{
|
||||
if (data[0] == 0x1b && data[1] == 0x5b && (data[2] == 0x41 || data[2] == 0x42 || data[2] == 0x43 || data[2] == 0x44))
|
||||
{
|
||||
// 1b 5b 41 (上箭头)
|
||||
// 1b 5b 42 (下箭头)
|
||||
// 1b 5b 43 (右箭头)
|
||||
// 1b 5b 44 (左箭头)
|
||||
m_pty_stat = PTY_STAT_WAIT_SERVER_ECHO;
|
||||
// EXLOGD("------ turn to PTY_STAT_WAIT_SERVER_ECHO, input ARROW.\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (len == 4)
|
||||
{
|
||||
if (data[0] == 0x1b && data[1] == 0x5b && data[2] == 0x33 && data[3] == 0x7e)
|
||||
{
|
||||
// 1b 5b 33 7e (删除一个字符)
|
||||
m_pty_stat = PTY_STAT_WAIT_SERVER_ECHO;
|
||||
// EXLOGD("------ turn to PTY_STAT_WAIT_SERVER_ECHO, input DEL.\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (len >= 512)
|
||||
{
|
||||
if (m_pty_stat != PTY_STAT_EXEC_MULTI_LINE_CMD)
|
||||
{
|
||||
m_pty_stat = PTY_STAT_NORMAL_WAIT_PROMPT;
|
||||
// EXLOGD("------ turn to PTY_STAT_NORMAL_WAIT_PROMPT, input too large.\n");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
int return_count = 0;
|
||||
bool valid_input = true;
|
||||
|
||||
int offset = 0;
|
||||
int last_return_pos = 0;
|
||||
for (; offset < len;)
|
||||
{
|
||||
uint8_t ch = data[offset];
|
||||
|
||||
switch (ch)
|
||||
{
|
||||
case 0x1b:
|
||||
if (offset + 1 < len)
|
||||
{
|
||||
if (data[offset + 1] == 0x5b)
|
||||
{
|
||||
valid_input = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case 0x0d:
|
||||
return_count++;
|
||||
last_return_pos = offset;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (!valid_input)
|
||||
break;
|
||||
|
||||
offset++;
|
||||
}
|
||||
|
||||
if (!valid_input)
|
||||
{
|
||||
if (m_pty_stat != PTY_STAT_EXEC_MULTI_LINE_CMD)
|
||||
{
|
||||
m_pty_stat = PTY_STAT_NORMAL_WAIT_PROMPT;
|
||||
// EXLOGD("------ turn to PTY_STAT_NORMAL_WAIT_PROMPT, input invalid.\n");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (return_count > 0)
|
||||
{
|
||||
std::string tmp_cmd((const char *) data, last_return_pos + 1);
|
||||
EXLOGD("[%s] Paste CMD=[%s]\n", m_owner->dbg_name().c_str(), tmp_cmd.c_str());
|
||||
|
||||
m_pty_stat = PTY_STAT_EXEC_MULTI_LINE_CMD;
|
||||
// EXLOGD("------ turn to PTY_STAT_EXEC_MULTI_LINE_CMD, maybe paste.\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
m_pty_stat = PTY_STAT_WAIT_SERVER_ECHO;
|
||||
// EXLOGD("------ turn to PTY_STAT_WAIT_SERVER_ECHO, input something.\n");
|
||||
}
|
||||
}
|
||||
|
||||
void SshChannelPair::process_pty_data_from_server(const uint8_t *data, uint32_t len)
|
||||
{
|
||||
if (data == nullptr || len == 0)
|
||||
return;
|
||||
|
||||
bool contains_prompt = false;
|
||||
if (m_pty_stat == PTY_STAT_NORMAL_WAIT_PROMPT
|
||||
|| m_pty_stat == PTY_STAT_TAB_WAIT_PROMPT
|
||||
|| m_pty_stat == PTY_STAT_MULTI_CMD_WAIT_PROMPT
|
||||
|| m_pty_stat == PTY_STAT_WAIT_SERVER_ECHO)
|
||||
{
|
||||
if (_contains_cmd_prompt(data, len))
|
||||
{
|
||||
contains_prompt = true;
|
||||
|
||||
if (m_pty_stat == PTY_STAT_NORMAL_WAIT_PROMPT)
|
||||
{
|
||||
// EXLOGD("------ turn to PTY_STAT_WAIT_CLIENT_INPUT, recv prompt after exec.\n");
|
||||
m_pty_stat = PTY_STAT_WAIT_CLIENT_INPUT;
|
||||
return;
|
||||
}
|
||||
else if (m_pty_stat == PTY_STAT_TAB_WAIT_PROMPT)
|
||||
{
|
||||
// EXLOGD("------ turn to PTY_STAT_WAIT_CLIENT_INPUT, recv prompt after TAB.\n");
|
||||
m_pty_stat = PTY_STAT_WAIT_CLIENT_INPUT;
|
||||
return;
|
||||
}
|
||||
else if (m_pty_stat == PTY_STAT_MULTI_CMD_WAIT_PROMPT)
|
||||
{
|
||||
// EXLOGD("------ turn to PTY_STAT_MULTI_CMD_WAIT_PROMPT, recv prompt while multi-exec.\n");
|
||||
m_pty_stat = PTY_STAT_EXEC_MULTI_LINE_CMD;
|
||||
return;
|
||||
}
|
||||
else if (m_pty_stat == PTY_STAT_WAIT_SERVER_ECHO)
|
||||
{
|
||||
// EXLOGD("------ turn to PTY_STAT_WAIT_CLIENT_INPUT, recv prompt while wait echo.\n");
|
||||
m_pty_stat = PTY_STAT_WAIT_CLIENT_INPUT;
|
||||
m_cmd.reset();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!contains_prompt)
|
||||
{
|
||||
if (m_pty_stat == PTY_STAT_NORMAL_WAIT_PROMPT
|
||||
|| m_pty_stat == PTY_STAT_TAB_WAIT_PROMPT
|
||||
|| m_pty_stat == PTY_STAT_MULTI_CMD_WAIT_PROMPT)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!(
|
||||
m_pty_stat == PTY_STAT_WAIT_SERVER_ECHO
|
||||
|| m_pty_stat == PTY_STAT_EXEC_MULTI_LINE_CMD
|
||||
|| m_pty_stat == PTY_STAT_MULTI_CMD_WAIT_PROMPT
|
||||
|| m_pty_stat == PTY_STAT_TAB_WAIT_PROMPT
|
||||
|| m_pty_stat == PTY_STAT_TAB_PRESSED
|
||||
))
|
||||
{
|
||||
// EXLOGD("------ keep PTY_STAT, recv but not in ECHO or multi-cmd mode.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (len > 512)
|
||||
{
|
||||
// EXLOGD("------ keep PTY_STAT, recv too large.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理输入回显,合成最终的命令行字符串
|
||||
int offset = 0;
|
||||
bool esc_mode = false;
|
||||
int esc_arg = 0;
|
||||
for (; offset < len;)
|
||||
{
|
||||
uint8_t 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 '>':
|
||||
m_cmd.reset();
|
||||
return;
|
||||
|
||||
case 0x4b:
|
||||
{ // 'K'
|
||||
if (0 == esc_arg)
|
||||
{
|
||||
// 删除光标到行尾的字符串
|
||||
m_cmd.erase_to_end();
|
||||
}
|
||||
else if (1 == esc_arg)
|
||||
{
|
||||
// 删除从开始到光标处的字符串
|
||||
m_cmd.erase_to_begin();
|
||||
}
|
||||
else if (2 == esc_arg)
|
||||
{
|
||||
// 删除整行
|
||||
m_cmd.reset();
|
||||
}
|
||||
|
||||
esc_mode = false;
|
||||
break;
|
||||
}
|
||||
case 0x43:
|
||||
{// ^[C
|
||||
// 光标右移
|
||||
if (esc_arg == 0)
|
||||
esc_arg = 1;
|
||||
m_cmd.cursor_move_right(esc_arg);
|
||||
esc_mode = false;
|
||||
break;
|
||||
}
|
||||
case 0x44:
|
||||
{ // ^[D
|
||||
// 光标左移
|
||||
if (esc_arg == 0)
|
||||
esc_arg = 1;
|
||||
m_cmd.cursor_move_left(esc_arg);
|
||||
esc_mode = false;
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x50:
|
||||
{
|
||||
// 'P' 删除指定数量的字符
|
||||
if (esc_arg == 0)
|
||||
esc_arg = 1;
|
||||
m_cmd.erase_chars(esc_arg);
|
||||
esc_mode = false;
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x40:
|
||||
{ // '@' 插入指定数量的空白字符
|
||||
if (esc_arg == 0)
|
||||
esc_arg = 1;
|
||||
m_cmd.insert_white_space(esc_arg);
|
||||
esc_mode = false;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
esc_mode = false;
|
||||
break;
|
||||
}
|
||||
|
||||
offset++;
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (ch)
|
||||
{
|
||||
case 0x07:
|
||||
// 响铃
|
||||
break;
|
||||
case 0x08:
|
||||
{
|
||||
// 光标左移
|
||||
m_cmd.cursor_move_left(1);
|
||||
break;
|
||||
}
|
||||
case 0x1b:
|
||||
{
|
||||
if (offset + 1 < len)
|
||||
{
|
||||
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 (m_pty_stat == PTY_STAT_EXEC_MULTI_LINE_CMD)
|
||||
{
|
||||
if (!m_cmd.empty())
|
||||
EXLOGD("[%s] one of multi-cmd, CMD=[%s]\n", m_owner->dbg_name().c_str(), m_cmd.str().c_str());
|
||||
m_cmd.reset();
|
||||
|
||||
m_pty_stat = PTY_STAT_MULTI_CMD_WAIT_PROMPT;
|
||||
// EXLOGD("------ turn to PTY_STAT_MULTI_CMD_WAIT_PROMPT, recv 0x0d0a after multi-exec.\n");
|
||||
|
||||
if (_contains_cmd_prompt(data, len))
|
||||
{
|
||||
m_pty_stat = PTY_STAT_EXEC_MULTI_LINE_CMD;
|
||||
// EXLOGD("------ turn to PTY_STAT_EXEC_MULTI_LINE_CMD, recv prompt after multi-exec.\n");
|
||||
}
|
||||
}
|
||||
else if (m_pty_stat == PTY_STAT_TAB_PRESSED)
|
||||
{
|
||||
m_pty_stat = PTY_STAT_TAB_WAIT_PROMPT;
|
||||
// EXLOGD("------ turn to PTY_STAT_TAB_WAIT_PROMPT, recv 0d0a after TAB pressed.\n");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
m_cmd.replace(ch);
|
||||
if (m_pty_stat == PTY_STAT_WAIT_SERVER_ECHO)
|
||||
{
|
||||
m_pty_stat = PTY_STAT_WAIT_CLIENT_INPUT;
|
||||
// EXLOGD("------ turn to PTY_STAT_WAIT_CLIENT_INPUT, recv something.\n");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
offset++;
|
||||
}
|
||||
|
||||
if (m_pty_stat == PTY_STAT_MULTI_CMD_WAIT_PROMPT && contains_prompt)
|
||||
{
|
||||
m_pty_stat = PTY_STAT_EXEC_MULTI_LINE_CMD;
|
||||
// EXLOGD("------ turn to PTY_STAT_EXEC_MULTI_LINE_CMD, recv prompt.\n");
|
||||
}
|
||||
}
|
||||
|
||||
bool SshChannelPair::_contains_cmd_prompt(const uint8_t *data, uint32_t len)
|
||||
{
|
||||
// 正常情况下,收到的服务端数据(一包数据不会太大,可以考虑限定在512字节范围内),从后向前查找 0x07,它的位置
|
||||
// 应该位于倒数256字节范围内(这之后的数据可能是命令行提示符的内容了,不会太长的)。继续向前找,应该能够找到正
|
||||
// 序为 1b 5d 30/31/32/33 3b ... 直到刚才的 07。满足这样格式的,99%可能处于命令行模式了,还有1%可能是Mac
|
||||
// 下,每次执行命令后会立刻返回两个提示符用于用户界面改变标题,然后才是命令执行的输出,最后再输出一次提示符。
|
||||
// 格式: 1b 5d Ps 3b Pt 07
|
||||
// Ps = 0 ====> '0'=0x30, Change Icon Name and Window Title to Pt.
|
||||
// Ps = 1 ====> '1'=0x31, Change Icon Name to Pt.
|
||||
// Ps = 2 ====> '2'=0x32, Change Window Title to Pt.
|
||||
// Ps = 3 ====> '3'=0x33, Set X property on top-level window.
|
||||
// Ps = 其他的值,我们就不care了。
|
||||
// https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Operating-System-Controls
|
||||
|
||||
// 服务端返回大量数据时,一一解析会导致操作变慢,需要有方法避免
|
||||
// 暂时的做法是数据长度超过一定阈值时,跳过解析判断,这种情况下不会是回显数据
|
||||
// 例如 cat 一个大文件,数据包一次可能会超过16KB。
|
||||
if (len >= 2048)
|
||||
return false;
|
||||
|
||||
bool found_0x07 = false;
|
||||
bool found_0x3b = false;
|
||||
bool found_Ps = false;
|
||||
bool found_0x5d = false;
|
||||
|
||||
int offset = static_cast<int>(len) - 1;
|
||||
|
||||
for (int i = 0; offset >= 0; i++)
|
||||
{
|
||||
if (i > 256)
|
||||
return false;
|
||||
|
||||
if (found_0x5d)
|
||||
return (data[offset] == 0x1b);
|
||||
|
||||
if (found_Ps)
|
||||
{
|
||||
found_0x5d = (data[offset] == 0x5d);
|
||||
if (!found_0x5d)
|
||||
return false;
|
||||
offset--;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (found_0x3b)
|
||||
{
|
||||
found_Ps = (data[offset] == 0x30 || data[offset] == 0x31 || data[offset] == 0x32);
|
||||
if (!found_Ps)
|
||||
return false;
|
||||
offset--;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!found_0x07)
|
||||
{
|
||||
found_0x07 = (data[offset] == 0x07);
|
||||
offset--;
|
||||
continue;
|
||||
}
|
||||
|
||||
found_0x3b = (data[offset] == 0x3b);
|
||||
offset--;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void SshChannelPair::process_sftp_command(ssh_channel ch, const uint8_t *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 (ch != rsc_tp2cli)
|
||||
return;
|
||||
|
||||
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");
|
||||
EXLOGD("[sftp-%s] SFTP INITIALIZE\n", m_owner->dbg_name().c_str());
|
||||
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
|
||||
EXLOGD("[sftp-%s] SSH_FXP_OPEN\n", m_owner->dbg_name().c_str());
|
||||
break;
|
||||
case 0x0b:
|
||||
// 0x0b = 11 = SSH_FXP_OPENDIR
|
||||
EXLOGD("[sftp-%s] SSH_FXP_OPENDIR\n", m_owner->dbg_name().c_str());
|
||||
break;
|
||||
case 0x0d:
|
||||
// 0x0d = 13 = SSH_FXP_REMOVE
|
||||
EXLOGD("[sftp-%s] SSH_FXP_REMOVE\n", m_owner->dbg_name().c_str());
|
||||
break;
|
||||
case 0x0e:
|
||||
// 0x0e = 14 = SSH_FXP_MKDIR
|
||||
EXLOGD("[sftp-%s] SSH_FXP_MKDIR\n", m_owner->dbg_name().c_str());
|
||||
break;
|
||||
case 0x0f:
|
||||
// 0x0f = 15 = SSH_FXP_RMDIR
|
||||
EXLOGD("[sftp-%s] SSH_FXP_RMDIR\n", m_owner->dbg_name().c_str());
|
||||
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]);
|
||||
EXLOGD("[sftp-%s] SSH_FXP_RENAME\n", m_owner->dbg_name().c_str());
|
||||
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]);
|
||||
EXLOGD("[sftp-%s] SSH_FXP_LINK\n", m_owner->dbg_name().c_str());
|
||||
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());
|
||||
}
|
||||
|
||||
EXLOGD("[sftp-%s] %s\n", m_owner->dbg_name().c_str(), msg);
|
||||
rec.record_command(0, msg);
|
||||
}
|
||||
|
||||
bool SshChannelPair::record_begin(const TPP_CONNECT_INFO *conn_info)
|
||||
{
|
||||
#ifndef TEST_SSH_SESSION_000000
|
||||
if (!g_ssh_env.session_begin(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, conn_info->protocol_sub_type, TP_SESS_STAT_STARTED)) {
|
||||
EXLOGE("[ssh] [channel:%d] can not update state, cannel begin failed.\n", channel_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
rec.begin(g_ssh_env.replay_path.c_str(), L"tp-ssh", db_id, 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", cp->channel_id, cp->state);
|
||||
|
||||
// 如果会话过程中没有发生错误,则将其状态改为结束,否则记录下错误值
|
||||
if (state == TP_SESS_STAT_RUNNING || state == TP_SESS_STAT_STARTED)
|
||||
state = TP_SESS_STAT_END;
|
||||
|
||||
g_ssh_env.session_end(m_owner->sid().c_str(), db_id, state);
|
||||
|
||||
db_id = 0;
|
||||
}
|
||||
else {
|
||||
// EXLOGD("[ssh] [channel:%d] when channel end, no db-id.\n", cp->channel_id);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
// ==================================================
|
||||
// SshCommand
|
||||
// ==================================================
|
||||
|
||||
SshCommand::SshCommand()
|
||||
{
|
||||
m_cmd.clear();
|
||||
m_pos = m_cmd.begin();
|
||||
}
|
||||
|
||||
SshCommand::~SshCommand()
|
||||
{
|
||||
}
|
||||
|
||||
void SshCommand::_dump(const char *msg)
|
||||
{
|
||||
// EXLOGD("CMD-BUFFER: %s [%s]\n", msg, str().c_str());
|
||||
}
|
|
@ -0,0 +1,233 @@
|
|||
#ifndef __SSH_CHANNEL_PAIR_H__
|
||||
#define __SSH_CHANNEL_PAIR_H__
|
||||
|
||||
#include <ex.h>
|
||||
#include <libssh/libssh.h>
|
||||
|
||||
#include "ssh_recorder.h"
|
||||
|
||||
class SshSession;
|
||||
|
||||
class SshCommand
|
||||
{
|
||||
public:
|
||||
SshCommand();
|
||||
virtual ~SshCommand();
|
||||
|
||||
void reset()
|
||||
{
|
||||
m_cmd.clear();
|
||||
m_pos = m_cmd.end();
|
||||
_dump("reset");
|
||||
}
|
||||
|
||||
std::string str()
|
||||
{
|
||||
if (empty())
|
||||
return "";
|
||||
else
|
||||
return std::string(m_cmd.begin(), m_cmd.end());
|
||||
}
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
return m_cmd.empty();
|
||||
}
|
||||
|
||||
void erase_to_end()
|
||||
{
|
||||
// 删除光标到行尾的字符串
|
||||
m_cmd.erase(m_pos, m_cmd.end());
|
||||
m_pos = m_cmd.end();
|
||||
_dump("erase to end");
|
||||
}
|
||||
|
||||
void erase_to_begin()
|
||||
{
|
||||
// 删除从开始到光标处的字符串
|
||||
m_cmd.erase(m_cmd.begin(), m_pos);
|
||||
m_pos = m_cmd.begin();
|
||||
_dump("erase to begin");
|
||||
}
|
||||
|
||||
void cursor_move_right(int count)
|
||||
{
|
||||
// 光标右移
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
if (m_pos != m_cmd.end())
|
||||
m_pos++;
|
||||
else
|
||||
break;
|
||||
}
|
||||
_dump("cursor move right");
|
||||
}
|
||||
|
||||
void cursor_move_left(int count)
|
||||
{
|
||||
// 光标左移
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
if (m_pos != m_cmd.begin())
|
||||
m_pos--;
|
||||
else
|
||||
break;
|
||||
}
|
||||
_dump("cursor move left");
|
||||
}
|
||||
|
||||
void erase_chars(int count)
|
||||
{
|
||||
// 删除指定数量的字符
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
if (m_pos != m_cmd.end())
|
||||
m_pos = m_cmd.erase(m_pos);
|
||||
else
|
||||
break;
|
||||
}
|
||||
_dump("erase char");
|
||||
}
|
||||
|
||||
void insert_white_space(int count)
|
||||
{
|
||||
// 插入指定数量的空白字符
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
m_pos = m_cmd.insert(m_pos, ' ');
|
||||
}
|
||||
_dump("insert white space");
|
||||
}
|
||||
|
||||
void replace(uint8_t ch)
|
||||
{
|
||||
if (m_pos != m_cmd.end())
|
||||
{
|
||||
m_pos = m_cmd.erase(m_pos);
|
||||
m_pos = m_cmd.insert(m_pos, ch);
|
||||
m_pos++;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_cmd.push_back(ch);
|
||||
//cmd_char_pos = cmd_char_list.end();
|
||||
m_pos = m_cmd.end();
|
||||
}
|
||||
_dump("replace char");
|
||||
}
|
||||
|
||||
void insert(const uint8_t *data, int len)
|
||||
{
|
||||
for (int i = 0; i < len; ++i)
|
||||
{
|
||||
if (m_pos == m_cmd.end())
|
||||
{
|
||||
m_cmd.push_back(data[i]);
|
||||
m_pos = m_cmd.end();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_pos = m_cmd.insert(m_pos, data[i]);
|
||||
m_pos++;
|
||||
}
|
||||
}
|
||||
_dump("insert chars");
|
||||
}
|
||||
|
||||
void insert(uint8_t ch)
|
||||
{
|
||||
if (m_pos == m_cmd.end())
|
||||
{
|
||||
m_cmd.push_back(ch);
|
||||
m_pos = m_cmd.end();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_pos = m_cmd.insert(m_pos, ch);
|
||||
m_pos++;
|
||||
}
|
||||
_dump("insert char");
|
||||
}
|
||||
|
||||
protected:
|
||||
void _dump(const char *msg);
|
||||
|
||||
private:
|
||||
std::list<char> m_cmd;
|
||||
std::list<char>::iterator m_pos;
|
||||
};
|
||||
|
||||
// SSH命令解析,有限状态机状态值
|
||||
enum PTY_STAT
|
||||
{
|
||||
PTY_STAT_NORMAL_WAIT_PROMPT = 0,
|
||||
PTY_STAT_MULTI_CMD_WAIT_PROMPT,
|
||||
PTY_STAT_TAB_PRESSED,
|
||||
PTY_STAT_TAB_WAIT_PROMPT,
|
||||
PTY_STAT_WAIT_CLIENT_INPUT,
|
||||
PTY_STAT_EXEC_MULTI_LINE_CMD,
|
||||
PTY_STAT_WAIT_SERVER_ECHO,
|
||||
};
|
||||
|
||||
|
||||
class SshChannelPair
|
||||
{
|
||||
friend class SshSession;
|
||||
|
||||
public:
|
||||
SshChannelPair(SshSession *owner, ssh_channel rsc_tp2cli, ssh_channel rsc_tp2srv);
|
||||
|
||||
virtual ~SshChannelPair();
|
||||
|
||||
void process_pty_data_from_client(const uint8_t *data, uint32_t len);
|
||||
void process_pty_data_from_server(const uint8_t *data, uint32_t len);
|
||||
|
||||
|
||||
void process_sftp_command(ssh_channel ch, const uint8_t *data, uint32_t len);
|
||||
|
||||
// when client<->server channel created, start to record.
|
||||
bool record_begin(const TPP_CONNECT_INFO *conn_info);
|
||||
|
||||
// stop record because channel closed.
|
||||
void record_end();
|
||||
|
||||
protected:
|
||||
bool _contains_cmd_prompt(const uint8_t *data, uint32_t len);
|
||||
|
||||
protected:
|
||||
SshSession *m_owner;
|
||||
|
||||
int win_width; // window width, in char count.
|
||||
int type; // TS_SSH_CHANNEL_TYPE_SHELL or TS_SSH_CHANNEL_TYPE_SFTP
|
||||
|
||||
ssh_channel rsc_tp2cli;
|
||||
ssh_channel rsc_tp2srv;
|
||||
|
||||
uint32_t last_access_timestamp;
|
||||
|
||||
TppSshRec rec;
|
||||
int db_id;
|
||||
|
||||
int state;
|
||||
int channel_id; // for debug only.
|
||||
|
||||
bool is_first_server_data;
|
||||
bool need_close;
|
||||
|
||||
// 是否处于命令行输入模式
|
||||
bool m_is_cmd_mode;
|
||||
bool m_recv_prompt;
|
||||
uint8_t m_client_last_char;
|
||||
SshCommand m_cmd;
|
||||
|
||||
// uint32_t m_input_flag;
|
||||
|
||||
// std::vector<uint8_t> m_last_input;
|
||||
|
||||
PTY_STAT m_pty_stat;
|
||||
};
|
||||
|
||||
typedef std::list<SshChannelPair *> TPChannelPairs;
|
||||
typedef std::map<ssh_channel, SshChannelPair *> channel_map;
|
||||
|
||||
#endif //__SSH_CHANNEL_PAIR_H__
|
|
@ -0,0 +1,257 @@
|
|||
#include "ssh_proxy.h"
|
||||
#include "tpp_env.h"
|
||||
|
||||
SshProxy g_ssh_proxy;
|
||||
|
||||
SshProxy::SshProxy() noexcept:
|
||||
ExThreadBase("ssh-proxy-thread"),
|
||||
m_bind(nullptr),
|
||||
m_host_port(0)
|
||||
{
|
||||
m_timer_counter_check_noop = 0;
|
||||
m_timer_counter_keep_alive = 0;
|
||||
m_noop_timeout_sec = 0;//900; // default to 15 minutes.
|
||||
m_listener_running = false;
|
||||
m_dbg_id = 0;
|
||||
}
|
||||
|
||||
SshProxy::~SshProxy()
|
||||
{
|
||||
if (m_bind)
|
||||
{
|
||||
ssh_bind_free(m_bind);
|
||||
m_bind = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool SshProxy::init()
|
||||
{
|
||||
m_host_ip = g_ssh_env.bind_ip;
|
||||
m_host_port = g_ssh_env.bind_port;
|
||||
|
||||
m_bind = ssh_bind_new();
|
||||
if (!m_bind)
|
||||
{
|
||||
EXLOGE("[ssh] can not create bind.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SSH_OK != ssh_bind_options_set(m_bind, SSH_BIND_OPTIONS_BINDADDR, m_host_ip.c_str()))
|
||||
{
|
||||
EXLOGE("[ssh] can not set bind option: SSH_BIND_OPTIONS_BINDADDR.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SSH_OK != ssh_bind_options_set(m_bind, SSH_BIND_OPTIONS_BINDPORT, &m_host_port))
|
||||
{
|
||||
EXLOGE("[ssh] can not set bind option: SSH_BIND_OPTIONS_BINDPORT.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
ex_wstr _key_file = g_ssh_env.etc_path;
|
||||
ex_path_join(_key_file, false, L"tp_ssh_server.key", NULL);
|
||||
ex_astr key_file;
|
||||
ex_wstr2astr(_key_file, key_file);
|
||||
|
||||
EXLOGV("[ssh] try to load ssh-server-key: %s\n", key_file.c_str());
|
||||
if (SSH_OK != ssh_bind_options_set(m_bind, SSH_BIND_OPTIONS_RSAKEY, key_file.c_str()))
|
||||
{
|
||||
EXLOGE("[ssh] can not set bind option: SSH_BIND_OPTIONS_RSAKEY.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ssh_bind_listen(m_bind) < 0)
|
||||
{
|
||||
EXLOGE("[ssh] can not listen on port %d: %s\n", m_host_port, ssh_get_error(m_bind));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SshProxy::timer()
|
||||
{
|
||||
// timer() will be called per one second by tp_core main thread.
|
||||
m_timer_counter_check_noop++;
|
||||
|
||||
// clean closed session every one second.
|
||||
{
|
||||
ExThreadSmartLock locker(m_lock);
|
||||
|
||||
for (auto it = m_sessions.begin(); it != m_sessions.end();)
|
||||
{
|
||||
// 检查通道是否已经关闭
|
||||
it->first->check_channels();
|
||||
// 检查会话是否已经关闭,如果会话已经完全关闭,则销毁之
|
||||
if (it->first->closed())
|
||||
{
|
||||
delete it->first;
|
||||
m_sessions.erase(it++);
|
||||
}
|
||||
else
|
||||
{
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_need_stop)
|
||||
return;
|
||||
}
|
||||
|
||||
// check no-op per 5 seconds.
|
||||
if (m_timer_counter_check_noop >= 5)
|
||||
{
|
||||
auto t_now = (ex_u32) time(nullptr);
|
||||
m_timer_counter_check_noop = 0;
|
||||
|
||||
ExThreadSmartLock locker(m_lock);
|
||||
|
||||
for (auto &session : m_sessions)
|
||||
{
|
||||
session.first->save_record();
|
||||
if (0 != m_noop_timeout_sec)
|
||||
session.first->check_noop_timeout(t_now, m_noop_timeout_sec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SshProxy::set_cfg(ex_u32 noop_timeout)
|
||||
{
|
||||
m_noop_timeout_sec = noop_timeout;
|
||||
}
|
||||
|
||||
void SshProxy::kill_sessions(const ex_astrs &sessions)
|
||||
{
|
||||
ExThreadSmartLock locker(m_lock);
|
||||
|
||||
for (auto &session : m_sessions)
|
||||
{
|
||||
for (const auto &sid : sessions)
|
||||
{
|
||||
if (session.first->sid() == sid)
|
||||
{
|
||||
EXLOGW("[ssh] kill session %s\n", sid.c_str());
|
||||
session.first->check_noop_timeout(0, 0); // 立即结束
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SshProxy::_thread_loop()
|
||||
{
|
||||
EXLOGI("[ssh] TeleportServer-SSH ready on %s:%d\n", m_host_ip.c_str(), m_host_port);
|
||||
|
||||
m_listener_running = true;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
ssh_session rs_tp2cli = ssh_new();
|
||||
|
||||
// #ifdef EX_DEBUG
|
||||
// int flag = SSH_LOG_FUNCTIONS;
|
||||
// ssh_options_set(rs_tp2cli, SSH_OPTIONS_LOG_VERBOSITY, &flag);
|
||||
// #endif
|
||||
|
||||
if (ssh_bind_accept(m_bind, rs_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(rs_tp2cli);
|
||||
break;
|
||||
}
|
||||
|
||||
struct sockaddr_storage sock_client{};
|
||||
|
||||
char ip[32] = { 0 };
|
||||
int len = sizeof(ip);
|
||||
|
||||
|
||||
#ifdef EX_OS_WIN32
|
||||
getpeername(ssh_get_fd(rs_tp2cli), (struct sockaddr *) &sock_client, &len);
|
||||
#else
|
||||
getpeername(ssh_get_fd(rs_tp2cli), (struct sockaddr *) &sock_client, (unsigned int *) &len);
|
||||
#endif
|
||||
auto addr = (sockaddr_in *) &sock_client;
|
||||
|
||||
if (0 != ex_ip4_name(addr, ip, sizeof(ip)))
|
||||
{
|
||||
EXLOGW("[ssh] can not parse client address into IP and port.\n");
|
||||
}
|
||||
|
||||
uint32_t dbg_id = m_dbg_id++;
|
||||
auto session = new SshSession(this, rs_tp2cli, dbg_id, ip, addr->sin_port);
|
||||
EXLOGW("[ssh] ------ NEW SSH SESSION [%s from %s:%d] ------\n", session->dbg_name().c_str(), ip, addr->sin_port);
|
||||
|
||||
{
|
||||
ExThreadSmartLock locker(m_lock);
|
||||
m_sessions.insert(std::make_pair(session, 0));
|
||||
}
|
||||
|
||||
session->start();
|
||||
}
|
||||
|
||||
// 通知所有session都立即结束
|
||||
{
|
||||
ExThreadSmartLock locker(m_lock);
|
||||
|
||||
|
||||
for (auto &session : m_sessions)
|
||||
{
|
||||
session.first->check_noop_timeout(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// 等待所有session完成关闭清理操作,工作线程退出
|
||||
for (;;)
|
||||
{
|
||||
// tp_core退出时会先停止timer线程,所以这里需要自己调用timer()来进行session状态检查
|
||||
timer();
|
||||
|
||||
{
|
||||
ExThreadSmartLock locker(m_lock);
|
||||
if (m_sessions.empty())
|
||||
break;
|
||||
ex_sleep_ms(100);
|
||||
}
|
||||
}
|
||||
|
||||
m_listener_running = false;
|
||||
EXLOGV("[ssh] main-loop end.\n");
|
||||
}
|
||||
|
||||
void SshProxy::_on_stop()
|
||||
{
|
||||
ExThreadBase::_on_stop();
|
||||
|
||||
if (m_is_running)
|
||||
{
|
||||
// 用一个变通的方式来结束阻塞中的监听,就是连接一下它。
|
||||
ex_astr host_ip = m_host_ip;
|
||||
if (host_ip == "0.0.0.0")
|
||||
host_ip = "127.0.0.1";
|
||||
|
||||
ssh_session _session = ssh_new();
|
||||
ssh_options_set(_session, SSH_OPTIONS_HOST, host_ip.c_str());
|
||||
ssh_options_set(_session, SSH_OPTIONS_PORT, &m_host_port);
|
||||
|
||||
int _timeout_us = 10;
|
||||
ssh_options_set(_session, SSH_OPTIONS_TIMEOUT, &_timeout_us);
|
||||
ssh_connect(_session);
|
||||
ex_sleep_ms(500);
|
||||
|
||||
ssh_disconnect(_session);
|
||||
ssh_free(_session);
|
||||
ex_sleep_ms(500);
|
||||
}
|
||||
|
||||
while (m_listener_running)
|
||||
{
|
||||
ex_sleep_ms(1000);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
#ifndef __SSH_PROXY_H__
|
||||
#define __SSH_PROXY_H__
|
||||
|
||||
#include "ssh_session.h"
|
||||
|
||||
#include <ex.h>
|
||||
|
||||
typedef std::map<SshSession *, unsigned char> ts_ssh_sessions;
|
||||
|
||||
class SshProxy : public ExThreadBase
|
||||
{
|
||||
public:
|
||||
SshProxy() noexcept;
|
||||
|
||||
~SshProxy() override;
|
||||
|
||||
bool init();
|
||||
|
||||
void timer();
|
||||
|
||||
void set_cfg(ex_u32 noop_timeout);
|
||||
|
||||
void kill_sessions(const ex_astrs &sessions);
|
||||
|
||||
protected:
|
||||
void _thread_loop() override;
|
||||
|
||||
void _on_stop() override;
|
||||
|
||||
private:
|
||||
ssh_bind m_bind;
|
||||
int m_timer_counter_check_noop;
|
||||
int m_timer_counter_keep_alive;
|
||||
|
||||
ExThreadLock m_lock;
|
||||
bool m_listener_running;
|
||||
|
||||
ex_astr m_host_ip;
|
||||
int m_host_port;
|
||||
|
||||
ts_ssh_sessions m_sessions;
|
||||
|
||||
ex_u32 m_noop_timeout_sec;
|
||||
|
||||
uint32_t m_dbg_id;
|
||||
};
|
||||
|
||||
extern SshProxy g_ssh_proxy;
|
||||
|
||||
#endif // __SSH_PROXY_H__
|
|
@ -1,4 +1,4 @@
|
|||
#include "tpssh_rec.h"
|
||||
#include "ssh_recorder.h"
|
||||
//#include <teleport_const.h>
|
||||
|
||||
static ex_u8 TPP_RECORD_MAGIC[4] = {'T', 'P', 'P', 'R'};
|
|
@ -3,12 +3,12 @@
|
|||
|
||||
#include "../../common/base_record.h"
|
||||
|
||||
#define TS_RECORD_TYPE_SSH_TERM_SIZE 0x01 // 终端大小(行数与列数)
|
||||
#define TS_RECORD_TYPE_SSH_DATA 0x02 // 用于展示的数据内容
|
||||
#define TS_RECORD_TYPE_SSH_TERM_SIZE 0x01 // 终端大小(行数与列数)
|
||||
#define TS_RECORD_TYPE_SSH_DATA 0x02 // 用于展示的数据内容
|
||||
|
||||
#pragma pack(push,1)
|
||||
|
||||
// 记录窗口大小改变的数据包
|
||||
// 记录窗口大小改变的数据包
|
||||
typedef struct TS_RECORD_WIN_SIZE
|
||||
{
|
||||
ex_u16 width;
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,197 @@
|
|||
#ifndef __SSH_SESSION_H__
|
||||
#define __SSH_SESSION_H__
|
||||
|
||||
#include <ex.h>
|
||||
|
||||
#include <libssh/libssh.h>
|
||||
#include <libssh/server.h>
|
||||
#include <libssh/callbacks.h>
|
||||
#include <libssh/sftp.h>
|
||||
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <queue>
|
||||
|
||||
#include "ssh_channel_pair.h"
|
||||
|
||||
#define TEST_SSH_SESSION_000000
|
||||
|
||||
#define TS_SSH_CHANNEL_TYPE_UNKNOWN 0
|
||||
#define TS_SSH_CHANNEL_TYPE_SHELL 1
|
||||
#define TS_SSH_CHANNEL_TYPE_SFTP 2
|
||||
|
||||
enum SSH_SESSION_STATUS
|
||||
{
|
||||
SSH_SESSION_STATE_CLOSED = 0,
|
||||
SSH_SESSION_STATE_STARTING,
|
||||
SSH_SESSION_STATE_AUTHING,
|
||||
SSH_SESSION_STATE_AUTH_END,
|
||||
SSH_SESSION_STATE_RUNNING,
|
||||
SSH_SESSION_STATE_CLOSING
|
||||
};
|
||||
|
||||
class SshProxy;
|
||||
|
||||
class SshSession;
|
||||
|
||||
class SshSession :
|
||||
public ExThreadBase
|
||||
{
|
||||
public:
|
||||
SshSession(SshProxy *proxy, ssh_session rs_tp2cli, uint32_t dbg_id, const char *client_ip, uint16_t client_port);
|
||||
|
||||
virtual ~SshSession();
|
||||
|
||||
// uint32_t dbg_id() const
|
||||
// {
|
||||
// return m_dbg_id;
|
||||
// }
|
||||
|
||||
const std::string &dbg_name() const
|
||||
{
|
||||
return m_dbg_name;
|
||||
}
|
||||
|
||||
const std::string &dbg_client() const
|
||||
{
|
||||
return m_dbg_client;
|
||||
}
|
||||
|
||||
const std::string &dbg_server() const
|
||||
{
|
||||
return m_dbg_server;
|
||||
}
|
||||
|
||||
const std::string &sid()
|
||||
{
|
||||
return m_sid;
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
||||
bool closed() const
|
||||
{
|
||||
return m_state == SSH_SESSION_STATE_CLOSED;
|
||||
}
|
||||
|
||||
ssh_session get_peer_raw_session(ssh_session session)
|
||||
{
|
||||
if (session == m_rs_tp2cli)
|
||||
return m_rs_tp2srv;
|
||||
else if (session == m_rs_tp2srv)
|
||||
return m_rs_tp2cli;
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// --------------------------
|
||||
// 通道管理
|
||||
// --------------------------
|
||||
void set_channel_tp2srv_callbacks(ssh_channel ch_tp2srv);
|
||||
bool make_channel_pair(ssh_channel ch_tp2cli, ssh_channel ch_tp2srv);
|
||||
|
||||
SshChannelPair *get_channel_pair(ssh_channel ch);
|
||||
void check_channels();
|
||||
|
||||
protected:
|
||||
void _thread_loop() override;
|
||||
|
||||
void _on_stop() override;
|
||||
|
||||
void _on_stopped() override;
|
||||
|
||||
// record an error when session connecting or auth-ing.
|
||||
// void _session_error(int err_code);
|
||||
|
||||
private:
|
||||
void _close_channels();
|
||||
|
||||
int _do_auth(const char *user, const char *secret);
|
||||
|
||||
void _set_last_error(int err_code);
|
||||
|
||||
bool _send(ssh_channel channel_to, int is_stderr, void *data, uint32_t len);
|
||||
|
||||
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_STATUS m_state;
|
||||
ssh_session m_rs_tp2cli;
|
||||
ssh_session m_rs_tp2srv;
|
||||
|
||||
ExThreadLock m_lock;
|
||||
|
||||
uint32_t m_dbg_id;
|
||||
std::string m_dbg_name;
|
||||
std::string m_dbg_client;
|
||||
std::string m_dbg_server;
|
||||
|
||||
TPP_CONNECT_INFO *m_conn_info;
|
||||
|
||||
std::string m_sid;
|
||||
std::string m_conn_ip;
|
||||
uint16_t m_conn_port;
|
||||
std::string m_acc_name;
|
||||
std::string m_acc_secret;
|
||||
uint32_t m_flags;
|
||||
int m_auth_type;
|
||||
bool m_allow_user_input_password;
|
||||
|
||||
bool m_first_auth;
|
||||
// 远程主机认证是否通过
|
||||
bool m_auth_passed;
|
||||
// 发生了不可逆的错误,需要关闭整个会话(包括所有的通道)
|
||||
bool m_fault;
|
||||
|
||||
// int m_ssh_ver;
|
||||
|
||||
// 一个ssh_session中可以打开多个ssh_channel
|
||||
// tp_channels m_channels;
|
||||
|
||||
// 管理两端的通道对
|
||||
TPChannelPairs m_pairs;
|
||||
// 用于快速查找
|
||||
channel_map m_channel_map;
|
||||
// 本会话中的所有通道(无论哪一端的)
|
||||
std::list<ssh_channel> m_channels;
|
||||
|
||||
|
||||
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__
|
|
@ -3,29 +3,36 @@
|
|||
TppSshEnv g_ssh_env;
|
||||
|
||||
TppSshEnv::TppSshEnv()
|
||||
{}
|
||||
{
|
||||
}
|
||||
|
||||
TppSshEnv::~TppSshEnv()
|
||||
{}
|
||||
{
|
||||
}
|
||||
|
||||
bool TppSshEnv::_on_init(TPP_INIT_ARGS* args) {
|
||||
bool TppSshEnv::_on_init(TPP_INIT_ARGS *args)
|
||||
{
|
||||
ex_path_join(replay_path, false, L"ssh", NULL);
|
||||
|
||||
ExIniSection* ps = args->cfg->GetSection(L"protocol-ssh");
|
||||
if (NULL == ps) {
|
||||
auto ps = args->cfg->GetSection(L"protocol-ssh");
|
||||
if (!ps)
|
||||
{
|
||||
EXLOGE("[ssh] invalid config(2).\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
ex_wstr tmp;
|
||||
if (!ps->GetStr(L"bind-ip", tmp)) {
|
||||
if (!ps->GetStr(L"bind-ip", tmp))
|
||||
{
|
||||
bind_ip = TS_SSH_PROXY_HOST;
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
ex_wstr2astr(tmp, bind_ip);
|
||||
}
|
||||
|
||||
if (!ps->GetInt(L"bind-port", bind_port)) {
|
||||
if (!ps->GetInt(L"bind-port", bind_port))
|
||||
{
|
||||
bind_port = TS_SSH_PROXY_PORT;
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ public:
|
|||
int bind_port;
|
||||
|
||||
private:
|
||||
bool _on_init(TPP_INIT_ARGS* args);
|
||||
bool _on_init(TPP_INIT_ARGS *args);
|
||||
};
|
||||
|
||||
extern TppSshEnv g_ssh_env;
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
#include "tpssh_proxy.h"
|
||||
#include "ssh_proxy.h"
|
||||
#include "tpp_env.h"
|
||||
|
||||
#include <teleport_const.h>
|
||||
#include <json/json.h>
|
||||
|
||||
TPP_API ex_rv tpp_init(TPP_INIT_ARGS* init_args)
|
||||
TPP_API ex_rv tpp_init(TPP_INIT_ARGS *init_args)
|
||||
{
|
||||
#ifdef EX_OS_UNIX
|
||||
ssh_threads_set_callbacks(ssh_threads_get_pthread());
|
||||
|
@ -36,12 +36,14 @@ TPP_API ex_rv tpp_stop(void)
|
|||
return 0;
|
||||
}
|
||||
|
||||
TPP_API void tpp_timer(void) {
|
||||
TPP_API void tpp_timer(void)
|
||||
{
|
||||
// be called per one second.
|
||||
g_ssh_proxy.timer();
|
||||
}
|
||||
|
||||
static ex_rv _set_runtime_config(const char* param) {
|
||||
static ex_rv tpp_cmd_set_runtime_config(const char *param)
|
||||
{
|
||||
Json::Value jp;
|
||||
Json::CharReaderBuilder jcrb;
|
||||
std::unique_ptr<Json::CharReader> const jreader(jcrb.newCharReader());
|
||||
|
@ -66,28 +68,30 @@ static ex_rv _set_runtime_config(const char* param) {
|
|||
return TPE_PARAM;
|
||||
}
|
||||
|
||||
static ex_rv _kill_sessions(const char* param) {
|
||||
static ex_rv tpp_cmd_kill_sessions(const char *param)
|
||||
{
|
||||
Json::Value jp;
|
||||
Json::CharReaderBuilder jcrb;
|
||||
std::unique_ptr<Json::CharReader> const jreader(jcrb.newCharReader());
|
||||
Json::CharReaderBuilder reader_builder;
|
||||
const char *str_json_begin = param;
|
||||
ex_astr err;
|
||||
|
||||
if (!jreader->parse(str_json_begin, param + strlen(param), &jp, &err))
|
||||
std::unique_ptr<Json::CharReader> const json_reader(reader_builder.newCharReader());
|
||||
if (!json_reader->parse(str_json_begin, param + strlen(param), &jp, &err))
|
||||
return TPE_JSON_FORMAT;
|
||||
|
||||
if (!jp.isArray())
|
||||
return TPE_PARAM;
|
||||
|
||||
ex_astrs ss;
|
||||
int cnt = jp.size();
|
||||
for (int i = 0; i < cnt; ++i)
|
||||
|
||||
for (const auto &item : jp)
|
||||
{
|
||||
if (!item.isString())
|
||||
{
|
||||
if (!jp[i].isString()) {
|
||||
return TPE_PARAM;
|
||||
}
|
||||
|
||||
ss.push_back(jp[i].asString());
|
||||
ss.push_back(item.asString());
|
||||
}
|
||||
|
||||
g_ssh_proxy.kill_sessions(ss);
|
||||
|
@ -95,19 +99,18 @@ static ex_rv _kill_sessions(const char* param) {
|
|||
return TPE_PARAM;
|
||||
}
|
||||
|
||||
TPP_API ex_rv tpp_command(ex_u32 cmd, const char* param) {
|
||||
switch (cmd) {
|
||||
TPP_API ex_rv tpp_command(ex_u32 cmd, const char *param)
|
||||
{
|
||||
if (!param || strlen(param) == 0)
|
||||
return TPE_PARAM;
|
||||
|
||||
switch (cmd)
|
||||
{
|
||||
case TPP_CMD_SET_RUNTIME_CFG:
|
||||
if (param == NULL || strlen(param) == 0)
|
||||
return TPE_PARAM;
|
||||
return _set_runtime_config(param);
|
||||
return tpp_cmd_set_runtime_config(param);
|
||||
case TPP_CMD_KILL_SESSIONS:
|
||||
if (param == NULL || strlen(param) == 0)
|
||||
return TPE_PARAM;
|
||||
return _kill_sessions(param);
|
||||
return tpp_cmd_kill_sessions(param);
|
||||
default:
|
||||
return TPE_UNKNOWN_CMD;
|
||||
}
|
||||
|
||||
return TPE_NOT_IMPLEMENT;
|
||||
}
|
||||
|
|
|
@ -1,401 +0,0 @@
|
|||
#include "tpssh_channel.h"
|
||||
#include "tpssh_session.h"
|
||||
#include "tpp_env.h"
|
||||
#include <teleport_const.h>
|
||||
|
||||
//===============================================================
|
||||
// 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);
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
#ifndef __TPSSH__CHANNEL_H__
|
||||
#define __TPSSH__CHANNEL_H__
|
||||
|
||||
#include <map>
|
||||
#include <list>
|
||||
|
||||
#include <ex.h>
|
||||
#include <libssh/libssh.h>
|
||||
|
||||
#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<char> cmd_char_list;
|
||||
std::list<char>::iterator cmd_char_pos;
|
||||
};
|
||||
|
||||
typedef std::list<SshChannelPair *> TPChannelPairs;
|
||||
typedef std::map<ssh_channel, SshChannelPair *> channel_map;
|
||||
|
||||
#endif // __TPSSH__CHANNEL_H__
|
|
@ -1,651 +0,0 @@
|
|||
#include "tpssh_cli.h"
|
||||
#include "tpssh_session.h"
|
||||
#include <teleport_const.h>
|
||||
|
||||
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<uint8_t *>(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<uint8_t *>(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<unsigned int>(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;
|
||||
// }
|
||||
// }
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
#ifndef __SSH_CLIENT_SIDE_H__
|
||||
#define __SSH_CLIENT_SIDE_H__
|
||||
|
||||
#include <ex.h>
|
||||
#include <libssh/libssh.h>
|
||||
#include <libssh/callbacks.h>
|
||||
#include <libssh/sftp.h>
|
||||
|
||||
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<ssh_channel> 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__
|
|
@ -1,250 +0,0 @@
|
|||
#include "tpssh_proxy.h"
|
||||
#include "tpp_env.h"
|
||||
|
||||
SshProxy g_ssh_proxy;
|
||||
|
||||
SshProxy::SshProxy() :
|
||||
ExThreadBase("ssh-proxy-thread"),
|
||||
m_bind(nullptr) {
|
||||
// m_session_dbg_base_id = 0;
|
||||
m_timer_counter_check_noop = 0;
|
||||
m_timer_counter_keep_alive = 0;
|
||||
m_cfg_noop_timeout_sec = 900; // default to 15 minutes.
|
||||
m_listener_running = false;
|
||||
}
|
||||
|
||||
SshProxy::~SshProxy() {
|
||||
if (!m_bind) {
|
||||
ssh_bind_free(m_bind);
|
||||
m_bind = nullptr;
|
||||
}
|
||||
|
||||
//ssh_finalize();
|
||||
}
|
||||
|
||||
bool SshProxy::init() {
|
||||
m_host_ip = g_ssh_env.bind_ip;
|
||||
m_host_port = g_ssh_env.bind_port;
|
||||
|
||||
m_bind = ssh_bind_new();
|
||||
if (!m_bind) {
|
||||
EXLOGE("[ssh] can not create bind.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SSH_OK != ssh_bind_options_set(m_bind, SSH_BIND_OPTIONS_BINDADDR, m_host_ip.c_str())) {
|
||||
EXLOGE("[ssh] can not set bind option: SSH_BIND_OPTIONS_BINDADDR.\n");
|
||||
return false;
|
||||
}
|
||||
if (SSH_OK != ssh_bind_options_set(m_bind, SSH_BIND_OPTIONS_BINDPORT, &m_host_port)) {
|
||||
EXLOGE("[ssh] can not set bind option: SSH_BIND_OPTIONS_BINDPORT.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
ex_wstr _key_file = g_ssh_env.etc_path;
|
||||
ex_path_join(_key_file, false, L"tp_ssh_server.key", NULL);
|
||||
ex_astr key_file;
|
||||
ex_wstr2astr(_key_file, key_file);
|
||||
|
||||
EXLOGV("[ssh] try to load ssh-server-key: %s\n", key_file.c_str());
|
||||
if (SSH_OK != ssh_bind_options_set(m_bind, SSH_BIND_OPTIONS_RSAKEY, key_file.c_str())) {
|
||||
EXLOGE("[ssh] can not set bind option: SSH_BIND_OPTIONS_RSAKEY.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ssh_bind_listen(m_bind) < 0) {
|
||||
EXLOGE("[ssh] listening to socket: %s\n", ssh_get_error(m_bind));
|
||||
return false;
|
||||
}
|
||||
|
||||
m_listener_running = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SshProxy::timer() {
|
||||
// timer() will be called per one second
|
||||
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);
|
||||
auto t_now = (uint32_t) time(nullptr);
|
||||
|
||||
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;
|
||||
// ExThreadSmartLock locker(m_lock);
|
||||
// auto it = m_sessions.begin();
|
||||
// for (; it != m_sessions.end(); ++it) {
|
||||
// (*it)->keep_alive();
|
||||
// }
|
||||
// }
|
||||
|
||||
{
|
||||
ExThreadSmartLock locker(m_lock);
|
||||
|
||||
auto it = m_sessions.begin();
|
||||
for (; it != m_sessions.end(); ++it) {
|
||||
(*it)->check_channels();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SshProxy::set_cfg(ex_u32 noop_timeout) {
|
||||
m_cfg_noop_timeout_sec = noop_timeout;
|
||||
}
|
||||
|
||||
void SshProxy::kill_sessions(const ex_astrs &sessions) {
|
||||
ExThreadSmartLock locker(m_lock);
|
||||
auto it = m_sessions.begin();
|
||||
for (; it != m_sessions.end(); ++it) {
|
||||
//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); // 立即结束
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SshProxy::_thread_loop() {
|
||||
EXLOGI("[ssh] TeleportServer-SSH ready on %s:%d\n", m_host_ip.c_str(), m_host_port);
|
||||
|
||||
for (;;) {
|
||||
// 注意,ssh_new()出来的指针,如果遇到停止标志,本函数内部就释放了,否则这个指针交给了SshSession类实例管理,其析构时会释放。
|
||||
ssh_session sess_tp2cli = ssh_new();
|
||||
|
||||
// #ifdef EX_DEBUG
|
||||
// int flag = SSH_LOG_FUNCTIONS;
|
||||
// ssh_options_set(sess_tp2cli, SSH_OPTIONS_LOG_VERBOSITY, &flag);
|
||||
// #endif
|
||||
|
||||
// ssh_set_blocking(sess_tp2cli, 0);
|
||||
|
||||
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_tp2cli);
|
||||
break;
|
||||
}
|
||||
|
||||
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_tp2cli), (struct sockaddr *) &sock_client, (unsigned int *) &len);
|
||||
#endif
|
||||
auto *addrin = (sockaddr_in *) &sock_client;
|
||||
|
||||
if (0 != ex_ip4_name(addrin, ip, sizeof(ip))) {
|
||||
ssh_free(sess_tp2cli);
|
||||
EXLOGE("[ssh] can not parse address of client.\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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.push_back(s);
|
||||
}
|
||||
|
||||
if (!s->start(sess_tp2cli)) {
|
||||
EXLOGE("[ssh] can not start session [%s]\n", s->dbg_name().c_str());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// listen stoped, ask all sessions stop too.
|
||||
{
|
||||
ExThreadSmartLock locker(m_lock);
|
||||
auto it = m_sessions.begin();
|
||||
for (; it != m_sessions.end(); ++it) {
|
||||
(*it)->check_noop_timeout(0, 0); // 立即结束
|
||||
}
|
||||
}
|
||||
|
||||
// and wait for all sessions stop.
|
||||
for (;;) {
|
||||
{
|
||||
ExThreadSmartLock locker(m_lock);
|
||||
timer();
|
||||
if (m_sessions.empty())
|
||||
break;
|
||||
ex_sleep_ms(500);
|
||||
}
|
||||
}
|
||||
|
||||
m_listener_running = false;
|
||||
EXLOGV("[ssh] main-loop end.\n");
|
||||
}
|
||||
|
||||
void SshProxy::_on_stop() {
|
||||
ExThreadBase::_on_stop();
|
||||
|
||||
if (m_is_running) {
|
||||
// 用一个变通的方式来结束阻塞中的监听,就是连接一下它。
|
||||
ex_astr host_ip = m_host_ip;
|
||||
if (host_ip == "0.0.0.0")
|
||||
host_ip = "127.0.0.1";
|
||||
|
||||
ssh_session _session = ssh_new();
|
||||
ssh_options_set(_session, SSH_OPTIONS_HOST, host_ip.c_str());
|
||||
ssh_options_set(_session, SSH_OPTIONS_PORT, &m_host_port);
|
||||
|
||||
int _timeout_us = 10;
|
||||
ssh_options_set(_session, SSH_OPTIONS_TIMEOUT, &_timeout_us);
|
||||
ssh_connect(_session);
|
||||
ex_sleep_ms(500);
|
||||
|
||||
ssh_disconnect(_session);
|
||||
ssh_free(_session);
|
||||
ex_sleep_ms(500);
|
||||
}
|
||||
|
||||
while (m_listener_running) {
|
||||
ex_sleep_ms(1000);
|
||||
}
|
||||
|
||||
// m_thread_mgr.stop_all();
|
||||
}
|
||||
|
||||
void SshProxy::_clean_closed_session() {
|
||||
ExThreadSmartLock locker(m_lock);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
#ifndef __SSH_PROXY_H__
|
||||
#define __SSH_PROXY_H__
|
||||
|
||||
#include "tpssh_session.h"
|
||||
|
||||
#include <ex.h>
|
||||
|
||||
typedef std::list<SshSession*> tp_ssh_sessions;
|
||||
|
||||
class SshProxy : public ExThreadBase
|
||||
{
|
||||
public:
|
||||
SshProxy();
|
||||
~SshProxy();
|
||||
|
||||
bool init();
|
||||
void timer();
|
||||
void set_cfg(ex_u32 noop_timeout);
|
||||
void kill_sessions(const ex_astrs& sessions);
|
||||
|
||||
protected:
|
||||
void _thread_loop();
|
||||
void _on_stop();
|
||||
|
||||
// 异步方式清理已经结束的会话实例
|
||||
void _clean_closed_session();
|
||||
|
||||
private:
|
||||
ssh_bind m_bind;
|
||||
int m_timer_counter_check_noop;
|
||||
int m_timer_counter_keep_alive;
|
||||
|
||||
ExThreadLock m_lock;
|
||||
bool m_listener_running;
|
||||
|
||||
ex_astr m_host_ip;
|
||||
int m_host_port;
|
||||
|
||||
tp_ssh_sessions m_sessions;
|
||||
|
||||
//
|
||||
ex_u32 m_cfg_noop_timeout_sec;
|
||||
};
|
||||
|
||||
extern SshProxy g_ssh_proxy;
|
||||
|
||||
#endif // __SSH_PROXY_H__
|
|
@ -1,223 +0,0 @@
|
|||
#include "tpssh_session.h"
|
||||
#include "tpssh_proxy.h"
|
||||
#include <teleport_const.h>
|
||||
|
||||
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<uint32_t>(time(nullptr));
|
||||
return it->second;
|
||||
}
|
|
@ -1,95 +0,0 @@
|
|||
#ifndef __TPSSH_SESSION_H__
|
||||
#define __TPSSH_SESSION_H__
|
||||
|
||||
#include <ex.h>
|
||||
#include <libssh/libssh.h>
|
||||
#include <libssh/server.h>
|
||||
|
||||
#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<uint32_t>(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__
|
|
@ -1,878 +0,0 @@
|
|||
#include "tpssh_srv.h"
|
||||
#include "tpssh_session.h"
|
||||
// #include "tpp_env.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <teleport_const.h>
|
||||
|
||||
#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<uint8_t *>(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;
|
||||
}
|
|
@ -1,192 +0,0 @@
|
|||
#ifndef __SSH_SESSION_H__
|
||||
#define __SSH_SESSION_H__
|
||||
|
||||
#include <ex.h>
|
||||
|
||||
#include <libssh/libssh.h>
|
||||
#include <libssh/server.h>
|
||||
#include <libssh/callbacks.h>
|
||||
#include <libssh/sftp.h>
|
||||
|
||||
#include <vector>
|
||||
#include <list>
|
||||
|
||||
#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<char> cmd_char_list;
|
||||
std::list<char>::iterator cmd_char_pos;
|
||||
};
|
||||
|
||||
typedef std::list<TP_SSH_CHANNEL_PAIR *> 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<ssh_channel> 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__
|
|
@ -104,11 +104,11 @@ TPP_API ex_rv tpp_command(ex_u32 cmd, const char* param) {
|
|||
case TPP_CMD_SET_RUNTIME_CFG:
|
||||
if (param == NULL || strlen(param) == 0)
|
||||
return TPE_PARAM;
|
||||
return _set_runtime_config(param);
|
||||
return tpp_cmd_set_runtime_config(param);
|
||||
case TPP_CMD_KILL_SESSIONS:
|
||||
if (param == NULL || strlen(param) == 0)
|
||||
return TPE_PARAM;
|
||||
return _kill_sessions(param);
|
||||
return tpp_cmd_kill_sessions(param);
|
||||
default:
|
||||
return TPE_UNKNOWN_CMD;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue