大幅改进ssh命令日志记录功能,并改进命令日志记录格式,为以后命令日志与录像回放同步显示做好准备。

pull/105/head
Apex Liu 2017-11-23 03:57:36 +08:00
parent a8629eaf60
commit 49708edd3c
12 changed files with 301 additions and 671 deletions

View File

@ -103,24 +103,26 @@ void TppSshRec::record_win_size_change(int width, int height)
record(TS_RECORD_TYPE_SSH_TERM_SIZE, (ex_u8*)&pkg, sizeof(TS_RECORD_WIN_SIZE));
}
void TppSshRec::record_command(const ex_astr& cmd)
// TODO: 为了录像回放和命令历史能够对应(比如点击命令直接跳到录像的对应时点),应该仿照录像数据包的方式记录相对时间偏移,而不是绝对时间。
void TppSshRec::record_command(int flag, const ex_astr& cmd)
{
char szTime[100] = { 0 };
#ifdef EX_OS_WIN32
SYSTEMTIME st;
GetLocalTime(&st);
sprintf_s(szTime, 100, "[%04d-%02d-%02d %02d:%02d:%02d] ", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
// SYSTEMTIME st;
// GetLocalTime(&st);
// sprintf_s(szTime, 100, "[%04d-%02d-%02d %02d:%02d:%02d %d] ", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, flag);
#else
time_t timep;
struct tm *p;
time(&timep);
p = localtime(&timep);
if (p == NULL)
return;
sprintf(szTime, "[%04d-%02d-%02d %02d:%02d:%02d] ", p->tm_year + 1900, p->tm_mon + 1, p->tm_mday, p->tm_hour, p->tm_min, p->tm_sec);
// time_t timep;
// struct tm *p;
// time(&timep);
// p = localtime(&timep);
// if (p == NULL)
// return;
// sprintf(szTime, "[%04d-%02d-%02d %02d:%02d:%02d %d] ", p->tm_year + 1900, p->tm_mon + 1, p->tm_mday, p->tm_hour, p->tm_min, p->tm_sec, flag);
#endif
size_t lenTime = strlen(szTime);
ex_strformat(szTime, 99, "%d,%d,", (ex_u32)(ex_get_tick_count() - m_start_time), flag);
size_t lenTime = strlen(szTime);
if (m_cmd_cache.size() + cmd.length() + lenTime > MAX_SIZE_PER_FILE)
_save_to_cmd_file();

View File

@ -26,7 +26,7 @@ public:
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(const ex_astr& cmd);
void record_command(int flag, const ex_astr& cmd);
void save_record();

View File

@ -17,10 +17,10 @@ TP_SSH_CHANNEL_PAIR::TP_SSH_CHANNEL_PAIR() {
is_first_server_data = true;
//last_client_char = TP_CHAR_NUL;
//cmd_flag = 0;
server_ready = false;
maybe_cmd = false;
process_srv = false;
client_single_char = false;
cmd_char_pos = cmd_char_list.begin();
}
@ -34,7 +34,7 @@ SshSession::SshSession(SshProxy *proxy, ssh_session sess_client) :
{
m_auth_type = TP_AUTH_TYPE_PASSWORD;
// m_is_first_server_data = true;
// m_is_first_server_data = true;
m_is_logon = false;
m_have_error = false;
@ -53,8 +53,8 @@ SshSession::SshSession(SshProxy *proxy, ssh_session sess_client) :
ssh_callbacks_init(&m_srv_channel_cb);
m_srv_channel_cb.userdata = this;
// m_command_flag = 0;
// m_cmd_char_pos = m_cmd_char_list.begin();
// m_command_flag = 0;
// m_cmd_char_pos = m_cmd_char_list.begin();
}
SshSession::~SshSession() {
@ -573,10 +573,11 @@ int SshSession::_on_client_pty_request(ssh_session session, ssh_channel channel,
return SSH_ERROR;
}
cp->win_width = x;
cp->rec.record_win_size_startup(x, y);
int err = ssh_channel_request_pty_size(cp->srv_channel, term, x, y);
if(err != SSH_OK)
if (err != SSH_OK)
EXLOGE("[ssh] pty request from server got %d\n", err);
return err;
}
@ -647,34 +648,41 @@ int SshSession::_on_client_channel_data(ssh_session session, ssh_channel channel
_this->m_recving_from_cli = true;
if (cp->type == TS_SSH_CHANNEL_TYPE_SHELL)
{
try
{
_this->_process_ssh_command(cp, TP_SSH_CLIENT_SIDE, (ex_u8*)data, len);
int _len = len;
if (cp->type == TS_SSH_CHANNEL_TYPE_SHELL) {
// 在收取服务端数据直到显示命令行提示符之前,不允许发送客户端数据到服务端,避免日志记录混乱。
if (!cp->server_ready) {
_this->m_recving_from_cli = false;
return 0;
}
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());
}
catch (...)
{
// 如果用户复制粘贴多行文本,我们将其拆分为每一行发送一次数据包
for (unsigned int i = 0; i < len; ++i) {
if (((ex_u8*)data)[i] == 0x0d) {
_len = i + 1;
break;
}
}
_this->_process_ssh_command(cp, TP_SSH_CLIENT_SIDE, (ex_u8*)data, _len);
ex_astr str(cp->cmd_char_list.begin(), cp->cmd_char_list.end());
ex_replace_all(str, "\r", "");
ex_replace_all(str, "\n", "");
EXLOGD("[ssh] -- [%s]\n", str.c_str());
}
else
{
_this->_process_sftp_command(cp, (ex_u8*)data, len);
else {
_this->_process_sftp_command(cp, (ex_u8*)data, _len);
}
int ret = 0;
if (is_stderr)
ret = ssh_channel_write_stderr(cp->srv_channel, data, len);
ret = ssh_channel_write_stderr(cp->srv_channel, data, _len);
else
ret = ssh_channel_write(cp->srv_channel, data, len);
ret = ssh_channel_write(cp->srv_channel, data, _len);
if (ret == SSH_ERROR) {
EXLOGE("[ssh] send data(%dB) to server failed. [%d][cli:%s][srv:%s]\n", len, ret, ssh_get_error(_this->m_cli_session), ssh_get_error(_this->m_srv_session));
EXLOGE("[ssh] send data(%dB) to server failed. [%d][cli:%s][srv:%s]\n", _len, ret, ssh_get_error(_this->m_cli_session), ssh_get_error(_this->m_srv_session));
ssh_channel_close(channel);
}
@ -694,6 +702,7 @@ int SshSession::_on_client_pty_win_change(ssh_session session, ssh_channel chann
return SSH_ERROR;
}
cp->win_width = width;
cp->rec.record_win_size_change(width, height);
return ssh_channel_change_pty_size(cp->srv_channel, width, height);
@ -743,12 +752,6 @@ int SshSession::_on_server_channel_data(ssh_session session, ssh_channel channel
if (_this->m_recving_from_srv)
return 0;
// TS_SSH_CHANNEL_INFO *info = _this->_get_cli_channel(channel);
// if (NULL == info || NULL == info->channel) {
// EXLOGE("[ssh] when receive server channel data, not found client channel.\n");
// _this->m_retcode = TP_SESS_STAT_ERR_INTERNAL;
// return SSH_ERROR;
// }
TP_SSH_CHANNEL_PAIR* cp = _this->_get_channel_pair(TP_SSH_SERVER_SIDE, channel);
if (NULL == cp) {
EXLOGE("[ssh] when receive server channel data, not found channel pair.\n");
@ -771,20 +774,19 @@ int SshSession::_on_server_channel_data(ssh_session session, ssh_channel channel
if (cp->type == TS_SSH_CHANNEL_TYPE_SHELL && !is_stderr)
{
try
{
_this->_process_ssh_command(cp, TP_SSH_SERVER_SIDE, (ex_u8*)data, len);
ex_astr str(cp->cmd_char_list.begin(), cp->cmd_char_list.end());
ex_replace_all(str, "\r", "");
ex_replace_all(str, "\n", "");
EXLOGD("[ssh] -- [%s]\n", str.c_str());
if (!cp->server_ready) {
if (len >= 2 && (((ex_u8*)data)[len - 2] != 0x0d && ((ex_u8*)data)[len - 1] != 0x0a)) {
cp->server_ready = true;
}
}
cp->rec.record(TS_RECORD_TYPE_SSH_DATA, (unsigned char *)data, len);
}
catch (...)
{
EXLOGE("[ssh] process ssh command got exception.\n");
}
_this->_process_ssh_command(cp, TP_SSH_SERVER_SIDE, (ex_u8*)data, len);
ex_astr str(cp->cmd_char_list.begin(), cp->cmd_char_list.end());
ex_replace_all(str, "\r", "");
ex_replace_all(str, "\n", "");
EXLOGD("[ssh] -- [%s]\n", str.c_str());
cp->rec.record(TS_RECORD_TYPE_SSH_DATA, (unsigned char *)data, len);
}
int ret = 0;
@ -797,7 +799,7 @@ int SshSession::_on_server_channel_data(ssh_session session, ssh_channel channel
if (cp->type != TS_SSH_CHANNEL_TYPE_SFTP)
{
char buf[256] = { 0 };
char buf[512] = { 0 };
const char *auth_mode = NULL;
if (_this->m_auth_type == TP_AUTH_TYPE_PASSWORD)
@ -807,16 +809,35 @@ int SshSession::_on_server_channel_data(ssh_session session, ssh_channel channel
else
auth_mode = "unknown";
int w = min(cp->win_width, 128);
ex_astr line(w, '=');
// char line[129] = { 0 };
// for (int i = 0; i < w; ++i)
// line[i] = '=';
// snprintf(buf, sizeof(buf),
// "\r\n\r\n"\
// "=============================================\r\n"\
// "Welcome to Teleport-SSH-Server...\r\n"\
// " - teleport to %s:%d\r\n"\
// " - authroized by %s\r\n"\
// "=============================================\r\n"\
// "\r\n",
// _this->m_conn_ip.c_str(),
// _this->m_conn_port, auth_mode
// );
snprintf(buf, sizeof(buf),
"\r\n\r\n"\
"=============================================\r\n"\
"%s\r\n"\
"Welcome to Teleport-SSH-Server...\r\n"\
" - teleport to %s:%d\r\n"\
" - authroized by %s\r\n"\
"=============================================\r\n"\
"%s\r\n"\
"\r\n",
line.c_str(),
_this->m_conn_ip.c_str(),
_this->m_conn_port, auth_mode
_this->m_conn_port, auth_mode,
line.c_str()
);
int buf_len = strlen(buf);
@ -874,372 +895,41 @@ void SshSession::_process_ssh_command(TP_SSH_CHANNEL_PAIR* cp, int from, const e
if (len == 0)
return;
// ls;ls
if (TP_SSH_CLIENT_SIDE == from) {
if (len >= 2) {
if (((ex_u8*)data)[len - 1] == 0x0d) {
// 疑似复制粘贴多行命令一次性执行,将其记录到日志文件中
ex_astr str((const char*)data, len - 1);
cp->rec.record_command(1, str);
cp->process_srv = false;
return;
}
}
if (TP_SSH_CLIENT_SIDE == from)
{
// 客户端输入回车时,可能时执行了一条命令,需要根据服务端返回的数据进行进一步判断
cp->maybe_cmd = (data[len - 1] == 0x0d);
if(cp->maybe_cmd)
if (cp->maybe_cmd)
EXLOGD("[ssh] maybe cmd.\n");
cp->process_srv = false;
if (len == 1)
{
switch (data[0]) {
case 0x08:// 08=Backspace (回删一个字符)
if (cp->cmd_char_pos != cp->cmd_char_list.begin())
{
cp->cmd_char_pos--;
cp->cmd_char_pos = cp->cmd_char_list.erase(cp->cmd_char_pos);
}
return;
case 0x09:// 09=TAB键需要根据服务端返回的数据进一步判断
cp->process_srv = true;
return;
case 0x0d:// 0d=回车
return;
case 0x7f:// 7f=DEL (删除一个字符)
if (cp->cmd_char_pos != cp->cmd_char_list.end())
{
cp->cmd_char_pos = cp->cmd_char_list.erase(cp->cmd_char_pos);
}
return;
}
// if (data[0] == 0x08 || data[0] == 0x09) // 08=光标左移
// {
// cp->cmd_flag = 1;
// return;
// }
// else if (data[0] == 0x7f) // Backspace (回删一个字符)
// {
// if (cp->cmd_char_pos != cp->cmd_char_list.begin())
// {
// cp->cmd_char_pos--;
// cp->cmd_char_pos = cp->cmd_char_list.erase(cp->cmd_char_pos);
// }
// return;
// }
// else if (data[0] == 0x1b)
// {
// // 按下 Esc 键
// return;
// }
if (!isprint(data[0]))
return;
}
else if (len == 3)
{
if ((data[0] == 0x1b && data[1] == 0x5b && data[2] == 0x41) // key-up
|| (data[0] == 0x1b && data[1] == 0x5b && data[2] == 0x42) // key-down
)
{
// 上下键是 bash 的历史记录
cp->process_srv = true;
return;
}
else if (data[0] == 0x1b && data[1] == 0x5b && data[2] == 0x43) // key-right
{
if (cp->cmd_char_pos != cp->cmd_char_list.end())
cp->cmd_char_pos++;
return;
}
else if (data[0] == 0x1b && data[1] == 0x5b && data[2] == 0x44) // key-left
{
if (cp->cmd_char_pos != cp->cmd_char_list.begin())
cp->cmd_char_pos--;
return;
}
else if (
(data[0] == 0x1b && data[1] == 0x4f && data[2] == 0x41)
|| (data[0] == 0x1b && data[1] == 0x4f && data[2] == 0x42)
|| (data[0] == 0x1b && data[1] == 0x4f && data[2] == 0x43)
|| (data[0] == 0x1b && data[1] == 0x4f && data[2] == 0x44)
)
{
// 编辑模式下的上下左右键
return;
}
}
else if (len > 3)
{
if (data[0] == 0x1b && data[1] == 0x5b)
{
cp->process_srv = true;
return;
}
}
int processed = 0;
// for (int i = 0; i < len; i++)
// {
// if (data[i] == 0x0d)
// {
// cp->cmd_flag = 0;
//
// for (int j = processed; j < i; ++j)
// {
// cp->cmd_char_pos = cp->cmd_char_list.insert(cp->cmd_char_pos, data[j]);
// cp->cmd_char_pos++;
// }
//
// processed = i + 1;
// }
// }
//
if (processed < len)
{
for (int j = processed; j < len; ++j)
{
cp->cmd_char_pos = cp->cmd_char_list.insert(cp->cmd_char_pos, data[j]);
cp->cmd_char_pos++;
}
}
// const ex_u8* d = data;
// int l = len;
// bool esc_mode = false;
// int esc_arg = 0;
//
// for (; l > 0; ) {
// ex_u8 ch = d[0];
//
// if (esc_mode)
// {
// switch (ch)
// {
// case '0':
// case '1':
// case '2':
// case '3':
// case '4':
// case '5':
// case '6':
// case '7':
// case '8':
// case '9':
// esc_arg = esc_arg * 10 + (ch - '0');
// break;
//
// case 0x3f:
// case ';':
// case '>':
// cp->cmd_char_list.clear();
// cp->cmd_char_pos = cp->cmd_char_list.begin();
// return;
// break;
//
// case 0x4b: // 'K'
// {
// if (0 == esc_arg)
// {
// // 删除光标到行尾的字符串
// cp->cmd_char_list.erase(cp->cmd_char_pos, cp->cmd_char_list.end());
// cp->cmd_char_pos = cp->cmd_char_list.end();
// }
// else if (1 == esc_arg)
// {
// // 删除从开始到光标处的字符串
// cp->cmd_char_list.erase(cp->cmd_char_list.begin(), cp->cmd_char_pos);
// cp->cmd_char_pos = cp->cmd_char_list.end();
// }
// else if (2 == esc_arg)
// {
// // 删除整行
// cp->cmd_char_list.clear();
// cp->cmd_char_pos = cp->cmd_char_list.begin();
// }
//
// esc_mode = false;
// break;
// }
// case 0x43: // ^[C
// {
// // 光标右移
// if (esc_arg == 0)
// esc_arg = 1;
// for (int j = 0; j < esc_arg; ++j)
// {
// if (cp->cmd_char_pos != cp->cmd_char_list.end())
// cp->cmd_char_pos++;
// }
// esc_mode = false;
// break;
// }
// case 0x44: // ^[D
// {
// // 光标左移
// if (esc_arg == 0)
// esc_arg = 1;
// for (int j = 0; j < esc_arg; ++j)
// {
// if (cp->cmd_char_pos != cp->cmd_char_list.begin())
// cp->cmd_char_pos--;
// }
// esc_mode = false;
// break;
// }
//
// case 0x50: // 'P' 删除指定数量的字符
// {
// if (esc_arg == 0)
// esc_arg = 1;
// for (int j = 0; j < esc_arg; ++j)
// {
// if (cp->cmd_char_pos != cp->cmd_char_list.end())
// cp->cmd_char_pos = cp->cmd_char_list.erase(cp->cmd_char_pos);
// }
// esc_mode = false;
// break;
// }
//
// case 0x40: // '@' 插入指定数量的空白字符
// {
// if (esc_arg == 0)
// esc_arg = 1;
// for (int j = 0; j < esc_arg; ++j)
// {
// cp->cmd_char_pos = cp->cmd_char_list.insert(cp->cmd_char_pos, ' ');
// }
// esc_mode = false;
// break;
// }
//
// default:
// esc_mode = false;
// break;
// }
//
// d += 1;
// l -= 1;
// continue;
// }
//
// switch (ch)
// {
// case 0x07:
// {
// // 响铃
// break;
// }
// case 0x08:
// {
// // 光标左移
// if (cp->cmd_char_pos != cp->cmd_char_list.begin())
// cp->cmd_char_pos--;
// break;
// }
// case 0x09:
// case 0x0d:
// {
// break;
// }
// case 0x1b:
// {
// //if (i + 1 < len)
// if (l > 1)
// {
// //if (data[i + 1] == 0x5b)
// if (d[1] == 0x5b)
// {
// esc_mode = true;
// esc_arg = 0;
//
// //i += 1;
// d += 1;
// l -= 1;
// break;
// }
// }
//
// break;
// }
// default:
// if (cp->cmd_char_pos != cp->cmd_char_list.end())
// {
// cp->cmd_char_pos = cp->cmd_char_list.erase(cp->cmd_char_pos);
// cp->cmd_char_pos = cp->cmd_char_list.insert(cp->cmd_char_pos, ch);
// cp->cmd_char_pos++;
// }
// else
// {
// cp->cmd_char_list.push_back(ch);
// cp->cmd_char_pos = cp->cmd_char_list.end();
// }
// }
//
// d += 1;
// l -= 1;
// }
// 有时在执行类似top命令的情况下输入一个字母'q'就退出程序,没有输入回车,可能会导致后续记录命令时将返回的命令行提示符作为命令
// 记录下来了,要避免这种情况,排除的方式是:客户端单个字母,后续服务端如果收到的是控制序列 1b 5b xx xx就不计做命令。
cp->client_single_char = (len == 1 && isprint(data[0]));
cp->process_srv = true;
}
else if (TP_SSH_SERVER_SIDE == from)
{
// TODO: 不能直接判断第一个字节,有时候客户端输入得非常快就回车时,服务端的回显字符可能跟回车换行符一起发回来,因此
// 应该将判断放到后面的循环中去。另外不能只判断一个0d需要判断两个字节 0d0a否则在编辑器模式vim/nano等下客户端输入也会记录
const ex_u8* d = data;
int l = len;
// 如果上一个客户端数据包的最后一个字节是 0d且当前服务端数据包以 0d0a 打头,则当前命令缓存区中的数据就是一条命令了,需要记录
//if (len >= 2 && (data[0] == 0x0d && data[1] == 0x0a)) {
if(data[0] == 0x0d) {
if (cp->maybe_cmd)
EXLOGD("[ssh] maybe cmd.\n");
if (cp->maybe_cmd) {
if (cp->cmd_char_list.size() > 0)
{
ex_astr str(cp->cmd_char_list.begin(), cp->cmd_char_list.end());
EXLOGD("[ssh] --==--==-- save cmd: [%s]\n", str.c_str());
cp->rec.record_command(str);
}
cp->cmd_char_list.clear();
cp->cmd_char_pos = cp->cmd_char_list.begin();
cp->maybe_cmd = false;
}
//else {
// cp->cmd_char_list.clear();
// cp->cmd_char_pos = cp->cmd_char_list.begin();
//}
else {
cp->process_srv = false;
}
//cp->maybe_cmd = false;
return;
}
if (cp->maybe_cmd) {
//if (!(len >= 2 && (data[0] == 0x0d && data[1] == 0x0a))) {
if(data[0] != 0x0d) {
cp->cmd_char_list.clear();
cp->cmd_char_pos = cp->cmd_char_list.begin();
cp->maybe_cmd = false;
return;
}
}
// if (cp->cmd_flag == 0)
// return;
else if (TP_SSH_SERVER_SIDE == from) {
if (!cp->process_srv)
return;
cp->process_srv = false;
int offset = 0;
bool esc_mode = false;
int esc_arg = 0;
for (; l > 0; ) {
ex_u8 ch = d[0];
for (; offset < len;) {
ex_u8 ch = data[offset];
if (esc_mode)
{
switch (ch)
{
if (esc_mode) {
switch (ch) {
case '0':
case '1':
case '2':
@ -1261,22 +951,18 @@ void SshSession::_process_ssh_command(TP_SSH_CHANNEL_PAIR* cp, int from, const e
return;
break;
case 0x4b: // 'K'
{
if (0 == esc_arg)
{
case 0x4b: { // 'K'
if (0 == esc_arg) {
// 删除光标到行尾的字符串
cp->cmd_char_list.erase(cp->cmd_char_pos, cp->cmd_char_list.end());
cp->cmd_char_pos = cp->cmd_char_list.end();
}
else if (1 == esc_arg)
{
else if (1 == esc_arg) {
// 删除从开始到光标处的字符串
cp->cmd_char_list.erase(cp->cmd_char_list.begin(), cp->cmd_char_pos);
cp->cmd_char_pos = cp->cmd_char_list.end();
}
else if (2 == esc_arg)
{
else if (2 == esc_arg) {
// 删除整行
cp->cmd_char_list.clear();
cp->cmd_char_pos = cp->cmd_char_list.begin();
@ -1285,26 +971,23 @@ void SshSession::_process_ssh_command(TP_SSH_CHANNEL_PAIR* cp, int from, const e
esc_mode = false;
break;
}
case 0x43: // ^[C
{
case 0x43: {// ^[C
// 光标右移
if (esc_arg == 0)
esc_arg = 1;
for (int j = 0; j < esc_arg; ++j)
{
for (int j = 0; j < esc_arg; ++j) {
if (cp->cmd_char_pos != cp->cmd_char_list.end())
cp->cmd_char_pos++;
}
esc_mode = false;
break;
}
case 0x44: // ^[D
{
case 0x44: { // ^[D
// 光标左移
if (esc_arg == 0)
esc_arg = 1;
for (int j = 0; j < esc_arg; ++j)
{
for (int j = 0; j < esc_arg; ++j) {
if (cp->cmd_char_pos != cp->cmd_char_list.begin())
cp->cmd_char_pos--;
}
@ -1312,12 +995,11 @@ void SshSession::_process_ssh_command(TP_SSH_CHANNEL_PAIR* cp, int from, const e
break;
}
case 0x50: // 'P' 删除指定数量的字符
{
case 0x50: {// 'P' 删除指定数量的字符
if (esc_arg == 0)
esc_arg = 1;
for (int j = 0; j < esc_arg; ++j)
{
for (int j = 0; j < esc_arg; ++j) {
if (cp->cmd_char_pos != cp->cmd_char_list.end())
cp->cmd_char_pos = cp->cmd_char_list.erase(cp->cmd_char_pos);
}
@ -1325,14 +1007,11 @@ void SshSession::_process_ssh_command(TP_SSH_CHANNEL_PAIR* cp, int from, const e
break;
}
case 0x40: // '@' 插入指定数量的空白字符
{
case 0x40: { // '@' 插入指定数量的空白字符
if (esc_arg == 0)
esc_arg = 1;
for (int j = 0; j < esc_arg; ++j)
{
cp->cmd_char_pos = cp->cmd_char_list.insert(cp->cmd_char_pos, ' ');
}
esc_mode = false;
break;
}
@ -1342,20 +1021,17 @@ void SshSession::_process_ssh_command(TP_SSH_CHANNEL_PAIR* cp, int from, const e
break;
}
d += 1;
l -= 1;
//d += 1;
//l -= 1;
offset++;
continue;
}
switch (ch)
{
switch (ch) {
case 0x07:
{
// 响铃
break;
}
case 0x08:
{
case 0x08: {
// 光标左移
if (cp->cmd_char_pos != cp->cmd_char_list.begin())
cp->cmd_char_pos--;
@ -1363,15 +1039,24 @@ void SshSession::_process_ssh_command(TP_SSH_CHANNEL_PAIR* cp, int from, const e
}
case 0x1b:
{
if (l > 1)
if (offset + 1 < len)
{
if (d[1] == 0x5b)
{
if (data[offset + 1] == 0x5b || data[offset + 1] == 0x5d) {
if (offset == 0 && cp->client_single_char) {
cp->cmd_char_list.clear();
cp->cmd_char_pos = cp->cmd_char_list.begin();
cp->maybe_cmd = false;
cp->process_srv = false;
cp->client_single_char = false;
return;
}
}
if (data[offset + 1] == 0x5b) {
esc_mode = true;
esc_arg = 0;
d += 1;
l -= 1;
offset += 1;
}
}
@ -1379,9 +1064,28 @@ void SshSession::_process_ssh_command(TP_SSH_CHANNEL_PAIR* cp, int from, const e
}
case 0x0d:
{
cp->cmd_char_list.clear();
cp->cmd_char_pos = cp->cmd_char_list.begin();
if (offset + 1 < len && data[offset + 1] == 0x0a) {
if (cp->maybe_cmd)
EXLOGD("[ssh] maybe cmd.\n");
if (cp->maybe_cmd) {
if (cp->cmd_char_list.size() > 0)
{
ex_astr str(cp->cmd_char_list.begin(), cp->cmd_char_list.end());
EXLOGD("[ssh] --==--==-- save cmd: [%s]\n", str.c_str());
cp->rec.record_command(0, str);
}
cp->cmd_char_list.clear();
cp->cmd_char_pos = cp->cmd_char_list.begin();
cp->maybe_cmd = false;
}
}
else {
cp->cmd_char_list.clear();
cp->cmd_char_pos = cp->cmd_char_list.begin();
}
cp->process_srv = false;
return;
break;
}
default:
@ -1398,174 +1102,8 @@ void SshSession::_process_ssh_command(TP_SSH_CHANNEL_PAIR* cp, int from, const e
}
}
d += 1;
l -= 1;
offset++;
}
// bool esc_mode = false;
// int esc_arg = 0;
// for (int i = 0; i < len; i++)
// {
// if (esc_mode)
// {
// switch (data[i])
// {
// case '0':
// case '1':
// case '2':
// case '3':
// case '4':
// case '5':
// case '6':
// case '7':
// case '8':
// case '9':
// esc_arg = esc_arg * 10 + (data[i] - '0');
// break;
//
// case 0x3f:
// case ';':
// case '>':
// cp->cmd_char_list.clear();
// cp->cmd_char_pos = cp->cmd_char_list.begin();
// return;
// break;
//
// case 0x4b: // 'K'
// {
// if (0 == esc_arg)
// {
// // 删除光标到行尾的字符串
// cp->cmd_char_list.erase(cp->cmd_char_pos, cp->cmd_char_list.end());
// cp->cmd_char_pos = cp->cmd_char_list.end();
// }
// else if (1 == esc_arg)
// {
// // 删除从开始到光标处的字符串
// cp->cmd_char_list.erase(cp->cmd_char_list.begin(), cp->cmd_char_pos);
// cp->cmd_char_pos = cp->cmd_char_list.end();
// }
// else if (2 == esc_arg)
// {
// // 删除整行
// cp->cmd_char_list.clear();
// cp->cmd_char_pos = cp->cmd_char_list.begin();
// }
//
// esc_mode = false;
// break;
// }
// case 0x43: // ^[C
// {
// // 光标右移
// if (esc_arg == 0)
// esc_arg = 1;
// for (int j = 0; j < esc_arg; ++j)
// {
// if (cp->cmd_char_pos != cp->cmd_char_list.end())
// cp->cmd_char_pos++;
// }
// esc_mode = false;
// break;
// }
// case 0x44: // ^[D
// {
// // 光标左移
// if (esc_arg == 0)
// esc_arg = 1;
// for (int j = 0; j < esc_arg; ++j)
// {
// if (cp->cmd_char_pos != cp->cmd_char_list.begin())
// cp->cmd_char_pos--;
// }
// esc_mode = false;
// break;
// }
//
// case 0x50: // 'P' 删除指定数量的字符
// {
// if (esc_arg == 0)
// esc_arg = 1;
// for (int j = 0; j < esc_arg; ++j)
// {
// if (cp->cmd_char_pos != cp->cmd_char_list.end())
// cp->cmd_char_pos = cp->cmd_char_list.erase(cp->cmd_char_pos);
// }
// esc_mode = false;
// break;
// }
//
// case 0x40: // '@' 插入指定数量的空白字符
// {
// if (esc_arg == 0)
// esc_arg = 1;
// for (int j = 0; j < esc_arg; ++j)
// {
// cp->cmd_char_pos = cp->cmd_char_list.insert(cp->cmd_char_pos, ' ');
// }
// esc_mode = false;
// break;
// }
//
// default:
// esc_mode = false;
// break;
// }
//
// continue;
// }
//
// switch (data[i])
// {
// case 0x07:
// {
// // 响铃
// break;
// }
// case 0x08:
// {
// // 光标左移
// if (cp->cmd_char_pos != cp->cmd_char_list.begin())
// cp->cmd_char_pos--;
// break;
// }
// case 0x1b:
// {
// if (i + 1 < len)
// {
// if (data[i + 1] == 0x5b)
// {
// esc_mode = true;
// esc_arg = 0;
//
// i += 1;
// break;
// }
// }
//
// break;
// }
// case 0x0d:
// {
// cp->cmd_char_list.clear();
// cp->cmd_char_pos = cp->cmd_char_list.begin();
//
// break;
// }
// default:
// if (cp->cmd_char_pos != cp->cmd_char_list.end())
// {
// cp->cmd_char_pos = cp->cmd_char_list.erase(cp->cmd_char_pos);
// cp->cmd_char_pos = cp->cmd_char_list.insert(cp->cmd_char_pos, data[i]);
// cp->cmd_char_pos++;
// }
// else
// {
// cp->cmd_char_list.push_back(data[i]);
// cp->cmd_char_pos = cp->cmd_char_list.end();
// }
// }
// }
}
return;
@ -1586,7 +1124,7 @@ void SshSession::_process_sftp_command(TP_SSH_CHANNEL_PAIR* cp, const ex_u8* dat
if (sftp_cmd == 0x01) {
// 0x01 = 1 = SSH_FXP_INIT
cp->rec.record_command("SFTP INITIALIZE\r\n");
cp->rec.record_command(0, "SFTP INITIALIZE\r\n");
return;
}
@ -1661,5 +1199,5 @@ void SshSession::_process_sftp_command(TP_SSH_CHANNEL_PAIR* cp, const ex_u8* dat
ex_strformat(msg, 2048, "%d:%s:%s:%s\r\n", sftp_cmd, act, str1.c_str(), str2.c_str());
}
cp->rec.record_command(msg);
cp->rec.record_command(0, msg);
}

View File

@ -23,10 +23,6 @@
class SshProxy;
class SshSession;
#define TP_CHAR_NUL 0x00
#define TP_CHAR_CR 0x0D
#define TP_CHAR_TAB 0x09
class TP_SSH_CHANNEL_PAIR {
friend class SshSession;
@ -45,13 +41,15 @@ private:
int db_id;
int channel_id; // for debug only.
int win_width; // window width, in char count.
bool is_first_server_data;
// for ssh command record cache.
//ex_u8 last_client_char; // 最近一次收到的客户端数据的最后一个字节
bool server_ready;
bool maybe_cmd;
//int cmd_flag;
bool process_srv;
bool client_single_char;
std::list<char> cmd_char_list;
std::list<char>::iterator cmd_char_pos;
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -83,6 +83,10 @@ label {
//font-style: italic;
}
.bold {
font-weight: bold;
}
hr.hr-sm {
//margin-top: 5px;
//margin-bottom: 5px;

View File

@ -147,7 +147,7 @@ body {
& > li.sub-title {
font-size: 14px;
color: darken(@top-navbar-color, 10%);
line-height: @header-height - 1px;
line-height: @header-height - 4px;
}
}
}

View File

@ -9,8 +9,11 @@
<%block name="extend_css">
<style type="text/css">
#no-op-msg {
#err-box, #op-box, #no-msg-box {
display: none;
}
#no-msg-box {
padding: 20px;
margin: 50px;
background-color: #fffed5;
@ -18,36 +21,63 @@
font-size: 120%;
}
#op-list {
display: none;
#op-list, .op-msg {
padding: 20px;
margin: 20px 10px 20px 10px;
margin: 10px;
background-color: #ffffff;
font-size: 14px;
font-size: 13px;
border-radius: 5px;
box-shadow: 1px 1px 2px rgba(0, 0, 0, .2);
}
.op-item {
margin-bottom: 3px;
margin: 2px 3px;
padding: 2px 5px;
}
.op-item.bold {
margin-bottom: 5px;
margin-left: -10px;
}
.time, .cmd {
font-family: Consolas, Lucida Console, Monaco, Courier, 'Courier New', monospace;
line-height: 15px;
padding: 0 5px;
padding: 2px 5px;
border-radius: 3px;
}
.time {
margin-right: 15px;
background-color: #d8d8d8;
background-color: #ececec;
}
.time.multi, .cmd-multi {
background-color: #fff2cb;
}
.cmd {
display: inline-block;
}
.cmd-danger {
background-color: #ffbba6;
background-color: #ffd2c2;
font-weight: bold;
}
.cmd-danger:before {
display: block;
position: relative;
width: 16px;
float: left;
margin-right: 3px;
margin-top: 0px;
color: #ff533e;
font-size: 16px;
content: "\f06a";
font-family: 'FontAwesome';
}
.cmd-info {
background-color: #b4fdb1;
}
@ -58,7 +88,7 @@
<div class="container-fluid top-navbar">
<div class="breadcrumb-container">
<ol class="breadcrumb">
<li><i class="fa fa-server"></i> ${self.attr.page_title_}</li>
<li><i class="fa fa-server"></i> <span id="page-title">${self.attr.page_title_}</span></li>
<li class="sub-title"><span id="recorder-info"></span></li>
</ol>
</div>
@ -66,10 +96,23 @@
</%block>
<div class="page-content">
<div id="no-op-msg">
<div id="err-box">
<div class="alert alert-danger">
错误,无法读取日志文件!<br/><br/>
错误原因:<span id="err-more-info" class="bold"></span>
</div>
</div>
<div id="no-msg-box">
他悄悄地来,又悄悄地走,挥一挥衣袖,没有留下任何操作~~~~
</div>
<div id="op-list"></div>
<div id="op-box">
<div id="op-list"></div>
<div class="op-msg">
<div class="op-item bold">图例说明:</div>
<div class="op-item"><span class="time multi">YYYY-mm-dd HH:MM:SS</span> <span class="cmd cmd-multi">此记录可能是被复制粘贴到SSH客户端的有可能是批量执行命令也可能是在做文本编辑详情见录像回放。</span></div>
<div class="op-item"><span class="time">YYYY-mm-dd HH:MM:SS</span> <span class="cmd cmd-danger">此命令可能是危险操作。</span></div>
</div>
</div>
</div>
<%block name="embed_js">
@ -82,30 +125,63 @@
var info_cmd = ['exit'];
$app.on_init = function (cb_stack, cb_args) {
$app.dom = {
page_title: $('#page-title')
, rec_info: $('#recorder-info')
, err_box: $('#err-box')
, err_more: $('#err-more-info')
, no_msg_box: $('#no-op-msg')
, op_box: $('#op-box')
, op_list: $('#op-list')
};
console.log($app.options);
var header = $app.options.header;
$('#recorder-info').html(tp_format_datetime(header.start) + ': ' + header.user_name + '@' + header.client_ip + ' 访问 ' + header.account + '@' + header.conn_ip + ':' + header.conn_port);
if ($app.options.count === 0) {
$('#no-op-msg').show();
} else {
var dom_op_list = $('#op-list');
var html = [];
for (var i = 0; i < $app.options.count; i++) {
var cmd_list = $app.options.op[i].c.split(' ');
var cmd_class = '';
if (_.intersection(cmd_list, danger_cmd).length > 0) {
cmd_class = ' cmd-danger';
} else if (_.intersection(cmd_list, info_cmd).length > 0) {
cmd_class = ' cmd-info';
}
html.push('<div class="op-item"><span class="time">' + $app.options.op[i].t + '</span> <span class="cmd' + cmd_class + '">' + $app.options.op[i].c + '</span></div>');
}
dom_op_list.append(html.join(''));
dom_op_list.show();
if ($app.options.code !== TPE_OK) {
$app.dom.page_title.text('错误');
$app.dom.rec_info.text('读取日志文件时发生错误');
$app.dom.err_more.text(tp_error_msg($app.options.code));
$app.dom.err_box.show();
cb_stack.exec();
return;
}
var header = $app.options.header;
$app.dom.rec_info.html(tp_format_datetime(header.start) + ': ' + header.user_name + '@' + header.client_ip + ' 访问 ' + header.account + '@' + header.conn_ip + ':' + header.conn_port);
var op = $app.options.op;
if (op.length === 0) {
$app.dom.no_msg_box.show();
cb_stack.exec();
return;
}
var html = [];
html.push('<div class="op-item bold">命令历史记录:</div>');
for (var i = 0; i < op.length; i++) {
var cmd_list = op[i].c.split(' ');
var cmd_class = 'cmd';
var time_class = 'time';
if (op[i].f === 1) {
time_class += ' multi';
cmd_class += ' cmd-multi';
}
if (_.intersection(cmd_list, danger_cmd).length > 0) {
cmd_class += ' cmd-danger';
} else if (_.intersection(cmd_list, info_cmd).length > 0) {
cmd_class += ' cmd-info';
}
var t = tp_format_datetime(header.start + parseInt(op[i].t/1000));
html.push('<div class="op-item"><span class="' + time_class + '">' + t + '</span> <span class="' + cmd_class + '">' + op[i].c + '</span></div>');
}
$app.dom.op_list.append(html.join(''));
$app.dom.op_box.show();
cb_stack.exec();
};
</script>

View File

@ -170,19 +170,25 @@ class ReplayHandler(TPBaseHandler):
class ComandLogHandler(TPBaseHandler):
@tornado.gen.coroutine
def get(self, protocol, record_id):
ret = self.check_privilege(TP_PRIVILEGE_OPS | TP_PRIVILEGE_OPS_AUZ | TP_PRIVILEGE_AUDIT_AUZ | TP_PRIVILEGE_AUDIT_OPS_HISTORY)
if ret != TPE_OK:
return
param = dict()
header, err = record.read_record_head(record_id)
if header is None:
return self.write('操作失败')
# return self.write('操作失败![{}]'.format(err))
param['code'] = err
return self.render('audit/record-ssh-cmd.mako', page_param=json.dumps(param))
# ret = dict()
# ret['header'] = header
# return self.write_json(0, data=ret)
param = dict()
param['header'] = header
param['count'] = 0
param['op'] = list()
param['code'] = TPE_OK
cmd_type = 0 # 0 = ssh, 1 = sftp
protocol = int(protocol)
@ -195,18 +201,24 @@ class ComandLogHandler(TPBaseHandler):
file = open(file_info, 'r')
data = file.readlines()
for i in range(len(data)):
cmd = data[i].split(',', 2)
if len(cmd) != 3:
continue
if 0 == i:
cmd = data[i][22:-1]
if 'SFTP INITIALIZE' == cmd:
if 'SFTP INITIALIZE' == cmd[2]:
cmd_type = 1
continue
t = int(cmd[0])
f = int(cmd[1])
if cmd_type == 0:
param['op'].append({'t': data[i][1:20], 'c': data[i][22:-1]})
param['op'].append({'t': t, 'f': f, 'c': cmd[2]})
else:
cmd_info = data[i][22:-1].split(':')
cmd_info = cmd[2].split(':')
if len(cmd_info) != 4:
continue
param['op'].append({'t': data[i][1:20], 'c': cmd_info[0], 'p1': cmd_info[2], 'p2': cmd_info[3]})
param['op'].append({'t': t, 'c': cmd[2], 'p1': cmd_info[2], 'p2': cmd_info[3]})
except:
pass
param['count'] = len(param['op'])