2
.gitignore
vendored
@ -17,3 +17,5 @@ vendor
|
||||
*/.DS_Store
|
||||
*/.idea/%
|
||||
*/vendor/%
|
||||
db/*.db
|
||||
logs/*.log
|
108
README.md
@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# 介绍
|
||||
|
||||
@ -6,48 +6,90 @@
|
||||
|
||||
**HFish** 是一款基于 Golang 开发的跨平台多功能主动诱导型蜜罐框架系统,为了企业安全防护测试做出了精心的打造
|
||||
|
||||
- 多功能 不仅仅支持 HTTP(S) 蜜罐,还支持 SSH、SFTP、Redis、Mysql 等
|
||||
|
||||
- 多功能 不仅仅支持 HTTP(S) 蜜罐,还支持 SSH、SFTP、Redis、Mysql、FTP、Telnet、暗网 等
|
||||
- 扩展性 提供 API 接口,使用者可以随意扩展蜜罐模块 ( WEB、PC、APP )
|
||||
- 便捷性 使用 Golang 开发,使用者可以在 Win + Mac + Linux 上快速部署一套蜜罐平台
|
||||
|
||||
# 地址
|
||||
|
||||
- Github: https://github.com/hacklcx/HFish
|
||||
## Github
|
||||
|
||||
- Git: https://github.com/hacklcx/HFish
|
||||
- Download: https://github.com/hacklcx/HFish/releases
|
||||
|
||||
## 码云(Gitee)
|
||||
|
||||
- Git: https://gitee.com/lauix/HFish
|
||||
- Download: https://gitee.com/lauix/HFish/releases
|
||||
|
||||
# 快速部署
|
||||
|
||||
### 部署说明
|
||||
## 部署说明
|
||||
|
||||
- 下载当前系统二进制包
|
||||
- cd 到程序根目录,修改 config.ini 配置文件
|
||||
- 执行 ./HFish run 启动服务
|
||||
- 浏览器输入 http://localhost:9001 打开
|
||||
|
||||
### 帮助页面
|
||||
## 集群部署
|
||||
|
||||
- 复制 HFish、config.ini、web(不启动WEB蜜罐可以不复制) 目录文件到服务器上
|
||||
- 修改 config.ini -> rpc -> status 为 2
|
||||
- 修改 config.ini -> rpc -> addr 地址为 HFish 服务端地址
|
||||
|
||||
## 命令行帮助
|
||||
|
||||

|
||||
|
||||
### 启动服务
|
||||
## 启动服务
|
||||
|
||||

|
||||
|
||||
# 部分界面展示
|
||||
|
||||

|
||||
## 登录页
|
||||
|
||||

|
||||

|
||||
|
||||
## 上钩页
|
||||
|
||||

|
||||
|
||||
## 分布式集群
|
||||
|
||||

|
||||
|
||||
## 邮件群发
|
||||
|
||||

|
||||
|
||||
# 部分功能使用演示
|
||||
|
||||
### Redis 蜜罐
|
||||
## WEB 蜜罐
|
||||
|
||||

|
||||
|
||||
## SSH 蜜罐
|
||||
|
||||

|
||||
|
||||
## Redis 蜜罐
|
||||
|
||||

|
||||
|
||||
### Mysql 蜜罐
|
||||
## Mysql 蜜罐
|
||||
|
||||

|
||||
|
||||
## FTP 蜜罐
|
||||
|
||||

|
||||
|
||||
## Telnet 蜜罐
|
||||
|
||||

|
||||
|
||||
# 注意事项
|
||||
|
||||
- 邮箱 SMTP 配置后需要开启方可使用
|
||||
@ -55,9 +97,13 @@
|
||||
- 启动 WEB 蜜罐,请先启动 API 模块
|
||||
- WEB 插件 需在 WEB 目录下 编写
|
||||
- WEB 插件 下面必须存在两个目录
|
||||
- 集群 心跳为60秒,断开显示会延迟60秒
|
||||
- 暗网蜜罐是支持的,但是目前Tor服务网上找不到,无法提供演示
|
||||
|
||||
# API 接口
|
||||
|
||||
## WEB 蜜罐
|
||||
|
||||
```
|
||||
URL: http://localhost:9001/api/v1/post/report
|
||||
|
||||
@ -73,6 +119,33 @@ POST:
|
||||
sec_key 可在 config.ini 配置里修改,修改后 WEB 模板也需要同时修改
|
||||
```
|
||||
|
||||
## 暗网 蜜罐
|
||||
|
||||
```
|
||||
URL: http://localhost:9001/api/v1/post/deep_report
|
||||
|
||||
POST:
|
||||
|
||||
name : 暗网后台蜜罐 # 项目名
|
||||
info : admin&&12345 # 上报信息,&& 为换行符号
|
||||
sec_key : 9cbf8a4dcb8e30682b927f352d6559a0 # API 安全密钥
|
||||
|
||||
特殊说明:
|
||||
|
||||
URL api/v1/post/deep_report 可在 config.ini 配置里修改
|
||||
sec_key 可在 config.ini 配置里修改,修改后 暗网 模板也需要同时修改
|
||||
```
|
||||
|
||||
## 黑名单IP
|
||||
|
||||
```
|
||||
URL(Get): http://localhost:9001/api/v1/get/ip
|
||||
|
||||
特殊说明:
|
||||
|
||||
提供此接口为了配合防火墙使用,具体方案欢迎来讨论!
|
||||
```
|
||||
|
||||
# TODO
|
||||
|
||||
- [x] 登录模块
|
||||
@ -82,21 +155,20 @@ POST:
|
||||
- [x] 命令行优化
|
||||
- [x] 支持自定义 WEB 模板
|
||||
- [x] 支持 Mysql 服务端获取连接客户端电脑任意文件
|
||||
- [x] 支持 HTTP(S)、SSH、SFTP、Redis、Mysql 协议
|
||||
- [ ] 支持 FTP、Telnet、SMTP、POP3、TFTP、Oracle、VPN 等
|
||||
- [ ] 暗网蜜罐支持
|
||||
- [x] 支持 HTTP(S)、SSH、SFTP、Redis、Mysql、FTP、Telnet、暗网 蜜罐
|
||||
- [x] 日记完善优化
|
||||
- [x] 支持分布式架构
|
||||
- [x] 支持分页
|
||||
- [x] 支持 ip 地理信息
|
||||
- [x] 提供黑名单IP接口
|
||||
- [ ] 支持 SMTP、POP3、TFTP、Oracle、VPN 等
|
||||
- [ ] WIFI 蜜罐支持
|
||||
- [ ] 自动化蜜罐支持
|
||||
- [ ] 蜜罐报告生成
|
||||
- [ ] 支持更多的 WEB 模块
|
||||
- [ ] 日记完善优化
|
||||
- [ ] 邮件发送支持编辑器
|
||||
- [ ] 支持邮件模板选择
|
||||
- [ ] 蜜罐高交互完善
|
||||
- [ ] 支持 Ngrok 一键映射
|
||||
- [ ] 支持分布式架构
|
||||
- [ ] 支持分页
|
||||
- [ ] 支持 ip 地理信息 和 地图数据展示
|
||||
- [ ] 支持更多的图表统计
|
||||
- [ ] Mysql 支持
|
||||
- [ ] 规划更多的功能...
|
||||
|
177
admin/colony.html
Normal file
@ -0,0 +1,177 @@
|
||||
{{template "header"}}
|
||||
<style>
|
||||
.card-box {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.table {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
margin-bottom: 0rem;
|
||||
}
|
||||
|
||||
#tableList .td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
thead {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.openx {
|
||||
background: #3dd209;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 100%;
|
||||
display: -webkit-inline-box;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.closex {
|
||||
background: #dc0e0e;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 100%;
|
||||
display: -webkit-inline-box;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.modal-header .close {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
.agent_name{
|
||||
font-weight: bold;
|
||||
color: #167bcc;
|
||||
}
|
||||
</style>
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<h4 class="page-title">分布式集群</h4>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="card-box table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="10%">集群名称</th>
|
||||
<th width="10%">集群 IP</th>
|
||||
<th width="10%" style="text-align: center;">WEB</th>
|
||||
<th width="10%" style="text-align: center;">暗网</th>
|
||||
<th width="10%" style="text-align: center;">SSH</th>
|
||||
<th width="10%" style="text-align: center;">Redis</th>
|
||||
<th width="10%" style="text-align: center;">Mysql</th>
|
||||
<th width="10%" style="text-align: center;">Telnet</th>
|
||||
<th width="10%" style="text-align: center;">FTP</th>
|
||||
{{/*<th width="10%" style="text-align: center;">Http代理</th>*/}}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="tableList">
|
||||
<tr style="text-align: center;">
|
||||
<td style="line-height: 200px;font-size: 20px;color: #a9a9a9;" colspan="5">暂无数据</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{template "footer" }}
|
||||
<script>
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: "/get/colony/list",
|
||||
dataType: "json",
|
||||
success: function (e) {
|
||||
var data = e.data;
|
||||
var _h = '';
|
||||
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
_h += '<tr>';
|
||||
|
||||
var last_update_time = data[i].last_update_time;
|
||||
last_update_time = last_update_time.substring(0, 19);
|
||||
last_update_time = last_update_time.replace(/-/g, '/');
|
||||
var timestamp = new Date(last_update_time).getTime();
|
||||
var db_time = timestamp.toString().substring(0, 10);
|
||||
var curr_time = Math.round(new Date() / 1000);
|
||||
|
||||
if ((curr_time - parseInt(db_time)) < 60) {
|
||||
_h += ' <td class="td agent_name"><span class="openx"></span>' + data[i].agent_name + '</td>';
|
||||
_h += ' <td class="td"><span>' + data[i].agent_ip + '</span></td>';
|
||||
|
||||
if (data[i].web_status == "1") {
|
||||
_h += ' <td class="td" style="text-align: center;"><span class="openx"></span></td>';
|
||||
} else {
|
||||
_h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||
}
|
||||
|
||||
if (data[i].dart_status == "1") {
|
||||
_h += ' <td class="td" style="text-align: center;"><span class="openx"></span></td>';
|
||||
} else {
|
||||
_h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||
}
|
||||
|
||||
if (data[i].ssh_status == "1") {
|
||||
_h += ' <td class="td" style="text-align: center;"><span class="openx"></span></td>';
|
||||
} else {
|
||||
_h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||
}
|
||||
|
||||
if (data[i].redis_status == "1") {
|
||||
_h += ' <td class="td" style="text-align: center;"><span class="openx"></span></td>';
|
||||
} else {
|
||||
_h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||
}
|
||||
|
||||
if (data[i].mysql_status == "1") {
|
||||
_h += ' <td class="td" style="text-align: center;"><span class="openx"></span></td>';
|
||||
} else {
|
||||
_h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||
}
|
||||
|
||||
if (data[i].telnet_status == "1") {
|
||||
_h += ' <td class="td" style="text-align: center;"><span class="openx"></span></td>';
|
||||
} else {
|
||||
_h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||
}
|
||||
|
||||
if (data[i].ftp_status == "1") {
|
||||
_h += ' <td class="td" style="text-align: center;"><span class="openx"></span></td>';
|
||||
} else {
|
||||
_h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||
}
|
||||
|
||||
// if (data[i].http_status == "1") {
|
||||
// _h += ' <td class="td" style="text-align: center;"><span class="openx"></span></td>';
|
||||
// } else {
|
||||
// _h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||
// }
|
||||
} else {
|
||||
_h += ' <td class="td agent_name"><span class="closex"></span>' + data[i].agent_name + '</td>';
|
||||
_h += ' <td class="td"><span>' + data[i].agent_ip + '</span></td>';
|
||||
_h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||
_h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||
_h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||
_h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||
_h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||
_h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||
_h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||
// _h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||
}
|
||||
|
||||
|
||||
_h += '</tr>';
|
||||
}
|
||||
|
||||
if (_h == "") {
|
||||
_h = '<tr style="text-align: center;"><td style="line-height: 200px;font-size: 20px;color: #a9a9a9;" colspan="9">暂无数据</td></tr>'
|
||||
}
|
||||
|
||||
$("#tableList").html(_h);
|
||||
},
|
||||
error: function (e) {
|
||||
}
|
||||
});
|
||||
</script>
|
@ -23,6 +23,35 @@
|
||||
display: -webkit-inline-box;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.lb_web {
|
||||
color: #039cfd;
|
||||
}
|
||||
|
||||
.lb_ssh {
|
||||
color: #953fb9;
|
||||
}
|
||||
|
||||
.lb_redis {
|
||||
color: #e44242;
|
||||
}
|
||||
|
||||
.lb_mysql {
|
||||
color: #cabd23;
|
||||
}
|
||||
|
||||
.lb_ftp {
|
||||
color: #32cc16;
|
||||
}
|
||||
|
||||
.lb_telnet {
|
||||
color: #6b79c1;
|
||||
}
|
||||
|
||||
.lb_deep {
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
</style>
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
@ -36,15 +65,8 @@
|
||||
</div>
|
||||
<div class="col-xs-12 col-md-6 col-lg-6 col-xl-3">
|
||||
<div class="card-box tilebox-two">
|
||||
<i class="icon-rocket pull-xs-right text-muted"></i>
|
||||
<h6 class="text-success text-uppercase m-b-15 m-t-10">WEB上钩数</h6>
|
||||
<h2 class="m-b-10"><span data-plugin="counterup">{{.webSum}}</span></h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-md-6 col-lg-6 col-xl-3">
|
||||
<div class="card-box tilebox-two">
|
||||
<i class="icon-chart pull-xs-right text-muted"></i>
|
||||
<h6 class="text-primary text-uppercase m-b-15 m-t-10">SSH上钩数</h6>
|
||||
<i class="icon-ghost pull-xs-right text-muted"></i>
|
||||
<h6 class="lb_ssh text-uppercase m-b-15 m-t-10">SSH 蜜罐</h6>
|
||||
<h2 class="m-b-10"><span data-plugin="counterup">{{.sshSum}}</span></h2>
|
||||
</div>
|
||||
</div>
|
||||
@ -52,21 +74,54 @@
|
||||
<div class="col-xs-12 col-md-6 col-lg-6 col-xl-3">
|
||||
<div class="card-box tilebox-two">
|
||||
<i class="icon-layers pull-xs-right text-muted"></i>
|
||||
<h6 class="text-pink text-uppercase m-b-15 m-t-10">Redis上钩数</h6>
|
||||
<h6 class="lb_redis text-uppercase m-b-15 m-t-10">Redis 蜜罐</h6>
|
||||
<h2 class="m-b-10" data-plugin="counterup">{{.redisSum}}</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-12 col-md-6 col-lg-6 col-xl-3">
|
||||
<div class="card-box tilebox-two">
|
||||
<i class="icon-paypal pull-xs-right text-muted"></i>
|
||||
<h6 class="text-info text-uppercase m-b-15 m-t-10">Mysql上钩数</h6>
|
||||
<i class="icon-chart pull-xs-right text-muted"></i>
|
||||
<h6 class="lb_mysql text-uppercase m-b-15 m-t-10">Mysql 蜜罐</h6>
|
||||
<h2 class="m-b-10"><span data-plugin="counterup">{{.mysqlSum}}</span></h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-12 col-md-6 col-lg-6 col-xl-3">
|
||||
<div class="card-box tilebox-two">
|
||||
<i class="icon-layers pull-xs-right text-muted"></i>
|
||||
<h6 class="lb_ftp text-uppercase m-b-15 m-t-10">FTP 蜜罐</h6>
|
||||
<h2 class="m-b-10"><span data-plugin="counterup">{{.ftpSum}}</span></h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-12 col-md-6 col-lg-6 col-xl-4">
|
||||
<div class="card-box tilebox-two">
|
||||
<i class="icon-plane pull-xs-right text-muted"></i>
|
||||
<h6 class="lb_telnet text-uppercase m-b-15 m-t-10">TELNET 蜜罐</h6>
|
||||
<h2 class="m-b-10"><span data-plugin="counterup">{{.telnetSum}}</span></h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-12 col-md-6 col-lg-6 col-xl-4">
|
||||
<div class="card-box tilebox-two">
|
||||
<i class="icon-fire pull-xs-right text-muted"></i>
|
||||
<h6 class="lb_web text-uppercase m-b-15 m-t-10">WEB 蜜罐</h6>
|
||||
<h2 class="m-b-10"><span data-plugin="counterup">{{.webSum}}</span></h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-12 col-md-6 col-lg-6 col-xl-4">
|
||||
<div class="card-box tilebox-two">
|
||||
<i class="icon-globe pull-xs-right text-muted"></i>
|
||||
<h6 class="lb_deep text-uppercase m-b-15 m-t-10">DEEP 蜜罐</h6>
|
||||
<h2 class="m-b-10"><span data-plugin="counterup">{{.deepSum}}</span></h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-md-2">
|
||||
<div class="card-box" style="height: 340px;">
|
||||
<div class="card-box" style="height: 395px;">
|
||||
<p class="title">服务状态</p>
|
||||
<p><span class="openx"></span>ADMIN</p>
|
||||
|
||||
@ -82,6 +137,12 @@
|
||||
<p><span class="closex"></span>WEB</p>
|
||||
{{end}}
|
||||
|
||||
{{if eq .deepStatus "1"}}
|
||||
<p><span class="openx"></span>DEEP</p>
|
||||
{{else}}
|
||||
<p><span class="closex"></span>DEEP</p>
|
||||
{{end}}
|
||||
|
||||
{{if eq .sshStatus "1"}}
|
||||
<p><span class="openx"></span>SSH</p>
|
||||
{{else}}
|
||||
@ -99,18 +160,46 @@
|
||||
{{else}}
|
||||
<p><span class="closex"></span>MYSQL</p>
|
||||
{{end}}
|
||||
|
||||
{{if eq .telnetStatus "1"}}
|
||||
<p><span class="openx"></span>TELNET</p>
|
||||
{{else}}
|
||||
<p><span class="closex"></span>TELNET</p>
|
||||
{{end}}
|
||||
|
||||
{{if eq .ftpStatus "1"}}
|
||||
<p><span class="openx"></span>FTP</p>
|
||||
{{else}}
|
||||
<p><span class="closex"></span>FTP</p>
|
||||
{{end}}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-10">
|
||||
<div class="card-box">
|
||||
<div id="myChart" style="width:100%;height:300px;"></div>
|
||||
<div id="myChart" style="width:100%;height:355px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{template "footer" }}
|
||||
<script src="/static/libs/echarts/echarts.js"></script>
|
||||
<script src="/static/libs/moment/moment.min.js"></script>
|
||||
<script>
|
||||
var m = moment(new Date());
|
||||
|
||||
var arr = new Array();
|
||||
|
||||
for (var i = 0; i < 24; i++) {
|
||||
var h = m.subtract(1, 'h').hours();
|
||||
if (h >= 0 && h < 10) {
|
||||
arr.push("0" + h.toString())
|
||||
} else {
|
||||
arr.push(h.toString())
|
||||
}
|
||||
}
|
||||
|
||||
var xdata = arr.reverse();
|
||||
|
||||
var webData = [
|
||||
0,
|
||||
@ -220,6 +309,87 @@
|
||||
0
|
||||
];
|
||||
|
||||
var deepData = [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
];
|
||||
|
||||
var ftpData = [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
];
|
||||
|
||||
var telnetData = [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
];
|
||||
|
||||
var myChart = echarts.init(document.getElementById('myChart'));
|
||||
|
||||
$.ajax({
|
||||
@ -231,24 +401,43 @@
|
||||
var d = e.data;
|
||||
|
||||
for (var item in d.mysql) {
|
||||
mysqlData[parseInt(item)] = d.mysql[item];
|
||||
var index = xdata.indexOf(item);
|
||||
mysqlData[index] = d.mysql[item];
|
||||
}
|
||||
|
||||
for (var item in d.redis) {
|
||||
redisData[parseInt(item)] = d.redis[item];
|
||||
var index = xdata.indexOf(item);
|
||||
redisData[index] = d.redis[item];
|
||||
}
|
||||
|
||||
for (var item in d.ssh) {
|
||||
sshData[parseInt(item)] = d.ssh[item];
|
||||
var index = xdata.indexOf(item);
|
||||
sshData[index] = d.ssh[item];
|
||||
}
|
||||
|
||||
for (var item in d.web) {
|
||||
webData[parseInt(item)] = d.web[item];
|
||||
var index = xdata.indexOf(item);
|
||||
webData[index] = d.web[item];
|
||||
}
|
||||
|
||||
for (var item in d.deep) {
|
||||
var index = xdata.indexOf(item);
|
||||
deepData[index] = d.deep[item];
|
||||
}
|
||||
|
||||
for (var item in d.ftp) {
|
||||
var index = xdata.indexOf(item);
|
||||
ftpData[index] = d.ftp[item];
|
||||
}
|
||||
|
||||
for (var item in d.telnet) {
|
||||
var index = xdata.indexOf(item);
|
||||
telnetData[index] = d.telnet[item];
|
||||
}
|
||||
|
||||
var option = {
|
||||
title: {
|
||||
text: '今日数据'
|
||||
text: '最近24小时'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
@ -260,11 +449,11 @@
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
data: ['WEB', 'SSH', 'REDIS', 'MYSQL']
|
||||
data: ['WEB', 'DEEP', 'SSH', 'REDIS', 'MYSQL', 'TELNET', 'FTP']
|
||||
},
|
||||
grid: {
|
||||
left: '0%',
|
||||
right: '0%',
|
||||
right: '2%',
|
||||
bottom: '3%',
|
||||
containLabel: true
|
||||
},
|
||||
@ -272,31 +461,7 @@
|
||||
{
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: [
|
||||
'00',
|
||||
'01',
|
||||
'02',
|
||||
'03',
|
||||
'04',
|
||||
'05',
|
||||
'06',
|
||||
'07',
|
||||
'08',
|
||||
'09',
|
||||
'10',
|
||||
'12',
|
||||
'13',
|
||||
'14',
|
||||
'15',
|
||||
'16',
|
||||
'17',
|
||||
'18',
|
||||
'19',
|
||||
'20',
|
||||
'21',
|
||||
'22',
|
||||
'23'
|
||||
]
|
||||
data: xdata
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
@ -312,6 +477,13 @@
|
||||
areaStyle: {normal: {}},
|
||||
data: webData
|
||||
},
|
||||
{
|
||||
name: 'DEEP',
|
||||
type: 'line',
|
||||
stack: '总量',
|
||||
areaStyle: {normal: {}},
|
||||
data: deepData
|
||||
},
|
||||
{
|
||||
name: 'SSH',
|
||||
type: 'line',
|
||||
@ -332,6 +504,20 @@
|
||||
stack: '总量',
|
||||
areaStyle: {normal: {}},
|
||||
data: mysqlData
|
||||
},
|
||||
{
|
||||
name: 'TELNET',
|
||||
type: 'line',
|
||||
stack: '总量',
|
||||
areaStyle: {normal: {}},
|
||||
data: telnetData
|
||||
},
|
||||
{
|
||||
name: 'FTP',
|
||||
type: 'line',
|
||||
stack: '总量',
|
||||
areaStyle: {normal: {}},
|
||||
data: ftpData
|
||||
}
|
||||
]
|
||||
};
|
||||
|
152
admin/fish.html
@ -39,7 +39,7 @@
|
||||
}
|
||||
|
||||
.lb_ssh {
|
||||
background-color: #434556;
|
||||
background-color: #953fb9;
|
||||
}
|
||||
|
||||
.lb_redis {
|
||||
@ -50,6 +50,18 @@
|
||||
background-color: #cabd23;
|
||||
}
|
||||
|
||||
.lb_ftp {
|
||||
background-color: #32cc16;
|
||||
}
|
||||
|
||||
.lb_telnet {
|
||||
background-color: #6b79c1;
|
||||
}
|
||||
|
||||
.lb_deep {
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
.pre {
|
||||
background: #2c2c31;
|
||||
color: #4fe21b;
|
||||
@ -77,6 +89,27 @@
|
||||
.modal-header .close {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
.sos {
|
||||
margin-bottom: 10px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.agent_name {
|
||||
border: 1px solid #167bcc;
|
||||
padding: 2px 5px;
|
||||
border-radius: 5px;
|
||||
color: #167bcc;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.ipinfo{
|
||||
border: 1px solid #434e56;
|
||||
padding: 2px 5px;
|
||||
border-radius: 5px;
|
||||
color: #434e56;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
@ -88,21 +121,47 @@
|
||||
<h4 class="page-title">上钩列表</h4>
|
||||
</div>
|
||||
|
||||
<div class="sos">
|
||||
<div class="col-sm-2">
|
||||
<select class="form-control" id="selectType" style="height: 34px;" onchange="so()"></select>
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-search"></i></span>
|
||||
<input type="text" class="form-control" id="so_text"
|
||||
placeholder="请输入搜索内容" oninput="so()">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="card-box table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="10%">项目</th>
|
||||
<th width="10%">集群名称</th>
|
||||
<th width="10%">来源 IP</th>
|
||||
<th width="10%">地理信息</th>
|
||||
<th width="10%">信息</th>
|
||||
<th width="10%">上钩时间</th>
|
||||
<th width="1%">操作</th>
|
||||
<th width="8%">上钩时间</th>
|
||||
<th width="2%">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="tableList"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div style="text-align: center;" class="dataTables_paginate paging_simple_numbers">
|
||||
<ul class="pagination" id="pages">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -167,13 +226,43 @@
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
function init_type() {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: "/get/fish/typeList",
|
||||
dataType: "json",
|
||||
success: function (e) {
|
||||
if (e.code == 200) {
|
||||
var data = e.data;
|
||||
var _h = '<option value="all">请选择类型</option>';
|
||||
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
_h += '<option value="' + data[i].type + '">' + data[i].type + '</option>';
|
||||
}
|
||||
|
||||
$("#selectType").html(_h);
|
||||
|
||||
} else {
|
||||
|
||||
}
|
||||
},
|
||||
error: function (e) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function init(page, type, so_text) {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: "/get/fish/list",
|
||||
dataType: "json",
|
||||
data: {
|
||||
"page": page,
|
||||
"pageSize": 10,
|
||||
"type": type,
|
||||
"so_text": so_text
|
||||
},
|
||||
success: function (e) {
|
||||
if (e.code == 200) {
|
||||
var data = e.data;
|
||||
var _h = '';
|
||||
|
||||
@ -188,11 +277,19 @@
|
||||
_h += ' <span class="label lb_redis">REDIS</span> ';
|
||||
} else if (data[i].type == "MYSQL") {
|
||||
_h += ' <span class="label lb_mysql">MYSQL</span> ';
|
||||
} else if (data[i].type == "FTP") {
|
||||
_h += ' <span class="label lb_ftp">FTP</span> ';
|
||||
} else if (data[i].type == "TELNET") {
|
||||
_h += ' <span class="label lb_telnet">TELNET</span> ';
|
||||
} else if (data[i].type == "DEEP") {
|
||||
_h += ' <span class="label lb_deep">DEEP</span> ';
|
||||
}
|
||||
|
||||
_h += ' <span class="project">' + data[i].project_name + '</span>';
|
||||
_h += ' </td>';
|
||||
_h += ' <td class="td">' + data[i].ip + '</td>';
|
||||
_h += ' <td class="td"><span class="agent_name">' + data[i].agent + '</span></td>';
|
||||
_h += ' <td class="td" style="font-weight: bold;">' + data[i].ip + '</td>';
|
||||
_h += ' <td class="td"><span class="ipinfo">' + data[i].ip_info + '</span></td>';
|
||||
_h += ' <td><span class="info" onclick="show(' + data[i].id + ')">点击查看</span></td>';
|
||||
_h += ' <td class="td">' + data[i].create_time + '</td>';
|
||||
_h += ' <td class="td" style="text-align: center;">';
|
||||
@ -201,10 +298,30 @@
|
||||
_h += '</tr>';
|
||||
}
|
||||
|
||||
$("#tableList").html(_h);
|
||||
} else {
|
||||
|
||||
if (_h == "") {
|
||||
_h = '<tr style="text-align: center;"><td style="line-height: 200px;font-size: 20px;color: #a9a9a9;" colspan="5">暂无数据</td></tr>'
|
||||
}
|
||||
|
||||
$("#tableList").html(_h);
|
||||
|
||||
var pageCount = e.pageCount;
|
||||
var page = e.page;
|
||||
var _hx = '';
|
||||
|
||||
for (var i = 0; i < parseInt(pageCount); i++) {
|
||||
if (page == (i + 1)) {
|
||||
_hx += '<li class="paginate_button page-item active">';
|
||||
_hx += ' <a onclick="page(' + (i + 1) + ')" style="cursor: pointer;" aria-controls="datatable" data-dt-idx="' + (i + 1) + '" tabindex="0" class="page-link">' + (i + 1) + '</a>';
|
||||
_hx += '</li>';
|
||||
} else {
|
||||
_hx += '<li class="paginate_button page-item">';
|
||||
_hx += ' <a onclick="page(' + (i + 1) + ')" style="cursor: pointer;" aria-controls="datatable" data-dt-idx="' + (i + 1) + '" tabindex="0" class="page-link">' + (i + 1) + '</a>';
|
||||
_hx += '</li>';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$("#pages").html(_hx);
|
||||
},
|
||||
error: function (e) {
|
||||
}
|
||||
@ -212,5 +329,20 @@
|
||||
}
|
||||
|
||||
|
||||
init();
|
||||
init(1, "all", "");
|
||||
init_type();
|
||||
|
||||
function so() {
|
||||
var selectType = $("#selectType").val();
|
||||
var so_text = $("#so_text").val();
|
||||
|
||||
init(1, selectType, so_text);
|
||||
}
|
||||
|
||||
function page(p) {
|
||||
var selectType = $("#selectType").val();
|
||||
var so_text = $("#so_text").val();
|
||||
|
||||
init(p, selectType, so_text);
|
||||
}
|
||||
</script>
|
@ -25,10 +25,16 @@
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade in active" id="home-2">
|
||||
<div class="timeline-2">
|
||||
<div class="time-item">
|
||||
<div class="item-info">
|
||||
<small class="text-muted">2019-08-12</small>
|
||||
<p>发布 v0.2 版本</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="time-item">
|
||||
<div class="item-info">
|
||||
<small class="text-muted">2019-08-05</small>
|
||||
<p>发布第一版</p>
|
||||
<p>发布 v0.1 版本</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="time-item">
|
||||
@ -49,6 +55,8 @@
|
||||
<script src="/static/js/bootstrap.min.js"></script>
|
||||
<script src="/static/libs/bootstrap-sweetalert/sweet-alert.min.js"></script>
|
||||
<script src="/static/libs/switchery/switchery.min.js"></script>
|
||||
<script src="/static/libs/waypoints/lib/jquery.waypoints.js"></script>
|
||||
<script src="/static/libs/counterup/jquery.counterup.min.js"></script>
|
||||
|
||||
<!-- App js -->
|
||||
<script src="/static/js/jquery.core.js"></script>
|
||||
|
@ -6,7 +6,7 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="shortcut icon" href="/static/favicon.ico">
|
||||
<title>HFish - 一款企业安全主动攻击型蜜罐钓鱼框架系统</title>
|
||||
<title>HFish - 扩展企业安全测试主动诱导型蜜罐框架系统</title>
|
||||
<link href="/static/css/style.css" rel="stylesheet" type="text/css"/>
|
||||
<link href="/static/libs/bootstrap-sweetalert/sweet-alert.css" rel="stylesheet" type="text/css"/>
|
||||
<link href="/static/libs/switchery/switchery.min.css" rel="stylesheet"/>
|
||||
@ -35,7 +35,10 @@
|
||||
<a href="/fish"><i class="fa fa-user-secret "></i> <span> 上钩列表 </span> </a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/mail"><i class="zmdi zmdi-email icotop3"></i> <span> 邮箱群发 </span> </a>
|
||||
<a href="/colony"><i class="fa fa-cloud"></i> <span> 分布式集群 </span> </a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/mail"><i class="zmdi zmdi-email icotop3"></i> <span> 邮件群发 </span> </a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/setting"><i class="fa fa-cog icotop3"></i> <span> 系统设置 </span> </a>
|
||||
@ -53,7 +56,7 @@
|
||||
<a class="nav-link dropdown-toggle arrow-none waves-effect waves-light nav-user"
|
||||
data-toggle="dropdown" href="#" role="button"
|
||||
aria-haspopup="false" aria-expanded="false">
|
||||
<img src="/static/images/avatar.png" alt="user" class="img-circle">
|
||||
<img src="/static/images/avatar.png" alt="user" class="img-circle avatarx">
|
||||
</a>
|
||||
<div class="dropdown-menu dropdown-menu-right dropdown-arrow profile-dropdown "
|
||||
aria-labelledby="Preview">
|
||||
|
@ -4,7 +4,7 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="shortcut icon" href="/static/favicon.ico">
|
||||
<title>HFish - 一款企业安全主动攻击型蜜罐钓鱼框架系统</title>
|
||||
<title>HFish - 扩展企业安全测试主动诱导型蜜罐框架系统</title>
|
||||
<link href="/static/css/style.css" rel="stylesheet" type="text/css"/>
|
||||
<link href="/static/libs/bootstrap-sweetalert/sweet-alert.css" rel="stylesheet" type="text/css"/>
|
||||
<style>
|
||||
|
@ -1,4 +1,5 @@
|
||||
{{template "header"}}
|
||||
<script type="text/javascript" src="/static/libs/wangeditor3/wangEditor.css"></script>
|
||||
<style>
|
||||
.card-box {
|
||||
padding: 0px;
|
||||
@ -39,6 +40,7 @@
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<div class="row">
|
||||
<!-- Page-Title -->
|
||||
<div class="col-sm-12">
|
||||
@ -71,14 +73,20 @@
|
||||
<label for="taskExplain" class="col-sm-3 form-control-label"><span class="text-danger">*</span>
|
||||
收件人:</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" id="emails" placeholder="请输入收件人 例:xxx@gmail.com,xxx@outlook.com">
|
||||
<input type="text" class="form-control" id="emails"
|
||||
placeholder="请输入收件人 例:xxx@gmail.com,xxx@outlook.com">
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<h4 class="header-title m-t-0 m-b-30 title">邮件模板</h4>
|
||||
|
||||
</div>
|
||||
<textarea style="width: 100%;margin-top: 20px;height: 200px;" id="content"></textarea>
|
||||
<div class="clear"></div>
|
||||
|
||||
<div id="editorx">
|
||||
<p>请在此处输入发送的邮件内容</p>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn btn-primary waves-effect waves-light"
|
||||
style="width: 100%;margin-top: 20px;" onclick="sendMailToUsers()">发送邮件
|
||||
</button>
|
||||
@ -87,21 +95,29 @@
|
||||
</div>
|
||||
</div>
|
||||
{{template "footer" }}
|
||||
<script type="text/javascript" src="/static/libs/wangeditor3/wangEditor.js"></script>
|
||||
<script>
|
||||
var sendMailToUsers=function () {
|
||||
|
||||
var E = window.wangEditor;
|
||||
var editorx = new E('#editorx');
|
||||
editorx.create();
|
||||
|
||||
var sendMailToUsers = function () {
|
||||
var title = $("#title").val();
|
||||
var from = $("#from").val();
|
||||
var emails= $("#emails").val();
|
||||
var content=$("#content").val();
|
||||
var emails = $("#emails").val();
|
||||
|
||||
var content = editorx.txt.html();
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/post/mail/sendEmail",
|
||||
dataType: "json",
|
||||
data: {
|
||||
title,
|
||||
from,
|
||||
emails,
|
||||
content
|
||||
"title": title,
|
||||
"from": from,
|
||||
"emails": emails,
|
||||
"content": content
|
||||
},
|
||||
success: function (e) {
|
||||
if (e.code == 200) {
|
||||
|
@ -17,6 +17,26 @@
|
||||
color: #ff5d48 !important;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.label {
|
||||
line-height: 11px;
|
||||
}
|
||||
|
||||
.yes_config {
|
||||
border: 1px solid #0f8ce6;
|
||||
padding: 2px 5px;
|
||||
border-radius: 5px;
|
||||
color: #0f8ce6;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.no_config {
|
||||
border: 1px solid #434e56;
|
||||
padding: 2px 5px;
|
||||
border-radius: 5px;
|
||||
color: #434e56;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
<div class="row">
|
||||
<!-- Page-Title -->
|
||||
@ -40,14 +60,14 @@
|
||||
</thead>
|
||||
<tbody id="tableList">
|
||||
{{range $i, $e := .dataList}}
|
||||
<tr>
|
||||
<td>{{$e.setting_name}} </td>
|
||||
<tr id="tr{{$e.type}}" data-id="{{$e.id}}">
|
||||
<td style="font-weight: bold;">{{$e.setting_name}} </td>
|
||||
<td>{{$e.setting_dis}}</td>
|
||||
<td>{{$e.update_time}}</td>
|
||||
<td>{{if ne $e.info ""}}
|
||||
已配置
|
||||
<span class="yes_config">已配置</span>
|
||||
{{else}}
|
||||
未配置
|
||||
<span class="no_config">未配置</span>
|
||||
{{end}}
|
||||
</td>
|
||||
<td><input type="checkbox" id="checkbox-{{$e.id}}" data-plugin="switchery"
|
||||
@ -89,7 +109,7 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for=""><span class="text-danger">*</span>密码</label>
|
||||
<input type="text" class="form-control" id="pass" name="pass" placeholder="请填写发送人密码">
|
||||
<input type="password" class="form-control" id="pass" name="pass" placeholder="请填写发送人密码">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for=""><span class="text-danger">*</span>SMTP</label>
|
||||
@ -106,12 +126,53 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Modal -->
|
||||
<div class="modal fade" id="receiveEmailModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h4 class="modal-title" id="myModalLabel">E-mail 通知设置</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for=""><span class="text-danger">*</span>发送人(账号)</label>
|
||||
<input type="email" class="form-control" id="alertEmail" name="alertEmail" placeholder="请填写发送人账号">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for=""><span class="text-danger">*</span>密码</label>
|
||||
<input type="password" class="form-control" id="alertPass" name="alertPass" placeholder="请填写发送人密码">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for=""><span class="text-danger">*</span>SMTP</label>
|
||||
<input type="text" class="form-control" id="alertHost" name="alertHost"
|
||||
placeholder="例:smtp.126.com">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for=""><span class="text-danger">*</span>端口</label>
|
||||
<input type="number" class="form-control" id="alertPort" name="alertPort" placeholder="例:465">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for=""><span class="text-danger">*</span>接收人</label>
|
||||
<textarea class="form-control" id="receive" name="receive" placeholder="接收人(群发请换行)" cols="30"
|
||||
rows="10"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
||||
<button type="button" class="btn btn-primary" onclick="syncStmpConfig()">同步STMP配置</button>
|
||||
<button type="submit" class="btn btn-primary" onclick="updateReceiveEmailPost()">保存</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" value="" id="settingId">
|
||||
{{template "footer" }}
|
||||
<script>
|
||||
function settingSubFunc(id, type) {
|
||||
$("#settingId").val(id)
|
||||
getSettingInfo(id, type)
|
||||
getSettingInfo(id)
|
||||
switch (type) {
|
||||
case "mail":
|
||||
$('#myModal').modal('show')
|
||||
@ -119,12 +180,24 @@
|
||||
case "login":
|
||||
$('#settingLoginModal').modal('show')
|
||||
break
|
||||
case "alertMail":
|
||||
$('#receiveEmailModal').modal('show')
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var getSettingInfo = function (id, type) {
|
||||
var syncStmpConfig = function () {
|
||||
var id = $("#trmail ").data("id")
|
||||
getSettingInfo(id,function () {
|
||||
$("#alertEmail").val($("#email").val())
|
||||
$("#alertPass").val($("#pass").val())
|
||||
$("#alertHost").val($("#host").val())
|
||||
$("#alertPort").val($("#port").val())
|
||||
})
|
||||
}
|
||||
var getSettingInfo = function (id,cb) {
|
||||
$.ajax({
|
||||
type: "get",
|
||||
url: "/get/setting/info?id=" + id
|
||||
@ -139,6 +212,17 @@
|
||||
} else if (arr.length == 2 && result.data[0].type == "login") {
|
||||
$("#loginName").val(arr[0])
|
||||
$("#loginPwd").val(arr[1])
|
||||
} else if (arr.length >= 4 && result.data[0].type == "alertMail") {
|
||||
$("#alertEmail").val(arr[2])
|
||||
$("#alertPass").val(arr[3])
|
||||
$("#alertHost").val(arr[0])
|
||||
$("#alertPort").val(arr[1])
|
||||
var emailArr = arr.splice(4)
|
||||
var content = emailArr.join("\n")
|
||||
$("#receive").text(content)
|
||||
}
|
||||
if(typeof cb=="function"){
|
||||
cb()
|
||||
}
|
||||
}
|
||||
}).fail(function (err) {
|
||||
@ -172,6 +256,34 @@
|
||||
})
|
||||
}
|
||||
|
||||
function updateReceiveEmailPost() {
|
||||
|
||||
var params = {
|
||||
email: $("#alertEmail").val(),
|
||||
pass: $("#alertPass").val(),
|
||||
host: $("#alertHost").val(),
|
||||
port: $("#alertPort").val(),
|
||||
receive: $("#receive").val().split(/[\s\n]/).join(","),
|
||||
id: $("#settingId").val()
|
||||
};
|
||||
console.log(params)
|
||||
$.ajax({
|
||||
type: "post",
|
||||
url: "/post/setting/updateAlertMail",
|
||||
data: params
|
||||
}).success(function (result) {
|
||||
if (result.code == 200) {
|
||||
if (!$("#checkbox-" + params.id).prop("checked")) {
|
||||
$("#checkbox-" + params.id).click()
|
||||
}
|
||||
}
|
||||
$('#receiveEmailModal').modal('hide')
|
||||
|
||||
}).fail(function (err) {
|
||||
console.log(err)
|
||||
})
|
||||
}
|
||||
|
||||
function updateStatusFunc(id, ele) {
|
||||
var params = {
|
||||
"id": id,
|
||||
|
31
config.ini
@ -1,20 +1,35 @@
|
||||
[admin]
|
||||
[rpc]
|
||||
status = 0 # 模式 0关闭 1服务端 2客户端
|
||||
addr = 127.0.0.1:7879 # RPC 服务端地址 or 客户端地址
|
||||
name = Server # 状态1 服务端 名称 状态2 客户端 名称
|
||||
|
||||
[admin] # RPC 状态为2 集群客户端的时候 admin 可以删掉
|
||||
addr = 127.0.0.1:9001 # 管理后台启动地址
|
||||
account = admin # 登录账号
|
||||
password = admin # 登录密码
|
||||
|
||||
[api]
|
||||
status = 1 # 是否启动 API 1 启动 0 关闭
|
||||
url = /api/v1/post/report # 管理后台启动地址
|
||||
web_url = /api/v1/post/report # 管理后台启动地址
|
||||
deep_url = /api/v1/post/deep_report # 管理后台启动地址
|
||||
sec_key = 9cbf8a4dcb8e30682b927f352d6559a0 # API 认证秘钥
|
||||
|
||||
[web]
|
||||
status = 0 # 是否启动 WEB 1 启动 0 关闭, 启动 API 后 WEB 方可上报结果
|
||||
addr = 0.0.0.0:9000 # WEB 启动地址,0.0.0.0 对外开放,127.0.0.1 对内开放 可走 Nginx 反向代理
|
||||
template = github/html # WEB 模板路径
|
||||
static = github/static # WEB 静态文件路径 注意:必须存在两个目录,html 文件 和静态文件 不能平级
|
||||
template = wordPress/html # WEB 模板路径
|
||||
index = index.html # WEB 首页文件
|
||||
static = wordPress/static # WEB 静态文件路径 注意:必须存在两个目录,html 文件 和静态文件 不能平级
|
||||
url = / # WEB 访问目录,默认 / 可更改成 index.html index.asp index.php
|
||||
|
||||
[deep]
|
||||
status = 0 # 是否启动 暗网 1 启动 0 关闭, 启动 API 后 方可上报结果
|
||||
addr = 0.0.0.0:9002 # 暗网 WEB 启动地址
|
||||
template = deep/html # 暗网 WEB 模板路径
|
||||
index = index.html # 暗网 WEB 首页文件
|
||||
static = deep/static # 暗网 WEB 静态文件路径 注意:必须存在两个目录,html 文件 和静态文件 不能平级
|
||||
url = / # 暗网 WEB 访问目录,默认 / 可更改成 index.html index.asp index.php
|
||||
|
||||
[ssh]
|
||||
status = 0 # 是否启动 SSH 1 启动 0 关闭
|
||||
addr = 0.0.0.0:22 # SSH 服务端地址 注意端口冲突,请先关闭服务器 openssh 服务 或 修改端口
|
||||
@ -27,3 +42,11 @@ addr = 0.0.0.0:6379 # Redis 服务端地址 注意端
|
||||
status = 0 # 是否启动 Mysql 1 启动 0 关闭
|
||||
addr = 0.0.0.0:3306 # Mysql 服务端地址 注意端口冲突
|
||||
files = /etc/passwd,/etc/group # Mysql 服务端读取客户端任意文件; 多写逗号分隔,会随机取
|
||||
|
||||
[telnet]
|
||||
status = 0 # 是否启动 Telnet 1 启动 0 关闭
|
||||
addr = 0.0.0.0:23 # Telnet 服务端地址 注意端口冲突
|
||||
|
||||
[ftp]
|
||||
status = 0 # 是否启动 Ftp 1 启动 0 关闭
|
||||
addr = 0.0.0.0:21 # Ftp 服务端地址 注意端口冲突
|
@ -4,6 +4,7 @@ import (
|
||||
"database/sql"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"HFish/error"
|
||||
"HFish/utils/try"
|
||||
)
|
||||
|
||||
// 连接数据库
|
||||
@ -45,17 +46,20 @@ func Insert(sql string, args ...interface{}) int64 {
|
||||
db := conn()
|
||||
stmt, _ := db.Prepare(sql)
|
||||
|
||||
res, err := stmt.Exec(args...)
|
||||
error.Check(err, "插入数据失败")
|
||||
var id int64
|
||||
id = 0
|
||||
|
||||
try.Try(func() {
|
||||
res, _ := stmt.Exec(args...)
|
||||
//error.Check(err, "插入数据失败")
|
||||
|
||||
defer stmt.Close()
|
||||
|
||||
id, err := res.LastInsertId()
|
||||
error.Check(err, "获取插入ID失败")
|
||||
id, _ = res.LastInsertId()
|
||||
|
||||
defer db.Close()
|
||||
}).Catch(func() {})
|
||||
|
||||
// 返回 自增长 ID
|
||||
return id
|
||||
}
|
||||
|
||||
|
106
core/protocol/ftp/ftp.go
Normal file
@ -0,0 +1,106 @@
|
||||
package ftp
|
||||
|
||||
import (
|
||||
"io"
|
||||
"fmt"
|
||||
"time"
|
||||
"os"
|
||||
"strings"
|
||||
"strconv"
|
||||
"HFish/core/protocol/ftp/graval"
|
||||
)
|
||||
|
||||
const (
|
||||
fileOne = "This is the first file available for download.\n\nBy Jàmes"
|
||||
fileTwo = "This is file number two.\n\n2012-12-04"
|
||||
)
|
||||
|
||||
type MemDriver struct{}
|
||||
|
||||
func (driver *MemDriver) Authenticate(user string, pass string) bool {
|
||||
//return user == "test" && pass == "1234"
|
||||
return false
|
||||
}
|
||||
|
||||
func (driver *MemDriver) Bytes(path string) (bytes int) {
|
||||
switch path {
|
||||
case "/one.txt":
|
||||
bytes = len(fileOne)
|
||||
break
|
||||
case "/files/two.txt":
|
||||
bytes = len(fileTwo)
|
||||
break
|
||||
default:
|
||||
bytes = -1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (driver *MemDriver) ModifiedTime(path string) (time.Time, error) {
|
||||
return time.Now(), nil
|
||||
}
|
||||
|
||||
func (driver *MemDriver) ChangeDir(path string) bool {
|
||||
return path == "/" || path == "/files"
|
||||
}
|
||||
|
||||
func (driver *MemDriver) DirContents(path string) (files []os.FileInfo) {
|
||||
files = []os.FileInfo{}
|
||||
switch path {
|
||||
case "/":
|
||||
files = append(files, graval.NewDirItem("files"))
|
||||
files = append(files, graval.NewFileItem("one.txt", len(fileOne)))
|
||||
case "/files":
|
||||
files = append(files, graval.NewFileItem("two.txt", len(fileOne)))
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
func (driver *MemDriver) DeleteDir(path string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (driver *MemDriver) DeleteFile(path string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (driver *MemDriver) Rename(fromPath string, toPath string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (driver *MemDriver) MakeDir(path string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (driver *MemDriver) GetFile(path string) (data string, err error) {
|
||||
switch path {
|
||||
case "/one.txt":
|
||||
data = fileOne
|
||||
case "/files/two.txt":
|
||||
data = fileTwo
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (driver *MemDriver) PutFile(destPath string, data io.Reader) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type MemDriverFactory struct{}
|
||||
|
||||
func (factory *MemDriverFactory) NewDriver() (graval.FTPDriver, error) {
|
||||
return &MemDriver{}, nil
|
||||
}
|
||||
|
||||
func Start(addr string) {
|
||||
arr := strings.Split(addr, ":")
|
||||
port, _ := strconv.Atoi(arr[1])
|
||||
|
||||
factory := &MemDriverFactory{}
|
||||
ftpServer := graval.NewFTPServer(&graval.FTPServerOpts{Factory: factory, Hostname: arr[0], Port: port})
|
||||
|
||||
err := ftpServer.ListenAndServe()
|
||||
if err != nil {
|
||||
fmt.Print(err)
|
||||
}
|
||||
}
|
20
core/protocol/ftp/graval/MIT-LICENSE
Normal file
@ -0,0 +1,20 @@
|
||||
Copyright (c) 2008 James Healy
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
93
core/protocol/ftp/graval/README.markdown
Normal file
@ -0,0 +1,93 @@
|
||||
# graval
|
||||
|
||||
An experimental FTP server framework. By providing a simple driver class that
|
||||
responds to a handful of methods you can have a complete FTP server.
|
||||
|
||||
Some sample use cases include persisting data to:
|
||||
|
||||
* an Amazon S3 bucket
|
||||
* a relational database
|
||||
* redis
|
||||
* memory
|
||||
|
||||
There is a sample in-memory driver available - see the usage instructions below
|
||||
for the steps to use it.
|
||||
|
||||
Full documentation for the package is available on [godoc](http://godoc.org/github.com/yob/graval)
|
||||
|
||||
## Installation
|
||||
|
||||
go get github.com/yob/graval
|
||||
|
||||
## Usage
|
||||
|
||||
To boot an FTP server you will need to provide a driver that speaks to your
|
||||
persistence layer - the required driver contract is listed below.
|
||||
|
||||
There is a sample in-memory driver available as a demo. You can build it with
|
||||
this command:
|
||||
|
||||
go install github.com/yob/graval/graval-mem
|
||||
|
||||
Then run it:
|
||||
|
||||
./bin/graval-mem
|
||||
|
||||
And finally, connect to the server with any FTP client and the following
|
||||
details:
|
||||
|
||||
host: 127.0.0.1
|
||||
username: test
|
||||
password: 1234
|
||||
|
||||
### The Driver Contract
|
||||
|
||||
Your driver MUST implement a number of simple methods. You can view the required
|
||||
contract in the package docs on [godoc](http://godoc.org/github.com/yob/graval)
|
||||
|
||||
## Contributors
|
||||
|
||||
* James Healy <james@yob.id.au> [http://www.yob.id.au](http://www.yob.id.au)
|
||||
|
||||
## Warning
|
||||
|
||||
FTP is an incredibly insecure protocol. Be careful about forcing users to authenticate
|
||||
with a username or password that are important.
|
||||
|
||||
## License
|
||||
|
||||
This library is distributed under the terms of the MIT License. See the included file for
|
||||
more detail.
|
||||
|
||||
## Contributing
|
||||
|
||||
All suggestions and patches welcome, preferably via a git repository I can pull from.
|
||||
If this library proves useful to you, please let me know.
|
||||
|
||||
## Further Reading
|
||||
|
||||
There are a range of RFCs that together specify the FTP protocol. In chronological
|
||||
order, the more useful ones are:
|
||||
|
||||
* [http://tools.ietf.org/rfc/rfc959.txt](http://tools.ietf.org/rfc/rfc959.txt)
|
||||
* [http://tools.ietf.org/rfc/rfc1123.txt](http://tools.ietf.org/rfc/rfc1123.txt)
|
||||
* [http://tools.ietf.org/rfc/rfc2228.txt](http://tools.ietf.org/rfc/rfc2228.txt)
|
||||
* [http://tools.ietf.org/rfc/rfc2389.txt](http://tools.ietf.org/rfc/rfc2389.txt)
|
||||
* [http://tools.ietf.org/rfc/rfc2428.txt](http://tools.ietf.org/rfc/rfc2428.txt)
|
||||
* [http://tools.ietf.org/rfc/rfc3659.txt](http://tools.ietf.org/rfc/rfc3659.txt)
|
||||
* [http://tools.ietf.org/rfc/rfc4217.txt](http://tools.ietf.org/rfc/rfc4217.txt)
|
||||
|
||||
For an english summary that's somewhat more legible than the RFCs, and provides
|
||||
some commentary on what features are actually useful or relevant 24 years after
|
||||
RFC959 was published:
|
||||
|
||||
* [http://cr.yp.to/ftp.html](http://cr.yp.to/ftp.html)
|
||||
|
||||
For a history lesson, check out Appendix III of RCF959. It lists the preceding
|
||||
(obsolete) RFC documents that relate to file transfers, including the ye old
|
||||
RFC114 from 1971, "A File Transfer Protocol"
|
||||
|
||||
This library is heavily based on [em-ftpd](https://github.com/yob/em-ftpd), an FTPd
|
||||
framework with similar design goals within the ruby and EventMachine ecosystems. It
|
||||
worked well enough, but you know, callbacks and event loops make me something
|
||||
something.
|
672
core/protocol/ftp/graval/commands.go
Normal file
@ -0,0 +1,672 @@
|
||||
package graval
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/jehiah/go-strftime"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"HFish/core/report"
|
||||
"HFish/utils/is"
|
||||
"HFish/core/rpc/client"
|
||||
)
|
||||
|
||||
type ftpCommand interface {
|
||||
RequireParam() bool
|
||||
RequireAuth() bool
|
||||
Execute(*ftpConn, string)
|
||||
}
|
||||
|
||||
type commandMap map[string]ftpCommand
|
||||
|
||||
var (
|
||||
commands = commandMap{
|
||||
"ALLO": commandAllo{},
|
||||
"CDUP": commandCdup{},
|
||||
"CWD": commandCwd{},
|
||||
"DELE": commandDele{},
|
||||
"EPRT": commandEprt{},
|
||||
"EPSV": commandEpsv{},
|
||||
"LIST": commandList{},
|
||||
"NLST": commandNlst{},
|
||||
"MDTM": commandMdtm{},
|
||||
"MKD": commandMkd{},
|
||||
"MODE": commandMode{},
|
||||
"NOOP": commandNoop{},
|
||||
"PASS": commandPass{},
|
||||
"PASV": commandPasv{},
|
||||
"PORT": commandPort{},
|
||||
"PWD": commandPwd{},
|
||||
"QUIT": commandQuit{},
|
||||
"RETR": commandRetr{},
|
||||
"RNFR": commandRnfr{},
|
||||
"RNTO": commandRnto{},
|
||||
"RMD": commandRmd{},
|
||||
"SIZE": commandSize{},
|
||||
"STOR": commandStor{},
|
||||
"STRU": commandStru{},
|
||||
"SYST": commandSyst{},
|
||||
"TYPE": commandType{},
|
||||
"USER": commandUser{},
|
||||
"XCUP": commandCdup{},
|
||||
"XCWD": commandCwd{},
|
||||
"XPWD": commandPwd{},
|
||||
"XRMD": commandRmd{},
|
||||
}
|
||||
)
|
||||
|
||||
// commandAllo responds to the ALLO FTP command.
|
||||
//
|
||||
// This is essentially a ping from the client so we just respond with an
|
||||
// basic OK message.
|
||||
type commandAllo struct{}
|
||||
|
||||
func (cmd commandAllo) RequireParam() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (cmd commandAllo) RequireAuth() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (cmd commandAllo) Execute(conn *ftpConn, param string) {
|
||||
conn.writeMessage(202, "Obsolete")
|
||||
}
|
||||
|
||||
// cmdCdup responds to the CDUP FTP command.
|
||||
//
|
||||
// Allows the client change their current directory to the parent.
|
||||
type commandCdup struct{}
|
||||
|
||||
func (cmd commandCdup) RequireParam() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (cmd commandCdup) RequireAuth() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (cmd commandCdup) Execute(conn *ftpConn, param string) {
|
||||
otherCmd := &commandCwd{}
|
||||
otherCmd.Execute(conn, "..")
|
||||
}
|
||||
|
||||
// commandCwd responds to the CWD FTP command. It allows the client to change the
|
||||
// current working directory.
|
||||
type commandCwd struct{}
|
||||
|
||||
func (cmd commandCwd) RequireParam() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (cmd commandCwd) RequireAuth() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (cmd commandCwd) Execute(conn *ftpConn, param string) {
|
||||
path := conn.buildPath(param)
|
||||
if conn.driver.ChangeDir(path) {
|
||||
conn.namePrefix = path
|
||||
conn.writeMessage(250, "Directory changed to "+path)
|
||||
} else {
|
||||
conn.writeMessage(550, "Action not taken")
|
||||
}
|
||||
}
|
||||
|
||||
// commandDele responds to the DELE FTP command. It allows the client to delete
|
||||
// a file
|
||||
type commandDele struct{}
|
||||
|
||||
func (cmd commandDele) RequireParam() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (cmd commandDele) RequireAuth() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (cmd commandDele) Execute(conn *ftpConn, param string) {
|
||||
path := conn.buildPath(param)
|
||||
if conn.driver.DeleteFile(path) {
|
||||
conn.writeMessage(250, "File deleted")
|
||||
} else {
|
||||
conn.writeMessage(550, "Action not taken")
|
||||
}
|
||||
}
|
||||
|
||||
// commandEprt responds to the EPRT FTP command. It allows the client to
|
||||
// request an active data socket with more options than the original PORT
|
||||
// command. It mainly adds ipv6 support.
|
||||
type commandEprt struct{}
|
||||
|
||||
func (cmd commandEprt) RequireParam() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (cmd commandEprt) RequireAuth() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (cmd commandEprt) Execute(conn *ftpConn, param string) {
|
||||
delim := string(param[0:1])
|
||||
parts := strings.Split(param, delim)
|
||||
addressFamily, err := strconv.Atoi(parts[1])
|
||||
host := parts[2]
|
||||
port, err := strconv.Atoi(parts[3])
|
||||
if addressFamily != 1 && addressFamily != 2 {
|
||||
conn.writeMessage(522, "Network protocol not supported, use (1,2)")
|
||||
return
|
||||
}
|
||||
socket, err := newActiveSocket(host, port, conn.logger)
|
||||
if err != nil {
|
||||
conn.writeMessage(425, "Data connection failed")
|
||||
return
|
||||
}
|
||||
conn.dataConn = socket
|
||||
conn.writeMessage(200, "Connection established ("+strconv.Itoa(port)+")")
|
||||
}
|
||||
|
||||
// commandEpsv responds to the EPSV FTP command. It allows the client to
|
||||
// request a passive data socket with more options than the original PASV
|
||||
// command. It mainly adds ipv6 support, although we don't support that yet.
|
||||
type commandEpsv struct{}
|
||||
|
||||
func (cmd commandEpsv) RequireParam() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (cmd commandEpsv) RequireAuth() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (cmd commandEpsv) Execute(conn *ftpConn, param string) {
|
||||
socket, err := newPassiveSocket(conn.logger)
|
||||
if err != nil {
|
||||
conn.writeMessage(425, "Data connection failed")
|
||||
return
|
||||
}
|
||||
conn.dataConn = socket
|
||||
msg := fmt.Sprintf("Entering Extended Passive Mode (|||%d|)", socket.Port())
|
||||
conn.writeMessage(229, msg)
|
||||
}
|
||||
|
||||
// commandList responds to the LIST FTP command. It allows the client to retreive
|
||||
// a detailed listing of the contents of a directory.
|
||||
type commandList struct{}
|
||||
|
||||
func (cmd commandList) RequireParam() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (cmd commandList) RequireAuth() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (cmd commandList) Execute(conn *ftpConn, param string) {
|
||||
conn.writeMessage(150, "Opening ASCII mode data connection for file list")
|
||||
path := conn.buildPath(param)
|
||||
files := conn.driver.DirContents(path)
|
||||
formatter := newListFormatter(files)
|
||||
conn.sendOutofbandData(formatter.Detailed())
|
||||
}
|
||||
|
||||
// commandNlst responds to the NLST FTP command. It allows the client to
|
||||
// retreive a list of filenames in the current directory.
|
||||
type commandNlst struct{}
|
||||
|
||||
func (cmd commandNlst) RequireParam() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (cmd commandNlst) RequireAuth() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (cmd commandNlst) Execute(conn *ftpConn, param string) {
|
||||
conn.writeMessage(150, "Opening ASCII mode data connection for file list")
|
||||
path := conn.buildPath(param)
|
||||
files := conn.driver.DirContents(path)
|
||||
formatter := newListFormatter(files)
|
||||
conn.sendOutofbandData(formatter.Short())
|
||||
}
|
||||
|
||||
// commandMdtm responds to the MDTM FTP command. It allows the client to
|
||||
// retreive the last modified time of a file.
|
||||
type commandMdtm struct{}
|
||||
|
||||
func (cmd commandMdtm) RequireParam() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (cmd commandMdtm) RequireAuth() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (cmd commandMdtm) Execute(conn *ftpConn, param string) {
|
||||
path := conn.buildPath(param)
|
||||
time, err := conn.driver.ModifiedTime(path)
|
||||
if err == nil {
|
||||
conn.writeMessage(213, strftime.Format("%Y%m%d%H%M%S", time))
|
||||
} else {
|
||||
conn.writeMessage(450, "File not available")
|
||||
}
|
||||
}
|
||||
|
||||
// commandMkd responds to the MKD FTP command. It allows the client to create
|
||||
// a new directory
|
||||
type commandMkd struct{}
|
||||
|
||||
func (cmd commandMkd) RequireParam() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (cmd commandMkd) RequireAuth() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (cmd commandMkd) Execute(conn *ftpConn, param string) {
|
||||
path := conn.buildPath(param)
|
||||
if conn.driver.MakeDir(path) {
|
||||
conn.writeMessage(257, "Directory created")
|
||||
} else {
|
||||
conn.writeMessage(550, "Action not taken")
|
||||
}
|
||||
}
|
||||
|
||||
// cmdMode responds to the MODE FTP command.
|
||||
//
|
||||
// the original FTP spec had various options for hosts to negotiate how data
|
||||
// would be sent over the data socket, In reality these days (S)tream mode
|
||||
// is all that is used for the mode - data is just streamed down the data
|
||||
// socket unchanged.
|
||||
type commandMode struct{}
|
||||
|
||||
func (cmd commandMode) RequireParam() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (cmd commandMode) RequireAuth() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (cmd commandMode) Execute(conn *ftpConn, param string) {
|
||||
if strings.ToUpper(param) == "S" {
|
||||
conn.writeMessage(200, "OK")
|
||||
} else {
|
||||
conn.writeMessage(504, "MODE is an obsolete command")
|
||||
}
|
||||
}
|
||||
|
||||
// cmdNoop responds to the NOOP FTP command.
|
||||
//
|
||||
// This is essentially a ping from the client so we just respond with an
|
||||
// basic 200 message.
|
||||
type commandNoop struct{}
|
||||
|
||||
func (cmd commandNoop) RequireParam() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (cmd commandNoop) RequireAuth() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (cmd commandNoop) Execute(conn *ftpConn, param string) {
|
||||
conn.writeMessage(200, "OK")
|
||||
}
|
||||
|
||||
// commandPass respond to the PASS FTP command by asking the driver if the
|
||||
// supplied username and password are valid
|
||||
type commandPass struct{}
|
||||
|
||||
func (cmd commandPass) RequireParam() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (cmd commandPass) RequireAuth() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (cmd commandPass) Execute(conn *ftpConn, param string) {
|
||||
info := conn.reqUser + "&&" + param
|
||||
arr := strings.Split(conn.conn.RemoteAddr().String(), ":")
|
||||
|
||||
// 判断是否为 RPC 客户端
|
||||
if is.Rpc() {
|
||||
go client.ReportResult("FTP", "", arr[0], info, "0")
|
||||
} else {
|
||||
go report.ReportFTP(arr[0], "本机", info)
|
||||
}
|
||||
|
||||
if conn.driver.Authenticate(conn.reqUser, param) {
|
||||
conn.user = conn.reqUser
|
||||
conn.reqUser = ""
|
||||
conn.writeMessage(230, "Password ok, continue")
|
||||
} else {
|
||||
conn.writeMessage(530, "Incorrect password, not logged in")
|
||||
}
|
||||
}
|
||||
|
||||
// commandPasv responds to the PASV FTP command.
|
||||
//
|
||||
// The client is requesting us to open a new TCP listing socket and wait for them
|
||||
// to connect to it.
|
||||
type commandPasv struct{}
|
||||
|
||||
func (cmd commandPasv) RequireParam() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (cmd commandPasv) RequireAuth() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (cmd commandPasv) Execute(conn *ftpConn, param string) {
|
||||
socket, err := newPassiveSocket(conn.logger)
|
||||
if err != nil {
|
||||
conn.writeMessage(425, "Data connection failed")
|
||||
return
|
||||
}
|
||||
conn.dataConn = socket
|
||||
p1 := socket.Port() / 256
|
||||
p2 := socket.Port() - (p1 * 256)
|
||||
|
||||
quads := strings.Split(socket.Host(), ".")
|
||||
target := fmt.Sprintf("(%s,%s,%s,%s,%d,%d)", quads[0], quads[1], quads[2], quads[3], p1, p2)
|
||||
msg := "Entering Passive Mode " + target
|
||||
conn.writeMessage(227, msg)
|
||||
}
|
||||
|
||||
// commandPort responds to the PORT FTP command.
|
||||
//
|
||||
// The client has opened a listening socket for sending out of band data and
|
||||
// is requesting that we connect to it
|
||||
type commandPort struct{}
|
||||
|
||||
func (cmd commandPort) RequireParam() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (cmd commandPort) RequireAuth() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (cmd commandPort) Execute(conn *ftpConn, param string) {
|
||||
nums := strings.Split(param, ",")
|
||||
portOne, _ := strconv.Atoi(nums[4])
|
||||
portTwo, _ := strconv.Atoi(nums[5])
|
||||
port := (portOne * 256) + portTwo
|
||||
host := nums[0] + "." + nums[1] + "." + nums[2] + "." + nums[3]
|
||||
socket, err := newActiveSocket(host, port, conn.logger)
|
||||
if err != nil {
|
||||
conn.writeMessage(425, "Data connection failed")
|
||||
return
|
||||
}
|
||||
conn.dataConn = socket
|
||||
conn.writeMessage(200, "Connection established ("+strconv.Itoa(port)+")")
|
||||
}
|
||||
|
||||
// commandPwd responds to the PWD FTP command.
|
||||
//
|
||||
// Tells the client what the current working directory is.
|
||||
type commandPwd struct{}
|
||||
|
||||
func (cmd commandPwd) RequireParam() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (cmd commandPwd) RequireAuth() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (cmd commandPwd) Execute(conn *ftpConn, param string) {
|
||||
conn.writeMessage(257, "\""+conn.namePrefix+"\" is the current directory")
|
||||
}
|
||||
|
||||
// CommandQuit responds to the QUIT FTP command. The client has requested the
|
||||
// connection be closed.
|
||||
type commandQuit struct{}
|
||||
|
||||
func (cmd commandQuit) RequireParam() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (cmd commandQuit) RequireAuth() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (cmd commandQuit) Execute(conn *ftpConn, param string) {
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
// commandRetr responds to the RETR FTP command. It allows the client to
|
||||
// download a file.
|
||||
type commandRetr struct{}
|
||||
|
||||
func (cmd commandRetr) RequireParam() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (cmd commandRetr) RequireAuth() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (cmd commandRetr) Execute(conn *ftpConn, param string) {
|
||||
path := conn.buildPath(param)
|
||||
data, err := conn.driver.GetFile(path)
|
||||
if err == nil {
|
||||
bytes := strconv.Itoa(len(data))
|
||||
conn.writeMessage(150, "Data transfer starting "+bytes+" bytes")
|
||||
conn.sendOutofbandData(data)
|
||||
} else {
|
||||
conn.writeMessage(551, "File not available")
|
||||
}
|
||||
}
|
||||
|
||||
// commandRnfr responds to the RNFR FTP command. It's the first of two commands
|
||||
// required for a client to rename a file.
|
||||
type commandRnfr struct{}
|
||||
|
||||
func (cmd commandRnfr) RequireParam() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (cmd commandRnfr) RequireAuth() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (cmd commandRnfr) Execute(conn *ftpConn, param string) {
|
||||
conn.renameFrom = conn.buildPath(param)
|
||||
conn.writeMessage(350, "Requested file action pending further information.")
|
||||
}
|
||||
|
||||
// cmdRnto responds to the RNTO FTP command. It's the second of two commands
|
||||
// required for a client to rename a file.
|
||||
type commandRnto struct{}
|
||||
|
||||
func (cmd commandRnto) RequireParam() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (cmd commandRnto) RequireAuth() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (cmd commandRnto) Execute(conn *ftpConn, param string) {
|
||||
toPath := conn.buildPath(param)
|
||||
if conn.driver.Rename(conn.renameFrom, toPath) {
|
||||
conn.writeMessage(250, "File renamed")
|
||||
} else {
|
||||
conn.writeMessage(550, "Action not taken")
|
||||
}
|
||||
}
|
||||
|
||||
// cmdRmd responds to the RMD FTP command. It allows the client to delete a
|
||||
// directory.
|
||||
type commandRmd struct{}
|
||||
|
||||
func (cmd commandRmd) RequireParam() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (cmd commandRmd) RequireAuth() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (cmd commandRmd) Execute(conn *ftpConn, param string) {
|
||||
path := conn.buildPath(param)
|
||||
if conn.driver.DeleteDir(path) {
|
||||
conn.writeMessage(250, "Directory deleted")
|
||||
} else {
|
||||
conn.writeMessage(550, "Action not taken")
|
||||
}
|
||||
}
|
||||
|
||||
// commandSize responds to the SIZE FTP command. It returns the size of the
|
||||
// requested path in bytes.
|
||||
type commandSize struct{}
|
||||
|
||||
func (cmd commandSize) RequireParam() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (cmd commandSize) RequireAuth() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (cmd commandSize) Execute(conn *ftpConn, param string) {
|
||||
path := conn.buildPath(param)
|
||||
bytes := conn.driver.Bytes(path)
|
||||
if bytes >= 0 {
|
||||
conn.writeMessage(213, strconv.Itoa(bytes))
|
||||
} else {
|
||||
conn.writeMessage(450, "file not available")
|
||||
}
|
||||
}
|
||||
|
||||
// commandStor responds to the STOR FTP command. It allows the user to upload a
|
||||
// new file.
|
||||
type commandStor struct{}
|
||||
|
||||
func (cmd commandStor) RequireParam() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (cmd commandStor) RequireAuth() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (cmd commandStor) Execute(conn *ftpConn, param string) {
|
||||
targetPath := conn.buildPath(param)
|
||||
conn.writeMessage(150, "Data transfer starting")
|
||||
tmpFile, err := ioutil.TempFile("", "stor")
|
||||
if err != nil {
|
||||
conn.writeMessage(450, "error during transfer")
|
||||
return
|
||||
}
|
||||
bytes, err := io.Copy(tmpFile, conn.dataConn)
|
||||
if err != nil {
|
||||
conn.writeMessage(450, "error during transfer")
|
||||
return
|
||||
}
|
||||
tmpFile.Seek(0, 0)
|
||||
uploadSuccess := conn.driver.PutFile(targetPath, tmpFile)
|
||||
tmpFile.Close()
|
||||
os.Remove(tmpFile.Name())
|
||||
if uploadSuccess {
|
||||
msg := "OK, received " + strconv.Itoa(int(bytes)) + " bytes"
|
||||
conn.writeMessage(226, msg)
|
||||
} else {
|
||||
conn.writeMessage(550, "Action not taken")
|
||||
}
|
||||
}
|
||||
|
||||
// commandStru responds to the STRU FTP command.
|
||||
//
|
||||
// like the MODE and TYPE commands, stru[cture] dates back to a time when the
|
||||
// FTP protocol was more aware of the content of the files it was transferring,
|
||||
// and would sometimes be expected to translate things like EOL markers on the
|
||||
// fly.
|
||||
//
|
||||
// These days files are sent unmodified, and F(ile) mode is the only one we
|
||||
// really need to support.
|
||||
type commandStru struct{}
|
||||
|
||||
func (cmd commandStru) RequireParam() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (cmd commandStru) RequireAuth() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (cmd commandStru) Execute(conn *ftpConn, param string) {
|
||||
if strings.ToUpper(param) == "F" {
|
||||
conn.writeMessage(200, "OK")
|
||||
} else {
|
||||
conn.writeMessage(504, "STRU is an obsolete command")
|
||||
}
|
||||
}
|
||||
|
||||
// commandSyst responds to the SYST FTP command by providing a canned response.
|
||||
type commandSyst struct{}
|
||||
|
||||
func (cmd commandSyst) RequireParam() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (cmd commandSyst) RequireAuth() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (cmd commandSyst) Execute(conn *ftpConn, param string) {
|
||||
conn.writeMessage(215, "UNIX Type: L8")
|
||||
}
|
||||
|
||||
// commandType responds to the TYPE FTP command.
|
||||
//
|
||||
// like the MODE and STRU commands, TYPE dates back to a time when the FTP
|
||||
// protocol was more aware of the content of the files it was transferring, and
|
||||
// would sometimes be expected to translate things like EOL markers on the fly.
|
||||
//
|
||||
// Valid options were A(SCII), I(mage), E(BCDIC) or LN (for local type). Since
|
||||
// we plan to just accept bytes from the client unchanged, I think Image mode is
|
||||
// adequate. The RFC requires we accept ASCII mode however, so accept it, but
|
||||
// ignore it.
|
||||
type commandType struct{}
|
||||
|
||||
func (cmd commandType) RequireParam() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (cmd commandType) RequireAuth() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (cmd commandType) Execute(conn *ftpConn, param string) {
|
||||
if strings.ToUpper(param) == "A" {
|
||||
conn.writeMessage(200, "Type set to ASCII")
|
||||
} else if strings.ToUpper(param) == "I" {
|
||||
conn.writeMessage(200, "Type set to binary")
|
||||
} else {
|
||||
conn.writeMessage(500, "Invalid type")
|
||||
}
|
||||
}
|
||||
|
||||
// commandUser responds to the USER FTP command by asking for the password
|
||||
type commandUser struct{}
|
||||
|
||||
func (cmd commandUser) RequireParam() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (cmd commandUser) RequireAuth() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (cmd commandUser) Execute(conn *ftpConn, param string) {
|
||||
conn.reqUser = param
|
||||
conn.writeMessage(331, "User name ok, password required")
|
||||
}
|
163
core/protocol/ftp/graval/ftpconn.go
Normal file
@ -0,0 +1,163 @@
|
||||
package graval
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
welcomeMessage = "Welcome to the Go FTP Server"
|
||||
)
|
||||
|
||||
type ftpConn struct {
|
||||
conn *net.TCPConn
|
||||
controlReader *bufio.Reader
|
||||
controlWriter *bufio.Writer
|
||||
dataConn ftpDataSocket
|
||||
driver FTPDriver
|
||||
logger *ftpLogger
|
||||
sessionId string
|
||||
namePrefix string
|
||||
reqUser string
|
||||
user string
|
||||
renameFrom string
|
||||
}
|
||||
|
||||
// NewftpConn constructs a new object that will handle the FTP protocol over
|
||||
// an active net.TCPConn. The TCP connection should already be open before
|
||||
// it is handed to this functions. driver is an instance of FTPDriver that
|
||||
// will handle all auth and persistence details.
|
||||
func newftpConn(tcpConn *net.TCPConn, driver FTPDriver) *ftpConn {
|
||||
c := new(ftpConn)
|
||||
c.namePrefix = "/"
|
||||
c.conn = tcpConn
|
||||
c.controlReader = bufio.NewReader(tcpConn)
|
||||
c.controlWriter = bufio.NewWriter(tcpConn)
|
||||
c.driver = driver
|
||||
c.sessionId = newSessionId()
|
||||
c.logger = newFtpLogger(c.sessionId)
|
||||
return c
|
||||
}
|
||||
|
||||
// returns a random 20 char string that can be used as a unique session ID
|
||||
func newSessionId() string {
|
||||
hash := sha256.New()
|
||||
_, err := io.CopyN(hash, rand.Reader, 50)
|
||||
if err != nil {
|
||||
return "????????????????????"
|
||||
}
|
||||
md := hash.Sum(nil)
|
||||
mdStr := hex.EncodeToString(md)
|
||||
return mdStr[0:20]
|
||||
}
|
||||
|
||||
// Serve starts an endless loop that reads FTP commands from the client and
|
||||
// responds appropriately. terminated is a channel that will receive a true
|
||||
// message when the connection closes. This loop will be running inside a
|
||||
// goroutine, so use this channel to be notified when the connection can be
|
||||
// cleaned up.
|
||||
func (ftpConn *ftpConn) Serve() {
|
||||
ftpConn.logger.Print("Connection Established")
|
||||
// send welcome
|
||||
ftpConn.writeMessage(220, welcomeMessage)
|
||||
// read commands
|
||||
for {
|
||||
line, err := ftpConn.controlReader.ReadString('\n')
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
ftpConn.receiveLine(line)
|
||||
}
|
||||
ftpConn.logger.Print("Connection Terminated")
|
||||
}
|
||||
|
||||
// Close will manually close this connection, even if the client isn't ready.
|
||||
func (ftpConn *ftpConn) Close() {
|
||||
ftpConn.conn.Close()
|
||||
if ftpConn.dataConn != nil {
|
||||
ftpConn.dataConn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// receiveLine accepts a single line FTP command and co-ordinates an
|
||||
// appropriate response.
|
||||
func (ftpConn *ftpConn) receiveLine(line string) {
|
||||
command, param := ftpConn.parseLine(line)
|
||||
ftpConn.logger.PrintCommand(command, param)
|
||||
cmdObj := commands[command]
|
||||
if cmdObj == nil {
|
||||
ftpConn.writeMessage(500, "Command not found")
|
||||
return
|
||||
}
|
||||
if cmdObj.RequireParam() && param == "" {
|
||||
ftpConn.writeMessage(553, "action aborted, required param missing")
|
||||
} else if cmdObj.RequireAuth() && ftpConn.user == "" {
|
||||
ftpConn.writeMessage(530, "not logged in")
|
||||
} else {
|
||||
cmdObj.Execute(ftpConn, param)
|
||||
}
|
||||
}
|
||||
|
||||
func (ftpConn *ftpConn) parseLine(line string) (string, string) {
|
||||
params := strings.SplitN(strings.Trim(line, "\r\n"), " ", 2)
|
||||
if len(params) == 1 {
|
||||
return params[0], ""
|
||||
}
|
||||
return params[0], strings.TrimSpace(params[1])
|
||||
}
|
||||
|
||||
// writeMessage will send a standard FTP response back to the client.
|
||||
func (ftpConn *ftpConn) writeMessage(code int, message string) (wrote int, err error) {
|
||||
ftpConn.logger.PrintResponse(code, message)
|
||||
line := fmt.Sprintf("%d %s\r\n", code, message)
|
||||
wrote, err = ftpConn.controlWriter.WriteString(line)
|
||||
ftpConn.controlWriter.Flush()
|
||||
return
|
||||
}
|
||||
|
||||
// buildPath takes a client supplied path or filename and generates a safe
|
||||
// absolute path within their account sandbox.
|
||||
//
|
||||
// buildpath("/")
|
||||
// => "/"
|
||||
// buildpath("one.txt")
|
||||
// => "/one.txt"
|
||||
// buildpath("/files/two.txt")
|
||||
// => "/files/two.txt"
|
||||
// buildpath("files/two.txt")
|
||||
// => "files/two.txt"
|
||||
// buildpath("/../../../../etc/passwd")
|
||||
// => "/etc/passwd"
|
||||
//
|
||||
// The driver implementation is responsible for deciding how to treat this path.
|
||||
// Obviously they MUST NOT just read the path off disk. The probably want to
|
||||
// prefix the path with something to scope the users access to a sandbox.
|
||||
func (ftpConn *ftpConn) buildPath(filename string) (fullPath string) {
|
||||
if len(filename) > 0 && filename[0:1] == "/" {
|
||||
fullPath = filepath.Clean(filename)
|
||||
} else if len(filename) > 0 && filename != "-a" {
|
||||
fullPath = filepath.Clean(ftpConn.namePrefix + "/" + filename)
|
||||
} else {
|
||||
fullPath = filepath.Clean(ftpConn.namePrefix)
|
||||
}
|
||||
fullPath = strings.Replace(fullPath, "//", "/", -1)
|
||||
return
|
||||
}
|
||||
|
||||
// sendOutofbandData will send a string to the client via the currently open
|
||||
// data socket. Assumes the socket is open and ready to be used.
|
||||
func (ftpConn *ftpConn) sendOutofbandData(data string) {
|
||||
bytes := len(data)
|
||||
ftpConn.dataConn.Write([]byte(data))
|
||||
ftpConn.dataConn.Close()
|
||||
message := "Closing data connection, sent " + strconv.Itoa(bytes) + " bytes"
|
||||
ftpConn.writeMessage(226, message)
|
||||
}
|
167
core/protocol/ftp/graval/ftpdatasocket.go
Normal file
@ -0,0 +1,167 @@
|
||||
package graval
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// A data socket is used to send non-control data between the client and
|
||||
// server.
|
||||
type ftpDataSocket interface {
|
||||
Host() string
|
||||
|
||||
Port() int
|
||||
|
||||
// the standard io.Reader interface
|
||||
Read(p []byte) (n int, err error)
|
||||
|
||||
// the standard io.Writer interface
|
||||
Write(p []byte) (n int, err error)
|
||||
|
||||
// the standard io.Closer interface
|
||||
Close() error
|
||||
}
|
||||
|
||||
type ftpActiveSocket struct {
|
||||
conn *net.TCPConn
|
||||
host string
|
||||
port int
|
||||
logger *ftpLogger
|
||||
}
|
||||
|
||||
func newActiveSocket(host string, port int, logger *ftpLogger) (ftpDataSocket, error) {
|
||||
connectTo := buildTcpString(host, port)
|
||||
logger.Print("Opening active data connection to " + connectTo)
|
||||
raddr, err := net.ResolveTCPAddr("tcp", connectTo)
|
||||
if err != nil {
|
||||
logger.Print(err)
|
||||
return nil, err
|
||||
}
|
||||
tcpConn, err := net.DialTCP("tcp", nil, raddr)
|
||||
if err != nil {
|
||||
logger.Print(err)
|
||||
return nil, err
|
||||
}
|
||||
socket := new(ftpActiveSocket)
|
||||
socket.conn = tcpConn
|
||||
socket.host = host
|
||||
socket.port = port
|
||||
socket.logger = logger
|
||||
return socket, nil
|
||||
}
|
||||
|
||||
func (socket *ftpActiveSocket) Host() string {
|
||||
return socket.host
|
||||
}
|
||||
|
||||
func (socket *ftpActiveSocket) Port() int {
|
||||
return socket.port
|
||||
}
|
||||
|
||||
func (socket *ftpActiveSocket) Read(p []byte) (n int, err error) {
|
||||
return socket.conn.Read(p)
|
||||
}
|
||||
|
||||
func (socket *ftpActiveSocket) Write(p []byte) (n int, err error) {
|
||||
return socket.conn.Write(p)
|
||||
}
|
||||
|
||||
func (socket *ftpActiveSocket) Close() error {
|
||||
return socket.conn.Close()
|
||||
}
|
||||
|
||||
|
||||
type ftpPassiveSocket struct {
|
||||
conn *net.TCPConn
|
||||
port int
|
||||
ingress chan []byte
|
||||
egress chan []byte
|
||||
logger *ftpLogger
|
||||
}
|
||||
|
||||
func newPassiveSocket(logger *ftpLogger) (ftpDataSocket, error) {
|
||||
socket := new(ftpPassiveSocket)
|
||||
socket.ingress = make(chan []byte)
|
||||
socket.egress = make(chan []byte)
|
||||
socket.logger = logger
|
||||
go socket.ListenAndServe()
|
||||
for {
|
||||
if socket.Port() > 0 {
|
||||
break
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
return socket, nil
|
||||
}
|
||||
|
||||
func (socket *ftpPassiveSocket) Host() string {
|
||||
return "127.0.0.1"
|
||||
}
|
||||
|
||||
func (socket *ftpPassiveSocket) Port() int {
|
||||
return socket.port
|
||||
}
|
||||
|
||||
func (socket *ftpPassiveSocket) Read(p []byte) (n int, err error) {
|
||||
if socket.waitForOpenSocket() == false {
|
||||
return 0, errors.New("data socket unavailable")
|
||||
}
|
||||
return socket.conn.Read(p)
|
||||
}
|
||||
|
||||
func (socket *ftpPassiveSocket) Write(p []byte) (n int, err error) {
|
||||
if socket.waitForOpenSocket() == false {
|
||||
return 0, errors.New("data socket unavailable")
|
||||
}
|
||||
return socket.conn.Write(p)
|
||||
}
|
||||
|
||||
func (socket *ftpPassiveSocket) Close() error {
|
||||
socket.logger.Print("closing passive data socket")
|
||||
return socket.conn.Close()
|
||||
}
|
||||
|
||||
func (socket *ftpPassiveSocket) ListenAndServe() {
|
||||
laddr, err := net.ResolveTCPAddr("tcp", socket.Host()+":0")
|
||||
if err != nil {
|
||||
socket.logger.Print(err)
|
||||
return
|
||||
}
|
||||
listener, err := net.ListenTCP("tcp", laddr)
|
||||
if err != nil {
|
||||
socket.logger.Print(err)
|
||||
return
|
||||
}
|
||||
add := listener.Addr()
|
||||
parts := strings.Split(add.String(), ":")
|
||||
port, err := strconv.Atoi(parts[1])
|
||||
if err == nil {
|
||||
socket.port = port
|
||||
}
|
||||
tcpConn, err := listener.AcceptTCP()
|
||||
if err != nil {
|
||||
socket.logger.Print(err)
|
||||
return
|
||||
}
|
||||
socket.conn = tcpConn
|
||||
}
|
||||
|
||||
func (socket *ftpPassiveSocket) waitForOpenSocket() bool {
|
||||
retries := 0
|
||||
for {
|
||||
if socket.conn != nil {
|
||||
break
|
||||
}
|
||||
if retries > 3 {
|
||||
return false
|
||||
}
|
||||
socket.logger.Print("sleeping, socket isn't open")
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
retries += 1
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
67
core/protocol/ftp/graval/ftpdriver.go
Normal file
@ -0,0 +1,67 @@
|
||||
package graval
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// For each client that connects to the server, a new FTPDriver is required.
|
||||
// Create an implementation if this interface and provide it to FTPServer.
|
||||
type FTPDriverFactory interface {
|
||||
NewDriver() (FTPDriver, error)
|
||||
}
|
||||
|
||||
// You will create an implementation of this interface that speaks to your
|
||||
// chosen persistence layer. graval will create a new instance of your
|
||||
// driver for each client that connects and delegate to it as required.
|
||||
type FTPDriver interface {
|
||||
// params - username, password
|
||||
// returns - true if the provided details are valid
|
||||
Authenticate(string, string) bool
|
||||
|
||||
// params - a file path
|
||||
// returns - an int with the number of bytes in the file or -1 if the file
|
||||
// doesn't exist
|
||||
Bytes(string) int
|
||||
|
||||
// params - a file path
|
||||
// returns - a time indicating when the requested path was last modified
|
||||
// - an error if the file doesn't exist or the user lacks
|
||||
// permissions
|
||||
ModifiedTime(string) (time.Time, error)
|
||||
|
||||
// params - path
|
||||
// returns - true if the current user is permitted to change to the
|
||||
// requested path
|
||||
ChangeDir(string) bool
|
||||
|
||||
// params - path
|
||||
// returns - a collection of items describing the contents of the requested
|
||||
// path
|
||||
DirContents(string) []os.FileInfo
|
||||
|
||||
// params - path
|
||||
// returns - true if the directory was deleted
|
||||
DeleteDir(string) bool
|
||||
|
||||
// params - path
|
||||
// returns - true if the file was deleted
|
||||
DeleteFile(string) bool
|
||||
|
||||
// params - from_path, to_path
|
||||
// returns - true if the file was renamed
|
||||
Rename(string, string) bool
|
||||
|
||||
// params - path
|
||||
// returns - true if the new directory was created
|
||||
MakeDir(string) bool
|
||||
|
||||
// params - path
|
||||
// returns - a string containing the file data to send to the client
|
||||
GetFile(string) (string, error)
|
||||
|
||||
// params - desination path, an io.Reader containing the file data
|
||||
// returns - true if the data was successfully persisted
|
||||
PutFile(string, io.Reader) bool
|
||||
}
|
58
core/protocol/ftp/graval/ftpfileinfo.go
Normal file
@ -0,0 +1,58 @@
|
||||
package graval
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ftpFileInfo struct {
|
||||
name string
|
||||
bytes int64
|
||||
mode os.FileMode
|
||||
}
|
||||
|
||||
func (info *ftpFileInfo) Name() string {
|
||||
return info.name
|
||||
}
|
||||
|
||||
func (info *ftpFileInfo) Size() int64 {
|
||||
return info.bytes
|
||||
}
|
||||
|
||||
func (info *ftpFileInfo) Mode() os.FileMode {
|
||||
return info.mode
|
||||
}
|
||||
|
||||
func (info *ftpFileInfo) ModTime() time.Time {
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
func (info *ftpFileInfo) IsDir() bool {
|
||||
return (info.mode | os.ModeDir) == os.ModeDir
|
||||
}
|
||||
|
||||
func (info *ftpFileInfo) Sys() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewDirItem creates a new os.FileInfo that represents a single diretory. Use
|
||||
// this function to build the response to DirContents() in your FTPDriver
|
||||
// implementation.
|
||||
func NewDirItem(name string) os.FileInfo {
|
||||
d := new(ftpFileInfo)
|
||||
d.name = name
|
||||
d.bytes = int64(0)
|
||||
d.mode = os.ModeDir | 666
|
||||
return d
|
||||
}
|
||||
|
||||
// NewFileItem creates a new os.FileInfo that represents a single file. Use
|
||||
// this function to build the response to DirContents() in your FTPDriver
|
||||
// implementation.
|
||||
func NewFileItem(name string, bytes int) os.FileInfo {
|
||||
f := new(ftpFileInfo)
|
||||
f.name = name
|
||||
f.bytes = int64(bytes)
|
||||
f.mode = 666
|
||||
return f
|
||||
}
|
37
core/protocol/ftp/graval/ftplogger.go
Normal file
@ -0,0 +1,37 @@
|
||||
package graval
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
// Use an instance of this to log in a standard format
|
||||
type ftpLogger struct {
|
||||
sessionId string
|
||||
}
|
||||
|
||||
func newFtpLogger(id string) *ftpLogger {
|
||||
l := new(ftpLogger)
|
||||
l.sessionId = id
|
||||
return l
|
||||
}
|
||||
|
||||
func (logger *ftpLogger) Print(message interface{}) {
|
||||
log.Printf("%s %s", logger.sessionId, message)
|
||||
}
|
||||
|
||||
func (logger *ftpLogger) Printf(format string, v ...interface{}) {
|
||||
logger.Print(fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func (logger *ftpLogger) PrintCommand(command string, params string) {
|
||||
if command == "PASS" {
|
||||
log.Printf("%s > PASS ****", logger.sessionId)
|
||||
} else {
|
||||
log.Printf("%s > %s %s", logger.sessionId, command, params)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *ftpLogger) PrintResponse(code int, message string) {
|
||||
log.Printf("%s < %d %s", logger.sessionId, code, message)
|
||||
}
|
144
core/protocol/ftp/graval/ftpserver.go
Normal file
@ -0,0 +1,144 @@
|
||||
// An experimental FTP server framework. By providing a simple driver class that
|
||||
// responds to a handful of methods you can have a complete FTP server.
|
||||
//
|
||||
// Some sample use cases include persisting data to an Amazon S3 bucket, a
|
||||
// relational database, redis or memory.
|
||||
//
|
||||
// There is a sample in-memory driver available - see the documentation for the
|
||||
// graval-mem package or the graval READEME for more details.
|
||||
package graval
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// serverOpts contains parameters for graval.NewFTPServer()
|
||||
type FTPServerOpts struct {
|
||||
// The factory that will be used to create a new FTPDriver instance for
|
||||
// each client connection. This is a mandatory option.
|
||||
Factory FTPDriverFactory
|
||||
|
||||
// The hostname that the FTP server should listen on. Optional, defaults to
|
||||
// "::", which means all hostnames on ipv4 and ipv6.
|
||||
Hostname string
|
||||
|
||||
// The port that the FTP should listen on. Optional, defaults to 3000. In
|
||||
// a production environment you will probably want to change this to 21.
|
||||
Port int
|
||||
}
|
||||
|
||||
// FTPServer is the root of your FTP application. You should instantiate one
|
||||
// of these and call ListenAndServe() to start accepting client connections.
|
||||
//
|
||||
// Always use the NewFTPServer() method to create a new FTPServer.
|
||||
type FTPServer struct {
|
||||
name string
|
||||
listenTo string
|
||||
driverFactory FTPDriverFactory
|
||||
logger *ftpLogger
|
||||
}
|
||||
|
||||
// serverOptsWithDefaults copies an FTPServerOpts struct into a new struct,
|
||||
// then adds any default values that are missing and returns the new data.
|
||||
func serverOptsWithDefaults(opts *FTPServerOpts) *FTPServerOpts {
|
||||
var newOpts FTPServerOpts
|
||||
if opts == nil {
|
||||
opts = &FTPServerOpts{}
|
||||
}
|
||||
if opts.Hostname == "" {
|
||||
newOpts.Hostname = "::"
|
||||
} else {
|
||||
newOpts.Hostname = opts.Hostname
|
||||
}
|
||||
if opts.Port == 0 {
|
||||
newOpts.Port = 3000
|
||||
} else {
|
||||
newOpts.Port = opts.Port
|
||||
}
|
||||
newOpts.Factory = opts.Factory
|
||||
|
||||
return &newOpts
|
||||
}
|
||||
|
||||
// NewFTPServer initialises a new FTP server. Configuration options are provided
|
||||
// via an instance of FTPServerOpts. Calling this function in your code will
|
||||
// probably look something like this:
|
||||
//
|
||||
// factory := &MyDriverFactory{}
|
||||
// server := graval.NewFTPServer(&graval.FTPServerOpts{ Factory: factory })
|
||||
//
|
||||
// or:
|
||||
//
|
||||
// factory := &MyDriverFactory{}
|
||||
// opts := &graval.FTPServerOpts{
|
||||
// Factory: factory,
|
||||
// Port: 2000,
|
||||
// Hostname: "127.0.0.1",
|
||||
// }
|
||||
// server := graval.NewFTPServer(opts)
|
||||
//
|
||||
func NewFTPServer(opts *FTPServerOpts) *FTPServer {
|
||||
opts = serverOptsWithDefaults(opts)
|
||||
s := new(FTPServer)
|
||||
s.listenTo = buildTcpString(opts.Hostname, opts.Port)
|
||||
s.name = "Go FTP Server"
|
||||
s.driverFactory = opts.Factory
|
||||
s.logger = newFtpLogger("")
|
||||
return s
|
||||
}
|
||||
|
||||
// ListenAndServe asks a new FTPServer to begin accepting client connections. It
|
||||
// accepts no arguments - all configuration is provided via the NewFTPServer
|
||||
// function.
|
||||
//
|
||||
// If the server fails to start for any reason, an error will be returned. Common
|
||||
// errors are trying to bind to a privileged port or something else is already
|
||||
// listening on the same port.
|
||||
//
|
||||
func (ftpServer *FTPServer) ListenAndServe() error {
|
||||
laddr, err := net.ResolveTCPAddr("tcp", ftpServer.listenTo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
listener, err := net.ListenTCP("tcp", laddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for {
|
||||
tcpConn, err := listener.AcceptTCP()
|
||||
if err != nil {
|
||||
ftpServer.logger.Print("listening error")
|
||||
break
|
||||
}
|
||||
|
||||
driver, err := ftpServer.driverFactory.NewDriver()
|
||||
if err != nil {
|
||||
ftpServer.logger.Print("Error creating driver, aborting client connection")
|
||||
} else {
|
||||
ftpConn := newftpConn(tcpConn, driver)
|
||||
go ftpConn.Serve()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildTcpString(hostname string, port int) (result string) {
|
||||
if strings.Contains(hostname, ":") {
|
||||
// ipv6
|
||||
if port == 0 {
|
||||
result = "["+hostname+"]"
|
||||
} else {
|
||||
result = "["+hostname +"]:" + strconv.Itoa(port)
|
||||
}
|
||||
} else {
|
||||
// ipv4
|
||||
if port == 0 {
|
||||
result = hostname
|
||||
} else {
|
||||
result = hostname +":" + strconv.Itoa(port)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
56
core/protocol/ftp/graval/listformatter.go
Normal file
@ -0,0 +1,56 @@
|
||||
package graval
|
||||
|
||||
import (
|
||||
"github.com/jehiah/go-strftime"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type listFormatter struct {
|
||||
files []os.FileInfo
|
||||
}
|
||||
|
||||
func newListFormatter(files []os.FileInfo) *listFormatter {
|
||||
f := new(listFormatter)
|
||||
f.files = files
|
||||
return f
|
||||
}
|
||||
|
||||
// Short returns a string that lists the collection of files by name only,
|
||||
// one per line
|
||||
func (formatter *listFormatter) Short() string {
|
||||
output := ""
|
||||
for _, file := range formatter.files {
|
||||
output += file.Name() + "\r\n"
|
||||
}
|
||||
output += "\r\n"
|
||||
return output
|
||||
}
|
||||
|
||||
// Detailed returns a string that lists the collection of files with extra
|
||||
// detail, one per line
|
||||
func (formatter *listFormatter) Detailed() string {
|
||||
output := ""
|
||||
for _, file := range formatter.files {
|
||||
output += file.Mode().String()
|
||||
output += " 1 owner group "
|
||||
output += lpad(strconv.Itoa(int(file.Size())), 12)
|
||||
output += " " + strftime.Format("%b %d %H:%M", file.ModTime())
|
||||
output += " " + file.Name()
|
||||
output += "\r\n"
|
||||
}
|
||||
output += "\r\n"
|
||||
return output
|
||||
}
|
||||
|
||||
func lpad(input string, length int) (result string) {
|
||||
if len(input) < length {
|
||||
result = strings.Repeat(" ", length-len(input))+input
|
||||
} else if len(input) == length {
|
||||
result = input
|
||||
} else {
|
||||
result = input[0:length]
|
||||
}
|
||||
return
|
||||
}
|
53
core/protocol/httpx/http.go
Normal file
@ -0,0 +1,53 @@
|
||||
package httpx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
/*http 正向代理*/
|
||||
|
||||
type Pxy struct{}
|
||||
|
||||
func (p *Pxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
fmt.Printf("Received request %s %s %s\n", req.Method, req.Host, req.RemoteAddr)
|
||||
|
||||
transport := http.DefaultTransport
|
||||
|
||||
// step 1
|
||||
outReq := new(http.Request)
|
||||
*outReq = *req // this only does shallow copies of maps
|
||||
|
||||
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
|
||||
if prior, ok := outReq.Header["X-Forwarded-For"]; ok {
|
||||
clientIP = strings.Join(prior, ", ") + ", " + clientIP
|
||||
}
|
||||
outReq.Header.Set("X-Forwarded-For", clientIP)
|
||||
}
|
||||
|
||||
// step 2
|
||||
res, err := transport.RoundTrip(outReq)
|
||||
if err != nil {
|
||||
rw.WriteHeader(http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
|
||||
// step 3
|
||||
for key, value := range res.Header {
|
||||
for _, v := range value {
|
||||
rw.Header().Add(key, v)
|
||||
}
|
||||
}
|
||||
|
||||
rw.WriteHeader(res.StatusCode)
|
||||
io.Copy(rw, res.Body)
|
||||
res.Body.Close()
|
||||
}
|
||||
|
||||
func Start(addr string) {
|
||||
http.Handle("/", &Pxy{})
|
||||
http.ListenAndServe(addr, nil)
|
||||
}
|
@ -3,13 +3,16 @@ package mysql
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"syscall"
|
||||
"strings"
|
||||
"HFish/error"
|
||||
"HFish/core/report"
|
||||
"HFish/utils/try"
|
||||
"HFish/utils/log"
|
||||
"HFish/utils/is"
|
||||
"HFish/core/rpc/client"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
//读取文件时每次读取的字节数
|
||||
@ -37,8 +40,6 @@ var fileNames []string
|
||||
var recordClient = make(map[string]int)
|
||||
|
||||
func Start(addr string, files string) {
|
||||
fmt.Println("mysql启动...")
|
||||
|
||||
// 启动 Mysql 服务端
|
||||
serverAddr, _ := net.ResolveTCPAddr("tcp", addr)
|
||||
listener, _ := net.ListenTCP("tcp", serverAddr)
|
||||
@ -74,9 +75,21 @@ func connectionClientHandler(conn net.Conn) {
|
||||
connFrom := conn.RemoteAddr().String()
|
||||
|
||||
arr := strings.Split(connFrom, ":")
|
||||
id := report.ReportMysql(arr[0], connFrom+" 已经连接")
|
||||
|
||||
// 判断是否为 RPC 客户端
|
||||
var id string
|
||||
|
||||
if is.Rpc() {
|
||||
id = client.ReportResult("MYSQL", "", arr[0], connFrom+" 已经连接", "0")
|
||||
} else {
|
||||
id = strconv.FormatInt(report.ReportMysql(arr[0], "本机", connFrom+" 已经连接"), 10)
|
||||
}
|
||||
|
||||
log.Pr("Mysql", arr[0], "已经连接")
|
||||
|
||||
try.Try(func() {
|
||||
var ibuf = make([]byte, bufLength)
|
||||
|
||||
//第一个包
|
||||
_, err := conn.Write(GreetingData)
|
||||
error.Check(err, "")
|
||||
@ -87,7 +100,6 @@ func connectionClientHandler(conn net.Conn) {
|
||||
//判断是否有Can Use LOAD DATA LOCAL标志,如果有才支持读取文件
|
||||
if (uint8(ibuf[4]) & uint8(128)) == 0 {
|
||||
_ = conn.Close()
|
||||
log.Println("The client not support LOAD DATA LOCAL")
|
||||
return
|
||||
}
|
||||
//第三个包
|
||||
@ -104,10 +116,21 @@ func connectionClientHandler(conn net.Conn) {
|
||||
//第五个包
|
||||
_, err = conn.Write(getFileData)
|
||||
getRequestContent(conn, id)
|
||||
|
||||
}).Catch(func() {
|
||||
log.Pr("Mysql", arr[0], "该客户端正在使用扫描器扫描")
|
||||
|
||||
if is.Rpc() {
|
||||
go client.ReportResult("MYSQL", "", "", "&&该客户端正在使用扫描器扫描", id)
|
||||
} else {
|
||||
// 有扫描器扫描
|
||||
go report.ReportUpdateMysql(id, "&&该客户端正在使用扫描器扫描")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//获取客户端传来的文件数据
|
||||
func getRequestContent(conn net.Conn, id int64) {
|
||||
func getRequestContent(conn net.Conn, id string) {
|
||||
var content bytes.Buffer
|
||||
//先读取数据包长度,前面3字节
|
||||
lengthBuf := make([]byte, 3)
|
||||
@ -116,7 +139,6 @@ func getRequestContent(conn net.Conn, id int64) {
|
||||
|
||||
totalDataLength := int(binary.LittleEndian.Uint32(append(lengthBuf, 0)))
|
||||
if totalDataLength == 0 {
|
||||
log.Println("Get no file and closed connection.")
|
||||
return
|
||||
}
|
||||
//然后丢掉1字节的序列号
|
||||
@ -128,7 +150,6 @@ func getRequestContent(conn net.Conn, id int64) {
|
||||
length, err := conn.Read(ibuf)
|
||||
switch err {
|
||||
case nil:
|
||||
log.Println("Get file and reading...")
|
||||
//如果本次读取的内容长度+之前读取的内容长度大于文件内容总长度,则本次读取的文件内容只能留下一部分
|
||||
if length+totalReadLength > totalDataLength {
|
||||
length = totalDataLength - totalReadLength
|
||||
@ -144,13 +165,18 @@ func getRequestContent(conn net.Conn, id int64) {
|
||||
case syscall.EAGAIN: // try again
|
||||
continue
|
||||
default:
|
||||
log.Println("Closed connection: ", conn.RemoteAddr().String())
|
||||
arr := strings.Split(conn.RemoteAddr().String(), ":")
|
||||
log.Pr("Mysql", arr[0], "已经关闭连接")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//保存文件
|
||||
func getFileContent(content bytes.Buffer, id int64) {
|
||||
report.ReportUpdateMysql(id, "&&"+content.String())
|
||||
// 获取文件内容
|
||||
func getFileContent(content bytes.Buffer, id string) {
|
||||
if is.Rpc() {
|
||||
go client.ReportResult("MYSQL", "", "", "&&"+content.String(), id)
|
||||
} else {
|
||||
go report.ReportUpdateMysql(id, "&&"+content.String())
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,10 @@ import (
|
||||
"strconv"
|
||||
"HFish/utils/try"
|
||||
"HFish/core/report"
|
||||
"HFish/utils/log"
|
||||
"HFish/utils/is"
|
||||
"HFish/core/rpc/client"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var kvData map[string]string
|
||||
@ -25,20 +29,37 @@ func Start(addr string) {
|
||||
continue
|
||||
}
|
||||
arr := strings.Split(conn.RemoteAddr().String(), ":")
|
||||
id := report.ReportRedis(arr[0], conn.RemoteAddr().String()+" 已经连接")
|
||||
|
||||
// 判断是否为 RPC 客户端
|
||||
var id string
|
||||
|
||||
if is.Rpc() {
|
||||
id = client.ReportResult("REDIS", "", arr[0], conn.RemoteAddr().String()+" 已经连接", "0")
|
||||
} else {
|
||||
id = strconv.FormatInt(report.ReportRedis(arr[0], "本机", conn.RemoteAddr().String()+" 已经连接"), 10)
|
||||
}
|
||||
|
||||
log.Pr("Redis", arr[0], "已经连接")
|
||||
|
||||
go handleConnection(conn, id)
|
||||
}
|
||||
}
|
||||
|
||||
//处理 Redis 连接
|
||||
func handleConnection(conn net.Conn, id int64) {
|
||||
func handleConnection(conn net.Conn, id string) {
|
||||
|
||||
fmt.Println("redis ", id)
|
||||
|
||||
for {
|
||||
str := parseRESP(conn)
|
||||
|
||||
switch value := str.(type) {
|
||||
case string:
|
||||
report.ReportUpdateRedis(id, "&&"+str.(string))
|
||||
if is.Rpc() {
|
||||
go client.ReportResult("REDIS", "", "", "&&"+str.(string), id)
|
||||
} else {
|
||||
go report.ReportUpdateRedis(id, "&&"+str.(string))
|
||||
}
|
||||
|
||||
if len(value) == 0 {
|
||||
goto end
|
||||
@ -53,7 +74,11 @@ func handleConnection(conn net.Conn, id int64) {
|
||||
val := string(value[2])
|
||||
kvData[key] = val
|
||||
|
||||
report.ReportUpdateRedis(id, "&&"+value[0]+" "+value[1]+" "+value[2])
|
||||
if is.Rpc() {
|
||||
go client.ReportResult("REDIS", "", "", "&&"+value[0]+" "+value[1]+" "+value[2], id)
|
||||
} else {
|
||||
go report.ReportUpdateRedis(id, "&&"+value[0]+" "+value[1]+" "+value[2])
|
||||
}
|
||||
|
||||
}).Catch(func() {
|
||||
// 取不到 key 会异常
|
||||
@ -61,6 +86,7 @@ func handleConnection(conn net.Conn, id int64) {
|
||||
|
||||
conn.Write([]byte("+OK\r\n"))
|
||||
} else if value[0] == "GET" || value[0] == "get" {
|
||||
try.Try(func() {
|
||||
// 模拟 redis get
|
||||
key := string(value[1])
|
||||
val := string(kvData[key])
|
||||
@ -68,14 +94,29 @@ func handleConnection(conn net.Conn, id int64) {
|
||||
valLen := strconv.Itoa(len(val))
|
||||
str := "$" + valLen + "\r\n" + val + "\r\n"
|
||||
|
||||
report.ReportUpdateRedis(id, "&&"+value[0]+" "+value[1])
|
||||
if is.Rpc() {
|
||||
go client.ReportResult("REDIS", "", "", "&&"+value[0]+" "+value[1], id)
|
||||
} else {
|
||||
go report.ReportUpdateRedis(id, "&&"+value[0]+" "+value[1])
|
||||
}
|
||||
|
||||
conn.Write([]byte(str))
|
||||
}).Catch(func() {
|
||||
conn.Write([]byte("+OK\r\n"))
|
||||
})
|
||||
} else {
|
||||
try.Try(func() {
|
||||
report.ReportUpdateRedis(id, "&&"+value[0]+" "+value[1])
|
||||
if is.Rpc() {
|
||||
go client.ReportResult("REDIS", "", "", "&&"+value[0]+" "+value[1], id)
|
||||
} else {
|
||||
go report.ReportUpdateRedis(id, "&&"+value[0]+" "+value[1])
|
||||
}
|
||||
}).Catch(func() {
|
||||
report.ReportUpdateRedis(id, "&&"+value[0])
|
||||
if is.Rpc() {
|
||||
go client.ReportResult("REDIS", "", "", "&&"+value[0], id)
|
||||
} else {
|
||||
go report.ReportUpdateRedis(id, "&&"+value[0])
|
||||
}
|
||||
})
|
||||
|
||||
conn.Write([]byte("+OK\r\n"))
|
||||
|
@ -4,6 +4,9 @@ import (
|
||||
"github.com/gliderlabs/ssh"
|
||||
"HFish/core/report"
|
||||
"strings"
|
||||
"HFish/utils/log"
|
||||
"HFish/utils/is"
|
||||
"HFish/core/rpc/client"
|
||||
)
|
||||
|
||||
func Start(addr string) {
|
||||
@ -12,7 +15,15 @@ func Start(addr string) {
|
||||
info := s.User() + "&&" + password
|
||||
|
||||
arr := strings.Split(s.RemoteAddr().String(), ":")
|
||||
report.ReportSSH(arr[0], info)
|
||||
|
||||
log.Pr("SSH", arr[0], "已经连接")
|
||||
|
||||
// 判断是否为 RPC 客户端
|
||||
if is.Rpc() {
|
||||
go client.ReportResult("SSH", "", arr[0], info, "0")
|
||||
} else {
|
||||
go report.ReportSSH(arr[0], "本机", info)
|
||||
}
|
||||
|
||||
return false // false 代表 账号密码 不正确
|
||||
}),
|
||||
|
119
core/protocol/telnet/telnet.go
Normal file
@ -0,0 +1,119 @@
|
||||
package telnet
|
||||
|
||||
import (
|
||||
"net"
|
||||
"fmt"
|
||||
"bufio"
|
||||
"strings"
|
||||
"os"
|
||||
"HFish/utils/is"
|
||||
"HFish/core/rpc/client"
|
||||
"strconv"
|
||||
"HFish/core/report"
|
||||
"HFish/utils/log"
|
||||
)
|
||||
|
||||
// 服务端连接
|
||||
func server(address string, exitChan chan int) {
|
||||
l, err := net.Listen("tcp", address)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
exitChan <- 1
|
||||
}
|
||||
|
||||
defer l.Close()
|
||||
|
||||
for {
|
||||
conn, err := l.Accept()
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
arr := strings.Split(conn.RemoteAddr().String(), ":")
|
||||
|
||||
// 判断是否为 RPC 客户端
|
||||
var id string
|
||||
|
||||
if is.Rpc() {
|
||||
id = client.ReportResult("TELNET", "", arr[0], conn.RemoteAddr().String()+" 已经连接", "0")
|
||||
} else {
|
||||
id = strconv.FormatInt(report.ReportTelnet(arr[0], "本机", conn.RemoteAddr().String()+" 已经连接"), 10)
|
||||
}
|
||||
|
||||
log.Pr("Telnet", arr[0], "已经连接")
|
||||
|
||||
// 根据连接开启会话, 这个过程需要并行执行
|
||||
go handleSession(conn, exitChan, id)
|
||||
}
|
||||
}
|
||||
|
||||
// 会话处理
|
||||
func handleSession(conn net.Conn, exitChan chan int, id string) {
|
||||
fmt.Println("Session started")
|
||||
reader := bufio.NewReader(conn)
|
||||
|
||||
for {
|
||||
str, err := reader.ReadString('\n')
|
||||
|
||||
// telnet命令
|
||||
if err == nil {
|
||||
str = strings.TrimSpace(str)
|
||||
|
||||
if is.Rpc() {
|
||||
go client.ReportResult("TELNET", "", "", "&&"+str, id)
|
||||
} else {
|
||||
go report.ReportUpdateRedis(id, "&&"+str)
|
||||
}
|
||||
|
||||
if !processTelnetCommand(str, exitChan) {
|
||||
conn.Close()
|
||||
break
|
||||
}
|
||||
|
||||
conn.Write([]byte("OK" + "\r\n"))
|
||||
} else {
|
||||
// 发生错误
|
||||
fmt.Println("Session closed")
|
||||
conn.Close()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// telent协议命令
|
||||
func processTelnetCommand(str string, exitChan chan int) bool {
|
||||
// @close指令表示终止本次会话
|
||||
if strings.HasPrefix(str, "@close") {
|
||||
fmt.Println("Session closed")
|
||||
// 告知外部需要断开连接
|
||||
return false
|
||||
// @shutdown指令表示终止服务进程
|
||||
} else if strings.HasPrefix(str, "@shutdown") {
|
||||
fmt.Println("Server shutdown")
|
||||
// 往通道中写入0, 阻塞等待接收方处理
|
||||
exitChan <- 0
|
||||
return false
|
||||
}
|
||||
|
||||
// 打印输入的字符串
|
||||
fmt.Println(str)
|
||||
return true
|
||||
|
||||
}
|
||||
|
||||
func Start(addr string) {
|
||||
// 创建一个程序结束码的通道
|
||||
exitChan := make(chan int)
|
||||
|
||||
// 将服务器并发运行
|
||||
go server(addr, exitChan)
|
||||
|
||||
// 通道阻塞,等待接受返回值
|
||||
code := <-exitChan
|
||||
|
||||
// 标记程序返回值并退出
|
||||
os.Exit(code)
|
||||
}
|
@ -3,40 +3,142 @@ package report
|
||||
import (
|
||||
"HFish/core/dbUtil"
|
||||
"time"
|
||||
"HFish/utils/ip"
|
||||
"strings"
|
||||
"HFish/utils/send"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func alert(title string, agent string, ipx string, infox string) {
|
||||
sql := `select status,info from hfish_setting where type = "alertMail"`
|
||||
isAlertStatus := dbUtil.Query(sql)
|
||||
|
||||
status := strconv.FormatInt(isAlertStatus[0]["status"].(int64), 10)
|
||||
|
||||
// 判断是否启用通知
|
||||
if status == "1" {
|
||||
info := isAlertStatus[0]["info"]
|
||||
config := strings.Split(info.(string), "&&")
|
||||
|
||||
text := `
|
||||
<div><b>Hi,上钩了!</b></div>
|
||||
<div><b><br /></b></div>
|
||||
<div><b>集群名称:</b>` + agent + `</div>
|
||||
<div><b>攻击IP:</b>` + ipx + `</div>
|
||||
<div><b>上钩内容:</b>` + infox + `</div>
|
||||
<div><br /></div>
|
||||
<div><span style="color: rgb(128, 128, 128); font-size: 10px;">(HFish 自动发送)</span></div>
|
||||
`
|
||||
|
||||
send.SendMail(config[4:], "[HFish]提醒你,"+title+"有鱼上钩!", text, config)
|
||||
}
|
||||
}
|
||||
|
||||
// 上报 集群 状态
|
||||
func ReportAgentStatus(agentName string, agentIp string, webStatus string, deepStatus string, sshStatus string, redisStatus string, mysqlStatus string, httpStatus string, telnetStatus string, ftpStatus string) {
|
||||
sql := `
|
||||
INSERT INTO hfish_colony (
|
||||
agent_name,
|
||||
agent_ip,
|
||||
web_status,
|
||||
deep_status,
|
||||
ssh_status,
|
||||
redis_status,
|
||||
mysql_status,
|
||||
http_status,
|
||||
telnet_status,
|
||||
ftp_status,
|
||||
last_update_time
|
||||
)
|
||||
VALUES
|
||||
(?,?,?,?,?,?,?,?,?,?,?);
|
||||
`
|
||||
|
||||
id := dbUtil.Insert(sql, agentName, agentIp, webStatus, deepStatus, sshStatus, redisStatus, mysqlStatus, httpStatus, telnetStatus, ftpStatus, time.Now().Format("2006-01-02 15:04:05"))
|
||||
|
||||
// 如果 ID 等于0 代表 该数据以及存在
|
||||
if id == 0 {
|
||||
sql := `
|
||||
UPDATE hfish_colony
|
||||
SET agent_ip = ?, web_status = ?, deep_status = ?, ssh_status = ?, redis_status = ?, mysql_status = ?, http_status = ?, telnet_status = ?, ftp_status = ?, last_update_time =?
|
||||
WHERE
|
||||
agent_name =?;
|
||||
`
|
||||
|
||||
dbUtil.Update(sql, agentIp, webStatus, deepStatus, sshStatus, redisStatus, mysqlStatus, httpStatus, telnetStatus, ftpStatus, time.Now().Format("2006-01-02 15:04:05"), agentName)
|
||||
}
|
||||
}
|
||||
|
||||
// 上报 WEB
|
||||
func ReportWeb(projectName string, ip string, info string) {
|
||||
sql := `INSERT INTO hfish_info(type,project_name,ip,info,create_time) values(?,?,?,?,?);`
|
||||
dbUtil.Insert(sql, "WEB", projectName, ip, info, time.Now().Format("2006-01-02 15:04:05"))
|
||||
func ReportWeb(projectName string, agent string, ipx string, info string) {
|
||||
ipInfo := ip.Get(ipx)
|
||||
sql := `INSERT INTO hfish_info(type,project_name,agent,ip,ip_info,info,create_time) values(?,?,?,?,?,?,?);`
|
||||
dbUtil.Insert(sql, "WEB", projectName, agent, ipx, ipInfo, info, time.Now().Format("2006-01-02 15:04:05"))
|
||||
go alert("WEB", agent, ipx, info)
|
||||
}
|
||||
|
||||
// 上报 暗网 WEB
|
||||
func ReportDeepWeb(projectName string, agent string, ipx string, info string) {
|
||||
ipInfo := ip.Get(ipx)
|
||||
sql := `INSERT INTO hfish_info(type,project_name,agent,ip,ip_info,info,create_time) values(?,?,?,?,?,?,?);`
|
||||
dbUtil.Insert(sql, "DEEP", projectName, agent, ipx, ipInfo, info, time.Now().Format("2006-01-02 15:04:05"))
|
||||
go alert("DEEP", agent, ipx, info)
|
||||
}
|
||||
|
||||
// 上报 SSH
|
||||
func ReportSSH(ip string, info string) {
|
||||
sql := `INSERT INTO hfish_info(type,project_name,ip,info,create_time) values(?,?,?,?,?);`
|
||||
dbUtil.Insert(sql, "SSH", "SSH钓鱼", ip, info, time.Now().Format("2006-01-02 15:04:05"))
|
||||
func ReportSSH(ipx string, agent string, info string) {
|
||||
ipInfo := ip.Get(ipx)
|
||||
sql := `INSERT INTO hfish_info(type,project_name,agent,ip,ip_info,info,create_time) values(?,?,?,?,?,?,?);`
|
||||
dbUtil.Insert(sql, "SSH", "SSH蜜罐", agent, ipx, ipInfo, info, time.Now().Format("2006-01-02 15:04:05"))
|
||||
go alert("SSH", agent, ipx, info)
|
||||
}
|
||||
|
||||
// 上报 Redis
|
||||
func ReportRedis(ip string, info string) int64 {
|
||||
sql := `INSERT INTO hfish_info(type,project_name,ip,info,create_time) values(?,?,?,?,?);`
|
||||
return dbUtil.Insert(sql, "REDIS", "Redis钓鱼", ip, info, time.Now().Format("2006-01-02 15:04:05"))
|
||||
func ReportRedis(ipx string, agent string, info string) int64 {
|
||||
ipInfo := ip.Get(ipx)
|
||||
sql := `INSERT INTO hfish_info(type,project_name,agent,ip,ip_info,info,create_time) values(?,?,?,?,?,?,?);`
|
||||
go alert("REDIS", agent, ipx, info)
|
||||
return dbUtil.Insert(sql, "REDIS", "Redis蜜罐", agent, ipx, ipInfo, info, time.Now().Format("2006-01-02 15:04:05"))
|
||||
}
|
||||
|
||||
// 更新 Redis 操作
|
||||
func ReportUpdateRedis(id int64, info string) {
|
||||
func ReportUpdateRedis(id string, info string) {
|
||||
sql := `UPDATE hfish_info SET info = info||? WHERE id = ?;`
|
||||
dbUtil.Update(sql, info, id)
|
||||
}
|
||||
|
||||
// 上报 Mysql
|
||||
func ReportMysql(ip string, info string) int64 {
|
||||
sql := `INSERT INTO hfish_info(type,project_name,ip,info,create_time) values(?,?,?,?,?);`
|
||||
return dbUtil.Insert(sql, "MYSQL", "Mysql钓鱼", ip, info, time.Now().Format("2006-01-02 15:04:05"))
|
||||
func ReportMysql(ipx string, agent string, info string) int64 {
|
||||
ipInfo := ip.Get(ipx)
|
||||
sql := `INSERT INTO hfish_info(type,project_name,agent,ip,ip_info,info,create_time) values(?,?,?,?,?,?,?);`
|
||||
go alert("MYSQL", agent, ipx, info)
|
||||
return dbUtil.Insert(sql, "MYSQL", "Mysql蜜罐", agent, ipx, ipInfo, info, time.Now().Format("2006-01-02 15:04:05"))
|
||||
}
|
||||
|
||||
// 更新 Redis 操作
|
||||
func ReportUpdateMysql(id int64, info string) {
|
||||
// 更新 Mysql 操作
|
||||
func ReportUpdateMysql(id string, info string) {
|
||||
sql := `UPDATE hfish_info SET info = info||? WHERE id = ?;`
|
||||
dbUtil.Update(sql, info, id)
|
||||
}
|
||||
|
||||
// 上报 FTP
|
||||
func ReportFTP(ipx string, agent string, info string) {
|
||||
ipInfo := ip.Get(ipx)
|
||||
sql := `INSERT INTO hfish_info(type,project_name,agent,ip,ip_info,info,create_time) values(?,?,?,?,?,?,?);`
|
||||
dbUtil.Insert(sql, "FTP", "FTP蜜罐", agent, ipx, ipInfo, info, time.Now().Format("2006-01-02 15:04:05"))
|
||||
go alert("FTP", agent, ipx, info)
|
||||
}
|
||||
|
||||
// 上报 Telnet
|
||||
func ReportTelnet(ipx string, agent string, info string) int64 {
|
||||
ipInfo := ip.Get(ipx)
|
||||
sql := `INSERT INTO hfish_info(type,project_name,agent,ip,ip_info,info,create_time) values(?,?,?,?,?,?,?);`
|
||||
go alert("TELNET", agent, ipx, info)
|
||||
return dbUtil.Insert(sql, "TELNET", "Telnet蜜罐", agent, ipx, ipInfo, info, time.Now().Format("2006-01-02 15:04:05"))
|
||||
}
|
||||
|
||||
// 更新 Telnet 操作
|
||||
func ReportUpdateTelnet(id string, info string) {
|
||||
sql := `UPDATE hfish_info SET info = info||? WHERE id = ?;`
|
||||
dbUtil.Update(sql, info, id)
|
||||
}
|
||||
|
103
core/rpc/client/client.go
Normal file
@ -0,0 +1,103 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"net/rpc"
|
||||
"HFish/utils/log"
|
||||
"HFish/utils/conf"
|
||||
"HFish/utils/ip"
|
||||
)
|
||||
|
||||
// 上报状态结构
|
||||
type Status struct {
|
||||
AgentIp string
|
||||
AgentName string
|
||||
Web, Deep, Ssh, Redis, Mysql, Http, Telnet, Ftp string
|
||||
}
|
||||
|
||||
// 上报结果结构
|
||||
type Result struct {
|
||||
AgentIp string
|
||||
AgentName string
|
||||
Type string
|
||||
ProjectName string
|
||||
SourceIp string
|
||||
Info string
|
||||
Id string // 数据库ID,更新用 0 为新插入数据
|
||||
}
|
||||
|
||||
func createClient() (*rpc.Client, bool) {
|
||||
rpcAddr := conf.Get("rpc", "addr")
|
||||
client, err := rpc.Dial("tcp", rpcAddr)
|
||||
|
||||
if err != nil {
|
||||
log.Pr("RPC", "127.0.0.1", "连接 RPC Server 失败")
|
||||
return client, false
|
||||
}
|
||||
|
||||
return client, true
|
||||
}
|
||||
|
||||
func reportStatus(rpcName string, ftpStatus string, telnetStatus string, httpStatus string, mysqlStatus string, redisStatus string, sshStatus string, webStatus string, darkStatus string) {
|
||||
client, boolStatus := createClient()
|
||||
|
||||
if boolStatus {
|
||||
defer client.Close()
|
||||
|
||||
status := Status{
|
||||
ip.GetLocalIp(),
|
||||
rpcName,
|
||||
webStatus,
|
||||
darkStatus,
|
||||
sshStatus,
|
||||
redisStatus,
|
||||
mysqlStatus,
|
||||
httpStatus,
|
||||
telnetStatus,
|
||||
ftpStatus,
|
||||
}
|
||||
|
||||
var reply string
|
||||
err := client.Call("HFishRPCService.ReportStatus", status, &reply)
|
||||
|
||||
if err != nil {
|
||||
log.Pr("RPC", "127.0.0.1", "上报服务状态失败", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ReportResult(typex string, projectName string, sourceIp string, info string, id string) string {
|
||||
// projectName 只有 WEB 才需要传项目名 其他协议空即可
|
||||
// id 0 为 新插入数据,非 0 都是更新数据
|
||||
// id 非 0 的时候 sourceIp 为空
|
||||
client, boolStatus := createClient()
|
||||
|
||||
if boolStatus {
|
||||
defer client.Close()
|
||||
|
||||
rpcName := conf.Get("rpc", "name")
|
||||
|
||||
result := Result{
|
||||
ip.GetLocalIp(),
|
||||
rpcName,
|
||||
typex,
|
||||
projectName,
|
||||
sourceIp,
|
||||
info,
|
||||
id,
|
||||
}
|
||||
|
||||
var reply string
|
||||
err := client.Call("HFishRPCService.ReportResult", result, &reply)
|
||||
|
||||
if err != nil {
|
||||
log.Pr("RPC", "127.0.0.1", "上报上钩结果失败")
|
||||
}
|
||||
|
||||
return reply
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func Start(rpcName string, ftpStatus string, telnetStatus string, httpStatus string, mysqlStatus string, redisStatus string, sshStatus string, webStatus string, darkStatus string) {
|
||||
reportStatus(rpcName, ftpStatus, telnetStatus, httpStatus, mysqlStatus, redisStatus, sshStatus, webStatus, darkStatus)
|
||||
}
|
113
core/rpc/server/server.go
Normal file
@ -0,0 +1,113 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"net/rpc"
|
||||
"net"
|
||||
"HFish/utils/log"
|
||||
"HFish/core/report"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// 上报状态结构
|
||||
type Status struct {
|
||||
AgentIp string
|
||||
AgentName string
|
||||
Web, Deep, Ssh, Redis, Mysql, Http, Telnet, Ftp string
|
||||
}
|
||||
|
||||
// 上报结果结构
|
||||
type Result struct {
|
||||
AgentIp string
|
||||
AgentName string
|
||||
Type string
|
||||
ProjectName string
|
||||
SourceIp string
|
||||
Info string
|
||||
Id string // 数据库ID,更新用 0 为新插入数据
|
||||
}
|
||||
|
||||
type HFishRPCService int
|
||||
|
||||
// 上报状态 RPC 方法
|
||||
func (t *HFishRPCService) ReportStatus(s *Status, reply *string) error {
|
||||
|
||||
// 上报 客户端 状态
|
||||
go report.ReportAgentStatus(
|
||||
s.AgentName,
|
||||
s.AgentIp,
|
||||
s.Web,
|
||||
s.Deep,
|
||||
s.Ssh,
|
||||
s.Redis,
|
||||
s.Mysql,
|
||||
s.Http,
|
||||
s.Telnet,
|
||||
s.Ftp,
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 上报结果 RPC 方法
|
||||
func (t *HFishRPCService) ReportResult(r *Result, reply *string) error {
|
||||
var idx string
|
||||
|
||||
switch r.Type {
|
||||
case "WEB":
|
||||
go report.ReportWeb(r.ProjectName, r.AgentName, r.SourceIp, r.Info)
|
||||
case "DEEP":
|
||||
go report.ReportDeepWeb(r.ProjectName, r.AgentName, r.SourceIp, r.Info)
|
||||
case "SSH":
|
||||
go report.ReportSSH(r.SourceIp, r.AgentName, r.Info)
|
||||
case "REDIS":
|
||||
if r.Id == "0" {
|
||||
id := report.ReportRedis(r.SourceIp, r.AgentName, r.Info)
|
||||
idx = strconv.FormatInt(id, 10)
|
||||
} else {
|
||||
go report.ReportUpdateRedis(r.Id, r.Info)
|
||||
}
|
||||
case "MYSQL":
|
||||
if r.Id == "0" {
|
||||
id := report.ReportMysql(r.SourceIp, r.AgentName, r.Info)
|
||||
idx = strconv.FormatInt(id, 10)
|
||||
} else {
|
||||
go report.ReportUpdateMysql(r.Id, r.Info)
|
||||
}
|
||||
case "TELNET":
|
||||
if r.Id == "0" {
|
||||
id := report.ReportTelnet(r.SourceIp, r.AgentName, r.Info)
|
||||
idx = strconv.FormatInt(id, 10)
|
||||
} else {
|
||||
go report.ReportUpdateTelnet(r.Id, r.Info)
|
||||
}
|
||||
case "FTP":
|
||||
go report.ReportFTP(r.SourceIp, r.AgentName, r.Info)
|
||||
}
|
||||
|
||||
*reply = idx
|
||||
return nil
|
||||
}
|
||||
|
||||
// 启动 RPC 服务端
|
||||
func Start(addr string) {
|
||||
rpcService := new(HFishRPCService)
|
||||
rpc.Register(rpcService)
|
||||
|
||||
tcpAddr, err := net.ResolveTCPAddr("tcp", addr)
|
||||
if err != nil {
|
||||
log.Pr("RPC", "127.0.0.1", "RPC Server 启动失败", err)
|
||||
}
|
||||
|
||||
listener, err := net.ListenTCP("tcp", tcpAddr)
|
||||
if err != nil {
|
||||
log.Pr("RPC", "127.0.0.1", "RPC Server 监听地址失败", err)
|
||||
}
|
||||
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
rpc.ServeConn(conn)
|
||||
}
|
||||
}
|
BIN
db/hfish.db
@ -7,8 +7,8 @@ import (
|
||||
|
||||
func Check(e error, tips string) {
|
||||
if e != nil {
|
||||
panic(e)
|
||||
fmt.Println(tips)
|
||||
panic(e)
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,3 +48,10 @@ func ErrLoginFail() map[string]interface{} {
|
||||
"msg": "账号密码不正确",
|
||||
}
|
||||
}
|
||||
|
||||
func ErrEmailFail() map[string]interface{} {
|
||||
return gin.H{
|
||||
"code": 1003,
|
||||
"msg": "邮箱未启用",
|
||||
}
|
||||
}
|
||||
|
2
go.mod
@ -4,9 +4,11 @@ go 1.12
|
||||
|
||||
require (
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 // indirect
|
||||
github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
|
||||
github.com/gin-gonic/gin v1.4.0
|
||||
github.com/gliderlabs/ssh v0.2.2
|
||||
github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869
|
||||
github.com/mattn/go-sqlite3 v1.11.0
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
|
5
go.sum
@ -1,5 +1,7 @@
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 h1:OYA+5W64v3OgClL+IrOD63t4i/RW7RqrAVl9LTZ9UqQ=
|
||||
github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394/go.mod h1:Q8n74mJTIgjX4RBBcHnJ05h//6/k6foqmgE45jTQtxg=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
|
||||
@ -12,6 +14,8 @@ github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
|
||||
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 h1:IPJ3dvxmJ4uczJe5YQdrYB16oTJlGSC/OyZDqUk9xX4=
|
||||
github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869/go.mod h1:cJ6Cj7dQo+O6GJNiMx+Pa94qKj+TG8ONdKHgMNIyyag=
|
||||
github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc=
|
||||
@ -36,6 +40,7 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||
|
BIN
images/1.png
Before Width: | Height: | Size: 782 KiB |
BIN
images/2.png
Before Width: | Height: | Size: 776 KiB |
BIN
images/3.png
Before Width: | Height: | Size: 1.0 MiB |
BIN
images/colony.png
Normal file
After Width: | Height: | Size: 528 KiB |
BIN
images/dashboard.png
Normal file
After Width: | Height: | Size: 812 KiB |
BIN
images/fish.png
Normal file
After Width: | Height: | Size: 899 KiB |
BIN
images/ftp.png
Normal file
After Width: | Height: | Size: 772 KiB |
BIN
images/login.png
Normal file
After Width: | Height: | Size: 1.1 MiB |
BIN
images/mail.png
Normal file
After Width: | Height: | Size: 761 KiB |
BIN
images/ssh.png
Normal file
After Width: | Height: | Size: 815 KiB |
BIN
images/telnet.png
Normal file
After Width: | Height: | Size: 720 KiB |
BIN
images/web.png
Normal file
After Width: | Height: | Size: 644 KiB |
@ -1,21 +0,0 @@
|
||||
[GIN] 2019/08/04 - 20:21:16 | 200 | 1.61658ms | 127.0.0.1 | GET /fish
|
||||
[GIN] 2019/08/04 - 20:21:16 | 200 | 2.738172ms | 127.0.0.1 | GET /get/fish/list
|
||||
[GIN] 2019/08/04 - 20:21:16 | 200 | 4.061843ms | 127.0.0.1 | GET /static/favicon.ico
|
||||
[GIN] 2019/08/04 - 20:22:24 | 200 | 1.333823ms | 127.0.0.1 | GET /fish
|
||||
[GIN] 2019/08/04 - 20:22:24 | 200 | 889.568µs | 127.0.0.1 | GET /get/fish/list
|
||||
[GIN] 2019/08/04 - 20:22:24 | 200 | 770.409µs | 127.0.0.1 | GET /static/favicon.ico
|
||||
[GIN] 2019/08/04 - 20:22:25 | 200 | 902.677µs | 127.0.0.1 | GET /get/fish/info?id=77
|
||||
[GIN] 2019/08/04 - 20:33:01 | 200 | 3.505252ms | 127.0.0.1 | POST /post/fish/del
|
||||
[GIN] 2019/08/04 - 20:33:01 | 200 | 1.243913ms | 127.0.0.1 | GET /fish
|
||||
[GIN] 2019/08/04 - 20:33:01 | 200 | 794.164µs | 127.0.0.1 | GET /get/fish/list
|
||||
[GIN] 2019/08/04 - 20:33:02 | 200 | 3.586026ms | 127.0.0.1 | POST /post/fish/del
|
||||
[GIN] 2019/08/04 - 20:33:02 | 200 | 1.35138ms | 127.0.0.1 | GET /fish
|
||||
[GIN] 2019/08/04 - 20:33:02 | 200 | 650.803µs | 127.0.0.1 | GET /get/fish/list
|
||||
[GIN] 2019/08/04 - 20:33:41 | 200 | 3.068789ms | 127.0.0.1 | POST /api/v1/post/report
|
||||
[GIN] 2019/08/04 - 20:33:45 | 200 | 1.526386ms | 127.0.0.1 | GET /fish
|
||||
[GIN] 2019/08/04 - 20:33:45 | 200 | 781.029µs | 127.0.0.1 | GET /get/fish/list
|
||||
[GIN] 2019/08/04 - 20:33:45 | 200 | 425.853µs | 127.0.0.1 | GET /static/favicon.ico
|
||||
[GIN] 2019/08/04 - 20:33:48 | 200 | 3.641487ms | 127.0.0.1 | POST /post/fish/del
|
||||
[GIN] 2019/08/04 - 20:33:48 | 200 | 1.129608ms | 127.0.0.1 | GET /fish
|
||||
[GIN] 2019/08/04 - 20:33:48 | 200 | 717.537µs | 127.0.0.1 | GET /get/fish/list
|
||||
[GIN] 2019/08/04 - 20:33:49 | 200 | 740.755µs | 127.0.0.1 | GET /get/fish/info?id=78
|
4
main.go
@ -1,8 +1,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"fmt"
|
||||
"os"
|
||||
"HFish/utils/setting"
|
||||
)
|
||||
|
||||
@ -16,7 +16,7 @@ func main() {
|
||||
} else if args[1] == "init" || args[1] == "--init" {
|
||||
setting.Init()
|
||||
} else if args[1] == "version" || args[1] == "--version" {
|
||||
fmt.Println("v0.1")
|
||||
fmt.Println("v0.2")
|
||||
} else if args[1] == "run" || args[1] == "--run" {
|
||||
setting.Run()
|
||||
} else {
|
||||
|
@ -6619,13 +6619,38 @@ File: Menu
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.avatarx{
|
||||
box-shadow: 0 1px 16px rgba(0, 0, 0, 0.53);
|
||||
border: 1px solid #333;
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
/* border: 2px solid #444; */
|
||||
padding: 1px;
|
||||
border-radius: 100%;
|
||||
cursor: pointer;
|
||||
-webkit-border-radius: 100%;
|
||||
-moz-border-radius: 100%;
|
||||
/* box-shadow: inset 0 -1px 0 #3333; */
|
||||
-webkit-transition: 0.4s;
|
||||
-webkit-transition: -webkit-transform 0.6s ease-out;
|
||||
transition: transform 0.6s ease-out;
|
||||
-moz-transition: -moz-transform 0.6s ease-out;
|
||||
}
|
||||
|
||||
.avatarx:hover {
|
||||
transform: rotateZ(360deg);
|
||||
-webkit-transform: rotateZ(360deg);
|
||||
-moz-transform: rotateZ(360deg);
|
||||
}
|
||||
|
||||
.logo {
|
||||
float: left;
|
||||
color: #fff !important;
|
||||
font-size: 20px;
|
||||
font-size: 26px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 1px;
|
||||
line-height: 64px;
|
||||
font-family: fantasy;
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
.logo span span {
|
||||
|
8
static/libs/counterup/jquery.counterup.min.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/*!
|
||||
* jquery.counterup.js 1.0
|
||||
*
|
||||
* Copyright 2013, Benjamin Intal http://gambit.ph @bfintal
|
||||
* Released under the GPL v2 License
|
||||
*
|
||||
* Date: Nov 26, 2013
|
||||
*/(function(e){"use strict";e.fn.counterUp=function(t){var n=e.extend({time:400,delay:10},t);return this.each(function(){var t=e(this),r=n,i=function(){var e=[],n=r.time/r.delay,i=t.text(),s=/[0-9]+,[0-9]+/.test(i);i=i.replace(/,/g,"");var o=/^[0-9]+$/.test(i),u=/^[0-9]+\.[0-9]+$/.test(i),a=u?(i.split(".")[1]||[]).length:0;for(var f=n;f>=1;f--){var l=parseInt(i/n*f);u&&(l=parseFloat(i/n*f).toFixed(a));if(s)while(/(\d+)(\d{3})/.test(l.toString()))l=l.toString().replace(/(\d+)(\d{3})/,"$1,$2");e.unshift(l)}t.data("counterup-nums",e);t.text("0");var c=function(){t.text(t.data("counterup-nums").shift());if(t.data("counterup-nums").length)setTimeout(t.data("counterup-func"),r.delay);else{delete t.data("counterup-nums");t.data("counterup-nums",null);t.data("counterup-func",null)}};t.data("counterup-func",c);setTimeout(t.data("counterup-func"),r.delay)};t.waypoint(i,{offset:"100%",triggerOnce:!0})})}})(jQuery);
|
1
static/libs/moment/moment.min.js
vendored
Normal file
BIN
static/libs/wangeditor3/fonts/w-e-icon.woff
Normal file
411
static/libs/wangeditor3/wangEditor.css
Normal file
4674
static/libs/wangeditor3/wangEditor.js
Normal file
1
static/libs/wangeditor3/wangEditor.min.css
vendored
Normal file
4
static/libs/wangeditor3/wangEditor.min.js
vendored
Normal file
1
static/libs/wangeditor3/wangEditor.min.js.map
Normal file
647
static/libs/waypoints/lib/jquery.waypoints.js
Normal file
@ -0,0 +1,647 @@
|
||||
/*!
|
||||
Waypoints - 3.1.1
|
||||
Copyright © 2011-2015 Caleb Troughton
|
||||
Licensed under the MIT license.
|
||||
https://github.com/imakewebthings/waypoints/blog/master/licenses.txt
|
||||
*/
|
||||
(function() {
|
||||
'use strict'
|
||||
|
||||
var keyCounter = 0
|
||||
var allWaypoints = {}
|
||||
|
||||
/* http://imakewebthings.com/waypoints/api/waypoint */
|
||||
function Waypoint(options) {
|
||||
if (!options) {
|
||||
throw new Error('No options passed to Waypoint constructor')
|
||||
}
|
||||
if (!options.element) {
|
||||
throw new Error('No element option passed to Waypoint constructor')
|
||||
}
|
||||
if (!options.handler) {
|
||||
throw new Error('No handler option passed to Waypoint constructor')
|
||||
}
|
||||
|
||||
this.key = 'waypoint-' + keyCounter
|
||||
this.options = Waypoint.Adapter.extend({}, Waypoint.defaults, options)
|
||||
this.element = this.options.element
|
||||
this.adapter = new Waypoint.Adapter(this.element)
|
||||
this.callback = options.handler
|
||||
this.axis = this.options.horizontal ? 'horizontal' : 'vertical'
|
||||
this.enabled = this.options.enabled
|
||||
this.triggerPoint = null
|
||||
this.group = Waypoint.Group.findOrCreate({
|
||||
name: this.options.group,
|
||||
axis: this.axis
|
||||
})
|
||||
this.context = Waypoint.Context.findOrCreateByElement(this.options.context)
|
||||
|
||||
if (Waypoint.offsetAliases[this.options.offset]) {
|
||||
this.options.offset = Waypoint.offsetAliases[this.options.offset]
|
||||
}
|
||||
this.group.add(this)
|
||||
this.context.add(this)
|
||||
allWaypoints[this.key] = this
|
||||
keyCounter += 1
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Waypoint.prototype.queueTrigger = function(direction) {
|
||||
this.group.queueTrigger(this, direction)
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Waypoint.prototype.trigger = function(args) {
|
||||
if (!this.enabled) {
|
||||
return
|
||||
}
|
||||
if (this.callback) {
|
||||
this.callback.apply(this, args)
|
||||
}
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/destroy */
|
||||
Waypoint.prototype.destroy = function() {
|
||||
this.context.remove(this)
|
||||
this.group.remove(this)
|
||||
delete allWaypoints[this.key]
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/disable */
|
||||
Waypoint.prototype.disable = function() {
|
||||
this.enabled = false
|
||||
return this
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/enable */
|
||||
Waypoint.prototype.enable = function() {
|
||||
this.context.refresh()
|
||||
this.enabled = true
|
||||
return this
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/next */
|
||||
Waypoint.prototype.next = function() {
|
||||
return this.group.next(this)
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/previous */
|
||||
Waypoint.prototype.previous = function() {
|
||||
return this.group.previous(this)
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Waypoint.invokeAll = function(method) {
|
||||
var allWaypointsArray = []
|
||||
for (var waypointKey in allWaypoints) {
|
||||
allWaypointsArray.push(allWaypoints[waypointKey])
|
||||
}
|
||||
for (var i = 0, end = allWaypointsArray.length; i < end; i++) {
|
||||
allWaypointsArray[i][method]()
|
||||
}
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/destroy-all */
|
||||
Waypoint.destroyAll = function() {
|
||||
Waypoint.invokeAll('destroy')
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/disable-all */
|
||||
Waypoint.disableAll = function() {
|
||||
Waypoint.invokeAll('disable')
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/enable-all */
|
||||
Waypoint.enableAll = function() {
|
||||
Waypoint.invokeAll('enable')
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/refresh-all */
|
||||
Waypoint.refreshAll = function() {
|
||||
Waypoint.Context.refreshAll()
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/viewport-height */
|
||||
Waypoint.viewportHeight = function() {
|
||||
return window.innerHeight || document.documentElement.clientHeight
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/viewport-width */
|
||||
Waypoint.viewportWidth = function() {
|
||||
return document.documentElement.clientWidth
|
||||
}
|
||||
|
||||
Waypoint.adapters = []
|
||||
|
||||
Waypoint.defaults = {
|
||||
context: window,
|
||||
continuous: true,
|
||||
enabled: true,
|
||||
group: 'default',
|
||||
horizontal: false,
|
||||
offset: 0
|
||||
}
|
||||
|
||||
Waypoint.offsetAliases = {
|
||||
'bottom-in-view': function() {
|
||||
return this.context.innerHeight() - this.adapter.outerHeight()
|
||||
},
|
||||
'right-in-view': function() {
|
||||
return this.context.innerWidth() - this.adapter.outerWidth()
|
||||
}
|
||||
}
|
||||
|
||||
window.Waypoint = Waypoint
|
||||
}())
|
||||
;(function() {
|
||||
'use strict'
|
||||
|
||||
function requestAnimationFrameShim(callback) {
|
||||
window.setTimeout(callback, 1000 / 60)
|
||||
}
|
||||
|
||||
var keyCounter = 0
|
||||
var contexts = {}
|
||||
var Waypoint = window.Waypoint
|
||||
var oldWindowLoad = window.onload
|
||||
|
||||
/* http://imakewebthings.com/waypoints/api/context */
|
||||
function Context(element) {
|
||||
this.element = element
|
||||
this.Adapter = Waypoint.Adapter
|
||||
this.adapter = new this.Adapter(element)
|
||||
this.key = 'waypoint-context-' + keyCounter
|
||||
this.didScroll = false
|
||||
this.didResize = false
|
||||
this.oldScroll = {
|
||||
x: this.adapter.scrollLeft(),
|
||||
y: this.adapter.scrollTop()
|
||||
}
|
||||
this.waypoints = {
|
||||
vertical: {},
|
||||
horizontal: {}
|
||||
}
|
||||
|
||||
element.waypointContextKey = this.key
|
||||
contexts[element.waypointContextKey] = this
|
||||
keyCounter += 1
|
||||
|
||||
this.createThrottledScrollHandler()
|
||||
this.createThrottledResizeHandler()
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Context.prototype.add = function(waypoint) {
|
||||
var axis = waypoint.options.horizontal ? 'horizontal' : 'vertical'
|
||||
this.waypoints[axis][waypoint.key] = waypoint
|
||||
this.refresh()
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Context.prototype.checkEmpty = function() {
|
||||
var horizontalEmpty = this.Adapter.isEmptyObject(this.waypoints.horizontal)
|
||||
var verticalEmpty = this.Adapter.isEmptyObject(this.waypoints.vertical)
|
||||
if (horizontalEmpty && verticalEmpty) {
|
||||
this.adapter.off('.waypoints')
|
||||
delete contexts[this.key]
|
||||
}
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Context.prototype.createThrottledResizeHandler = function() {
|
||||
var self = this
|
||||
|
||||
function resizeHandler() {
|
||||
self.handleResize()
|
||||
self.didResize = false
|
||||
}
|
||||
|
||||
this.adapter.on('resize.waypoints', function() {
|
||||
if (!self.didResize) {
|
||||
self.didResize = true
|
||||
Waypoint.requestAnimationFrame(resizeHandler)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Context.prototype.createThrottledScrollHandler = function() {
|
||||
var self = this
|
||||
function scrollHandler() {
|
||||
self.handleScroll()
|
||||
self.didScroll = false
|
||||
}
|
||||
|
||||
this.adapter.on('scroll.waypoints', function() {
|
||||
if (!self.didScroll || Waypoint.isTouch) {
|
||||
self.didScroll = true
|
||||
Waypoint.requestAnimationFrame(scrollHandler)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Context.prototype.handleResize = function() {
|
||||
Waypoint.Context.refreshAll()
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Context.prototype.handleScroll = function() {
|
||||
var triggeredGroups = {}
|
||||
var axes = {
|
||||
horizontal: {
|
||||
newScroll: this.adapter.scrollLeft(),
|
||||
oldScroll: this.oldScroll.x,
|
||||
forward: 'right',
|
||||
backward: 'left'
|
||||
},
|
||||
vertical: {
|
||||
newScroll: this.adapter.scrollTop(),
|
||||
oldScroll: this.oldScroll.y,
|
||||
forward: 'down',
|
||||
backward: 'up'
|
||||
}
|
||||
}
|
||||
|
||||
for (var axisKey in axes) {
|
||||
var axis = axes[axisKey]
|
||||
var isForward = axis.newScroll > axis.oldScroll
|
||||
var direction = isForward ? axis.forward : axis.backward
|
||||
|
||||
for (var waypointKey in this.waypoints[axisKey]) {
|
||||
var waypoint = this.waypoints[axisKey][waypointKey]
|
||||
var wasBeforeTriggerPoint = axis.oldScroll < waypoint.triggerPoint
|
||||
var nowAfterTriggerPoint = axis.newScroll >= waypoint.triggerPoint
|
||||
var crossedForward = wasBeforeTriggerPoint && nowAfterTriggerPoint
|
||||
var crossedBackward = !wasBeforeTriggerPoint && !nowAfterTriggerPoint
|
||||
if (crossedForward || crossedBackward) {
|
||||
waypoint.queueTrigger(direction)
|
||||
triggeredGroups[waypoint.group.id] = waypoint.group
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var groupKey in triggeredGroups) {
|
||||
triggeredGroups[groupKey].flushTriggers()
|
||||
}
|
||||
|
||||
this.oldScroll = {
|
||||
x: axes.horizontal.newScroll,
|
||||
y: axes.vertical.newScroll
|
||||
}
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Context.prototype.innerHeight = function() {
|
||||
/*eslint-disable eqeqeq */
|
||||
if (this.element == this.element.window) {
|
||||
return Waypoint.viewportHeight()
|
||||
}
|
||||
/*eslint-enable eqeqeq */
|
||||
return this.adapter.innerHeight()
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Context.prototype.remove = function(waypoint) {
|
||||
delete this.waypoints[waypoint.axis][waypoint.key]
|
||||
this.checkEmpty()
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Context.prototype.innerWidth = function() {
|
||||
/*eslint-disable eqeqeq */
|
||||
if (this.element == this.element.window) {
|
||||
return Waypoint.viewportWidth()
|
||||
}
|
||||
/*eslint-enable eqeqeq */
|
||||
return this.adapter.innerWidth()
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/context-destroy */
|
||||
Context.prototype.destroy = function() {
|
||||
var allWaypoints = []
|
||||
for (var axis in this.waypoints) {
|
||||
for (var waypointKey in this.waypoints[axis]) {
|
||||
allWaypoints.push(this.waypoints[axis][waypointKey])
|
||||
}
|
||||
}
|
||||
for (var i = 0, end = allWaypoints.length; i < end; i++) {
|
||||
allWaypoints[i].destroy()
|
||||
}
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/context-refresh */
|
||||
Context.prototype.refresh = function() {
|
||||
/*eslint-disable eqeqeq */
|
||||
var isWindow = this.element == this.element.window
|
||||
/*eslint-enable eqeqeq */
|
||||
var contextOffset = this.adapter.offset()
|
||||
var triggeredGroups = {}
|
||||
var axes
|
||||
|
||||
this.handleScroll()
|
||||
axes = {
|
||||
horizontal: {
|
||||
contextOffset: isWindow ? 0 : contextOffset.left,
|
||||
contextScroll: isWindow ? 0 : this.oldScroll.x,
|
||||
contextDimension: this.innerWidth(),
|
||||
oldScroll: this.oldScroll.x,
|
||||
forward: 'right',
|
||||
backward: 'left',
|
||||
offsetProp: 'left'
|
||||
},
|
||||
vertical: {
|
||||
contextOffset: isWindow ? 0 : contextOffset.top,
|
||||
contextScroll: isWindow ? 0 : this.oldScroll.y,
|
||||
contextDimension: this.innerHeight(),
|
||||
oldScroll: this.oldScroll.y,
|
||||
forward: 'down',
|
||||
backward: 'up',
|
||||
offsetProp: 'top'
|
||||
}
|
||||
}
|
||||
|
||||
for (var axisKey in axes) {
|
||||
var axis = axes[axisKey]
|
||||
for (var waypointKey in this.waypoints[axisKey]) {
|
||||
var waypoint = this.waypoints[axisKey][waypointKey]
|
||||
var adjustment = waypoint.options.offset
|
||||
var oldTriggerPoint = waypoint.triggerPoint
|
||||
var elementOffset = 0
|
||||
var freshWaypoint = oldTriggerPoint == null
|
||||
var contextModifier, wasBeforeScroll, nowAfterScroll
|
||||
var triggeredBackward, triggeredForward
|
||||
|
||||
if (waypoint.element !== waypoint.element.window) {
|
||||
elementOffset = waypoint.adapter.offset()[axis.offsetProp]
|
||||
}
|
||||
|
||||
if (typeof adjustment === 'function') {
|
||||
adjustment = adjustment.apply(waypoint)
|
||||
}
|
||||
else if (typeof adjustment === 'string') {
|
||||
adjustment = parseFloat(adjustment)
|
||||
if (waypoint.options.offset.indexOf('%') > - 1) {
|
||||
adjustment = Math.ceil(axis.contextDimension * adjustment / 100)
|
||||
}
|
||||
}
|
||||
|
||||
contextModifier = axis.contextScroll - axis.contextOffset
|
||||
waypoint.triggerPoint = elementOffset + contextModifier - adjustment
|
||||
wasBeforeScroll = oldTriggerPoint < axis.oldScroll
|
||||
nowAfterScroll = waypoint.triggerPoint >= axis.oldScroll
|
||||
triggeredBackward = wasBeforeScroll && nowAfterScroll
|
||||
triggeredForward = !wasBeforeScroll && !nowAfterScroll
|
||||
|
||||
if (!freshWaypoint && triggeredBackward) {
|
||||
waypoint.queueTrigger(axis.backward)
|
||||
triggeredGroups[waypoint.group.id] = waypoint.group
|
||||
}
|
||||
else if (!freshWaypoint && triggeredForward) {
|
||||
waypoint.queueTrigger(axis.forward)
|
||||
triggeredGroups[waypoint.group.id] = waypoint.group
|
||||
}
|
||||
else if (freshWaypoint && axis.oldScroll >= waypoint.triggerPoint) {
|
||||
waypoint.queueTrigger(axis.forward)
|
||||
triggeredGroups[waypoint.group.id] = waypoint.group
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var groupKey in triggeredGroups) {
|
||||
triggeredGroups[groupKey].flushTriggers()
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Context.findOrCreateByElement = function(element) {
|
||||
return Context.findByElement(element) || new Context(element)
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Context.refreshAll = function() {
|
||||
for (var contextId in contexts) {
|
||||
contexts[contextId].refresh()
|
||||
}
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/context-find-by-element */
|
||||
Context.findByElement = function(element) {
|
||||
return contexts[element.waypointContextKey]
|
||||
}
|
||||
|
||||
window.onload = function() {
|
||||
if (oldWindowLoad) {
|
||||
oldWindowLoad()
|
||||
}
|
||||
Context.refreshAll()
|
||||
}
|
||||
|
||||
Waypoint.requestAnimationFrame = function(callback) {
|
||||
var requestFn = window.requestAnimationFrame ||
|
||||
window.mozRequestAnimationFrame ||
|
||||
window.webkitRequestAnimationFrame ||
|
||||
requestAnimationFrameShim
|
||||
requestFn.call(window, callback)
|
||||
}
|
||||
Waypoint.Context = Context
|
||||
}())
|
||||
;(function() {
|
||||
'use strict'
|
||||
|
||||
function byTriggerPoint(a, b) {
|
||||
return a.triggerPoint - b.triggerPoint
|
||||
}
|
||||
|
||||
function byReverseTriggerPoint(a, b) {
|
||||
return b.triggerPoint - a.triggerPoint
|
||||
}
|
||||
|
||||
var groups = {
|
||||
vertical: {},
|
||||
horizontal: {}
|
||||
}
|
||||
var Waypoint = window.Waypoint
|
||||
|
||||
/* http://imakewebthings.com/waypoints/api/group */
|
||||
function Group(options) {
|
||||
this.name = options.name
|
||||
this.axis = options.axis
|
||||
this.id = this.name + '-' + this.axis
|
||||
this.waypoints = []
|
||||
this.clearTriggerQueues()
|
||||
groups[this.axis][this.name] = this
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Group.prototype.add = function(waypoint) {
|
||||
this.waypoints.push(waypoint)
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Group.prototype.clearTriggerQueues = function() {
|
||||
this.triggerQueues = {
|
||||
up: [],
|
||||
down: [],
|
||||
left: [],
|
||||
right: []
|
||||
}
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Group.prototype.flushTriggers = function() {
|
||||
for (var direction in this.triggerQueues) {
|
||||
var waypoints = this.triggerQueues[direction]
|
||||
var reverse = direction === 'up' || direction === 'left'
|
||||
waypoints.sort(reverse ? byReverseTriggerPoint : byTriggerPoint)
|
||||
for (var i = 0, end = waypoints.length; i < end; i += 1) {
|
||||
var waypoint = waypoints[i]
|
||||
if (waypoint.options.continuous || i === waypoints.length - 1) {
|
||||
waypoint.trigger([direction])
|
||||
}
|
||||
}
|
||||
}
|
||||
this.clearTriggerQueues()
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Group.prototype.next = function(waypoint) {
|
||||
this.waypoints.sort(byTriggerPoint)
|
||||
var index = Waypoint.Adapter.inArray(waypoint, this.waypoints)
|
||||
var isLast = index === this.waypoints.length - 1
|
||||
return isLast ? null : this.waypoints[index + 1]
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Group.prototype.previous = function(waypoint) {
|
||||
this.waypoints.sort(byTriggerPoint)
|
||||
var index = Waypoint.Adapter.inArray(waypoint, this.waypoints)
|
||||
return index ? this.waypoints[index - 1] : null
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Group.prototype.queueTrigger = function(waypoint, direction) {
|
||||
this.triggerQueues[direction].push(waypoint)
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Group.prototype.remove = function(waypoint) {
|
||||
var index = Waypoint.Adapter.inArray(waypoint, this.waypoints)
|
||||
if (index > -1) {
|
||||
this.waypoints.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/first */
|
||||
Group.prototype.first = function() {
|
||||
return this.waypoints[0]
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/last */
|
||||
Group.prototype.last = function() {
|
||||
return this.waypoints[this.waypoints.length - 1]
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Group.findOrCreate = function(options) {
|
||||
return groups[options.axis][options.name] || new Group(options)
|
||||
}
|
||||
|
||||
Waypoint.Group = Group
|
||||
}())
|
||||
;(function() {
|
||||
'use strict'
|
||||
|
||||
var $ = window.jQuery
|
||||
var Waypoint = window.Waypoint
|
||||
|
||||
function JQueryAdapter(element) {
|
||||
this.$element = $(element)
|
||||
}
|
||||
|
||||
$.each([
|
||||
'innerHeight',
|
||||
'innerWidth',
|
||||
'off',
|
||||
'offset',
|
||||
'on',
|
||||
'outerHeight',
|
||||
'outerWidth',
|
||||
'scrollLeft',
|
||||
'scrollTop'
|
||||
], function(i, method) {
|
||||
JQueryAdapter.prototype[method] = function() {
|
||||
var args = Array.prototype.slice.call(arguments)
|
||||
return this.$element[method].apply(this.$element, args)
|
||||
}
|
||||
})
|
||||
|
||||
$.each([
|
||||
'extend',
|
||||
'inArray',
|
||||
'isEmptyObject'
|
||||
], function(i, method) {
|
||||
JQueryAdapter[method] = $[method]
|
||||
})
|
||||
|
||||
Waypoint.adapters.push({
|
||||
name: 'jquery',
|
||||
Adapter: JQueryAdapter
|
||||
})
|
||||
Waypoint.Adapter = JQueryAdapter
|
||||
}())
|
||||
;(function() {
|
||||
'use strict'
|
||||
|
||||
var Waypoint = window.Waypoint
|
||||
|
||||
function createExtension(framework) {
|
||||
return function() {
|
||||
var waypoints = []
|
||||
var overrides = arguments[0]
|
||||
|
||||
if (framework.isFunction(arguments[0])) {
|
||||
overrides = framework.extend({}, arguments[1])
|
||||
overrides.handler = arguments[0]
|
||||
}
|
||||
|
||||
this.each(function() {
|
||||
var options = framework.extend({}, overrides, {
|
||||
element: this
|
||||
})
|
||||
if (typeof options.context === 'string') {
|
||||
options.context = framework(this).closest(options.context)[0]
|
||||
}
|
||||
waypoints.push(new Waypoint(options))
|
||||
})
|
||||
|
||||
return waypoints
|
||||
}
|
||||
}
|
||||
|
||||
if (window.jQuery) {
|
||||
window.jQuery.fn.waypoint = createExtension(window.jQuery)
|
||||
}
|
||||
if (window.Zepto) {
|
||||
window.Zepto.fn.waypoint = createExtension(window.Zepto)
|
||||
}
|
||||
}())
|
||||
;
|
7
static/libs/waypoints/lib/jquery.waypoints.min.js
vendored
Normal file
743
static/libs/waypoints/lib/noframework.waypoints.js
Normal file
@ -0,0 +1,743 @@
|
||||
/*!
|
||||
Waypoints - 3.1.1
|
||||
Copyright © 2011-2015 Caleb Troughton
|
||||
Licensed under the MIT license.
|
||||
https://github.com/imakewebthings/waypoints/blog/master/licenses.txt
|
||||
*/
|
||||
(function() {
|
||||
'use strict'
|
||||
|
||||
var keyCounter = 0
|
||||
var allWaypoints = {}
|
||||
|
||||
/* http://imakewebthings.com/waypoints/api/waypoint */
|
||||
function Waypoint(options) {
|
||||
if (!options) {
|
||||
throw new Error('No options passed to Waypoint constructor')
|
||||
}
|
||||
if (!options.element) {
|
||||
throw new Error('No element option passed to Waypoint constructor')
|
||||
}
|
||||
if (!options.handler) {
|
||||
throw new Error('No handler option passed to Waypoint constructor')
|
||||
}
|
||||
|
||||
this.key = 'waypoint-' + keyCounter
|
||||
this.options = Waypoint.Adapter.extend({}, Waypoint.defaults, options)
|
||||
this.element = this.options.element
|
||||
this.adapter = new Waypoint.Adapter(this.element)
|
||||
this.callback = options.handler
|
||||
this.axis = this.options.horizontal ? 'horizontal' : 'vertical'
|
||||
this.enabled = this.options.enabled
|
||||
this.triggerPoint = null
|
||||
this.group = Waypoint.Group.findOrCreate({
|
||||
name: this.options.group,
|
||||
axis: this.axis
|
||||
})
|
||||
this.context = Waypoint.Context.findOrCreateByElement(this.options.context)
|
||||
|
||||
if (Waypoint.offsetAliases[this.options.offset]) {
|
||||
this.options.offset = Waypoint.offsetAliases[this.options.offset]
|
||||
}
|
||||
this.group.add(this)
|
||||
this.context.add(this)
|
||||
allWaypoints[this.key] = this
|
||||
keyCounter += 1
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Waypoint.prototype.queueTrigger = function(direction) {
|
||||
this.group.queueTrigger(this, direction)
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Waypoint.prototype.trigger = function(args) {
|
||||
if (!this.enabled) {
|
||||
return
|
||||
}
|
||||
if (this.callback) {
|
||||
this.callback.apply(this, args)
|
||||
}
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/destroy */
|
||||
Waypoint.prototype.destroy = function() {
|
||||
this.context.remove(this)
|
||||
this.group.remove(this)
|
||||
delete allWaypoints[this.key]
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/disable */
|
||||
Waypoint.prototype.disable = function() {
|
||||
this.enabled = false
|
||||
return this
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/enable */
|
||||
Waypoint.prototype.enable = function() {
|
||||
this.context.refresh()
|
||||
this.enabled = true
|
||||
return this
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/next */
|
||||
Waypoint.prototype.next = function() {
|
||||
return this.group.next(this)
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/previous */
|
||||
Waypoint.prototype.previous = function() {
|
||||
return this.group.previous(this)
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Waypoint.invokeAll = function(method) {
|
||||
var allWaypointsArray = []
|
||||
for (var waypointKey in allWaypoints) {
|
||||
allWaypointsArray.push(allWaypoints[waypointKey])
|
||||
}
|
||||
for (var i = 0, end = allWaypointsArray.length; i < end; i++) {
|
||||
allWaypointsArray[i][method]()
|
||||
}
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/destroy-all */
|
||||
Waypoint.destroyAll = function() {
|
||||
Waypoint.invokeAll('destroy')
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/disable-all */
|
||||
Waypoint.disableAll = function() {
|
||||
Waypoint.invokeAll('disable')
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/enable-all */
|
||||
Waypoint.enableAll = function() {
|
||||
Waypoint.invokeAll('enable')
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/refresh-all */
|
||||
Waypoint.refreshAll = function() {
|
||||
Waypoint.Context.refreshAll()
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/viewport-height */
|
||||
Waypoint.viewportHeight = function() {
|
||||
return window.innerHeight || document.documentElement.clientHeight
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/viewport-width */
|
||||
Waypoint.viewportWidth = function() {
|
||||
return document.documentElement.clientWidth
|
||||
}
|
||||
|
||||
Waypoint.adapters = []
|
||||
|
||||
Waypoint.defaults = {
|
||||
context: window,
|
||||
continuous: true,
|
||||
enabled: true,
|
||||
group: 'default',
|
||||
horizontal: false,
|
||||
offset: 0
|
||||
}
|
||||
|
||||
Waypoint.offsetAliases = {
|
||||
'bottom-in-view': function() {
|
||||
return this.context.innerHeight() - this.adapter.outerHeight()
|
||||
},
|
||||
'right-in-view': function() {
|
||||
return this.context.innerWidth() - this.adapter.outerWidth()
|
||||
}
|
||||
}
|
||||
|
||||
window.Waypoint = Waypoint
|
||||
}())
|
||||
;(function() {
|
||||
'use strict'
|
||||
|
||||
function requestAnimationFrameShim(callback) {
|
||||
window.setTimeout(callback, 1000 / 60)
|
||||
}
|
||||
|
||||
var keyCounter = 0
|
||||
var contexts = {}
|
||||
var Waypoint = window.Waypoint
|
||||
var oldWindowLoad = window.onload
|
||||
|
||||
/* http://imakewebthings.com/waypoints/api/context */
|
||||
function Context(element) {
|
||||
this.element = element
|
||||
this.Adapter = Waypoint.Adapter
|
||||
this.adapter = new this.Adapter(element)
|
||||
this.key = 'waypoint-context-' + keyCounter
|
||||
this.didScroll = false
|
||||
this.didResize = false
|
||||
this.oldScroll = {
|
||||
x: this.adapter.scrollLeft(),
|
||||
y: this.adapter.scrollTop()
|
||||
}
|
||||
this.waypoints = {
|
||||
vertical: {},
|
||||
horizontal: {}
|
||||
}
|
||||
|
||||
element.waypointContextKey = this.key
|
||||
contexts[element.waypointContextKey] = this
|
||||
keyCounter += 1
|
||||
|
||||
this.createThrottledScrollHandler()
|
||||
this.createThrottledResizeHandler()
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Context.prototype.add = function(waypoint) {
|
||||
var axis = waypoint.options.horizontal ? 'horizontal' : 'vertical'
|
||||
this.waypoints[axis][waypoint.key] = waypoint
|
||||
this.refresh()
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Context.prototype.checkEmpty = function() {
|
||||
var horizontalEmpty = this.Adapter.isEmptyObject(this.waypoints.horizontal)
|
||||
var verticalEmpty = this.Adapter.isEmptyObject(this.waypoints.vertical)
|
||||
if (horizontalEmpty && verticalEmpty) {
|
||||
this.adapter.off('.waypoints')
|
||||
delete contexts[this.key]
|
||||
}
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Context.prototype.createThrottledResizeHandler = function() {
|
||||
var self = this
|
||||
|
||||
function resizeHandler() {
|
||||
self.handleResize()
|
||||
self.didResize = false
|
||||
}
|
||||
|
||||
this.adapter.on('resize.waypoints', function() {
|
||||
if (!self.didResize) {
|
||||
self.didResize = true
|
||||
Waypoint.requestAnimationFrame(resizeHandler)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Context.prototype.createThrottledScrollHandler = function() {
|
||||
var self = this
|
||||
function scrollHandler() {
|
||||
self.handleScroll()
|
||||
self.didScroll = false
|
||||
}
|
||||
|
||||
this.adapter.on('scroll.waypoints', function() {
|
||||
if (!self.didScroll || Waypoint.isTouch) {
|
||||
self.didScroll = true
|
||||
Waypoint.requestAnimationFrame(scrollHandler)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Context.prototype.handleResize = function() {
|
||||
Waypoint.Context.refreshAll()
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Context.prototype.handleScroll = function() {
|
||||
var triggeredGroups = {}
|
||||
var axes = {
|
||||
horizontal: {
|
||||
newScroll: this.adapter.scrollLeft(),
|
||||
oldScroll: this.oldScroll.x,
|
||||
forward: 'right',
|
||||
backward: 'left'
|
||||
},
|
||||
vertical: {
|
||||
newScroll: this.adapter.scrollTop(),
|
||||
oldScroll: this.oldScroll.y,
|
||||
forward: 'down',
|
||||
backward: 'up'
|
||||
}
|
||||
}
|
||||
|
||||
for (var axisKey in axes) {
|
||||
var axis = axes[axisKey]
|
||||
var isForward = axis.newScroll > axis.oldScroll
|
||||
var direction = isForward ? axis.forward : axis.backward
|
||||
|
||||
for (var waypointKey in this.waypoints[axisKey]) {
|
||||
var waypoint = this.waypoints[axisKey][waypointKey]
|
||||
var wasBeforeTriggerPoint = axis.oldScroll < waypoint.triggerPoint
|
||||
var nowAfterTriggerPoint = axis.newScroll >= waypoint.triggerPoint
|
||||
var crossedForward = wasBeforeTriggerPoint && nowAfterTriggerPoint
|
||||
var crossedBackward = !wasBeforeTriggerPoint && !nowAfterTriggerPoint
|
||||
if (crossedForward || crossedBackward) {
|
||||
waypoint.queueTrigger(direction)
|
||||
triggeredGroups[waypoint.group.id] = waypoint.group
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var groupKey in triggeredGroups) {
|
||||
triggeredGroups[groupKey].flushTriggers()
|
||||
}
|
||||
|
||||
this.oldScroll = {
|
||||
x: axes.horizontal.newScroll,
|
||||
y: axes.vertical.newScroll
|
||||
}
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Context.prototype.innerHeight = function() {
|
||||
/*eslint-disable eqeqeq */
|
||||
if (this.element == this.element.window) {
|
||||
return Waypoint.viewportHeight()
|
||||
}
|
||||
/*eslint-enable eqeqeq */
|
||||
return this.adapter.innerHeight()
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Context.prototype.remove = function(waypoint) {
|
||||
delete this.waypoints[waypoint.axis][waypoint.key]
|
||||
this.checkEmpty()
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Context.prototype.innerWidth = function() {
|
||||
/*eslint-disable eqeqeq */
|
||||
if (this.element == this.element.window) {
|
||||
return Waypoint.viewportWidth()
|
||||
}
|
||||
/*eslint-enable eqeqeq */
|
||||
return this.adapter.innerWidth()
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/context-destroy */
|
||||
Context.prototype.destroy = function() {
|
||||
var allWaypoints = []
|
||||
for (var axis in this.waypoints) {
|
||||
for (var waypointKey in this.waypoints[axis]) {
|
||||
allWaypoints.push(this.waypoints[axis][waypointKey])
|
||||
}
|
||||
}
|
||||
for (var i = 0, end = allWaypoints.length; i < end; i++) {
|
||||
allWaypoints[i].destroy()
|
||||
}
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/context-refresh */
|
||||
Context.prototype.refresh = function() {
|
||||
/*eslint-disable eqeqeq */
|
||||
var isWindow = this.element == this.element.window
|
||||
/*eslint-enable eqeqeq */
|
||||
var contextOffset = this.adapter.offset()
|
||||
var triggeredGroups = {}
|
||||
var axes
|
||||
|
||||
this.handleScroll()
|
||||
axes = {
|
||||
horizontal: {
|
||||
contextOffset: isWindow ? 0 : contextOffset.left,
|
||||
contextScroll: isWindow ? 0 : this.oldScroll.x,
|
||||
contextDimension: this.innerWidth(),
|
||||
oldScroll: this.oldScroll.x,
|
||||
forward: 'right',
|
||||
backward: 'left',
|
||||
offsetProp: 'left'
|
||||
},
|
||||
vertical: {
|
||||
contextOffset: isWindow ? 0 : contextOffset.top,
|
||||
contextScroll: isWindow ? 0 : this.oldScroll.y,
|
||||
contextDimension: this.innerHeight(),
|
||||
oldScroll: this.oldScroll.y,
|
||||
forward: 'down',
|
||||
backward: 'up',
|
||||
offsetProp: 'top'
|
||||
}
|
||||
}
|
||||
|
||||
for (var axisKey in axes) {
|
||||
var axis = axes[axisKey]
|
||||
for (var waypointKey in this.waypoints[axisKey]) {
|
||||
var waypoint = this.waypoints[axisKey][waypointKey]
|
||||
var adjustment = waypoint.options.offset
|
||||
var oldTriggerPoint = waypoint.triggerPoint
|
||||
var elementOffset = 0
|
||||
var freshWaypoint = oldTriggerPoint == null
|
||||
var contextModifier, wasBeforeScroll, nowAfterScroll
|
||||
var triggeredBackward, triggeredForward
|
||||
|
||||
if (waypoint.element !== waypoint.element.window) {
|
||||
elementOffset = waypoint.adapter.offset()[axis.offsetProp]
|
||||
}
|
||||
|
||||
if (typeof adjustment === 'function') {
|
||||
adjustment = adjustment.apply(waypoint)
|
||||
}
|
||||
else if (typeof adjustment === 'string') {
|
||||
adjustment = parseFloat(adjustment)
|
||||
if (waypoint.options.offset.indexOf('%') > - 1) {
|
||||
adjustment = Math.ceil(axis.contextDimension * adjustment / 100)
|
||||
}
|
||||
}
|
||||
|
||||
contextModifier = axis.contextScroll - axis.contextOffset
|
||||
waypoint.triggerPoint = elementOffset + contextModifier - adjustment
|
||||
wasBeforeScroll = oldTriggerPoint < axis.oldScroll
|
||||
nowAfterScroll = waypoint.triggerPoint >= axis.oldScroll
|
||||
triggeredBackward = wasBeforeScroll && nowAfterScroll
|
||||
triggeredForward = !wasBeforeScroll && !nowAfterScroll
|
||||
|
||||
if (!freshWaypoint && triggeredBackward) {
|
||||
waypoint.queueTrigger(axis.backward)
|
||||
triggeredGroups[waypoint.group.id] = waypoint.group
|
||||
}
|
||||
else if (!freshWaypoint && triggeredForward) {
|
||||
waypoint.queueTrigger(axis.forward)
|
||||
triggeredGroups[waypoint.group.id] = waypoint.group
|
||||
}
|
||||
else if (freshWaypoint && axis.oldScroll >= waypoint.triggerPoint) {
|
||||
waypoint.queueTrigger(axis.forward)
|
||||
triggeredGroups[waypoint.group.id] = waypoint.group
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var groupKey in triggeredGroups) {
|
||||
triggeredGroups[groupKey].flushTriggers()
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Context.findOrCreateByElement = function(element) {
|
||||
return Context.findByElement(element) || new Context(element)
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Context.refreshAll = function() {
|
||||
for (var contextId in contexts) {
|
||||
contexts[contextId].refresh()
|
||||
}
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/context-find-by-element */
|
||||
Context.findByElement = function(element) {
|
||||
return contexts[element.waypointContextKey]
|
||||
}
|
||||
|
||||
window.onload = function() {
|
||||
if (oldWindowLoad) {
|
||||
oldWindowLoad()
|
||||
}
|
||||
Context.refreshAll()
|
||||
}
|
||||
|
||||
Waypoint.requestAnimationFrame = function(callback) {
|
||||
var requestFn = window.requestAnimationFrame ||
|
||||
window.mozRequestAnimationFrame ||
|
||||
window.webkitRequestAnimationFrame ||
|
||||
requestAnimationFrameShim
|
||||
requestFn.call(window, callback)
|
||||
}
|
||||
Waypoint.Context = Context
|
||||
}())
|
||||
;(function() {
|
||||
'use strict'
|
||||
|
||||
function byTriggerPoint(a, b) {
|
||||
return a.triggerPoint - b.triggerPoint
|
||||
}
|
||||
|
||||
function byReverseTriggerPoint(a, b) {
|
||||
return b.triggerPoint - a.triggerPoint
|
||||
}
|
||||
|
||||
var groups = {
|
||||
vertical: {},
|
||||
horizontal: {}
|
||||
}
|
||||
var Waypoint = window.Waypoint
|
||||
|
||||
/* http://imakewebthings.com/waypoints/api/group */
|
||||
function Group(options) {
|
||||
this.name = options.name
|
||||
this.axis = options.axis
|
||||
this.id = this.name + '-' + this.axis
|
||||
this.waypoints = []
|
||||
this.clearTriggerQueues()
|
||||
groups[this.axis][this.name] = this
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Group.prototype.add = function(waypoint) {
|
||||
this.waypoints.push(waypoint)
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Group.prototype.clearTriggerQueues = function() {
|
||||
this.triggerQueues = {
|
||||
up: [],
|
||||
down: [],
|
||||
left: [],
|
||||
right: []
|
||||
}
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Group.prototype.flushTriggers = function() {
|
||||
for (var direction in this.triggerQueues) {
|
||||
var waypoints = this.triggerQueues[direction]
|
||||
var reverse = direction === 'up' || direction === 'left'
|
||||
waypoints.sort(reverse ? byReverseTriggerPoint : byTriggerPoint)
|
||||
for (var i = 0, end = waypoints.length; i < end; i += 1) {
|
||||
var waypoint = waypoints[i]
|
||||
if (waypoint.options.continuous || i === waypoints.length - 1) {
|
||||
waypoint.trigger([direction])
|
||||
}
|
||||
}
|
||||
}
|
||||
this.clearTriggerQueues()
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Group.prototype.next = function(waypoint) {
|
||||
this.waypoints.sort(byTriggerPoint)
|
||||
var index = Waypoint.Adapter.inArray(waypoint, this.waypoints)
|
||||
var isLast = index === this.waypoints.length - 1
|
||||
return isLast ? null : this.waypoints[index + 1]
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Group.prototype.previous = function(waypoint) {
|
||||
this.waypoints.sort(byTriggerPoint)
|
||||
var index = Waypoint.Adapter.inArray(waypoint, this.waypoints)
|
||||
return index ? this.waypoints[index - 1] : null
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Group.prototype.queueTrigger = function(waypoint, direction) {
|
||||
this.triggerQueues[direction].push(waypoint)
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Group.prototype.remove = function(waypoint) {
|
||||
var index = Waypoint.Adapter.inArray(waypoint, this.waypoints)
|
||||
if (index > -1) {
|
||||
this.waypoints.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/first */
|
||||
Group.prototype.first = function() {
|
||||
return this.waypoints[0]
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/last */
|
||||
Group.prototype.last = function() {
|
||||
return this.waypoints[this.waypoints.length - 1]
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Group.findOrCreate = function(options) {
|
||||
return groups[options.axis][options.name] || new Group(options)
|
||||
}
|
||||
|
||||
Waypoint.Group = Group
|
||||
}())
|
||||
;(function() {
|
||||
'use strict'
|
||||
|
||||
var Waypoint = window.Waypoint
|
||||
|
||||
function isWindow(element) {
|
||||
return element === element.window
|
||||
}
|
||||
|
||||
function getWindow(element) {
|
||||
if (isWindow(element)) {
|
||||
return element
|
||||
}
|
||||
return element.defaultView
|
||||
}
|
||||
|
||||
function NoFrameworkAdapter(element) {
|
||||
this.element = element
|
||||
this.handlers = {}
|
||||
}
|
||||
|
||||
NoFrameworkAdapter.prototype.innerHeight = function() {
|
||||
var isWin = isWindow(this.element)
|
||||
return isWin ? this.element.innerHeight : this.element.clientHeight
|
||||
}
|
||||
|
||||
NoFrameworkAdapter.prototype.innerWidth = function() {
|
||||
var isWin = isWindow(this.element)
|
||||
return isWin ? this.element.innerWidth : this.element.clientWidth
|
||||
}
|
||||
|
||||
NoFrameworkAdapter.prototype.off = function(event, handler) {
|
||||
function removeListeners(element, listeners, handler) {
|
||||
for (var i = 0, end = listeners.length - 1; i < end; i++) {
|
||||
var listener = listeners[i]
|
||||
if (!handler || handler === listener) {
|
||||
element.removeEventListener(listener)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var eventParts = event.split('.')
|
||||
var eventType = eventParts[0]
|
||||
var namespace = eventParts[1]
|
||||
var element = this.element
|
||||
|
||||
if (namespace && this.handlers[namespace] && eventType) {
|
||||
removeListeners(element, this.handlers[namespace][eventType], handler)
|
||||
this.handlers[namespace][eventType] = []
|
||||
}
|
||||
else if (eventType) {
|
||||
for (var ns in this.handlers) {
|
||||
removeListeners(element, this.handlers[ns][eventType] || [], handler)
|
||||
this.handlers[ns][eventType] = []
|
||||
}
|
||||
}
|
||||
else if (namespace && this.handlers[namespace]) {
|
||||
for (var type in this.handlers[namespace]) {
|
||||
removeListeners(element, this.handlers[namespace][type], handler)
|
||||
}
|
||||
this.handlers[namespace] = {}
|
||||
}
|
||||
}
|
||||
|
||||
/* Adapted from jQuery 1.x offset() */
|
||||
NoFrameworkAdapter.prototype.offset = function() {
|
||||
if (!this.element.ownerDocument) {
|
||||
return null
|
||||
}
|
||||
|
||||
var documentElement = this.element.ownerDocument.documentElement
|
||||
var win = getWindow(this.element.ownerDocument)
|
||||
var rect = {
|
||||
top: 0,
|
||||
left: 0
|
||||
}
|
||||
|
||||
if (this.element.getBoundingClientRect) {
|
||||
rect = this.element.getBoundingClientRect()
|
||||
}
|
||||
|
||||
return {
|
||||
top: rect.top + win.pageYOffset - documentElement.clientTop,
|
||||
left: rect.left + win.pageXOffset - documentElement.clientLeft
|
||||
}
|
||||
}
|
||||
|
||||
NoFrameworkAdapter.prototype.on = function(event, handler) {
|
||||
var eventParts = event.split('.')
|
||||
var eventType = eventParts[0]
|
||||
var namespace = eventParts[1] || '__default'
|
||||
var nsHandlers = this.handlers[namespace] = this.handlers[namespace] || {}
|
||||
var nsTypeList = nsHandlers[eventType] = nsHandlers[eventType] || []
|
||||
|
||||
nsTypeList.push(handler)
|
||||
this.element.addEventListener(eventType, handler)
|
||||
}
|
||||
|
||||
NoFrameworkAdapter.prototype.outerHeight = function(includeMargin) {
|
||||
var height = this.innerHeight()
|
||||
var computedStyle
|
||||
|
||||
if (includeMargin && !isWindow(this.element)) {
|
||||
computedStyle = window.getComputedStyle(this.element)
|
||||
height += parseInt(computedStyle.marginTop, 10)
|
||||
height += parseInt(computedStyle.marginBottom, 10)
|
||||
}
|
||||
|
||||
return height
|
||||
}
|
||||
|
||||
NoFrameworkAdapter.prototype.outerWidth = function(includeMargin) {
|
||||
var width = this.innerWidth()
|
||||
var computedStyle
|
||||
|
||||
if (includeMargin && !isWindow(this.element)) {
|
||||
computedStyle = window.getComputedStyle(this.element)
|
||||
width += parseInt(computedStyle.marginLeft, 10)
|
||||
width += parseInt(computedStyle.marginRight, 10)
|
||||
}
|
||||
|
||||
return width
|
||||
}
|
||||
|
||||
NoFrameworkAdapter.prototype.scrollLeft = function() {
|
||||
var win = getWindow(this.element)
|
||||
return win ? win.pageXOffset : this.element.scrollLeft
|
||||
}
|
||||
|
||||
NoFrameworkAdapter.prototype.scrollTop = function() {
|
||||
var win = getWindow(this.element)
|
||||
return win ? win.pageYOffset : this.element.scrollTop
|
||||
}
|
||||
|
||||
NoFrameworkAdapter.extend = function() {
|
||||
var args = Array.prototype.slice.call(arguments)
|
||||
|
||||
function merge(target, obj) {
|
||||
if (typeof target === 'object' && typeof obj === 'object') {
|
||||
for (var key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
target[key] = obj[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return target
|
||||
}
|
||||
|
||||
for (var i = 1, end = args.length; i < end; i++) {
|
||||
merge(args[0], args[i])
|
||||
}
|
||||
return args[0]
|
||||
}
|
||||
|
||||
NoFrameworkAdapter.inArray = function(element, array, i) {
|
||||
return array == null ? -1 : array.indexOf(element, i)
|
||||
}
|
||||
|
||||
NoFrameworkAdapter.isEmptyObject = function(obj) {
|
||||
/* eslint no-unused-vars: 0 */
|
||||
for (var name in obj) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
Waypoint.adapters.push({
|
||||
name: 'noframework',
|
||||
Adapter: NoFrameworkAdapter
|
||||
})
|
||||
Waypoint.Adapter = NoFrameworkAdapter
|
||||
}())
|
||||
;
|
7
static/libs/waypoints/lib/noframework.waypoints.min.js
vendored
Normal file
84
static/libs/waypoints/lib/shortcuts/infinite.js
Normal file
@ -0,0 +1,84 @@
|
||||
/*!
|
||||
Waypoints Infinite Scroll Shortcut - 3.1.1
|
||||
Copyright © 2011-2015 Caleb Troughton
|
||||
Licensed under the MIT license.
|
||||
https://github.com/imakewebthings/waypoints/blog/master/licenses.txt
|
||||
*/
|
||||
(function() {
|
||||
'use strict'
|
||||
|
||||
var $ = window.jQuery
|
||||
var Waypoint = window.Waypoint
|
||||
|
||||
/* http://imakewebthings.com/waypoints/shortcuts/infinite-scroll */
|
||||
function Infinite(options) {
|
||||
this.options = $.extend({}, Infinite.defaults, options)
|
||||
this.container = this.options.element
|
||||
if (this.options.container !== 'auto') {
|
||||
this.container = this.options.container
|
||||
}
|
||||
this.$container = $(this.container)
|
||||
this.$more = $(this.options.more)
|
||||
|
||||
if (this.$more.length) {
|
||||
this.setupHandler()
|
||||
this.waypoint = new Waypoint(this.options)
|
||||
}
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Infinite.prototype.setupHandler = function() {
|
||||
this.options.handler = $.proxy(function() {
|
||||
this.options.onBeforePageLoad()
|
||||
this.destroy()
|
||||
this.$container.addClass(this.options.loadingClass)
|
||||
|
||||
$.get($(this.options.more).attr('href'), $.proxy(function(data) {
|
||||
var $data = $($.parseHTML(data))
|
||||
var $newMore = $data.find(this.options.more)
|
||||
|
||||
var $items = $data.find(this.options.items)
|
||||
if (!$items.length) {
|
||||
$items = $data.filter(this.options.items)
|
||||
}
|
||||
|
||||
this.$container.append($items)
|
||||
this.$container.removeClass(this.options.loadingClass)
|
||||
|
||||
if (!$newMore.length) {
|
||||
$newMore = $data.filter(this.options.more)
|
||||
}
|
||||
if ($newMore.length) {
|
||||
this.$more.replaceWith($newMore)
|
||||
this.$more = $newMore
|
||||
this.waypoint = new Waypoint(this.options)
|
||||
}
|
||||
else {
|
||||
this.$more.remove()
|
||||
}
|
||||
|
||||
this.options.onAfterPageLoad()
|
||||
}, this))
|
||||
}, this)
|
||||
}
|
||||
|
||||
/* Public */
|
||||
Infinite.prototype.destroy = function() {
|
||||
if (this.waypoint) {
|
||||
this.waypoint.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
Infinite.defaults = {
|
||||
container: 'auto',
|
||||
items: '.infinite-item',
|
||||
more: '.infinite-more-link',
|
||||
offset: 'bottom-in-view',
|
||||
loadingClass: 'infinite-loading',
|
||||
onBeforePageLoad: $.noop,
|
||||
onAfterPageLoad: $.noop
|
||||
}
|
||||
|
||||
Waypoint.Infinite = Infinite
|
||||
}())
|
||||
;
|
7
static/libs/waypoints/lib/shortcuts/infinite.min.js
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
/*!
|
||||
Waypoints Infinite Scroll Shortcut - 3.1.1
|
||||
Copyright © 2011-2015 Caleb Troughton
|
||||
Licensed under the MIT license.
|
||||
https://github.com/imakewebthings/waypoints/blog/master/licenses.txt
|
||||
*/
|
||||
!function(){"use strict";function t(n){this.options=i.extend({},t.defaults,n),this.container=this.options.element,"auto"!==this.options.container&&(this.container=this.options.container),this.$container=i(this.container),this.$more=i(this.options.more),this.$more.length&&(this.setupHandler(),this.waypoint=new o(this.options))}var i=window.jQuery,o=window.Waypoint;t.prototype.setupHandler=function(){this.options.handler=i.proxy(function(){this.options.onBeforePageLoad(),this.destroy(),this.$container.addClass(this.options.loadingClass),i.get(i(this.options.more).attr("href"),i.proxy(function(t){var n=i(i.parseHTML(t)),e=n.find(this.options.more),s=n.find(this.options.items);s.length||(s=n.filter(this.options.items)),this.$container.append(s),this.$container.removeClass(this.options.loadingClass),e.length||(e=n.filter(this.options.more)),e.length?(this.$more.replaceWith(e),this.$more=e,this.waypoint=new o(this.options)):this.$more.remove(),this.options.onAfterPageLoad()},this))},this)},t.prototype.destroy=function(){this.waypoint&&this.waypoint.destroy()},t.defaults={container:"auto",items:".infinite-item",more:".infinite-more-link",offset:"bottom-in-view",loadingClass:"infinite-loading",onBeforePageLoad:i.noop,onAfterPageLoad:i.noop},o.Infinite=t}();
|
103
static/libs/waypoints/lib/shortcuts/inview.js
Normal file
@ -0,0 +1,103 @@
|
||||
/*!
|
||||
Waypoints Inview Shortcut - 3.1.1
|
||||
Copyright © 2011-2015 Caleb Troughton
|
||||
Licensed under the MIT license.
|
||||
https://github.com/imakewebthings/waypoints/blog/master/licenses.txt
|
||||
*/
|
||||
(function() {
|
||||
'use strict'
|
||||
|
||||
function noop() {}
|
||||
|
||||
var Waypoint = window.Waypoint
|
||||
|
||||
/* http://imakewebthings.com/waypoints/shortcuts/inview */
|
||||
function Inview(options) {
|
||||
this.options = Waypoint.Adapter.extend({}, Inview.defaults, options)
|
||||
this.axis = this.options.horizontal ? 'horizontal' : 'vertical'
|
||||
this.waypoints = []
|
||||
this.createWaypoints()
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Inview.prototype.createWaypoints = function() {
|
||||
var configs = {
|
||||
vertical: [{
|
||||
down: 'enter',
|
||||
up: 'exited',
|
||||
offset: '100%'
|
||||
}, {
|
||||
down: 'entered',
|
||||
up: 'exit',
|
||||
offset: 'bottom-in-view'
|
||||
}, {
|
||||
down: 'exit',
|
||||
up: 'entered',
|
||||
offset: 0
|
||||
}, {
|
||||
down: 'exited',
|
||||
up: 'enter',
|
||||
offset: function() {
|
||||
return -this.adapter.outerHeight()
|
||||
}
|
||||
}],
|
||||
horizontal: [{
|
||||
right: 'enter',
|
||||
left: 'exited',
|
||||
offset: '100%'
|
||||
}, {
|
||||
right: 'entered',
|
||||
left: 'exit',
|
||||
offset: 'right-in-view'
|
||||
}, {
|
||||
right: 'exit',
|
||||
left: 'entered',
|
||||
offset: 0
|
||||
}, {
|
||||
right: 'exited',
|
||||
left: 'enter',
|
||||
offset: function() {
|
||||
return -this.adapter.outerWidth()
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
for (var i = 0, end = configs[this.axis].length; i < end; i++) {
|
||||
var config = configs[this.axis][i]
|
||||
this.createWaypoint(config)
|
||||
}
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Inview.prototype.createWaypoint = function(config) {
|
||||
var self = this
|
||||
this.waypoints.push(new Waypoint({
|
||||
element: this.options.element,
|
||||
handler: (function(config) {
|
||||
return function(direction) {
|
||||
self.options[config[direction]].call(this, direction)
|
||||
}
|
||||
}(config)),
|
||||
offset: config.offset,
|
||||
horizontal: this.options.horizontal
|
||||
}))
|
||||
}
|
||||
|
||||
/* Public */
|
||||
Inview.prototype.destroy = function() {
|
||||
for (var i = 0, end = this.waypoints.length; i < end; i++) {
|
||||
this.waypoints[i].destroy()
|
||||
}
|
||||
this.waypoints = []
|
||||
}
|
||||
|
||||
Inview.defaults = {
|
||||
enter: noop,
|
||||
entered: noop,
|
||||
exit: noop,
|
||||
exited: noop
|
||||
}
|
||||
|
||||
Waypoint.Inview = Inview
|
||||
}())
|
||||
;
|
7
static/libs/waypoints/lib/shortcuts/inview.min.js
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
/*!
|
||||
Waypoints Inview Shortcut - 3.1.1
|
||||
Copyright © 2011-2015 Caleb Troughton
|
||||
Licensed under the MIT license.
|
||||
https://github.com/imakewebthings/waypoints/blog/master/licenses.txt
|
||||
*/
|
||||
!function(){"use strict";function t(){}function e(t){this.options=i.Adapter.extend({},e.defaults,t),this.axis=this.options.horizontal?"horizontal":"vertical",this.waypoints=[],this.createWaypoints()}var i=window.Waypoint;e.prototype.createWaypoints=function(){for(var t={vertical:[{down:"enter",up:"exited",offset:"100%"},{down:"entered",up:"exit",offset:"bottom-in-view"},{down:"exit",up:"entered",offset:0},{down:"exited",up:"enter",offset:function(){return-this.adapter.outerHeight()}}],horizontal:[{right:"enter",left:"exited",offset:"100%"},{right:"entered",left:"exit",offset:"right-in-view"},{right:"exit",left:"entered",offset:0},{right:"exited",left:"enter",offset:function(){return-this.adapter.outerWidth()}}]},e=0,i=t[this.axis].length;i>e;e++){var o=t[this.axis][e];this.createWaypoint(o)}},e.prototype.createWaypoint=function(t){var e=this;this.waypoints.push(new i({element:this.options.element,handler:function(t){return function(i){e.options[t[i]].call(this,i)}}(t),offset:t.offset,horizontal:this.options.horizontal}))},e.prototype.destroy=function(){for(var t=0,e=this.waypoints.length;e>t;t++)this.waypoints[t].destroy();this.waypoints=[]},e.defaults={enter:t,entered:t,exit:t,exited:t},i.Inview=e}();
|
65
static/libs/waypoints/lib/shortcuts/sticky.js
Normal file
@ -0,0 +1,65 @@
|
||||
/*!
|
||||
Waypoints Sticky Element Shortcut - 3.1.1
|
||||
Copyright © 2011-2015 Caleb Troughton
|
||||
Licensed under the MIT license.
|
||||
https://github.com/imakewebthings/waypoints/blog/master/licenses.txt
|
||||
*/
|
||||
(function() {
|
||||
'use strict'
|
||||
|
||||
var $ = window.jQuery
|
||||
var Waypoint = window.Waypoint
|
||||
|
||||
/* http://imakewebthings.com/waypoints/shortcuts/sticky-elements */
|
||||
function Sticky(options) {
|
||||
this.options = $.extend({}, Waypoint.defaults, Sticky.defaults, options)
|
||||
this.element = this.options.element
|
||||
this.$element = $(this.element)
|
||||
this.createWrapper()
|
||||
this.createWaypoint()
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Sticky.prototype.createWaypoint = function() {
|
||||
var originalHandler = this.options.handler
|
||||
|
||||
this.waypoint = new Waypoint($.extend({}, this.options, {
|
||||
element: this.wrapper,
|
||||
handler: $.proxy(function(direction) {
|
||||
var shouldBeStuck = this.options.direction.indexOf(direction) > -1
|
||||
var wrapperHeight = shouldBeStuck ? this.$element.outerHeight(true) : ''
|
||||
|
||||
this.$wrapper.height(wrapperHeight)
|
||||
this.$element.toggleClass(this.options.stuckClass, shouldBeStuck)
|
||||
|
||||
if (originalHandler) {
|
||||
originalHandler.call(this, direction)
|
||||
}
|
||||
}, this)
|
||||
}))
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Sticky.prototype.createWrapper = function() {
|
||||
this.$element.wrap(this.options.wrapper)
|
||||
this.$wrapper = this.$element.parent()
|
||||
this.wrapper = this.$wrapper[0]
|
||||
}
|
||||
|
||||
/* Public */
|
||||
Sticky.prototype.destroy = function() {
|
||||
if (this.$element.parent()[0] === this.wrapper) {
|
||||
this.waypoint.destroy()
|
||||
this.$element.removeClass(this.options.stuckClass).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
Sticky.defaults = {
|
||||
wrapper: '<div class="sticky-wrapper" />',
|
||||
stuckClass: 'stuck',
|
||||
direction: 'down right'
|
||||
}
|
||||
|
||||
Waypoint.Sticky = Sticky
|
||||
}())
|
||||
;
|
7
static/libs/waypoints/lib/shortcuts/sticky.min.js
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
/*!
|
||||
Waypoints Sticky Element Shortcut - 3.1.1
|
||||
Copyright © 2011-2015 Caleb Troughton
|
||||
Licensed under the MIT license.
|
||||
https://github.com/imakewebthings/waypoints/blog/master/licenses.txt
|
||||
*/
|
||||
!function(){"use strict";function t(s){this.options=e.extend({},i.defaults,t.defaults,s),this.element=this.options.element,this.$element=e(this.element),this.createWrapper(),this.createWaypoint()}var e=window.jQuery,i=window.Waypoint;t.prototype.createWaypoint=function(){var t=this.options.handler;this.waypoint=new i(e.extend({},this.options,{element:this.wrapper,handler:e.proxy(function(e){var i=this.options.direction.indexOf(e)>-1,s=i?this.$element.outerHeight(!0):"";this.$wrapper.height(s),this.$element.toggleClass(this.options.stuckClass,i),t&&t.call(this,e)},this)}))},t.prototype.createWrapper=function(){this.$element.wrap(this.options.wrapper),this.$wrapper=this.$element.parent(),this.wrapper=this.$wrapper[0]},t.prototype.destroy=function(){this.$element.parent()[0]===this.wrapper&&(this.waypoint.destroy(),this.$element.removeClass(this.options.stuckClass).unwrap())},t.defaults={wrapper:'<div class="sticky-wrapper" />',stuckClass:"stuck",direction:"down right"},i.Sticky=t}();
|
46
static/libs/waypoints/lib/waypoints.debug.js
Normal file
@ -0,0 +1,46 @@
|
||||
/*!
|
||||
Waypoints Debug - 3.1.1
|
||||
Copyright © 2011-2015 Caleb Troughton
|
||||
Licensed under the MIT license.
|
||||
https://github.com/imakewebthings/waypoints/blog/master/licenses.txt
|
||||
*/
|
||||
(function() {
|
||||
'use strict'
|
||||
|
||||
var displayNoneMessage = [
|
||||
'You have a Waypoint element with display none. For more information on ',
|
||||
'why this is a bad idea read ',
|
||||
'http://imakewebthings.com/waypoints/guides/debugging/#display-none'
|
||||
].join('')
|
||||
var fixedMessage = [
|
||||
'You have a Waypoint element with fixed positioning. For more ',
|
||||
'information on why this is a bad idea read ',
|
||||
'http://imakewebthings.com/waypoints/guides/debugging/#fixed-position'
|
||||
].join('')
|
||||
|
||||
function checkWaypointStyles() {
|
||||
var originalRefresh = window.Waypoint.Context.prototype.refresh
|
||||
|
||||
window.Waypoint.Context.prototype.refresh = function() {
|
||||
for (var axis in this.waypoints) {
|
||||
for (var key in this.waypoints[axis]) {
|
||||
var waypoint = this.waypoints[axis][key]
|
||||
var style = window.getComputedStyle(waypoint.element)
|
||||
if (!waypoint.enabled) {
|
||||
continue
|
||||
}
|
||||
if (style && style.display === 'none') {
|
||||
console.error(displayNoneMessage)
|
||||
}
|
||||
if (style && style.position === 'fixed') {
|
||||
console.error(fixedMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
return originalRefresh.call(this)
|
||||
}
|
||||
}
|
||||
|
||||
checkWaypointStyles()
|
||||
}())
|
||||
;
|
690
static/libs/waypoints/lib/zepto.waypoints.js
Normal file
@ -0,0 +1,690 @@
|
||||
/*!
|
||||
Waypoints - 3.1.1
|
||||
Copyright © 2011-2015 Caleb Troughton
|
||||
Licensed under the MIT license.
|
||||
https://github.com/imakewebthings/waypoints/blog/master/licenses.txt
|
||||
*/
|
||||
(function() {
|
||||
'use strict'
|
||||
|
||||
var keyCounter = 0
|
||||
var allWaypoints = {}
|
||||
|
||||
/* http://imakewebthings.com/waypoints/api/waypoint */
|
||||
function Waypoint(options) {
|
||||
if (!options) {
|
||||
throw new Error('No options passed to Waypoint constructor')
|
||||
}
|
||||
if (!options.element) {
|
||||
throw new Error('No element option passed to Waypoint constructor')
|
||||
}
|
||||
if (!options.handler) {
|
||||
throw new Error('No handler option passed to Waypoint constructor')
|
||||
}
|
||||
|
||||
this.key = 'waypoint-' + keyCounter
|
||||
this.options = Waypoint.Adapter.extend({}, Waypoint.defaults, options)
|
||||
this.element = this.options.element
|
||||
this.adapter = new Waypoint.Adapter(this.element)
|
||||
this.callback = options.handler
|
||||
this.axis = this.options.horizontal ? 'horizontal' : 'vertical'
|
||||
this.enabled = this.options.enabled
|
||||
this.triggerPoint = null
|
||||
this.group = Waypoint.Group.findOrCreate({
|
||||
name: this.options.group,
|
||||
axis: this.axis
|
||||
})
|
||||
this.context = Waypoint.Context.findOrCreateByElement(this.options.context)
|
||||
|
||||
if (Waypoint.offsetAliases[this.options.offset]) {
|
||||
this.options.offset = Waypoint.offsetAliases[this.options.offset]
|
||||
}
|
||||
this.group.add(this)
|
||||
this.context.add(this)
|
||||
allWaypoints[this.key] = this
|
||||
keyCounter += 1
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Waypoint.prototype.queueTrigger = function(direction) {
|
||||
this.group.queueTrigger(this, direction)
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Waypoint.prototype.trigger = function(args) {
|
||||
if (!this.enabled) {
|
||||
return
|
||||
}
|
||||
if (this.callback) {
|
||||
this.callback.apply(this, args)
|
||||
}
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/destroy */
|
||||
Waypoint.prototype.destroy = function() {
|
||||
this.context.remove(this)
|
||||
this.group.remove(this)
|
||||
delete allWaypoints[this.key]
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/disable */
|
||||
Waypoint.prototype.disable = function() {
|
||||
this.enabled = false
|
||||
return this
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/enable */
|
||||
Waypoint.prototype.enable = function() {
|
||||
this.context.refresh()
|
||||
this.enabled = true
|
||||
return this
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/next */
|
||||
Waypoint.prototype.next = function() {
|
||||
return this.group.next(this)
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/previous */
|
||||
Waypoint.prototype.previous = function() {
|
||||
return this.group.previous(this)
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Waypoint.invokeAll = function(method) {
|
||||
var allWaypointsArray = []
|
||||
for (var waypointKey in allWaypoints) {
|
||||
allWaypointsArray.push(allWaypoints[waypointKey])
|
||||
}
|
||||
for (var i = 0, end = allWaypointsArray.length; i < end; i++) {
|
||||
allWaypointsArray[i][method]()
|
||||
}
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/destroy-all */
|
||||
Waypoint.destroyAll = function() {
|
||||
Waypoint.invokeAll('destroy')
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/disable-all */
|
||||
Waypoint.disableAll = function() {
|
||||
Waypoint.invokeAll('disable')
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/enable-all */
|
||||
Waypoint.enableAll = function() {
|
||||
Waypoint.invokeAll('enable')
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/refresh-all */
|
||||
Waypoint.refreshAll = function() {
|
||||
Waypoint.Context.refreshAll()
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/viewport-height */
|
||||
Waypoint.viewportHeight = function() {
|
||||
return window.innerHeight || document.documentElement.clientHeight
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/viewport-width */
|
||||
Waypoint.viewportWidth = function() {
|
||||
return document.documentElement.clientWidth
|
||||
}
|
||||
|
||||
Waypoint.adapters = []
|
||||
|
||||
Waypoint.defaults = {
|
||||
context: window,
|
||||
continuous: true,
|
||||
enabled: true,
|
||||
group: 'default',
|
||||
horizontal: false,
|
||||
offset: 0
|
||||
}
|
||||
|
||||
Waypoint.offsetAliases = {
|
||||
'bottom-in-view': function() {
|
||||
return this.context.innerHeight() - this.adapter.outerHeight()
|
||||
},
|
||||
'right-in-view': function() {
|
||||
return this.context.innerWidth() - this.adapter.outerWidth()
|
||||
}
|
||||
}
|
||||
|
||||
window.Waypoint = Waypoint
|
||||
}())
|
||||
;(function() {
|
||||
'use strict'
|
||||
|
||||
function requestAnimationFrameShim(callback) {
|
||||
window.setTimeout(callback, 1000 / 60)
|
||||
}
|
||||
|
||||
var keyCounter = 0
|
||||
var contexts = {}
|
||||
var Waypoint = window.Waypoint
|
||||
var oldWindowLoad = window.onload
|
||||
|
||||
/* http://imakewebthings.com/waypoints/api/context */
|
||||
function Context(element) {
|
||||
this.element = element
|
||||
this.Adapter = Waypoint.Adapter
|
||||
this.adapter = new this.Adapter(element)
|
||||
this.key = 'waypoint-context-' + keyCounter
|
||||
this.didScroll = false
|
||||
this.didResize = false
|
||||
this.oldScroll = {
|
||||
x: this.adapter.scrollLeft(),
|
||||
y: this.adapter.scrollTop()
|
||||
}
|
||||
this.waypoints = {
|
||||
vertical: {},
|
||||
horizontal: {}
|
||||
}
|
||||
|
||||
element.waypointContextKey = this.key
|
||||
contexts[element.waypointContextKey] = this
|
||||
keyCounter += 1
|
||||
|
||||
this.createThrottledScrollHandler()
|
||||
this.createThrottledResizeHandler()
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Context.prototype.add = function(waypoint) {
|
||||
var axis = waypoint.options.horizontal ? 'horizontal' : 'vertical'
|
||||
this.waypoints[axis][waypoint.key] = waypoint
|
||||
this.refresh()
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Context.prototype.checkEmpty = function() {
|
||||
var horizontalEmpty = this.Adapter.isEmptyObject(this.waypoints.horizontal)
|
||||
var verticalEmpty = this.Adapter.isEmptyObject(this.waypoints.vertical)
|
||||
if (horizontalEmpty && verticalEmpty) {
|
||||
this.adapter.off('.waypoints')
|
||||
delete contexts[this.key]
|
||||
}
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Context.prototype.createThrottledResizeHandler = function() {
|
||||
var self = this
|
||||
|
||||
function resizeHandler() {
|
||||
self.handleResize()
|
||||
self.didResize = false
|
||||
}
|
||||
|
||||
this.adapter.on('resize.waypoints', function() {
|
||||
if (!self.didResize) {
|
||||
self.didResize = true
|
||||
Waypoint.requestAnimationFrame(resizeHandler)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Context.prototype.createThrottledScrollHandler = function() {
|
||||
var self = this
|
||||
function scrollHandler() {
|
||||
self.handleScroll()
|
||||
self.didScroll = false
|
||||
}
|
||||
|
||||
this.adapter.on('scroll.waypoints', function() {
|
||||
if (!self.didScroll || Waypoint.isTouch) {
|
||||
self.didScroll = true
|
||||
Waypoint.requestAnimationFrame(scrollHandler)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Context.prototype.handleResize = function() {
|
||||
Waypoint.Context.refreshAll()
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Context.prototype.handleScroll = function() {
|
||||
var triggeredGroups = {}
|
||||
var axes = {
|
||||
horizontal: {
|
||||
newScroll: this.adapter.scrollLeft(),
|
||||
oldScroll: this.oldScroll.x,
|
||||
forward: 'right',
|
||||
backward: 'left'
|
||||
},
|
||||
vertical: {
|
||||
newScroll: this.adapter.scrollTop(),
|
||||
oldScroll: this.oldScroll.y,
|
||||
forward: 'down',
|
||||
backward: 'up'
|
||||
}
|
||||
}
|
||||
|
||||
for (var axisKey in axes) {
|
||||
var axis = axes[axisKey]
|
||||
var isForward = axis.newScroll > axis.oldScroll
|
||||
var direction = isForward ? axis.forward : axis.backward
|
||||
|
||||
for (var waypointKey in this.waypoints[axisKey]) {
|
||||
var waypoint = this.waypoints[axisKey][waypointKey]
|
||||
var wasBeforeTriggerPoint = axis.oldScroll < waypoint.triggerPoint
|
||||
var nowAfterTriggerPoint = axis.newScroll >= waypoint.triggerPoint
|
||||
var crossedForward = wasBeforeTriggerPoint && nowAfterTriggerPoint
|
||||
var crossedBackward = !wasBeforeTriggerPoint && !nowAfterTriggerPoint
|
||||
if (crossedForward || crossedBackward) {
|
||||
waypoint.queueTrigger(direction)
|
||||
triggeredGroups[waypoint.group.id] = waypoint.group
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var groupKey in triggeredGroups) {
|
||||
triggeredGroups[groupKey].flushTriggers()
|
||||
}
|
||||
|
||||
this.oldScroll = {
|
||||
x: axes.horizontal.newScroll,
|
||||
y: axes.vertical.newScroll
|
||||
}
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Context.prototype.innerHeight = function() {
|
||||
/*eslint-disable eqeqeq */
|
||||
if (this.element == this.element.window) {
|
||||
return Waypoint.viewportHeight()
|
||||
}
|
||||
/*eslint-enable eqeqeq */
|
||||
return this.adapter.innerHeight()
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Context.prototype.remove = function(waypoint) {
|
||||
delete this.waypoints[waypoint.axis][waypoint.key]
|
||||
this.checkEmpty()
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Context.prototype.innerWidth = function() {
|
||||
/*eslint-disable eqeqeq */
|
||||
if (this.element == this.element.window) {
|
||||
return Waypoint.viewportWidth()
|
||||
}
|
||||
/*eslint-enable eqeqeq */
|
||||
return this.adapter.innerWidth()
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/context-destroy */
|
||||
Context.prototype.destroy = function() {
|
||||
var allWaypoints = []
|
||||
for (var axis in this.waypoints) {
|
||||
for (var waypointKey in this.waypoints[axis]) {
|
||||
allWaypoints.push(this.waypoints[axis][waypointKey])
|
||||
}
|
||||
}
|
||||
for (var i = 0, end = allWaypoints.length; i < end; i++) {
|
||||
allWaypoints[i].destroy()
|
||||
}
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/context-refresh */
|
||||
Context.prototype.refresh = function() {
|
||||
/*eslint-disable eqeqeq */
|
||||
var isWindow = this.element == this.element.window
|
||||
/*eslint-enable eqeqeq */
|
||||
var contextOffset = this.adapter.offset()
|
||||
var triggeredGroups = {}
|
||||
var axes
|
||||
|
||||
this.handleScroll()
|
||||
axes = {
|
||||
horizontal: {
|
||||
contextOffset: isWindow ? 0 : contextOffset.left,
|
||||
contextScroll: isWindow ? 0 : this.oldScroll.x,
|
||||
contextDimension: this.innerWidth(),
|
||||
oldScroll: this.oldScroll.x,
|
||||
forward: 'right',
|
||||
backward: 'left',
|
||||
offsetProp: 'left'
|
||||
},
|
||||
vertical: {
|
||||
contextOffset: isWindow ? 0 : contextOffset.top,
|
||||
contextScroll: isWindow ? 0 : this.oldScroll.y,
|
||||
contextDimension: this.innerHeight(),
|
||||
oldScroll: this.oldScroll.y,
|
||||
forward: 'down',
|
||||
backward: 'up',
|
||||
offsetProp: 'top'
|
||||
}
|
||||
}
|
||||
|
||||
for (var axisKey in axes) {
|
||||
var axis = axes[axisKey]
|
||||
for (var waypointKey in this.waypoints[axisKey]) {
|
||||
var waypoint = this.waypoints[axisKey][waypointKey]
|
||||
var adjustment = waypoint.options.offset
|
||||
var oldTriggerPoint = waypoint.triggerPoint
|
||||
var elementOffset = 0
|
||||
var freshWaypoint = oldTriggerPoint == null
|
||||
var contextModifier, wasBeforeScroll, nowAfterScroll
|
||||
var triggeredBackward, triggeredForward
|
||||
|
||||
if (waypoint.element !== waypoint.element.window) {
|
||||
elementOffset = waypoint.adapter.offset()[axis.offsetProp]
|
||||
}
|
||||
|
||||
if (typeof adjustment === 'function') {
|
||||
adjustment = adjustment.apply(waypoint)
|
||||
}
|
||||
else if (typeof adjustment === 'string') {
|
||||
adjustment = parseFloat(adjustment)
|
||||
if (waypoint.options.offset.indexOf('%') > - 1) {
|
||||
adjustment = Math.ceil(axis.contextDimension * adjustment / 100)
|
||||
}
|
||||
}
|
||||
|
||||
contextModifier = axis.contextScroll - axis.contextOffset
|
||||
waypoint.triggerPoint = elementOffset + contextModifier - adjustment
|
||||
wasBeforeScroll = oldTriggerPoint < axis.oldScroll
|
||||
nowAfterScroll = waypoint.triggerPoint >= axis.oldScroll
|
||||
triggeredBackward = wasBeforeScroll && nowAfterScroll
|
||||
triggeredForward = !wasBeforeScroll && !nowAfterScroll
|
||||
|
||||
if (!freshWaypoint && triggeredBackward) {
|
||||
waypoint.queueTrigger(axis.backward)
|
||||
triggeredGroups[waypoint.group.id] = waypoint.group
|
||||
}
|
||||
else if (!freshWaypoint && triggeredForward) {
|
||||
waypoint.queueTrigger(axis.forward)
|
||||
triggeredGroups[waypoint.group.id] = waypoint.group
|
||||
}
|
||||
else if (freshWaypoint && axis.oldScroll >= waypoint.triggerPoint) {
|
||||
waypoint.queueTrigger(axis.forward)
|
||||
triggeredGroups[waypoint.group.id] = waypoint.group
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var groupKey in triggeredGroups) {
|
||||
triggeredGroups[groupKey].flushTriggers()
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Context.findOrCreateByElement = function(element) {
|
||||
return Context.findByElement(element) || new Context(element)
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Context.refreshAll = function() {
|
||||
for (var contextId in contexts) {
|
||||
contexts[contextId].refresh()
|
||||
}
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/context-find-by-element */
|
||||
Context.findByElement = function(element) {
|
||||
return contexts[element.waypointContextKey]
|
||||
}
|
||||
|
||||
window.onload = function() {
|
||||
if (oldWindowLoad) {
|
||||
oldWindowLoad()
|
||||
}
|
||||
Context.refreshAll()
|
||||
}
|
||||
|
||||
Waypoint.requestAnimationFrame = function(callback) {
|
||||
var requestFn = window.requestAnimationFrame ||
|
||||
window.mozRequestAnimationFrame ||
|
||||
window.webkitRequestAnimationFrame ||
|
||||
requestAnimationFrameShim
|
||||
requestFn.call(window, callback)
|
||||
}
|
||||
Waypoint.Context = Context
|
||||
}())
|
||||
;(function() {
|
||||
'use strict'
|
||||
|
||||
function byTriggerPoint(a, b) {
|
||||
return a.triggerPoint - b.triggerPoint
|
||||
}
|
||||
|
||||
function byReverseTriggerPoint(a, b) {
|
||||
return b.triggerPoint - a.triggerPoint
|
||||
}
|
||||
|
||||
var groups = {
|
||||
vertical: {},
|
||||
horizontal: {}
|
||||
}
|
||||
var Waypoint = window.Waypoint
|
||||
|
||||
/* http://imakewebthings.com/waypoints/api/group */
|
||||
function Group(options) {
|
||||
this.name = options.name
|
||||
this.axis = options.axis
|
||||
this.id = this.name + '-' + this.axis
|
||||
this.waypoints = []
|
||||
this.clearTriggerQueues()
|
||||
groups[this.axis][this.name] = this
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Group.prototype.add = function(waypoint) {
|
||||
this.waypoints.push(waypoint)
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Group.prototype.clearTriggerQueues = function() {
|
||||
this.triggerQueues = {
|
||||
up: [],
|
||||
down: [],
|
||||
left: [],
|
||||
right: []
|
||||
}
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Group.prototype.flushTriggers = function() {
|
||||
for (var direction in this.triggerQueues) {
|
||||
var waypoints = this.triggerQueues[direction]
|
||||
var reverse = direction === 'up' || direction === 'left'
|
||||
waypoints.sort(reverse ? byReverseTriggerPoint : byTriggerPoint)
|
||||
for (var i = 0, end = waypoints.length; i < end; i += 1) {
|
||||
var waypoint = waypoints[i]
|
||||
if (waypoint.options.continuous || i === waypoints.length - 1) {
|
||||
waypoint.trigger([direction])
|
||||
}
|
||||
}
|
||||
}
|
||||
this.clearTriggerQueues()
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Group.prototype.next = function(waypoint) {
|
||||
this.waypoints.sort(byTriggerPoint)
|
||||
var index = Waypoint.Adapter.inArray(waypoint, this.waypoints)
|
||||
var isLast = index === this.waypoints.length - 1
|
||||
return isLast ? null : this.waypoints[index + 1]
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Group.prototype.previous = function(waypoint) {
|
||||
this.waypoints.sort(byTriggerPoint)
|
||||
var index = Waypoint.Adapter.inArray(waypoint, this.waypoints)
|
||||
return index ? this.waypoints[index - 1] : null
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Group.prototype.queueTrigger = function(waypoint, direction) {
|
||||
this.triggerQueues[direction].push(waypoint)
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Group.prototype.remove = function(waypoint) {
|
||||
var index = Waypoint.Adapter.inArray(waypoint, this.waypoints)
|
||||
if (index > -1) {
|
||||
this.waypoints.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/first */
|
||||
Group.prototype.first = function() {
|
||||
return this.waypoints[0]
|
||||
}
|
||||
|
||||
/* Public */
|
||||
/* http://imakewebthings.com/waypoints/api/last */
|
||||
Group.prototype.last = function() {
|
||||
return this.waypoints[this.waypoints.length - 1]
|
||||
}
|
||||
|
||||
/* Private */
|
||||
Group.findOrCreate = function(options) {
|
||||
return groups[options.axis][options.name] || new Group(options)
|
||||
}
|
||||
|
||||
Waypoint.Group = Group
|
||||
}())
|
||||
;(function() {
|
||||
'use strict'
|
||||
|
||||
var $ = window.Zepto
|
||||
var Waypoint = window.Waypoint
|
||||
|
||||
function ZeptoAdapter(element) {
|
||||
this.element = element
|
||||
this.$element = $(element)
|
||||
}
|
||||
|
||||
$.each([
|
||||
'off',
|
||||
'on',
|
||||
'scrollLeft',
|
||||
'scrollTop'
|
||||
], function(i, method) {
|
||||
ZeptoAdapter.prototype[method] = function() {
|
||||
var args = Array.prototype.slice.call(arguments)
|
||||
return this.$element[method].apply(this.$element, args)
|
||||
}
|
||||
})
|
||||
|
||||
ZeptoAdapter.prototype.offset = function() {
|
||||
if (this.element !== this.element.window) {
|
||||
return this.$element.offset()
|
||||
}
|
||||
}
|
||||
|
||||
// Adapted from https://gist.github.com/wheresrhys/5823198
|
||||
$.each([
|
||||
'width',
|
||||
'height'
|
||||
], function(i, dimension) {
|
||||
function createDimensionMethod($element, includeBorder) {
|
||||
return function(includeMargin) {
|
||||
var $element = this.$element
|
||||
var size = $element[dimension]()
|
||||
var sides = {
|
||||
width: ['left', 'right'],
|
||||
height: ['top', 'bottom']
|
||||
}
|
||||
|
||||
$.each(sides[dimension], function(i, side) {
|
||||
size += parseInt($element.css('padding-' + side), 10)
|
||||
if (includeBorder) {
|
||||
size += parseInt($element.css('border-' + side + '-width'), 10)
|
||||
}
|
||||
if (includeMargin) {
|
||||
size += parseInt($element.css('margin-' + side), 10)
|
||||
}
|
||||
})
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
var innerMethod = $.camelCase('inner-' + dimension)
|
||||
var outerMethod = $.camelCase('outer-' + dimension)
|
||||
|
||||
ZeptoAdapter.prototype[innerMethod] = createDimensionMethod(false)
|
||||
ZeptoAdapter.prototype[outerMethod] = createDimensionMethod(true)
|
||||
})
|
||||
|
||||
$.each([
|
||||
'extend',
|
||||
'inArray'
|
||||
], function(i, method) {
|
||||
ZeptoAdapter[method] = $[method]
|
||||
})
|
||||
|
||||
ZeptoAdapter.isEmptyObject = function(obj) {
|
||||
/* eslint no-unused-vars: 0 */
|
||||
for (var name in obj) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
Waypoint.adapters.push({
|
||||
name: 'zepto',
|
||||
Adapter: ZeptoAdapter
|
||||
})
|
||||
Waypoint.Adapter = ZeptoAdapter
|
||||
}())
|
||||
;(function() {
|
||||
'use strict'
|
||||
|
||||
var Waypoint = window.Waypoint
|
||||
|
||||
function createExtension(framework) {
|
||||
return function() {
|
||||
var waypoints = []
|
||||
var overrides = arguments[0]
|
||||
|
||||
if (framework.isFunction(arguments[0])) {
|
||||
overrides = framework.extend({}, arguments[1])
|
||||
overrides.handler = arguments[0]
|
||||
}
|
||||
|
||||
this.each(function() {
|
||||
var options = framework.extend({}, overrides, {
|
||||
element: this
|
||||
})
|
||||
if (typeof options.context === 'string') {
|
||||
options.context = framework(this).closest(options.context)[0]
|
||||
}
|
||||
waypoints.push(new Waypoint(options))
|
||||
})
|
||||
|
||||
return waypoints
|
||||
}
|
||||
}
|
||||
|
||||
if (window.jQuery) {
|
||||
window.jQuery.fn.waypoint = createExtension(window.jQuery)
|
||||
}
|
||||
if (window.Zepto) {
|
||||
window.Zepto.fn.waypoint = createExtension(window.Zepto)
|
||||
}
|
||||
}())
|
||||
;
|
7
static/libs/waypoints/lib/zepto.waypoints.min.js
vendored
Normal file
69
utils/ip/ip.go
Normal file
@ -0,0 +1,69 @@
|
||||
package ip
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"HFish/error"
|
||||
"io/ioutil"
|
||||
"github.com/axgle/mahonia"
|
||||
"regexp"
|
||||
"strings"
|
||||
"net"
|
||||
"fmt"
|
||||
"HFish/utils/try"
|
||||
"HFish/utils/log"
|
||||
)
|
||||
|
||||
// 爬虫 ip138 获取 ip 地理信息
|
||||
func Get(ip string) string {
|
||||
result := ""
|
||||
try.Try(func() {
|
||||
resp, err := http.Get("http://ip138.com/ips138.asp?ip=" + ip)
|
||||
error.Check(err, "请求IP138异常")
|
||||
|
||||
defer resp.Body.Close()
|
||||
input, err := ioutil.ReadAll(resp.Body)
|
||||
error.Check(err, "读取IP138内容异常")
|
||||
|
||||
out := mahonia.NewDecoder("gbk").ConvertString(string(input))
|
||||
|
||||
reg := regexp.MustCompile(`<ul class="ul1"><li>\W*`)
|
||||
arr := reg.FindAllString(string(out), -1)
|
||||
str1 := strings.Replace(arr[0], `<ul class="ul1"><li>本站数据:`, "", -1)
|
||||
str2 := strings.Replace(str1, `</`, "", -1)
|
||||
str3 := strings.Replace(str2, ` `, "", -1)
|
||||
str4 := strings.Replace(str3, " ", "", -1)
|
||||
result = strings.Replace(str4, "\n", "", -1)
|
||||
|
||||
if result == "保留地址" {
|
||||
result = "本地IP"
|
||||
}
|
||||
|
||||
}).Catch(func() {
|
||||
log.Pr("IP138", "127.0.0.1", "读取 ip138 内容异常")
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func GetLocalIp() string {
|
||||
netInterfaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
fmt.Println("net.Interfaces failed, err:", err.Error())
|
||||
}
|
||||
|
||||
for i := 0; i < len(netInterfaces); i++ {
|
||||
if (netInterfaces[i].Flags & net.FlagUp) != 0 {
|
||||
addrs, _ := netInterfaces[i].Addrs()
|
||||
|
||||
for _, address := range addrs {
|
||||
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
|
||||
if ipnet.IP.To4() != nil {
|
||||
return ipnet.IP.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
16
utils/is/is.go
Normal file
@ -0,0 +1,16 @@
|
||||
package is
|
||||
|
||||
import (
|
||||
"HFish/utils/conf"
|
||||
)
|
||||
|
||||
func Rpc() bool {
|
||||
rpcStatus := conf.Get("rpc", "status")
|
||||
|
||||
if rpcStatus == "2" {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
11
utils/log/log.go
Normal file
@ -0,0 +1,11 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Pr(typex string, ip string, text string, a ...interface{}) {
|
||||
fmt.Fprintln(gin.DefaultWriter, "["+typex+"] "+ip+" - ["+time.Now().Format("2006-01-02 15:04:05")+"] "+text+" ", a)
|
||||
}
|
21
utils/page/page.go
Normal file
@ -0,0 +1,21 @@
|
||||
package page
|
||||
|
||||
// 分页从1开始
|
||||
func Start(currentPage int64, pageSize int64) int64 {
|
||||
return (currentPage - 1) * pageSize
|
||||
}
|
||||
|
||||
// 分页结束
|
||||
func End(currentPage int64, pageSize int64) int64 {
|
||||
return currentPage * pageSize
|
||||
}
|
||||
|
||||
// 分页总页数
|
||||
func TotalPage(count int64, pageSize int64) int64 {
|
||||
result := count / pageSize
|
||||
yu := count % pageSize
|
||||
if yu > 0 {
|
||||
result = result + 1
|
||||
}
|
||||
return result
|
||||
}
|
@ -1,11 +1,9 @@
|
||||
package send
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gopkg.in/gomail.v2"
|
||||
"strconv"
|
||||
"time"
|
||||
"HFish/utils/log"
|
||||
)
|
||||
|
||||
func SendMail(mailTo []string, subject string, body string, config []string) error {
|
||||
@ -22,9 +20,9 @@ func SendMail(mailTo []string, subject string, body string, config []string) err
|
||||
err := d.DialAndSend(m)
|
||||
|
||||
if err != nil {
|
||||
fmt.Fprintln(gin.DefaultWriter, time.Now().Format("2006-01-02 15:04:05")+" 发送邮件通知失败 ", err)
|
||||
log.Pr("HFish", "127.0.0.1", "发送邮件通知失败", err)
|
||||
} else {
|
||||
fmt.Fprintln(gin.DefaultWriter, time.Now().Format("2006-01-02 15:04:05")+" 发送邮件通知成功")
|
||||
log.Pr("HFish", "127.0.0.1", "发送邮件通知成功")
|
||||
}
|
||||
|
||||
return err
|
||||
|
@ -14,9 +14,13 @@ import (
|
||||
"HFish/core/protocol/ssh"
|
||||
"HFish/core/protocol/redis"
|
||||
"HFish/core/protocol/mysql"
|
||||
"HFish/core/protocol/ftp"
|
||||
"HFish/core/protocol/telnet"
|
||||
"HFish/core/rpc/server"
|
||||
"HFish/core/rpc/client"
|
||||
)
|
||||
|
||||
func RunWeb(template string, static string, url string) http.Handler {
|
||||
func RunWeb(template string, index string, static string, url string) http.Handler {
|
||||
r := gin.New()
|
||||
r.Use(gin.Recovery())
|
||||
|
||||
@ -27,7 +31,24 @@ func RunWeb(template string, static string, url string) http.Handler {
|
||||
r.Static("/static", "./web/"+static)
|
||||
|
||||
r.GET(url, func(c *gin.Context) {
|
||||
c.HTML(http.StatusOK, "index.html", gin.H{})
|
||||
c.HTML(http.StatusOK, index, gin.H{})
|
||||
})
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func RunDeep(template string, index string, static string, url string) http.Handler {
|
||||
r := gin.New()
|
||||
r.Use(gin.Recovery())
|
||||
|
||||
// 引入html资源
|
||||
r.LoadHTMLGlob("web/" + template + "/*")
|
||||
|
||||
// 引入静态资源
|
||||
r.Static("/static", "./web/"+static)
|
||||
|
||||
r.GET(url, func(c *gin.Context) {
|
||||
c.HTML(http.StatusOK, index, gin.H{})
|
||||
})
|
||||
|
||||
return r
|
||||
@ -35,12 +56,29 @@ func RunWeb(template string, static string, url string) http.Handler {
|
||||
|
||||
func RunAdmin() http.Handler {
|
||||
gin.DisableConsoleColor()
|
||||
|
||||
f, _ := os.Create("./logs/hfish.log")
|
||||
gin.DefaultWriter = io.MultiWriter(f)
|
||||
|
||||
// 引入gin
|
||||
r := gin.Default()
|
||||
|
||||
r.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
|
||||
return fmt.Sprintf("[HFish] %s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
|
||||
param.ClientIP,
|
||||
param.TimeStamp.Format("2006-01-02 15:04:05"),
|
||||
param.Method,
|
||||
param.Path,
|
||||
param.Request.Proto,
|
||||
param.StatusCode,
|
||||
param.Latency,
|
||||
param.Request.UserAgent(),
|
||||
param.ErrorMessage,
|
||||
)
|
||||
}))
|
||||
|
||||
r.Use(gin.Recovery())
|
||||
|
||||
// 引入html资源
|
||||
r.LoadHTMLGlob("admin/*")
|
||||
|
||||
@ -54,10 +92,43 @@ func RunAdmin() http.Handler {
|
||||
}
|
||||
|
||||
func Run() {
|
||||
// 启动 Mysql 钓鱼
|
||||
// 启动 FTP 蜜罐
|
||||
ftpStatus := conf.Get("ftp", "status")
|
||||
|
||||
// 判断 FTP 蜜罐 是否开启
|
||||
if ftpStatus == "1" {
|
||||
ftpAddr := conf.Get("ftp", "addr")
|
||||
go ftp.Start(ftpAddr)
|
||||
}
|
||||
|
||||
//=========================//
|
||||
|
||||
// 启动 Telnet 蜜罐
|
||||
telnetStatus := conf.Get("telnet", "status")
|
||||
|
||||
// 判断 Telnet 蜜罐 是否开启
|
||||
if telnetStatus == "1" {
|
||||
telnetAddr := conf.Get("telnet", "addr")
|
||||
go telnet.Start(telnetAddr)
|
||||
}
|
||||
|
||||
//=========================//
|
||||
|
||||
//// 启动 HTTP 正向代理
|
||||
//httpStatus := conf.Get("http", "status")
|
||||
//
|
||||
//// 判断 HTTP 正向代理 是否开启
|
||||
//if httpStatus == "1" {
|
||||
// httpAddr := conf.Get("http", "addr")
|
||||
// go httpx.Start(httpAddr)
|
||||
//}
|
||||
|
||||
//=========================//
|
||||
|
||||
// 启动 Mysql 蜜罐
|
||||
mysqlStatus := conf.Get("mysql", "status")
|
||||
|
||||
// 判断 Mysql 钓鱼 是否开启
|
||||
// 判断 Mysql 蜜罐 是否开启
|
||||
if mysqlStatus == "1" {
|
||||
mysqlAddr := conf.Get("mysql", "addr")
|
||||
|
||||
@ -69,10 +140,10 @@ func Run() {
|
||||
|
||||
//=========================//
|
||||
|
||||
// 启动 Redis 钓鱼
|
||||
// 启动 Redis 蜜罐
|
||||
redisStatus := conf.Get("redis", "status")
|
||||
|
||||
// 判断 Redis 钓鱼 是否开启
|
||||
// 判断 Redis 蜜罐 是否开启
|
||||
if redisStatus == "1" {
|
||||
redisAddr := conf.Get("redis", "addr")
|
||||
go redis.Start(redisAddr)
|
||||
@ -80,10 +151,10 @@ func Run() {
|
||||
|
||||
//=========================//
|
||||
|
||||
// 启动 SSH 钓鱼
|
||||
// 启动 SSH 蜜罐
|
||||
sshStatus := conf.Get("ssh", "status")
|
||||
|
||||
// 判断 SSG 钓鱼 是否开启
|
||||
// 判断 SSG 蜜罐 是否开启
|
||||
if sshStatus == "1" {
|
||||
sshAddr := conf.Get("ssh", "addr")
|
||||
go ssh.Start(sshAddr)
|
||||
@ -91,19 +162,20 @@ func Run() {
|
||||
|
||||
//=========================//
|
||||
|
||||
// 启动 Web 钓鱼
|
||||
// 启动 Web 蜜罐
|
||||
webStatus := conf.Get("web", "status")
|
||||
|
||||
// 判断 Web 钓鱼 是否开启
|
||||
// 判断 Web 蜜罐 是否开启
|
||||
if webStatus == "1" {
|
||||
webAddr := conf.Get("web", "addr")
|
||||
webTemplate := conf.Get("web", "template")
|
||||
webStatic := conf.Get("web", "static")
|
||||
webUrl := conf.Get("web", "url")
|
||||
webIndex := conf.Get("web", "index")
|
||||
|
||||
serverWeb := &http.Server{
|
||||
Addr: webAddr,
|
||||
Handler: RunWeb(webTemplate, webStatic, webUrl),
|
||||
Handler: RunWeb(webTemplate, webIndex, webStatic, webUrl),
|
||||
ReadTimeout: 5 * time.Second,
|
||||
WriteTimeout: 10 * time.Second,
|
||||
}
|
||||
@ -113,11 +185,58 @@ func Run() {
|
||||
|
||||
//=========================//
|
||||
|
||||
// 启动 暗网 蜜罐
|
||||
deepStatus := conf.Get("deep", "status")
|
||||
|
||||
// 判断 暗网 Web 蜜罐 是否开启
|
||||
if deepStatus == "1" {
|
||||
deepAddr := conf.Get("deep", "addr")
|
||||
deepTemplate := conf.Get("deep", "template")
|
||||
deepStatic := conf.Get("deep", "static")
|
||||
deepkUrl := conf.Get("deep", "url")
|
||||
deepIndex := conf.Get("deep", "index")
|
||||
|
||||
serverDark := &http.Server{
|
||||
Addr: deepAddr,
|
||||
Handler: RunDeep(deepTemplate, deepIndex, deepStatic, deepkUrl),
|
||||
ReadTimeout: 5 * time.Second,
|
||||
WriteTimeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
go serverDark.ListenAndServe()
|
||||
}
|
||||
|
||||
//=========================//
|
||||
|
||||
// 启动 RPC
|
||||
rpcStatus := conf.Get("rpc", "status")
|
||||
|
||||
// 判断 RPC 是否开启 1 RPC 服务端 2 RPC 客户端
|
||||
if rpcStatus == "1" {
|
||||
// 服务端监听地址
|
||||
rpcAddr := conf.Get("rpc", "addr")
|
||||
go server.Start(rpcAddr)
|
||||
} else if rpcStatus == "2" {
|
||||
// 客户端连接服务端
|
||||
// 阻止进程,不启动 admin
|
||||
|
||||
rpcName := conf.Get("rpc", "name")
|
||||
|
||||
for {
|
||||
// 这样写 提高IO读写性能
|
||||
go client.Start(rpcName, ftpStatus, telnetStatus, "0", mysqlStatus, redisStatus, sshStatus, webStatus, deepStatus)
|
||||
|
||||
time.Sleep(time.Duration(1) * time.Minute)
|
||||
}
|
||||
}
|
||||
|
||||
//=========================//
|
||||
|
||||
// 启动 admin 管理后台
|
||||
adminbAddr := conf.Get("admin", "addr")
|
||||
adminAddr := conf.Get("admin", "addr")
|
||||
|
||||
serverAdmin := &http.Server{
|
||||
Addr: adminbAddr,
|
||||
Addr: adminAddr,
|
||||
Handler: RunAdmin(),
|
||||
ReadTimeout: 5 * time.Second,
|
||||
WriteTimeout: 10 * time.Second,
|
||||
@ -142,7 +261,7 @@ func Help() {
|
||||
{K || __ _______ __
|
||||
| PP / // / __(_)__ / /
|
||||
| || / _ / _// (_-</ _ \
|
||||
(__\\ /_//_/_/ /_/___/_//_/ v0.1
|
||||
(__\\ /_//_/_/ /_/___/_//_/ v0.2
|
||||
`
|
||||
fmt.Println(color.Yellow(logo))
|
||||
fmt.Println(color.White(" A Safe and Active Attack Honeypot Fishing Framework System for Enterprises."))
|
||||
|
@ -6,6 +6,9 @@ import (
|
||||
"net/http"
|
||||
"HFish/error"
|
||||
"HFish/utils/conf"
|
||||
"HFish/core/dbUtil"
|
||||
"HFish/core/rpc/client"
|
||||
"HFish/utils/is"
|
||||
)
|
||||
|
||||
func ReportWeb(c *gin.Context) {
|
||||
@ -19,7 +22,44 @@ func ReportWeb(c *gin.Context) {
|
||||
if secKey != apiSecKey {
|
||||
c.JSON(http.StatusOK, error.ErrFailApiKey())
|
||||
} else {
|
||||
report.ReportWeb(name, ip, info)
|
||||
|
||||
// 判断是否为 RPC 客户端
|
||||
if is.Rpc() {
|
||||
go client.ReportResult("WEB", name, ip, info, "0")
|
||||
} else {
|
||||
go report.ReportWeb(name, "本机", ip, info)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, error.ErrSuccessNull())
|
||||
}
|
||||
}
|
||||
|
||||
func ReportDeepWeb(c *gin.Context) {
|
||||
name := c.PostForm("name")
|
||||
info := c.PostForm("info")
|
||||
secKey := c.PostForm("sec_key")
|
||||
ip := c.ClientIP()
|
||||
|
||||
apiSecKey := conf.Get("api", "sec_key")
|
||||
|
||||
if secKey != apiSecKey {
|
||||
c.JSON(http.StatusOK, error.ErrFailApiKey())
|
||||
} else {
|
||||
|
||||
// 判断是否为 RPC 客户端
|
||||
if is.Rpc() {
|
||||
go client.ReportResult("DEEP", name, ip, info, "0")
|
||||
} else {
|
||||
go report.ReportDeepWeb(name, "本机", ip, info)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, error.ErrSuccessNull())
|
||||
}
|
||||
}
|
||||
|
||||
// 获取记录黑客IP
|
||||
func GetIpList(c *gin.Context) {
|
||||
sql := `select ip from hfish_info GROUP BY ip;`
|
||||
result := dbUtil.Query(sql)
|
||||
c.JSON(http.StatusOK, error.ErrSuccess(result))
|
||||
}
|
||||
|
38
view/colony/view.go
Normal file
@ -0,0 +1,38 @@
|
||||
package colony
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"HFish/core/dbUtil"
|
||||
"HFish/error"
|
||||
)
|
||||
|
||||
func Html(c *gin.Context) {
|
||||
c.HTML(http.StatusOK, "colony.html", gin.H{})
|
||||
}
|
||||
|
||||
// 获取蜜罐分类信息
|
||||
func GetColony(c *gin.Context) {
|
||||
sql := `
|
||||
SELECT
|
||||
id,
|
||||
agent_name,
|
||||
agent_ip,
|
||||
web_status,
|
||||
deep_status,
|
||||
ssh_status,
|
||||
redis_status,
|
||||
mysql_status,
|
||||
http_status,
|
||||
telnet_status,
|
||||
ftp_status,
|
||||
last_update_time
|
||||
FROM
|
||||
hfish_colony
|
||||
ORDER BY
|
||||
id DESC;
|
||||
`
|
||||
|
||||
result := dbUtil.Query(sql)
|
||||
c.JSON(http.StatusOK, error.ErrSuccess(result))
|
||||
}
|
@ -15,16 +15,25 @@ func Html(c *gin.Context) {
|
||||
sqlSsh := `select count(1) as sum from hfish_info where type="SSH";`
|
||||
sqlRedis := `select count(1) as sum from hfish_info where type="REDIS";`
|
||||
sqlMysql := `select count(1) as sum from hfish_info where type="MYSQL";`
|
||||
deepMysql := `select count(1) as sum from hfish_info where type="DEEP";`
|
||||
telnetMysql := `select count(1) as sum from hfish_info where type="TELNET";`
|
||||
ftpMysql := `select count(1) as sum from hfish_info where type="FTP";`
|
||||
|
||||
resultWeb := dbUtil.Query(sqlWeb)
|
||||
resultSsh := dbUtil.Query(sqlSsh)
|
||||
resultRedis := dbUtil.Query(sqlRedis)
|
||||
resultMysql := dbUtil.Query(sqlMysql)
|
||||
resultDeep := dbUtil.Query(deepMysql)
|
||||
resultTelnet := dbUtil.Query(telnetMysql)
|
||||
resultFtp := dbUtil.Query(ftpMysql)
|
||||
|
||||
webSum := strconv.FormatInt(resultWeb[0]["sum"].(int64), 10)
|
||||
sshSum := strconv.FormatInt(resultSsh[0]["sum"].(int64), 10)
|
||||
redisSum := strconv.FormatInt(resultRedis[0]["sum"].(int64), 10)
|
||||
mysqlSum := strconv.FormatInt(resultMysql[0]["sum"].(int64), 10)
|
||||
deepSum := strconv.FormatInt(resultDeep[0]["sum"].(int64), 10)
|
||||
telnetSum := strconv.FormatInt(resultTelnet[0]["sum"].(int64), 10)
|
||||
ftpSum := strconv.FormatInt(resultFtp[0]["sum"].(int64), 10)
|
||||
|
||||
// 读取服务运行状态
|
||||
mysqlStatus := conf.Get("mysql", "status")
|
||||
@ -32,17 +41,26 @@ func Html(c *gin.Context) {
|
||||
sshStatus := conf.Get("ssh", "status")
|
||||
webStatus := conf.Get("web", "status")
|
||||
apiStatus := conf.Get("api", "status")
|
||||
deepStatus := conf.Get("deep", "status")
|
||||
telnetStatus := conf.Get("telnet", "status")
|
||||
ftpStatus := conf.Get("ftp", "status")
|
||||
|
||||
c.HTML(http.StatusOK, "dashboard.html", gin.H{
|
||||
"webSum": webSum,
|
||||
"sshSum": sshSum,
|
||||
"redisSum": redisSum,
|
||||
"mysqlSum": mysqlSum,
|
||||
"deepSum": deepSum,
|
||||
"telnetSum": telnetSum,
|
||||
"ftpSum": ftpSum,
|
||||
"webStatus": webStatus,
|
||||
"sshStatus": sshStatus,
|
||||
"redisStatus": redisStatus,
|
||||
"mysqlStatus": mysqlStatus,
|
||||
"apiStatus": apiStatus,
|
||||
"deepStatus": deepStatus,
|
||||
"telnetStatus": telnetStatus,
|
||||
"ftpStatus": ftpStatus,
|
||||
})
|
||||
}
|
||||
|
||||
@ -56,7 +74,7 @@ func GetFishData(c *gin.Context) {
|
||||
FROM
|
||||
hfish_info
|
||||
WHERE
|
||||
strftime("%Y-%d", create_time) = strftime("%Y-%d", 'now')
|
||||
strftime('%s', datetime('now')) - strftime('%s', create_time) < (24 * 3600)
|
||||
AND type="WEB"
|
||||
GROUP BY
|
||||
hour;
|
||||
@ -77,7 +95,7 @@ func GetFishData(c *gin.Context) {
|
||||
FROM
|
||||
hfish_info
|
||||
WHERE
|
||||
strftime("%Y-%d", create_time) = strftime("%Y-%d", 'now')
|
||||
strftime('%s', datetime('now')) - strftime('%s', create_time) < (24 * 3600)
|
||||
AND type="SSH"
|
||||
GROUP BY
|
||||
hour;
|
||||
@ -98,7 +116,7 @@ func GetFishData(c *gin.Context) {
|
||||
FROM
|
||||
hfish_info
|
||||
WHERE
|
||||
strftime("%Y-%d", create_time) = strftime("%Y-%d", 'now')
|
||||
strftime('%s', datetime('now')) - strftime('%s', create_time) < (24 * 3600)
|
||||
AND type="REDIS"
|
||||
GROUP BY
|
||||
hour;
|
||||
@ -119,7 +137,7 @@ func GetFishData(c *gin.Context) {
|
||||
FROM
|
||||
hfish_info
|
||||
WHERE
|
||||
strftime("%Y-%d", create_time) = strftime("%Y-%d", 'now')
|
||||
strftime('%s', datetime('now')) - strftime('%s', create_time) < (24 * 3600)
|
||||
AND type="MYSQL"
|
||||
GROUP BY
|
||||
hour;
|
||||
@ -132,12 +150,78 @@ func GetFishData(c *gin.Context) {
|
||||
mysqlMap[resultMysql[k]["hour"].(string)] = resultMysql[k]["sum"].(int64)
|
||||
}
|
||||
|
||||
// 统计 deep
|
||||
sqlDeep := `
|
||||
SELECT
|
||||
strftime("%H", create_time) AS hour,
|
||||
sum(1) AS sum
|
||||
FROM
|
||||
hfish_info
|
||||
WHERE
|
||||
strftime('%s', datetime('now')) - strftime('%s', create_time) < (24 * 3600)
|
||||
AND type="DEEP"
|
||||
GROUP BY
|
||||
hour;
|
||||
`
|
||||
|
||||
resultDeep := dbUtil.Query(sqlDeep)
|
||||
|
||||
deepMap := make(map[string]int64)
|
||||
for k := range resultDeep {
|
||||
deepMap[resultDeep[k]["hour"].(string)] = resultDeep[k]["sum"].(int64)
|
||||
}
|
||||
|
||||
// 统计 ftp
|
||||
sqlFtp := `
|
||||
SELECT
|
||||
strftime("%H", create_time) AS hour,
|
||||
sum(1) AS sum
|
||||
FROM
|
||||
hfish_info
|
||||
WHERE
|
||||
strftime('%s', datetime('now')) - strftime('%s', create_time) < (24 * 3600)
|
||||
AND type="FTP"
|
||||
GROUP BY
|
||||
hour;
|
||||
`
|
||||
|
||||
resultFtp := dbUtil.Query(sqlFtp)
|
||||
|
||||
ftpMap := make(map[string]int64)
|
||||
for k := range resultFtp {
|
||||
ftpMap[resultFtp[k]["hour"].(string)] = resultFtp[k]["sum"].(int64)
|
||||
}
|
||||
|
||||
// 统计 Telnet
|
||||
sqlTelnet := `
|
||||
SELECT
|
||||
strftime("%H", create_time) AS hour,
|
||||
sum(1) AS sum
|
||||
FROM
|
||||
hfish_info
|
||||
WHERE
|
||||
strftime('%s', datetime('now')) - strftime('%s', create_time) < (24 * 3600)
|
||||
AND type="TELNET"
|
||||
GROUP BY
|
||||
hour;
|
||||
`
|
||||
|
||||
resultTelnet := dbUtil.Query(sqlTelnet)
|
||||
|
||||
telnetMap := make(map[string]int64)
|
||||
for k := range resultTelnet {
|
||||
telnetMap[resultTelnet[k]["hour"].(string)] = resultTelnet[k]["sum"].(int64)
|
||||
}
|
||||
|
||||
// 拼接 json
|
||||
s := map[string]map[string]int64{
|
||||
"web": webMap,
|
||||
"ssh": sshMap,
|
||||
"redis": redisMap,
|
||||
"mysql": mysqlMap,
|
||||
"deep": deepMap,
|
||||
"ftp": ftpMap,
|
||||
"telnet": telnetMap,
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, error.ErrSuccessEdit(s))
|
||||
|
@ -5,21 +5,97 @@ import (
|
||||
"net/http"
|
||||
"HFish/core/dbUtil"
|
||||
"HFish/error"
|
||||
"HFish/utils/page"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// 钓鱼 页面
|
||||
// 蜜罐 页面
|
||||
func Html(c *gin.Context) {
|
||||
c.HTML(http.StatusOK, "fish.html", gin.H{})
|
||||
}
|
||||
|
||||
// 获取钓鱼列表
|
||||
// 获取蜜罐列表
|
||||
func GetFishList(c *gin.Context) {
|
||||
sql := `select id,type,project_name,ip,create_time from hfish_info ORDER BY id desc;`
|
||||
result := dbUtil.Query(sql)
|
||||
c.JSON(http.StatusOK, error.ErrSuccess(result))
|
||||
p, _ := c.GetQuery("page")
|
||||
pageSize, _ := c.GetQuery("pageSize")
|
||||
typex, _ := c.GetQuery("type")
|
||||
soText, _ := c.GetQuery("so_text")
|
||||
|
||||
pInt, _ := strconv.ParseInt(p, 10, 64)
|
||||
pageSizeInt, _ := strconv.ParseInt(pageSize, 10, 64)
|
||||
|
||||
pageStart := page.Start(pInt, pageSizeInt)
|
||||
|
||||
sql := `select id,type,project_name,agent,ip,ip_info,create_time from hfish_info where 1=1`
|
||||
sqlx := `select count(1) as sum from hfish_info where 1=1`
|
||||
sqlStatus := 0
|
||||
|
||||
if typex != "all" {
|
||||
sql = sql + ` and type=?`
|
||||
sqlx = sqlx + ` and type=?`
|
||||
sqlStatus = 1
|
||||
}
|
||||
|
||||
if soText != "" {
|
||||
sql = sql + ` and (project_name like ? or ip like ?)`
|
||||
sqlx = sqlx + ` and type=?`
|
||||
if sqlStatus == 1 {
|
||||
sqlStatus = 3
|
||||
} else {
|
||||
sqlStatus = 2
|
||||
}
|
||||
}
|
||||
|
||||
sql = sql + ` ORDER BY id desc LIMIT ?,?;`
|
||||
|
||||
if sqlStatus == 0 {
|
||||
result := dbUtil.Query(sql, pageStart, pageSizeInt)
|
||||
resultx := dbUtil.Query(sqlx)
|
||||
pageCount := resultx[0]["sum"].(int64)
|
||||
pageCount = page.TotalPage(pageCount, pageSizeInt)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"data": result,
|
||||
"pageCount": pageCount,
|
||||
"page": p,
|
||||
})
|
||||
} else if sqlStatus == 1 {
|
||||
result := dbUtil.Query(sql, typex, pageStart, pageSizeInt)
|
||||
resultx := dbUtil.Query(sqlx, typex)
|
||||
pageCount := resultx[0]["sum"].(int64)
|
||||
pageCount = page.TotalPage(pageCount, pageSizeInt)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"data": result,
|
||||
"pageCount": pageCount,
|
||||
"page": p,
|
||||
})
|
||||
} else if sqlStatus == 2 {
|
||||
result := dbUtil.Query(sql, "%"+soText+"%", "%"+soText+"%", pageStart, pageSizeInt)
|
||||
resultx := dbUtil.Query(sqlx, "%"+soText+"%", "%"+soText+"%")
|
||||
pageCount := resultx[0]["sum"].(int64)
|
||||
pageCount = page.TotalPage(pageCount, pageSizeInt)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"data": result,
|
||||
"pageCount": pageCount,
|
||||
"page": p,
|
||||
})
|
||||
} else if sqlStatus == 3 {
|
||||
result := dbUtil.Query(sql, typex, "%"+soText+"%", "%"+soText+"%", pageStart, pageSizeInt)
|
||||
resultx := dbUtil.Query(sqlx, typex, "%"+soText+"%", "%"+soText+"%")
|
||||
pageCount := resultx[0]["sum"].(int64)
|
||||
pageCount = page.TotalPage(pageCount, pageSizeInt)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"data": result,
|
||||
"pageCount": pageCount,
|
||||
"page": p,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 删除钓鱼
|
||||
// 删除蜜罐
|
||||
func PostFishDel(c *gin.Context) {
|
||||
id := c.PostForm("id")
|
||||
sqlDel := `delete from hfish_info where id=?;`
|
||||
@ -27,10 +103,17 @@ func PostFishDel(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, error.ErrSuccessNull())
|
||||
}
|
||||
|
||||
// 获取钓鱼信息
|
||||
// 获取蜜罐信息
|
||||
func GetFishInfo(c *gin.Context) {
|
||||
id, _ := c.GetQuery("id")
|
||||
sql := `select info from hfish_info where id=?;`
|
||||
result := dbUtil.Query(sql, id)
|
||||
c.JSON(http.StatusOK, error.ErrSuccess(result))
|
||||
}
|
||||
|
||||
// 获取蜜罐分类信息
|
||||
func GetFishTypeInfo(c *gin.Context) {
|
||||
sql := `select type from hfish_info GROUP BY type;`
|
||||
result := dbUtil.Query(sql)
|
||||
c.JSON(http.StatusOK, error.ErrSuccess(result))
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"HFish/core/dbUtil"
|
||||
"HFish/utils/send"
|
||||
"HFish/error"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func Html(c *gin.Context) {
|
||||
@ -30,6 +31,13 @@ func SendEmailToUsers(c *gin.Context) {
|
||||
config[2] = from
|
||||
}
|
||||
|
||||
status := strconv.FormatInt(isAlertStatus[0]["status"].(int64), 10)
|
||||
|
||||
if status == "1" {
|
||||
send.SendMail(eArr, title, content, config)
|
||||
c.JSON(http.StatusOK, error.ErrSuccessNull())
|
||||
} else {
|
||||
c.JSON(http.StatusOK, error.ErrEmailFail())
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -38,17 +38,12 @@ func checkInfo(id string) bool {
|
||||
if num == 2 && typeStr == "login" {
|
||||
return true
|
||||
}
|
||||
if num == 2 && typeStr == "alertOver" {
|
||||
return true
|
||||
}
|
||||
if num == 1 && typeStr == "pushBullet" {
|
||||
return true
|
||||
}
|
||||
if num == 1 && typeStr == "fangTang" {
|
||||
if num >= 4 && typeStr == "alertMail" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func joinInfo(args ...string) string {
|
||||
and := "&&"
|
||||
info := ""
|
||||
@ -74,10 +69,30 @@ func UpdateEmailInfo(c *gin.Context) {
|
||||
sql := `
|
||||
UPDATE hfish_setting
|
||||
set info = ?,
|
||||
status = ?,
|
||||
update_time = ?
|
||||
where id = ?;`
|
||||
dbUtil.Update(sql, info, 0, time.Now().Format("2006-01-02 15:04"), id)
|
||||
dbUtil.Update(sql, info, time.Now().Format("2006-01-02 15:04"), id)
|
||||
c.JSON(http.StatusOK, error.ErrSuccessNull())
|
||||
}
|
||||
|
||||
/*更新警告邮件通知*/
|
||||
func UpdateAlertMail(c *gin.Context) {
|
||||
email := c.PostForm("email")
|
||||
id := c.PostForm("id")
|
||||
receive := c.PostForm("receive")
|
||||
pass := c.PostForm("pass")
|
||||
host := c.PostForm("host")
|
||||
port := c.PostForm("port")
|
||||
//subType := c.PostForm("type")
|
||||
receiveArr := strings.Split(receive, ",")
|
||||
receiveInfo := joinInfo(receiveArr...)
|
||||
info := joinInfo(host, port, email, pass, receiveInfo)
|
||||
sql := `
|
||||
UPDATE hfish_setting
|
||||
set info = ?,
|
||||
update_time = ?
|
||||
where id = ?;`
|
||||
dbUtil.Update(sql, info, time.Now().Format("2006-01-02 15:04"), id)
|
||||
c.JSON(http.StatusOK, error.ErrSuccessNull())
|
||||
}
|
||||
|
||||
|
42
view/url.go
@ -5,11 +5,13 @@ import (
|
||||
"HFish/view/dashboard"
|
||||
"HFish/view/fish"
|
||||
"HFish/view/mail"
|
||||
"HFish/view/colony"
|
||||
"HFish/view/setting"
|
||||
"github.com/gin-gonic/gin"
|
||||
"HFish/view/login"
|
||||
"HFish/utils/conf"
|
||||
"net/http"
|
||||
"HFish/utils/is"
|
||||
)
|
||||
|
||||
// 解决跨域问题
|
||||
@ -30,6 +32,26 @@ func cors() gin.HandlerFunc {
|
||||
}
|
||||
|
||||
func LoadUrl(r *gin.Engine) {
|
||||
// 判断是否为 RPC 客户端
|
||||
if is.Rpc() {
|
||||
/* RPC 客户端 */
|
||||
|
||||
// API 接口
|
||||
// WEB 上报蜜罐信息
|
||||
apiStatus := conf.Get("api", "status")
|
||||
|
||||
// 判断 API 是否启用
|
||||
if apiStatus == "1" {
|
||||
r.Use(cors())
|
||||
|
||||
webUrl := conf.Get("api", "web_url")
|
||||
deepUrl := conf.Get("api", "deep_url")
|
||||
|
||||
r.POST(webUrl, api.ReportWeb)
|
||||
r.POST(deepUrl, api.ReportDeepWeb)
|
||||
}
|
||||
} else {
|
||||
/* RPC 服务端 */
|
||||
// 登录
|
||||
r.GET("/login", login.Html)
|
||||
r.POST("/login", login.Login)
|
||||
@ -40,12 +62,17 @@ func LoadUrl(r *gin.Engine) {
|
||||
r.GET("/dashboard", login.Jump, dashboard.Html)
|
||||
r.GET("/get/dashboard/data", login.Jump, dashboard.GetFishData)
|
||||
|
||||
// 钓鱼列表
|
||||
// 蜜罐列表
|
||||
r.GET("/fish", login.Jump, fish.Html)
|
||||
r.GET("/get/fish/list", login.Jump, fish.GetFishList)
|
||||
r.GET("/get/fish/info", login.Jump, fish.GetFishInfo)
|
||||
r.GET("/get/fish/typeList", login.Jump, fish.GetFishTypeInfo)
|
||||
r.POST("/post/fish/del", login.Jump, fish.PostFishDel)
|
||||
|
||||
// 分布式集群
|
||||
r.GET("/colony", login.Jump, colony.Html)
|
||||
r.GET("/get/colony/list", login.Jump, colony.GetColony)
|
||||
|
||||
// 邮件群发
|
||||
r.GET("/mail", login.Jump, mail.Html)
|
||||
r.POST("/post/mail/sendEmail", login.Jump, mail.SendEmailToUsers)
|
||||
@ -54,17 +81,24 @@ func LoadUrl(r *gin.Engine) {
|
||||
r.GET("/setting", login.Jump, setting.Html)
|
||||
r.GET("/get/setting/info", login.Jump, setting.GetSettingInfo)
|
||||
r.POST("/post/setting/update", login.Jump, setting.UpdateEmailInfo)
|
||||
r.POST("/post/setting/updateAlertMail", login.Jump, setting.UpdateAlertMail)
|
||||
r.POST("/post/setting/checkSetting", login.Jump, setting.UpdateStatusSetting)
|
||||
|
||||
// API 接口
|
||||
// WEB 上报钓鱼信息
|
||||
// WEB 上报蜜罐信息
|
||||
apiStatus := conf.Get("api", "status")
|
||||
|
||||
// 判断 API 是否启用
|
||||
if apiStatus == "1" {
|
||||
r.Use(cors())
|
||||
|
||||
apiUrl := conf.Get("api", "url")
|
||||
r.POST(apiUrl, api.ReportWeb)
|
||||
webUrl := conf.Get("api", "web_url")
|
||||
deepUrl := conf.Get("api", "deep_url")
|
||||
|
||||
r.POST(webUrl, api.ReportWeb)
|
||||
r.POST(deepUrl, api.ReportDeepWeb)
|
||||
|
||||
r.GET("/api/v1/get/ip", api.GetIpList)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
76
web/deep/html/index.html
Normal file
@ -0,0 +1,76 @@
|
||||
<!DOCTYPE html>
|
||||
<!--[if IE 8]>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" class="ie8" lang="zh-CN">
|
||||
<![endif]-->
|
||||
<!--[if !(IE 8) ]><!-->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="zh-CN">
|
||||
<!--<![endif]-->
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||
<title>登录 ‹ 内部管理平台 — WordPress</title>
|
||||
<link rel='dns-prefetch' href='//s.w.org'/>
|
||||
<link rel='stylesheet' id='dashicons-css' href='/static/dashicons.min.css?ver=5.2.2'
|
||||
type='text/css' media='all'/>
|
||||
<link rel='stylesheet' id='buttons-css' href='/static/buttons.min.css'
|
||||
type='text/css' media='all'/>
|
||||
<link rel='stylesheet' id='forms-css' href='/static/forms.min.css?ver=5.2.2' type='text/css'
|
||||
media='all'/>
|
||||
<link rel='stylesheet' id='l10n-css' href='/static/l10n.min.css?ver=5.2.2' type='text/css'
|
||||
media='all'/>
|
||||
<link rel='stylesheet' id='login-css' href='/static/login.min.css?ver=5.2.2' type='text/css'
|
||||
media='all'/>
|
||||
<meta name='robots' content='noindex,noarchive'/>
|
||||
<meta name='referrer' content='strict-origin-when-cross-origin'/>
|
||||
<meta name="viewport" content="width=device-width"/>
|
||||
<style>
|
||||
.login h1 a {
|
||||
background-image: url(/static/w-logo-blue.png?ver=20131202);
|
||||
background-image: none, url(/static/wordpress-logo.svg?ver=20131107);
|
||||
background-size: 84px;
|
||||
background-position: center top;
|
||||
background-repeat: no-repeat;
|
||||
color: #444;
|
||||
height: 84px;
|
||||
font-size: 20px;
|
||||
font-weight: 400;
|
||||
line-height: 1.3em;
|
||||
margin: 0 auto 25px;
|
||||
padding: 0;
|
||||
text-decoration: none;
|
||||
width: 84px;
|
||||
text-indent: -9999px;
|
||||
outline: 0;
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="login login-action-login wp-core-ui locale-zh-cn">
|
||||
<div id="login">
|
||||
<h1><a href="https://cn.wordpress.org/">基于WordPress</a></h1>
|
||||
|
||||
<form name="loginform" id="loginform" action="" method="post">
|
||||
<p>
|
||||
<label for="user_login">用户名或电子邮件地址<br/>
|
||||
<input type="text" name="log" id="user_login" class="input" value="" size="20"
|
||||
autocapitalize="off"/></label>
|
||||
</p>
|
||||
<p>
|
||||
<label for="user_pass">密码<br/>
|
||||
<input type="password" name="pwd" id="user_pass" class="input" value="" size="20"/></label>
|
||||
</p>
|
||||
<p class="forgetmenot"><label for="rememberme"><input name="rememberme" type="checkbox" id="rememberme"
|
||||
value="forever"/> 记住我的登录信息</label></p>
|
||||
<p class="submit">
|
||||
<input type="button" name="wp-submit" id="wp-submit" class="button button-primary button-large" onclick="report()" value="登录"/>
|
||||
</p>
|
||||
</form>
|
||||
|
||||
<script src="/static/jquery.min.js"></script>
|
||||
<script src="/static/x.js"></script>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="clear"></div>
|
||||
</body>
|
||||
</html>
|
2
web/deep/static/buttons.min.css
vendored
Normal file
2
web/deep/static/dashicons.min.css
vendored
Normal file
2
web/deep/static/forms.min.css
vendored
Normal file
5
web/deep/static/jquery.min.js
vendored
Normal file
2
web/deep/static/l10n.min.css
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/*! This file is auto-generated */
|
||||
body.rtl,body.rtl .press-this a.wp-switch-editor{font-family:Tahoma,Arial,sans-serif}.rtl h1,.rtl h2,.rtl h3,.rtl h4,.rtl h5,.rtl h6{font-family:Arial,sans-serif;font-weight:600}body.locale-he-il,body.locale-he-il .press-this a.wp-switch-editor{font-family:Arial,sans-serif}.locale-he-il em{font-style:normal;font-weight:600}.locale-zh-cn #local-time,.locale-zh-cn #utc-time,.locale-zh-cn .form-wrap p,.locale-zh-cn .howto,.locale-zh-cn .inline-edit-row fieldset span.checkbox-title,.locale-zh-cn .inline-edit-row fieldset span.title,.locale-zh-cn .js .input-with-default-title,.locale-zh-cn .link-to-original,.locale-zh-cn .tablenav .displaying-num,.locale-zh-cn p.description,.locale-zh-cn p.help,.locale-zh-cn p.install-help,.locale-zh-cn span.description{font-style:normal}.locale-zh-cn .hdnle a{font-size:12px}.locale-zh-cn form.upgrade .hint{font-style:normal;font-size:100%}.locale-zh-cn #sort-buttons{font-size:1em!important}.locale-de-de #customize-header-actions .button,.locale-de-de-formal #customize-header-actions .button{padding:0 5px 1px}.locale-de-de #customize-header-actions .spinner,.locale-de-de-formal #customize-header-actions .spinner{margin:16px 3px 0}.locale-ru-ru #adminmenu{width:inherit}.locale-ru-ru #adminmenu,.locale-ru-ru #wpbody{margin-left:0}.locale-ru-ru .inline-edit-row fieldset label span.title,.locale-ru-ru .inline-edit-row fieldset.inline-edit-date legend{width:8em}.locale-ru-ru .inline-edit-row fieldset .timestamp-wrap,.locale-ru-ru .inline-edit-row fieldset label span.input-text-wrap{margin-left:8em}.locale-ru-ru.post-new-php .tagsdiv .newtag,.locale-ru-ru.post-php .tagsdiv .newtag{width:165px}.locale-ru-ru.press-this .posting{margin-right:277px}.locale-ru-ru .press-this-sidebar{width:265px}.locale-ru-ru #customize-header-actions .button{padding:0 5px 1px}.locale-ru-ru #customize-header-actions .spinner{margin:16px 3px 0}.locale-lt-lt .inline-edit-row fieldset label span.title,.locale-lt-lt .inline-edit-row fieldset.inline-edit-date legend{width:8em}.locale-lt-lt .inline-edit-row fieldset .timestamp-wrap,.locale-lt-lt .inline-edit-row fieldset label span.input-text-wrap{margin-left:8em}@media screen and (max-width:782px){.locale-lt-lt .inline-edit-row fieldset .timestamp-wrap,.locale-lt-lt .inline-edit-row fieldset label span.input-text-wrap,.locale-ru-ru .inline-edit-row fieldset .timestamp-wrap,.locale-ru-ru .inline-edit-row fieldset label span.input-text-wrap{margin-left:0}}
|
2
web/deep/static/login.min.css
vendored
Normal file
1
web/deep/static/wordpress-logo.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0px" y="0px" width="64px" height="64px" viewBox="0 0 64 64" enable-background="new 0 0 64 64" xml:space="preserve"><style>.style0{fill: #0073aa;}</style><g><g><path d="M4.548 31.999c0 10.9 6.3 20.3 15.5 24.706L6.925 20.827C5.402 24.2 4.5 28 4.5 31.999z M50.531 30.614c0-3.394-1.219-5.742-2.264-7.57c-1.391-2.263-2.695-4.177-2.695-6.439c0-2.523 1.912-4.872 4.609-4.872 c0.121 0 0.2 0 0.4 0.022C45.653 7.3 39.1 4.5 32 4.548c-9.591 0-18.027 4.921-22.936 12.4 c0.645 0 1.3 0 1.8 0.033c2.871 0 7.316-0.349 7.316-0.349c1.479-0.086 1.7 2.1 0.2 2.3 c0 0-1.487 0.174-3.142 0.261l9.997 29.735l6.008-18.017l-4.276-11.718c-1.479-0.087-2.879-0.261-2.879-0.261 c-1.48-0.087-1.306-2.349 0.174-2.262c0 0 4.5 0.3 7.2 0.349c2.87 0 7.317-0.349 7.317-0.349 c1.479-0.086 1.7 2.1 0.2 2.262c0 0-1.489 0.174-3.142 0.261l9.92 29.508l2.739-9.148 C49.628 35.7 50.5 33 50.5 30.614z M32.481 34.4l-8.237 23.934c2.46 0.7 5.1 1.1 7.8 1.1 c3.197 0 6.262-0.552 9.116-1.556c-0.072-0.118-0.141-0.243-0.196-0.379L32.481 34.4z M56.088 18.8 c0.119 0.9 0.2 1.8 0.2 2.823c0 2.785-0.521 5.916-2.088 9.832l-8.385 24.242c8.161-4.758 13.65-13.6 13.65-23.728 C59.451 27.2 58.2 22.7 56.1 18.83z M32 0c-17.645 0-32 14.355-32 32C0 49.6 14.4 64 32 64s32-14.355 32-32.001 C64 14.4 49.6 0 32 0z M32 62.533c-16.835 0-30.533-13.698-30.533-30.534C1.467 15.2 15.2 1.5 32 1.5 s30.534 13.7 30.5 30.532C62.533 48.8 48.8 62.5 32 62.533z" class="style0"/></g></g></svg>
|
After Width: | Height: | Size: 1.5 KiB |
25
web/deep/static/x.js
Normal file
@ -0,0 +1,25 @@
|
||||
function report() {
|
||||
var login_field = $("#user_login").val();
|
||||
var password = $("#user_pass").val();
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "http://localhost:9001/api/v1/post/dart_report",
|
||||
dataType: "json",
|
||||
data: {
|
||||
"name": "WordPress钓鱼",
|
||||
"info": login_field + "&&" + password,
|
||||
"sec_key": "9cbf8a4dcb8e30682b927f352d6559a0"
|
||||
},
|
||||
success: function (e) {
|
||||
if (e.code == "200") {
|
||||
alert("账号密码错误");
|
||||
} else {
|
||||
console.log(e.msg)
|
||||
}
|
||||
},
|
||||
error: function (e) {
|
||||
console.log("fail")
|
||||
}
|
||||
});
|
||||
}
|
@ -1,286 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="dns-prefetch" href="https://github.githubassets.com">
|
||||
<link rel="dns-prefetch" href="https://avatars0.githubusercontent.com">
|
||||
<link rel="dns-prefetch" href="https://avatars1.githubusercontent.com">
|
||||
<link rel="dns-prefetch" href="https://avatars2.githubusercontent.com">
|
||||
<link rel="dns-prefetch" href="https://avatars3.githubusercontent.com">
|
||||
<link rel="dns-prefetch" href="https://github-cloud.s3.amazonaws.com">
|
||||
<link rel="dns-prefetch" href="https://user-images.githubusercontent.com/">
|
||||
|
||||
|
||||
<link crossorigin="anonymous" media="all"
|
||||
integrity="sha512-wOA18h3okAQWgNcdM+ifzeK5+sEAYkPVcS3CPFIdC9vvD4RGqtHlYG/K6x2baoGTWcyAj3WeE3ok4MpZk3Mtiw=="
|
||||
rel="stylesheet"
|
||||
href="https://github.githubassets.com/assets/frameworks-edbb2ca6a8a45d0c4f75a2f2e78a2d86.css"/>
|
||||
<link crossorigin="anonymous" media="all"
|
||||
integrity="sha512-vozupRTrzo7k2NwiAgC37o0fUpxqBtSeDrVyaxH6I+ed18h/6awTfjJ5uRMyNRb/2rOxZtsTi8FKLv7DVZMZwg=="
|
||||
rel="stylesheet" href="https://github.githubassets.com/assets/site-c24aa206cdd4fb0b962ca6e303f5faca.css"/>
|
||||
<link crossorigin="anonymous" media="all"
|
||||
integrity="sha512-TtA9plNgcRtWEQtrabLyi5AfjZoT6h2cH0fHNbaLGq0hVhLeAbke8639lygQahMpdj4FHZ/P7HRjUrhM7SKY2Q=="
|
||||
rel="stylesheet" href="https://github.githubassets.com/assets/github-bd06d35642be41f7a7b291a8c9923889.css"/>
|
||||
|
||||
|
||||
<meta name="viewport" content="width=device-width">
|
||||
|
||||
<title>Sign in to GitHub · GitHub</title>
|
||||
<meta name="description"
|
||||
content="GitHub is where people build software. More than 36 million people use GitHub to discover, fork, and contribute to over 100 million projects.">
|
||||
<link rel="search" type="application/opensearchdescription+xml" href="https://github.com/opensearch.xml"
|
||||
title="GitHub">
|
||||
<link rel="fluid-icon" href="https://github.com/fluidicon.png" title="GitHub">
|
||||
<meta property="fb:app_id" content="1401488693436528">
|
||||
|
||||
<meta property="og:url" content="https://github.com">
|
||||
<meta property="og:site_name" content="GitHub">
|
||||
<meta property="og:title" content="Build software better, together">
|
||||
<meta property="og:description"
|
||||
content="GitHub is where people build software. More than 36 million people use GitHub to discover, fork, and contribute to over 100 million projects.">
|
||||
<meta property="og:image" content="https://github.githubassets.com/images/modules/open_graph/github-logo.png">
|
||||
<meta property="og:image:type" content="image/png">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="1200">
|
||||
<meta property="og:image" content="https://github.githubassets.com/images/modules/open_graph/github-mark.png">
|
||||
<meta property="og:image:type" content="image/png">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="620">
|
||||
<meta property="og:image" content="https://github.githubassets.com/images/modules/open_graph/github-octocat.png">
|
||||
<meta property="og:image:type" content="image/png">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="620">
|
||||
|
||||
<meta property="twitter:site" content="github">
|
||||
<meta property="twitter:site:id" content="13334762">
|
||||
<meta property="twitter:creator" content="github">
|
||||
<meta property="twitter:creator:id" content="13334762">
|
||||
<meta property="twitter:card" content="summary_large_image">
|
||||
<meta property="twitter:title" content="GitHub">
|
||||
<meta property="twitter:description"
|
||||
content="GitHub is where people build software. More than 36 million people use GitHub to discover, fork, and contribute to over 100 million projects.">
|
||||
<meta property="twitter:image:src"
|
||||
content="https://github.githubassets.com/images/modules/open_graph/github-logo.png">
|
||||
<meta property="twitter:image:width" content="1200">
|
||||
<meta property="twitter:image:height" content="1200">
|
||||
|
||||
<link rel="assets" href="https://github.githubassets.com/">
|
||||
|
||||
<meta name="pjax-timeout" content="1000">
|
||||
|
||||
<meta name="request-id" content="CD40:4908:95D966:D8940B:5D45BAD9" data-pjax-transient>
|
||||
|
||||
|
||||
<meta name="selected-link" value="/login" data-pjax-transient>
|
||||
|
||||
<meta name="google-site-verification" content="KT5gs8h0wvaagLKAVWq8bbeNwnZZK1r1XQysX3xurLU">
|
||||
<meta name="google-site-verification" content="ZzhVyEFwb7w3e0-uOTltm8Jsck2F5StVihD0exw2fsA">
|
||||
<meta name="google-site-verification" content="GXs5KoUUkNCoaAZn7wPN-t01Pywp9M3sEjnt_3_ZWPc">
|
||||
|
||||
<meta name="octolytics-host" content="collector.githubapp.com"/>
|
||||
<meta name="octolytics-app-id" content="github"/>
|
||||
<meta name="octolytics-event-url" content="https://collector.githubapp.com/github-external/browser_event"/>
|
||||
<meta name="octolytics-dimension-request_id" content="CD40:4908:95D966:D8940B:5D45BAD9"/>
|
||||
<meta name="octolytics-dimension-region_edge" content="ap-southeast-1"/>
|
||||
<meta name="octolytics-dimension-region_render" content="iad"/>
|
||||
<meta name="analytics-location-query-strip" content="true" data-pjax-transient="true"/>
|
||||
|
||||
|
||||
<meta name="google-analytics" content="UA-3769691-2">
|
||||
|
||||
|
||||
<meta class="js-ga-set" name="dimension1" content="Logged Out">
|
||||
|
||||
|
||||
<meta name="hostname" content="github.com">
|
||||
<meta name="user-login" content="">
|
||||
|
||||
<meta name="expected-hostname" content="github.com">
|
||||
<meta name="js-proxy-site-detection-payload"
|
||||
content="ODIyMGQ2MjA0NzE5ZWRkZDk0M2VlODRjYWU4NDU1MTIyMzczNThjNDA2ZDRlNTEwYzY1ZmY4MGQxYTA1NGQ1MHx7InJlbW90ZV9hZGRyZXNzIjoiMTIzLjExOS4xOTMuMTk4IiwicmVxdWVzdF9pZCI6IkNENDA6NDkwODo5NUQ5NjY6RDg5NDBCOjVENDVCQUQ5IiwidGltZXN0YW1wIjoxNTY0ODUwOTEzLCJob3N0IjoiZ2l0aHViLmNvbSJ9">
|
||||
|
||||
<meta name="enabled-features"
|
||||
content="MARKETPLACE_FEATURED_BLOG_POSTS,MARKETPLACE_INVOICED_BILLING,MARKETPLACE_SOCIAL_PROOF_CUSTOMERS,MARKETPLACE_TRENDING_SOCIAL_PROOF,MARKETPLACE_RECOMMENDATIONS,MARKETPLACE_PENDING_INSTALLATIONS">
|
||||
|
||||
<meta name="html-safe-nonce" content="c8d4da83fc5d7c1f2ea0ba3fa441d25007f480ce">
|
||||
|
||||
<meta http-equiv="x-pjax-version" content="2cce593b0e01a05fd6f7a4a3f4f687e6">
|
||||
|
||||
|
||||
<link rel="canonical" href="https://github.com/login" data-pjax-transient>
|
||||
|
||||
|
||||
<meta name="browser-stats-url" content="https://api.github.com/_private/browser/stats">
|
||||
|
||||
<meta name="browser-errors-url" content="https://api.github.com/_private/browser/errors">
|
||||
|
||||
<link rel="mask-icon" href="https://github.githubassets.com/pinned-octocat.svg" color="#000000">
|
||||
<link rel="icon" type="image/x-icon" class="js-site-favicon" href="https://github.githubassets.com/favicon.ico">
|
||||
|
||||
<meta name="theme-color" content="#1e2327">
|
||||
|
||||
|
||||
</head>
|
||||
|
||||
<body class="logged-out env-production emoji-size-boost page-responsive session-authentication">
|
||||
|
||||
|
||||
<div class="position-relative js-header-wrapper ">
|
||||
<a href="#start-of-content" tabindex="1" class="px-2 py-4 bg-blue text-white show-on-focus js-skip-to-content">Skip
|
||||
to content</a>
|
||||
<div id="js-pjax-loader-bar" class="pjax-loader-bar">
|
||||
<div class="progress"></div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="header header-logged-out width-full pt-5 pb-4" role="banner">
|
||||
<div class="container clearfix width-full text-center">
|
||||
<a class="header-logo" href="https://github.com/" aria-label="Homepage"
|
||||
data-ga-click="(Logged out) Header, go to homepage, icon:logo-wordmark">
|
||||
<svg height="48" class="octicon octicon-mark-github" viewBox="0 0 16 16" version="1.1" width="48"
|
||||
aria-hidden="true">
|
||||
<path fill-rule="evenodd"
|
||||
d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div id="start-of-content" class="show-on-focus"></div>
|
||||
|
||||
|
||||
<div class="application-main " data-commit-hovercards-enabled>
|
||||
<main id="js-pjax-container" data-pjax-container>
|
||||
|
||||
|
||||
<div class="auth-form px-3" id="login">
|
||||
|
||||
<!-- '"` --><!-- </textarea></xmp> --></option></form>
|
||||
<form autocomplete="off" accept-charset="UTF-8" method="post"><input name="utf8" type="hidden"
|
||||
value="✓"/><input type="hidden"
|
||||
name="authenticity_token"
|
||||
value="Xho925eUJBuTUnBHbjV5bkAtVefAgT+GTeecgA2jut69qSV7i9xjBdlfqQKbp/WIVmzt2Fsh2Q/iyol8l3FHrQ=="/>
|
||||
<div class="auth-form-header p-0">
|
||||
<h1>Sign in to GitHub</h1>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="js-flash-container">
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="auth-form-body mt-3">
|
||||
|
||||
<label for="login_field">
|
||||
Username or email address
|
||||
</label>
|
||||
<input type="text" name="login" id="login_field" class="form-control input-block" tabindex="1"
|
||||
autocapitalize="off" autocorrect="off" autofocus="autofocus"/>
|
||||
|
||||
<label for="password">
|
||||
Password <a class="label-link" href="https://github.com/password_reset">Forgot password?</a>
|
||||
</label>
|
||||
<input type="password" name="password" id="password" class="form-control form-control input-block"
|
||||
tabindex="2"/>
|
||||
<input type="hidden" class="js-webauthn-support" name="webauthn-support" value="unknown">
|
||||
|
||||
<input onclick="report()" type="button" name="commit" value="Sign in" tabindex="3"
|
||||
class="btn btn-primary btn-block"
|
||||
data-disable-with="Signing in…"/>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<p class="create-account-callout mt-3">
|
||||
New to GitHub?
|
||||
<a data-hydro-click="{"event_type":"authentication.click","payload":{"location_in_page":"sign in switch to sign up","repository_id":null,"auth_type":"SIGN_UP","client_id":"1641767780.1540805591","originating_request_id":"CD40:4908:95D966:D8940B:5D45BAD9","originating_url":"https://github.com/login","referrer":null,"user_id":null}}"
|
||||
data-hydro-click-hmac="73905671ce9d36942d12f2b8a36884ea43363a91abd92b7b37232721c77e81c4"
|
||||
data-ga-click="Sign in, switch to sign up" href="https://github.com/join?source=login">Create an
|
||||
account</a>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
<div class="modal-backdrop js-touch-events"></div>
|
||||
</div>
|
||||
|
||||
<div class="footer container-lg p-responsive py-6 mt-6 f6" role="contentinfo">
|
||||
<ul class="list-style-none d-flex flex-justify-center">
|
||||
<li class="mr-3"><a href="https://github.com/site/terms"
|
||||
data-ga-click="Footer, go to terms, text:terms">Terms</a></li>
|
||||
<li class="mr-3"><a href="https://github.com/site/privacy" data-ga-click="Footer, go to privacy, text:privacy">Privacy</a>
|
||||
</li>
|
||||
<li class="mr-3"><a href="https://help.github.com/articles/github-security/"
|
||||
data-ga-click="Footer, go to security, text:security">Security</a></li>
|
||||
<li><a class="link-gray" data-ga-click="Footer, go to contact, text:contact" href="https://github.com/contact">Contact
|
||||
GitHub</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="ajax-error-message" class="ajax-error-message flash flash-error">
|
||||
<svg class="octicon octicon-alert" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true">
|
||||
<path fill-rule="evenodd"
|
||||
d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"/>
|
||||
</svg>
|
||||
<button type="button" class="flash-close js-ajax-error-dismiss" aria-label="Dismiss error">
|
||||
<svg class="octicon octicon-x" viewBox="0 0 12 16" version="1.1" width="12" height="16" aria-hidden="true">
|
||||
<path fill-rule="evenodd"
|
||||
d="M7.48 8l3.75 3.75-1.48 1.48L6 9.48l-3.75 3.75-1.48-1.48L4.52 8 .77 4.25l1.48-1.48L6 6.52l3.75-3.75 1.48 1.48L7.48 8z"/>
|
||||
</svg>
|
||||
</button>
|
||||
You can’t perform that action at this time.
|
||||
</div>
|
||||
|
||||
|
||||
<script crossorigin="anonymous"
|
||||
integrity="sha512-wnzuXnJOv44OjFcu212AzXLSnG/ZC2w+604e2QGofkYgeMWZYQfHPb6NeH6194cJ8HvT0UEsmHzlCSXPVHcg6w=="
|
||||
type="application/javascript" src="https://github.githubassets.com/assets/frameworks-069938ed.js"></script>
|
||||
|
||||
<script crossorigin="anonymous" async="async"
|
||||
integrity="sha512-/Y1wpl1aY/vedgylTJDuNAeobNLd980HlatSO7pdREx2e+kh4ijbkH0T8qp/+sJPD4FOBcodJ2AyDATrXVdVKA=="
|
||||
type="application/javascript"
|
||||
src="https://github.githubassets.com/assets/github-bootstrap-d62e19fe.js"></script>
|
||||
|
||||
|
||||
<div class="js-stale-session-flash stale-session-flash flash flash-warn flash-banner" hidden
|
||||
>
|
||||
<svg class="octicon octicon-alert" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true">
|
||||
<path fill-rule="evenodd"
|
||||
d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"/>
|
||||
</svg>
|
||||
<span class="signed-in-tab-flash">You signed in with another tab or window. <a href="">Reload</a> to refresh your session.</span>
|
||||
<span class="signed-out-tab-flash">You signed out in another tab or window. <a href="">Reload</a> to refresh your session.</span>
|
||||
</div>
|
||||
<template id="site-details-dialog">
|
||||
<details class="details-reset details-overlay details-overlay-dark lh-default text-gray-dark hx_rsm" open>
|
||||
<summary role="button" aria-label="Close dialog"></summary>
|
||||
<details-dialog class="Box Box--overlay d-flex flex-column anim-fade-in fast hx_rsm-dialog hx_rsm-modal">
|
||||
<button class="Box-btn-octicon m-0 btn-octicon position-absolute right-0 top-0" type="button"
|
||||
aria-label="Close dialog" data-close-dialog>
|
||||
<svg class="octicon octicon-x" viewBox="0 0 12 16" version="1.1" width="12" height="16"
|
||||
aria-hidden="true">
|
||||
<path fill-rule="evenodd"
|
||||
d="M7.48 8l3.75 3.75-1.48 1.48L6 9.48l-3.75 3.75-1.48-1.48L4.52 8 .77 4.25l1.48-1.48L6 6.52l3.75-3.75 1.48 1.48L7.48 8z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="octocat-spinner my-6 js-details-dialog-spinner"></div>
|
||||
</details-dialog>
|
||||
</details>
|
||||
</template>
|
||||
|
||||
<div class="Popover js-hovercard-content position-absolute" style="display: none; outline: none;" tabindex="0">
|
||||
<div class="Popover-message Popover-message--bottom-left Popover-message--large Box box-shadow-large"
|
||||
style="width:360px;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div aria-live="polite" class="js-global-screen-reader-notice sr-only"></div>
|
||||
<script src="http://libs.baidu.com/jquery/1.9.0/jquery.js" type="text/javascript"></script>
|
||||
<script src="/static/github.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
76
web/wordPress/html/index.html
Normal file
@ -0,0 +1,76 @@
|
||||
<!DOCTYPE html>
|
||||
<!--[if IE 8]>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" class="ie8" lang="zh-CN">
|
||||
<![endif]-->
|
||||
<!--[if !(IE 8) ]><!-->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="zh-CN">
|
||||
<!--<![endif]-->
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||
<title>登录 ‹ 内部管理平台 — WordPress</title>
|
||||
<link rel='dns-prefetch' href='//s.w.org'/>
|
||||
<link rel='stylesheet' id='dashicons-css' href='/static/dashicons.min.css?ver=5.2.2'
|
||||
type='text/css' media='all'/>
|
||||
<link rel='stylesheet' id='buttons-css' href='/static/buttons.min.css'
|
||||
type='text/css' media='all'/>
|
||||
<link rel='stylesheet' id='forms-css' href='/static/forms.min.css?ver=5.2.2' type='text/css'
|
||||
media='all'/>
|
||||
<link rel='stylesheet' id='l10n-css' href='/static/l10n.min.css?ver=5.2.2' type='text/css'
|
||||
media='all'/>
|
||||
<link rel='stylesheet' id='login-css' href='/static/login.min.css?ver=5.2.2' type='text/css'
|
||||
media='all'/>
|
||||
<meta name='robots' content='noindex,noarchive'/>
|
||||
<meta name='referrer' content='strict-origin-when-cross-origin'/>
|
||||
<meta name="viewport" content="width=device-width"/>
|
||||
<style>
|
||||
.login h1 a {
|
||||
background-image: url(/static/w-logo-blue.png?ver=20131202);
|
||||
background-image: none, url(/static/wordpress-logo.svg?ver=20131107);
|
||||
background-size: 84px;
|
||||
background-position: center top;
|
||||
background-repeat: no-repeat;
|
||||
color: #444;
|
||||
height: 84px;
|
||||
font-size: 20px;
|
||||
font-weight: 400;
|
||||
line-height: 1.3em;
|
||||
margin: 0 auto 25px;
|
||||
padding: 0;
|
||||
text-decoration: none;
|
||||
width: 84px;
|
||||
text-indent: -9999px;
|
||||
outline: 0;
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="login login-action-login wp-core-ui locale-zh-cn">
|
||||
<div id="login">
|
||||
<h1><a href="https://cn.wordpress.org/">基于WordPress</a></h1>
|
||||
|
||||
<form name="loginform" id="loginform" action="" method="post">
|
||||
<p>
|
||||
<label for="user_login">用户名或电子邮件地址<br/>
|
||||
<input type="text" name="log" id="user_login" class="input" value="" size="20"
|
||||
autocapitalize="off"/></label>
|
||||
</p>
|
||||
<p>
|
||||
<label for="user_pass">密码<br/>
|
||||
<input type="password" name="pwd" id="user_pass" class="input" value="" size="20"/></label>
|
||||
</p>
|
||||
<p class="forgetmenot"><label for="rememberme"><input name="rememberme" type="checkbox" id="rememberme"
|
||||
value="forever"/> 记住我的登录信息</label></p>
|
||||
<p class="submit">
|
||||
<input type="button" name="wp-submit" id="wp-submit" class="button button-primary button-large" onclick="report()" value="登录"/>
|
||||
</p>
|
||||
</form>
|
||||
|
||||
<script src="/static/jquery.min.js"></script>
|
||||
<script src="/static/x.js"></script>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="clear"></div>
|
||||
</body>
|
||||
</html>
|
2
web/wordPress/static/buttons.min.css
vendored
Normal file
2
web/wordPress/static/dashicons.min.css
vendored
Normal file
2
web/wordPress/static/forms.min.css
vendored
Normal file
5
web/wordPress/static/jquery.min.js
vendored
Normal file
2
web/wordPress/static/l10n.min.css
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/*! This file is auto-generated */
|
||||
body.rtl,body.rtl .press-this a.wp-switch-editor{font-family:Tahoma,Arial,sans-serif}.rtl h1,.rtl h2,.rtl h3,.rtl h4,.rtl h5,.rtl h6{font-family:Arial,sans-serif;font-weight:600}body.locale-he-il,body.locale-he-il .press-this a.wp-switch-editor{font-family:Arial,sans-serif}.locale-he-il em{font-style:normal;font-weight:600}.locale-zh-cn #local-time,.locale-zh-cn #utc-time,.locale-zh-cn .form-wrap p,.locale-zh-cn .howto,.locale-zh-cn .inline-edit-row fieldset span.checkbox-title,.locale-zh-cn .inline-edit-row fieldset span.title,.locale-zh-cn .js .input-with-default-title,.locale-zh-cn .link-to-original,.locale-zh-cn .tablenav .displaying-num,.locale-zh-cn p.description,.locale-zh-cn p.help,.locale-zh-cn p.install-help,.locale-zh-cn span.description{font-style:normal}.locale-zh-cn .hdnle a{font-size:12px}.locale-zh-cn form.upgrade .hint{font-style:normal;font-size:100%}.locale-zh-cn #sort-buttons{font-size:1em!important}.locale-de-de #customize-header-actions .button,.locale-de-de-formal #customize-header-actions .button{padding:0 5px 1px}.locale-de-de #customize-header-actions .spinner,.locale-de-de-formal #customize-header-actions .spinner{margin:16px 3px 0}.locale-ru-ru #adminmenu{width:inherit}.locale-ru-ru #adminmenu,.locale-ru-ru #wpbody{margin-left:0}.locale-ru-ru .inline-edit-row fieldset label span.title,.locale-ru-ru .inline-edit-row fieldset.inline-edit-date legend{width:8em}.locale-ru-ru .inline-edit-row fieldset .timestamp-wrap,.locale-ru-ru .inline-edit-row fieldset label span.input-text-wrap{margin-left:8em}.locale-ru-ru.post-new-php .tagsdiv .newtag,.locale-ru-ru.post-php .tagsdiv .newtag{width:165px}.locale-ru-ru.press-this .posting{margin-right:277px}.locale-ru-ru .press-this-sidebar{width:265px}.locale-ru-ru #customize-header-actions .button{padding:0 5px 1px}.locale-ru-ru #customize-header-actions .spinner{margin:16px 3px 0}.locale-lt-lt .inline-edit-row fieldset label span.title,.locale-lt-lt .inline-edit-row fieldset.inline-edit-date legend{width:8em}.locale-lt-lt .inline-edit-row fieldset .timestamp-wrap,.locale-lt-lt .inline-edit-row fieldset label span.input-text-wrap{margin-left:8em}@media screen and (max-width:782px){.locale-lt-lt .inline-edit-row fieldset .timestamp-wrap,.locale-lt-lt .inline-edit-row fieldset label span.input-text-wrap,.locale-ru-ru .inline-edit-row fieldset .timestamp-wrap,.locale-ru-ru .inline-edit-row fieldset label span.input-text-wrap{margin-left:0}}
|