大幅度改进SSH模块,增加稳定性。

dev
Apex Liu 2020-10-15 02:50:33 +08:00
parent 85e6f4dd28
commit b7d9f6f0d8
27 changed files with 2955 additions and 3103 deletions

View File

@ -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" />

View File

@ -802,7 +802,7 @@ class BuilderMacOS(BuilderBase):
def _build_libssh(self, file_name):
# cc.n('skip build libssh on macOS.')
# return
if not self._download_libssh(file_name):
return
if not os.path.exists(self.LIBSSH_PATH_SRC):
@ -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)

View File

@ -6,49 +6,59 @@
#include <list>
#ifdef EX_OS_WIN32
# include <process.h>
# include <process.h>
typedef HANDLE EX_THREAD_HANDLE;
#else
# include <pthread.h>
# include <pthread.h>
# include <sys/time.h>
typedef pthread_t EX_THREAD_HANDLE;
#endif
class ExThreadManager;
class ExThreadBase
{
public:
ExThreadBase(const char* thread_name);
virtual ~ExThreadBase();
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();
// 结束线程等待wait_timeout_ms毫秒如果wait_timeout_ms为0则无限等待
bool stop();
// 直接结束线程(强杀,不建议使用)
bool terminate();
// 创建并启动线程执行被重载了的run()函数)
bool start();
// 结束线程等待wait_timeout_ms毫秒如果wait_timeout_ms为0则无限等待
bool stop();
// 直接结束线程(强杀,不建议使用)
bool terminate();
protected:
// main loop of this thread.
virtual void _thread_loop() = 0;
// called by another thread when thread ready to stop.
virtual void _on_stop() {};
// main loop of this thread.
virtual void _thread_loop() = 0;
// called by another thread when thread ready to 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);
static unsigned int WINAPI _thread_func(LPVOID lpParam);
#else
static void* _thread_func(void * pParam);
static void* _thread_func(void* pParam);
#endif
protected:
ex_astr m_thread_name;
EX_THREAD_HANDLE m_handle;
bool m_is_running;
bool m_need_stop;
ex_astr m_thread_name;
EX_THREAD_HANDLE m_handle;
bool m_is_running;
bool m_need_stop;
};
@ -56,17 +66,17 @@ protected:
class ExThreadLock
{
public:
ExThreadLock();
virtual ~ExThreadLock();
ExThreadLock();
virtual ~ExThreadLock();
void lock();
void unlock();
void lock();
void unlock();
private:
#ifdef EX_OS_WIN32
CRITICAL_SECTION m_locker;
CRITICAL_SECTION m_locker;
#else
pthread_mutex_t m_locker;
pthread_mutex_t m_locker;
#endif
};
@ -74,40 +84,121 @@ private:
class ExThreadSmartLock
{
public:
ExThreadSmartLock(ExThreadLock& lock) : m_lock(lock)
{
m_lock.lock();
}
~ExThreadSmartLock()
{
m_lock.unlock();
}
explicit ExThreadSmartLock(ExThreadLock& lock) :
m_lock(lock)
{
m_lock.lock();
}
~ExThreadSmartLock()
{
m_lock.unlock();
}
private:
ExThreadLock& m_lock;
ExThreadLock& m_lock;
};
typedef std::list<ExThreadBase*> ex_threads;
class ExThreadManager
{
friend class ExThreadBase;
friend class ExThreadBase;
public:
ExThreadManager();
virtual ~ExThreadManager();
ExThreadManager();
virtual ~ExThreadManager();
void stop_all();
void stop_all();
//private:
void add(ExThreadBase* tb);
void remove(ExThreadBase* tb);
//private:
void add(ExThreadBase* tb);
void remove(ExThreadBase* tb);
private:
ExThreadLock m_lock;
ex_threads m_threads;
ExThreadLock m_lock;
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);

View File

@ -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__

View File

@ -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;

View File

@ -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());
}

View File

@ -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__

View File

@ -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);
}
}

View File

@ -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__

View File

@ -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'};

View File

@ -1,54 +1,54 @@
#ifndef __TPP_SSH_RECORDER_H__
#define __TPP_SSH_RECORDER_H__
#include "../../common/base_record.h"
#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;
ex_u16 height;
}TS_RECORD_WIN_SIZE;
#pragma pack(pop)
class TppSshRec : public TppRecBase
{
public:
TppSshRec();
virtual ~TppSshRec();
void record(ex_u8 type, const ex_u8* data, size_t size);
void record_win_size_startup(int width, int height);
void record_win_size_change(int width, int height);
void record_command(int flag, const ex_astr& cmd);
void save_record();
protected:
bool _on_begin(const TPP_CONNECT_INFO* info);
bool _on_end();
bool _save_to_info_file();
bool _save_to_data_file();
bool _save_to_cmd_file();
protected:
TS_RECORD_HEADER m_head;
bool m_header_changed;
MemBuffer m_cmd_cache;
bool m_save_full_header;
FILE* m_file_info;
FILE* m_file_data;
FILE* m_file_cmd;
};
#endif // __TPP_SSH_RECORDER_H__
#ifndef __TPP_SSH_RECORDER_H__
#define __TPP_SSH_RECORDER_H__
#include "../../common/base_record.h"
#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;
ex_u16 height;
}TS_RECORD_WIN_SIZE;
#pragma pack(pop)
class TppSshRec : public TppRecBase
{
public:
TppSshRec();
virtual ~TppSshRec();
void record(ex_u8 type, const ex_u8* data, size_t size);
void record_win_size_startup(int width, int height);
void record_win_size_change(int width, int height);
void record_command(int flag, const ex_astr& cmd);
void save_record();
protected:
bool _on_begin(const TPP_CONNECT_INFO* info);
bool _on_end();
bool _save_to_info_file();
bool _save_to_data_file();
bool _save_to_cmd_file();
protected:
TS_RECORD_HEADER m_head;
bool m_header_changed;
MemBuffer m_cmd_cache;
bool m_save_full_header;
FILE* m_file_info;
FILE* m_file_data;
FILE* m_file_cmd;
};
#endif // __TPP_SSH_RECORDER_H__

File diff suppressed because it is too large Load Diff

View File

@ -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__

View File

@ -3,31 +3,38 @@
TppSshEnv g_ssh_env;
TppSshEnv::TppSshEnv()
{}
{
}
TppSshEnv::~TppSshEnv()
{}
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) {
EXLOGE("[ssh] invalid config(2).\n");
return false;
}
ex_wstr tmp;
if (!ps->GetStr(L"bind-ip", tmp)) {
bind_ip = TS_SSH_PROXY_HOST;
}
else {
ex_wstr2astr(tmp, bind_ip);
}
if (!ps->GetInt(L"bind-port", bind_port)) {
bind_port = TS_SSH_PROXY_PORT;
}
return true;
{
}
bool TppSshEnv::_on_init(TPP_INIT_ARGS *args)
{
ex_path_join(replay_path, false, L"ssh", NULL);
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))
{
bind_ip = TS_SSH_PROXY_HOST;
}
else
{
ex_wstr2astr(tmp, bind_ip);
}
if (!ps->GetInt(L"bind-port", bind_port))
{
bind_port = TS_SSH_PROXY_PORT;
}
return true;
}

View File

@ -6,15 +6,15 @@
class TppSshEnv : public TppEnvBase
{
public:
TppSshEnv();
~TppSshEnv();
TppSshEnv();
~TppSshEnv();
public:
ex_astr bind_ip;
int bind_port;
ex_astr bind_ip;
int bind_port;
private:
bool _on_init(TPP_INIT_ARGS* args);
bool _on_init(TPP_INIT_ARGS *args);
};
extern TppSshEnv g_ssh_env;

View File

@ -1,113 +1,116 @@
#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());
ssh_init();
ssh_threads_set_callbacks(ssh_threads_get_pthread());
ssh_init();
#else
ssh_init();
ssh_init();
#endif
if (!g_ssh_env.init(init_args))
return TPE_FAILED;
if (!g_ssh_env.init(init_args))
return TPE_FAILED;
return 0;
return 0;
}
TPP_API ex_rv tpp_start(void)
{
if (!g_ssh_proxy.init())
return TPE_FAILED;
if (!g_ssh_proxy.start())
return TPE_FAILED;
if (!g_ssh_proxy.init())
return TPE_FAILED;
if (!g_ssh_proxy.start())
return TPE_FAILED;
return 0;
return 0;
}
TPP_API ex_rv tpp_stop(void)
{
g_ssh_proxy.stop();
g_ssh_proxy.stop();
ssh_finalize();
return 0;
return 0;
}
TPP_API void tpp_timer(void) {
// be called per one second.
g_ssh_proxy.timer();
TPP_API void tpp_timer(void)
{
// be called per one second.
g_ssh_proxy.timer();
}
static ex_rv _set_runtime_config(const char* param) {
Json::Value jp;
Json::CharReaderBuilder jcrb;
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());
const char *str_json_begin = param;
ex_astr err;
const char *str_json_begin = param;
ex_astr err;
if (!jreader->parse(str_json_begin, param + strlen(param), &jp, &err))
return TPE_JSON_FORMAT;
if (!jp.isObject())
return TPE_PARAM;
if (!jp.isObject())
return TPE_PARAM;
if (jp["noop_timeout"].isNull() || !jp["noop_timeout"].isUInt())
return TPE_PARAM;
if (jp["noop_timeout"].isNull() || !jp["noop_timeout"].isUInt())
return TPE_PARAM;
ex_u32 noop_timeout = jp["noop_timeout"].asUInt();
if (noop_timeout == 0)
return TPE_PARAM;
ex_u32 noop_timeout = jp["noop_timeout"].asUInt();
if (noop_timeout == 0)
return TPE_PARAM;
g_ssh_proxy.set_cfg(noop_timeout * 60);
g_ssh_proxy.set_cfg(noop_timeout * 60);
return TPE_PARAM;
return TPE_PARAM;
}
static ex_rv _kill_sessions(const char* param) {
Json::Value jp;
Json::CharReaderBuilder jcrb;
std::unique_ptr<Json::CharReader> const jreader(jcrb.newCharReader());
const char *str_json_begin = param;
ex_astr err;
static ex_rv tpp_cmd_kill_sessions(const char *param)
{
Json::Value jp;
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;
if (!jp.isArray())
return TPE_PARAM;
ex_astrs ss;
int cnt = jp.size();
for (int i = 0; i < cnt; ++i)
{
if (!jp[i].isString()) {
return TPE_PARAM;
}
ex_astrs ss;
ss.push_back(jp[i].asString());
}
for (const auto &item : jp)
{
if (!item.isString())
{
return TPE_PARAM;
}
g_ssh_proxy.kill_sessions(ss);
ss.push_back(item.asString());
}
return TPE_PARAM;
g_ssh_proxy.kill_sessions(ss);
return TPE_PARAM;
}
TPP_API ex_rv tpp_command(ex_u32 cmd, const char* param) {
switch (cmd) {
case TPP_CMD_SET_RUNTIME_CFG:
if (param == NULL || strlen(param) == 0)
return TPE_PARAM;
return _set_runtime_config(param);
case TPP_CMD_KILL_SESSIONS:
if (param == NULL || strlen(param) == 0)
return TPE_PARAM;
return _kill_sessions(param);
default:
return TPE_UNKNOWN_CMD;
}
TPP_API ex_rv tpp_command(ex_u32 cmd, const char *param)
{
if (!param || strlen(param) == 0)
return TPE_PARAM;
return TPE_NOT_IMPLEMENT;
switch (cmd)
{
case TPP_CMD_SET_RUNTIME_CFG:
return tpp_cmd_set_runtime_config(param);
case TPP_CMD_KILL_SESSIONS:
return tpp_cmd_kill_sessions(param);
default:
return TPE_UNKNOWN_CMD;
}
}

View File

@ -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);
}

View File

@ -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__

View File

@ -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;
// }
// }
}

View File

@ -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__

View File

@ -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;
}
}
}

View File

@ -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__

View File

@ -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;
}

View File

@ -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__

View File

@ -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;
}

View File

@ -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__

View File

@ -42,7 +42,7 @@ TPP_API void tpp_timer(void) {
static ex_rv _set_runtime_config(const char* param) {
// Json::Value jp;
// Json::Reader jreader;
//
//
// if (!jreader.parse(param, jp))
Json::CharReaderBuilder jcrb;
std::unique_ptr<Json::CharReader> const jreader(jcrb.newCharReader());
@ -70,7 +70,7 @@ static ex_rv _set_runtime_config(const char* param) {
static ex_rv _kill_sessions(const char* param) {
// Json::Value jp;
// Json::Reader jreader;
//
//
// if (!jreader.parse(param, jp))
Json::CharReaderBuilder jcrb;
std::unique_ptr<Json::CharReader> const jreader(jcrb.newCharReader());
@ -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;
}