Merge pull request #3 from hacklcx/dev

Dev
This commit is contained in:
SanJin 2019-08-12 21:17:51 +08:00 committed by GitHub
commit ca2c886e61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
103 changed files with 11482 additions and 599 deletions

4
.gitignore vendored
View File

@ -16,4 +16,6 @@ vendor
.DS_Store
*/.DS_Store
*/.idea/%
*/vendor/%
*/vendor/%
db/*.db
logs/*.log

108
README.md
View File

@ -1,4 +1,4 @@
![1.png](./images/1.png)
![dashboard.png](./images/dashboard.png)
# 介绍
@ -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 服务端地址
## 命令行帮助
![help.png](./images/help.png)
### 启动服务
## 启动服务
![run.png](./images/run.png)
# 部分界面展示
![3.png](./images/3.png)
## 登录页
![2.png](./images/2.png)
![login.png](./images/login.png)
## 上钩页
![fish.png](./images/fish.png)
## 分布式集群
![colony.png](./images/colony.png)
## 邮件群发
![mail.png](./images/mail.png)
# 部分功能使用演示
### Redis 蜜罐
## WEB 蜜罐
![web.png](./images/web.png)
## SSH 蜜罐
![ssh.png](./images/ssh.png)
## Redis 蜜罐
![redis.png](./images/redis.png)
### Mysql 蜜罐
## Mysql 蜜罐
![mysql.png](./images/mysql.png)
## FTP 蜜罐
![ftp.png](./images/ftp.png)
## Telnet 蜜罐
![telnet.png](./images/telnet.png)
# 注意事项
- 邮箱 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
View 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>

View File

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

View File

@ -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,32 +89,79 @@
.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">
{{/*<div class="btn-group pull-right m-t-15">*/}}
{{/*<a type="button" class="btn btn-custom" href="#">导出 <span class="m-l-5"><i*/}}
{{/*class="zmdi zmdi-alarm-plus"></i></span>*/}}
{{/*</a>*/}}
{{/*</div>*/}}
{{/*<div class="btn-group pull-right m-t-15">*/}}
{{/*<a type="button" class="btn btn-custom" href="#">导出 <span class="m-l-5"><i*/}}
{{/*class="zmdi zmdi-alarm-plus"></i></span>*/}}
{{/*</a>*/}}
{{/*</div>*/}}
<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,41 +226,22 @@
});
}
function init() {
function init_type() {
$.ajax({
type: "GET",
url: "/get/fish/list",
url: "/get/fish/typeList",
dataType: "json",
success: function (e) {
if (e.code == 200) {
var data = e.data;
var _h = '';
var _h = '<option value="all">请选择类型</option>';
for (var i = 0; i < data.length; i++) {
_h += '<tr>';
_h += ' <td class="td">';
if (data[i].type == "WEB") {
_h += ' <span class="label label-primary">WEB</span> ';
} else if (data[i].type == "SSH") {
_h += ' <span class="label lb_ssh">SSH</span> ';
} else if (data[i].type == "REDIS") {
_h += ' <span class="label lb_redis">REDIS</span> ';
} else if (data[i].type == "MYSQL") {
_h += ' <span class="label lb_mysql">MYSQL</span> ';
}
_h += ' <span class="project">' + data[i].project_name + '</span>';
_h += ' </td>';
_h += ' <td class="td">' + data[i].ip + '</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;">';
_h += ' <i class="fa fa-trash-o" onclick="del(' + data[i].id + ')"></i>';
_h += ' </td>';
_h += '</tr>';
_h += '<option value="' + data[i].type + '">' + data[i].type + '</option>';
}
$("#tableList").html(_h);
$("#selectType").html(_h);
} else {
}
@ -211,6 +251,98 @@
});
}
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) {
var data = e.data;
var _h = '';
init();
for (var i = 0; i < data.length; i++) {
_h += '<tr>';
_h += ' <td class="td">';
if (data[i].type == "WEB") {
_h += ' <span class="label label-primary">WEB</span> ';
} else if (data[i].type == "SSH") {
_h += ' <span class="label lb_ssh">SSH</span> ';
} else if (data[i].type == "REDIS") {
_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"><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;">';
_h += ' <i class="fa fa-trash-o" onclick="del(' + data[i].id + ')"></i>';
_h += ' </td>';
_h += '</tr>';
}
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) {
}
});
}
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>

View File

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

View File

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

View File

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

View File

@ -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();
$.ajax({
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) {
@ -112,7 +128,7 @@
},
error: function (e) {
swal("发送失败", "请 Github 提交 Issues", 'error');
}
})
}
})
}
</script>

View File

@ -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">&times;</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,7 +212,18 @@
} 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) {
console.log(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,

View File

@ -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 服务 或 修改端口
@ -26,4 +41,12 @@ addr = 0.0.0.0:6379 # Redis 服务端地址 注意端
[mysql]
status = 0 # 是否启动 Mysql 1 启动 0 关闭
addr = 0.0.0.0:3306 # Mysql 服务端地址 注意端口冲突
files = /etc/passwd,/etc/group # 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 服务端地址 注意端口冲突

View File

@ -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
defer stmt.Close()
try.Try(func() {
res, _ := stmt.Exec(args...)
//error.Check(err, "插入数据失败")
id, err := res.LastInsertId()
error.Check(err, "获取插入ID失败")
defer stmt.Close()
defer db.Close()
id, _ = res.LastInsertId()
defer db.Close()
}).Catch(func() {})
// 返回 自增长 ID
return id
}

106
core/protocol/ftp/ftp.go Normal file
View 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)
}
}

View 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.

View 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.

View 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")
}

View 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)
}

View 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
}

View 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
}

View 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
}

View 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)
}

View 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
}

View 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
}

View 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)
}

View File

@ -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,40 +75,62 @@ func connectionClientHandler(conn net.Conn) {
connFrom := conn.RemoteAddr().String()
arr := strings.Split(connFrom, ":")
id := report.ReportMysql(arr[0], connFrom+" 已经连接")
var ibuf = make([]byte, bufLength)
//第一个包
_, err := conn.Write(GreetingData)
error.Check(err, "")
// 判断是否为 RPC 客户端
var id string
//第二个包
_, err = conn.Read(ibuf[0: bufLength-1])
//判断是否有Can Use LOAD DATA LOCAL标志如果有才支持读取文件
if (uint8(ibuf[4]) & uint8(128)) == 0 {
_ = conn.Close()
log.Println("The client not support LOAD DATA LOCAL")
return
if is.Rpc() {
id = client.ReportResult("MYSQL", "", arr[0], connFrom+" 已经连接", "0")
} else {
id = strconv.FormatInt(report.ReportMysql(arr[0], "本机", connFrom+" 已经连接"), 10)
}
//第三个包
_, err = conn.Write(OkData)
//第四个包
_, err = conn.Read(ibuf[0: bufLength-1])
log.Pr("Mysql", arr[0], "已经连接")
//这里根据客户端连接的次数来选择读取文件列表里面的第几个文件
ip, _, _ := net.SplitHostPort(conn.RemoteAddr().String())
getFileData := []byte{byte(len(fileNames[recordClient[ip]]) + 1), 0x00, 0x00, 0x01, 0xfb}
getFileData = append(getFileData, fileNames[recordClient[ip]]...)
try.Try(func() {
var ibuf = make([]byte, bufLength)
//第五个包
_, err = conn.Write(getFileData)
getRequestContent(conn, id)
//第一个包
_, err := conn.Write(GreetingData)
error.Check(err, "")
//第二个包
_, err = conn.Read(ibuf[0: bufLength-1])
//判断是否有Can Use LOAD DATA LOCAL标志如果有才支持读取文件
if (uint8(ibuf[4]) & uint8(128)) == 0 {
_ = conn.Close()
return
}
//第三个包
_, err = conn.Write(OkData)
//第四个包
_, err = conn.Read(ibuf[0: bufLength-1])
//这里根据客户端连接的次数来选择读取文件列表里面的第几个文件
ip, _, _ := net.SplitHostPort(conn.RemoteAddr().String())
getFileData := []byte{byte(len(fileNames[recordClient[ip]]) + 1), 0x00, 0x00, 0x01, 0xfb}
getFileData = append(getFileData, fileNames[recordClient[ip]]...)
//第五个包
_, 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())
}
}

View File

@ -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,21 +86,37 @@ func handleConnection(conn net.Conn, id int64) {
conn.Write([]byte("+OK\r\n"))
} else if value[0] == "GET" || value[0] == "get" {
// 模拟 redis get
key := string(value[1])
val := string(kvData[key])
try.Try(func() {
// 模拟 redis get
key := string(value[1])
val := string(kvData[key])
valLen := strconv.Itoa(len(val))
str := "$" + valLen + "\r\n" + val + "\r\n"
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))
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"))

View File

@ -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 代表 账号密码 不正确
}),

View 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)
}

View File

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

Binary file not shown.

View File

@ -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
View File

@ -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
View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 782 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 776 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

BIN
images/colony.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 KiB

BIN
images/dashboard.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 812 KiB

BIN
images/fish.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 899 KiB

BIN
images/ftp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 772 KiB

BIN
images/login.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
images/mail.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 761 KiB

BIN
images/ssh.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 815 KiB

BIN
images/telnet.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 720 KiB

BIN
images/web.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 644 KiB

View File

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

View File

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

View File

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

View 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

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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

File diff suppressed because one or more lines are too long

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

File diff suppressed because one or more lines are too long

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

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

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

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

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

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

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

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

File diff suppressed because one or more lines are too long

69
utils/ip/ip.go Normal file
View 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
View 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
View 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
View 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
}

View File

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

View File

@ -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."))

View File

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

View File

@ -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,
"webStatus": webStatus,
"sshStatus": sshStatus,
"redisStatus": redisStatus,
"mysqlStatus": mysqlStatus,
"apiStatus": apiStatus,
"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,
"web": webMap,
"ssh": sshMap,
"redis": redisMap,
"mysql": mysqlMap,
"deep": deepMap,
"ftp": ftpMap,
"telnet": telnetMap,
}
c.JSON(http.StatusOK, error.ErrSuccessEdit(s))

View File

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

View File

@ -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
}
send.SendMail(eArr, title, content, config)
c.JSON(http.StatusOK, error.ErrSuccessNull())
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())
}
}

View File

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

View File

@ -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,41 +32,73 @@ func cors() gin.HandlerFunc {
}
func LoadUrl(r *gin.Engine) {
// 登录
r.GET("/login", login.Html)
r.POST("/login", login.Login)
r.GET("/logout", login.Logout)
// 判断是否为 RPC 客户端
if is.Rpc() {
/* RPC 客户端 */
// 仪表盘
r.GET("/", login.Jump, dashboard.Html)
r.GET("/dashboard", login.Jump, dashboard.Html)
r.GET("/get/dashboard/data", login.Jump, dashboard.GetFishData)
// API 接口
// WEB 上报蜜罐信息
apiStatus := conf.Get("api", "status")
// 钓鱼列表
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.POST("/post/fish/del", login.Jump, fish.PostFishDel)
// 判断 API 是否启用
if apiStatus == "1" {
r.Use(cors())
// 邮件群发
r.GET("/mail", login.Jump, mail.Html)
r.POST("/post/mail/sendEmail", login.Jump, mail.SendEmailToUsers)
webUrl := conf.Get("api", "web_url")
deepUrl := conf.Get("api", "deep_url")
// 设置
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/checkSetting", login.Jump, setting.UpdateStatusSetting)
r.POST(webUrl, api.ReportWeb)
r.POST(deepUrl, api.ReportDeepWeb)
}
} else {
/* RPC 服务端 */
// 登录
r.GET("/login", login.Html)
r.POST("/login", login.Login)
r.GET("/logout", login.Logout)
// API 接口
// WEB 上报钓鱼信息
apiStatus := conf.Get("api", "status")
// 仪表盘
r.GET("/", login.Jump, dashboard.Html)
r.GET("/dashboard", login.Jump, dashboard.Html)
r.GET("/get/dashboard/data", login.Jump, dashboard.GetFishData)
// 判断 API 是否启用
if apiStatus == "1" {
r.Use(cors())
// 蜜罐列表
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)
apiUrl := conf.Get("api", "url")
r.POST(apiUrl, api.ReportWeb)
// 分布式集群
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)
// 设置
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 上报蜜罐信息
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)
r.GET("/api/v1/get/ip", api.GetIpList)
}
}
}

76
web/deep/html/index.html Normal file
View 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>登录 &lsaquo; 内部管理平台 &#8212; 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

File diff suppressed because one or more lines are too long

2
web/deep/static/dashicons.min.css vendored Normal file

File diff suppressed because one or more lines are too long

2
web/deep/static/forms.min.css vendored Normal file

File diff suppressed because one or more lines are too long

5
web/deep/static/jquery.min.js vendored Normal file

File diff suppressed because one or more lines are too long

2
web/deep/static/l10n.min.css vendored Normal file
View 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

File diff suppressed because one or more lines are too long

View 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
View 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")
}
});
}

View File

@ -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="&#x2713;"/><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="{&quot;event_type&quot;:&quot;authentication.click&quot;,&quot;payload&quot;:{&quot;location_in_page&quot;:&quot;sign in switch to sign up&quot;,&quot;repository_id&quot;:null,&quot;auth_type&quot;:&quot;SIGN_UP&quot;,&quot;client_id&quot;:&quot;1641767780.1540805591&quot;,&quot;originating_request_id&quot;:&quot;CD40:4908:95D966:D8940B:5D45BAD9&quot;,&quot;originating_url&quot;:&quot;https://github.com/login&quot;,&quot;referrer&quot;:null,&quot;user_id&quot;: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 cant 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>

View 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>登录 &lsaquo; 内部管理平台 &#8212; 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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
web/wordPress/static/forms.min.css vendored Normal file

File diff suppressed because one or more lines are too long

5
web/wordPress/static/jquery.min.js vendored Normal file

File diff suppressed because one or more lines are too long

2
web/wordPress/static/l10n.min.css vendored Normal file
View 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}}

Some files were not shown because too many files have changed in this diff Show More