mirror of https://github.com/tp4a/teleport
支持记录SFTP操作日志了,能记录文件打开、删除,目录创建、删除,改名以及创建符号链接等操作。
parent
428dc323f3
commit
ef23397be8
|
@ -409,7 +409,7 @@ TS_SSH_CHANNEL_INFO *SshSession::_get_srv_channel(ssh_channel cli_channel) {
|
||||||
return it->second;
|
return it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SshSession::_process_command(int from, const ex_u8* data, int len)
|
void SshSession::_process_ssh_command(int from, const ex_u8* data, int len)
|
||||||
{
|
{
|
||||||
if (TS_SSH_DATA_FROM_CLIENT == from)
|
if (TS_SSH_DATA_FROM_CLIENT == from)
|
||||||
{
|
{
|
||||||
|
@ -683,6 +683,100 @@ void SshSession::_process_command(int from, const ex_u8* data, int len)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SshSession::_process_sftp_command(const ex_u8* data, int len) {
|
||||||
|
// SFTP protocol: https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13
|
||||||
|
//EXLOG_BIN(data, len, "[sftp] client channel data");
|
||||||
|
|
||||||
|
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
|
||||||
|
m_rec.record_command("SFTP INITIALIZE\r\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 需要的数据至少14字节
|
||||||
|
// uint32 + byte + uint32 + (uint32 + char + ...)
|
||||||
|
// pkg_len + cmd + req_id + string( length + content...)
|
||||||
|
if (len < 14)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ex_u8* str1_ptr = (ex_u8*)data + 9;
|
||||||
|
int str1_len = (int)((str1_ptr[0] << 24) | (str1_ptr[1] << 16) | (str1_ptr[2] << 8) | str1_ptr[3]);
|
||||||
|
// if (str1_len + 9 != pkg_len)
|
||||||
|
// return;
|
||||||
|
ex_u8* str2_ptr = NULL;// (ex_u8*)data + 13;
|
||||||
|
int str2_len = 0;// (int)((data[9] << 24) | (data[10] << 16) | (data[11] << 8) | data[12]);
|
||||||
|
|
||||||
|
|
||||||
|
char* act = NULL;
|
||||||
|
switch (sftp_cmd) {
|
||||||
|
case 0x03:
|
||||||
|
// 0x03 = 3 = SSH_FXP_OPEN
|
||||||
|
act = "open file";
|
||||||
|
break;
|
||||||
|
// case 0x0b:
|
||||||
|
// // 0x0b = 11 = SSH_FXP_OPENDIR
|
||||||
|
// act = "open dir";
|
||||||
|
// break;
|
||||||
|
case 0x0d:
|
||||||
|
// 0x0d = 13 = SSH_FXP_REMOVE
|
||||||
|
act = "remove file";
|
||||||
|
break;
|
||||||
|
case 0x0e:
|
||||||
|
// 0x0e = 14 = SSH_FXP_MKDIR
|
||||||
|
act = "create dir";
|
||||||
|
break;
|
||||||
|
case 0x0f:
|
||||||
|
// 0x0f = 15 = SSH_FXP_RMDIR
|
||||||
|
act = "remove dir";
|
||||||
|
break;
|
||||||
|
case 0x12:
|
||||||
|
// 0x12 = 18 = SSH_FXP_RENAME
|
||||||
|
// rename操作数据中包含两个字符串
|
||||||
|
act = "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操作数据中包含两个字符串,前者是新的链接文件名,后者是现有被链接的文件名
|
||||||
|
act = "create 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:%s:%s:\r\n", sftp_cmd, act, 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:%s:%s:%s\r\n", sftp_cmd, act, str1.c_str(), str2.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
m_rec.record_command(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
int SshSession::_on_client_pty_request(ssh_session session, ssh_channel channel, const char *term, int x, int y, int px,
|
int SshSession::_on_client_pty_request(ssh_session session, ssh_channel channel, const char *term, int x, int y, int px,
|
||||||
int py, void *userdata) {
|
int py, void *userdata) {
|
||||||
SshSession *_this = (SshSession *)userdata;
|
SshSession *_this = (SshSession *)userdata;
|
||||||
|
@ -809,12 +903,16 @@ int SshSession::_on_client_channel_data(ssh_session session, ssh_channel channel
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_this->_process_command(TS_SSH_DATA_FROM_CLIENT, (ex_u8*)data, len);
|
_this->_process_ssh_command(TS_SSH_DATA_FROM_CLIENT, (ex_u8*)data, len);
|
||||||
}
|
}
|
||||||
catch (...)
|
catch (...)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_this->_process_sftp_command((ex_u8*)data, len);
|
||||||
|
}
|
||||||
|
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
if (is_stderr)
|
if (is_stderr)
|
||||||
|
@ -908,7 +1006,7 @@ int SshSession::_on_server_channel_data(ssh_session session, ssh_channel channel
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_this->_process_command(TS_SSH_DATA_FROM_SERVER, (ex_u8*)data, len);
|
_this->_process_ssh_command(TS_SSH_DATA_FROM_SERVER, (ex_u8*)data, len);
|
||||||
_this->m_rec.record(TS_RECORD_TYPE_SSH_DATA, (unsigned char *)data, len);
|
_this->m_rec.record(TS_RECORD_TYPE_SSH_DATA, (unsigned char *)data, len);
|
||||||
}
|
}
|
||||||
catch (...)
|
catch (...)
|
||||||
|
|
|
@ -62,7 +62,8 @@ protected:
|
||||||
void _thread_loop(void);
|
void _thread_loop(void);
|
||||||
void _set_stop_flag(void);
|
void _set_stop_flag(void);
|
||||||
|
|
||||||
void _process_command(int from, const ex_u8* data, int len);
|
void _process_ssh_command(int from, const ex_u8* data, int len);
|
||||||
|
void _process_sftp_command(const ex_u8* data, int len);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void _run(void);
|
void _run(void);
|
||||||
|
|
|
@ -80,10 +80,20 @@ class ReplayStaticFileHandler(tornado.web.StaticFileHandler):
|
||||||
class ComandLogHandler(TPBaseAdminAuthHandler):
|
class ComandLogHandler(TPBaseAdminAuthHandler):
|
||||||
def get(self, protocol, record_id):
|
def get(self, protocol, record_id):
|
||||||
|
|
||||||
|
header = record.read_record_head(record_id)
|
||||||
|
if header is None:
|
||||||
|
return self.write_json(-3, '操作失败')
|
||||||
|
|
||||||
|
# ret = dict()
|
||||||
|
# ret['header'] = header
|
||||||
|
# return self.write_json(0, data=ret)
|
||||||
|
|
||||||
param = dict()
|
param = dict()
|
||||||
|
param['header'] = header
|
||||||
param['count'] = 0
|
param['count'] = 0
|
||||||
param['op'] = list()
|
param['op'] = list()
|
||||||
|
|
||||||
|
cmd_type = 0 # 0 = ssh, 1 = sftp
|
||||||
protocol = int(protocol)
|
protocol = int(protocol)
|
||||||
if protocol == 1:
|
if protocol == 1:
|
||||||
pass
|
pass
|
||||||
|
@ -94,12 +104,26 @@ class ComandLogHandler(TPBaseAdminAuthHandler):
|
||||||
file = open(file_info, 'r')
|
file = open(file_info, 'r')
|
||||||
data = file.readlines()
|
data = file.readlines()
|
||||||
for i in range(len(data)):
|
for i in range(len(data)):
|
||||||
|
if 0 == i:
|
||||||
|
cmd = data[i][22:-1]
|
||||||
|
if 'SFTP INITIALIZE' == cmd:
|
||||||
|
cmd_type = 1
|
||||||
|
continue
|
||||||
|
if cmd_type == 0:
|
||||||
param['op'].append({'t': data[i][1:20], 'c': data[i][22:-1]})
|
param['op'].append({'t': data[i][1:20], 'c': data[i][22:-1]})
|
||||||
|
else:
|
||||||
|
cmd_info = data[i][22:-1].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]})
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
param['count'] = len(param['op'])
|
param['count'] = len(param['op'])
|
||||||
|
|
||||||
|
if cmd_type == 0:
|
||||||
self.render('log/record-ssh-cmd.mako', page_param=json.dumps(param))
|
self.render('log/record-ssh-cmd.mako', page_param=json.dumps(param))
|
||||||
|
else:
|
||||||
|
self.render('log/record-sftp-cmd.mako', page_param=json.dumps(param))
|
||||||
|
|
||||||
|
|
||||||
class RecordGetHeader(TPBaseAdminAuthJsonHandler):
|
class RecordGetHeader(TPBaseAdminAuthJsonHandler):
|
||||||
|
|
|
@ -0,0 +1,125 @@
|
||||||
|
<%!
|
||||||
|
page_title_ = 'SFTP操作记录'
|
||||||
|
%>
|
||||||
|
|
||||||
|
<%inherit file="../page_no_sidebar_base.mako"/>
|
||||||
|
<%block name="extend_js">
|
||||||
|
</%block>
|
||||||
|
|
||||||
|
<%block name="breadcrumb">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li><i class="fa fa-file-text-o"></i> ${self.attr.page_title_}</li>
|
||||||
|
<li><span id="recorder-info"></span></li>
|
||||||
|
</ol>
|
||||||
|
</%block>
|
||||||
|
|
||||||
|
<%block name="extend_css">
|
||||||
|
<style type="text/css">
|
||||||
|
#no-op-msg {
|
||||||
|
display: none;
|
||||||
|
padding: 20px;
|
||||||
|
margin: 50px;
|
||||||
|
background-color: #fffed5;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 120%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#op-list {
|
||||||
|
display: none;
|
||||||
|
padding: 20px;
|
||||||
|
margin: 20px 10px 20px 10px;
|
||||||
|
background-color: #ffffff;
|
||||||
|
font-size: 14px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.op-item {
|
||||||
|
margin-bottom: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time, .cmd, .path {
|
||||||
|
font-family: Consolas, Lucida Console, Monaco, Courier, 'Courier New', monospace;
|
||||||
|
font-size:13px;
|
||||||
|
line-height: 15px;
|
||||||
|
padding: 0 5px;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time {
|
||||||
|
margin-right: 15px;
|
||||||
|
background-color: #d8d8d8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path {
|
||||||
|
margin:0 5px 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmd-danger {
|
||||||
|
background-color: #ffbba6;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmd-info {
|
||||||
|
background-color: #b4fdb1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</%block>
|
||||||
|
|
||||||
|
<div class="page-content">
|
||||||
|
<div id="no-op-msg">
|
||||||
|
他悄悄地来,又悄悄地走,挥一挥衣袖,没有留下任何操作~~~~
|
||||||
|
</div>
|
||||||
|
<div id="op-list"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%block name="embed_js">
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
ywl.add_page_options(${page_param});
|
||||||
|
|
||||||
|
ywl.on_init = function (cb_stack, cb_args) {
|
||||||
|
|
||||||
|
if (ywl.page_options.count === 0) {
|
||||||
|
$('#no-op-msg').show();
|
||||||
|
} else {
|
||||||
|
var header = ywl.page_options.header;
|
||||||
|
$('#recorder-info').html(header.account + ' 于 ' + format_datetime(header.start) + ' 访问 ' + header.user_name + '@' + header.ip + ':' + header.port);
|
||||||
|
|
||||||
|
var dom_op_list = $('#op-list');
|
||||||
|
var html = [];
|
||||||
|
for (var i = 0; i < ywl.page_options.count; i++) {
|
||||||
|
html.push('<div class="op-item"><span class="time">' + ywl.page_options.op[i].t + '</span> ');
|
||||||
|
|
||||||
|
if (ywl.page_options.op[i].c === '3') {
|
||||||
|
html.push('<span class="cmd">打开文件</span>');
|
||||||
|
html.push('<span class="path">' + ywl.page_options.op[i].p1 + '</span>');
|
||||||
|
} else if (ywl.page_options.op[i].c === '13') {
|
||||||
|
html.push('<span class="cmd cmd-danger">删除文件</span>');
|
||||||
|
html.push('<span class="path cmd-danger">' + ywl.page_options.op[i].p1 + '</span>');
|
||||||
|
} else if (ywl.page_options.op[i].c === '14') {
|
||||||
|
html.push('<span class="cmd">创建目录</span>');
|
||||||
|
html.push('<span class="path">' + ywl.page_options.op[i].p1 + '</span>');
|
||||||
|
} else if (ywl.page_options.op[i].c === '15') {
|
||||||
|
html.push('<span class="cmd cmd-danger">删除目录</span>');
|
||||||
|
html.push('<span class="path cmd-danger">' + ywl.page_options.op[i].p1 + '</span>');
|
||||||
|
} else if (ywl.page_options.op[i].c === '18') {
|
||||||
|
html.push('<span class="cmd cmd-info">更改名称</span>');
|
||||||
|
html.push('<span class="path cmd-info">' + ywl.page_options.op[i].p1 + '</span>');
|
||||||
|
html.push('<i class="fa fa-arrow-circle-right"></i>');
|
||||||
|
html.push('<span class="path cmd-info">' + ywl.page_options.op[i].p2 + '</span>');
|
||||||
|
} else if (ywl.page_options.op[i].c === '21') {
|
||||||
|
html.push('<span class="cmd">创建链接</span>');
|
||||||
|
html.push('<span class="path">' + ywl.page_options.op[i].p2 + '</span>');
|
||||||
|
html.push('<i class="fa fa-arrow-right"></i>');
|
||||||
|
html.push('<span class="path">' + ywl.page_options.op[i].p1 + '</span>');
|
||||||
|
}
|
||||||
|
|
||||||
|
html.push('</div>');
|
||||||
|
}
|
||||||
|
dom_op_list.append(html.join(''));
|
||||||
|
dom_op_list.show();
|
||||||
|
}
|
||||||
|
cb_stack.exec();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
</%block>
|
|
@ -1,5 +1,5 @@
|
||||||
<%!
|
<%!
|
||||||
page_title_ = '操作记录'
|
page_title_ = 'SSH操作记录'
|
||||||
%>
|
%>
|
||||||
|
|
||||||
<%inherit file="../page_no_sidebar_base.mako"/>
|
<%inherit file="../page_no_sidebar_base.mako"/>
|
||||||
|
@ -8,7 +8,8 @@
|
||||||
|
|
||||||
<%block name="breadcrumb">
|
<%block name="breadcrumb">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li><i class="fa fa-server"></i> ${self.attr.page_title_}</li>
|
<li><i class="fa fa-file-text-o"></i> ${self.attr.page_title_}</li>
|
||||||
|
<li><span id="recorder-info"></span></li>
|
||||||
</ol>
|
</ol>
|
||||||
</%block>
|
</%block>
|
||||||
|
|
||||||
|
@ -75,9 +76,12 @@
|
||||||
var info_cmd = ['exit'];
|
var info_cmd = ['exit'];
|
||||||
|
|
||||||
ywl.on_init = function (cb_stack, cb_args) {
|
ywl.on_init = function (cb_stack, cb_args) {
|
||||||
if (ywl.page_options.count == 0) {
|
if (ywl.page_options.count === 0) {
|
||||||
$('#no-op-msg').show();
|
$('#no-op-msg').show();
|
||||||
} else {
|
} else {
|
||||||
|
var header = ywl.page_options.header;
|
||||||
|
$('#recorder-info').html(header.account + ' 于 ' + format_datetime(header.start) + ' 访问 ' + header.user_name + '@' + header.ip + ':' + header.port);
|
||||||
|
|
||||||
var dom_op_list = $('#op-list');
|
var dom_op_list = $('#op-list');
|
||||||
var html = [];
|
var html = [];
|
||||||
for (var i = 0; i < ywl.page_options.count; i++) {
|
for (var i = 0; i < ywl.page_options.count; i++) {
|
||||||
|
@ -90,7 +94,7 @@
|
||||||
cmd_class = ' cmd-info';
|
cmd_class = ' cmd-info';
|
||||||
}
|
}
|
||||||
|
|
||||||
html.push('<div class="op-item"><span class="time">' + ywl.page_options.op[i].t + '</span> <span class="cmd' + cmd_class + '">' + ywl.page_options.op[i].c + '</span></li></div>');
|
html.push('<div class="op-item"><span class="time">' + ywl.page_options.op[i].t + '</span> <span class="cmd' + cmd_class + '">' + ywl.page_options.op[i].c + '</span></div>');
|
||||||
}
|
}
|
||||||
dom_op_list.append(html.join(''));
|
dom_op_list.append(html.join(''));
|
||||||
dom_op_list.show();
|
dom_op_list.show();
|
||||||
|
|
Loading…
Reference in New Issue