30 Commits

Author SHA1 Message Date
bjdgyc
ddba116fbf 修复登陆密码判断bug 2021-03-02 15:28:08 +08:00
bjdgyc
dd1eae5d32 Merge pull request #7 from xbclub/master
add systemd service
2021-03-01 17:31:38 +08:00
yii
879b9114ac add systemd service 2021-03-01 17:12:54 +08:00
bjdgyc
e5c4a47a37 修改分支为main,添加otp开关 2021-03-01 16:28:50 +08:00
bjdgyc
a0669e1e32 Update go.yml 2021-03-01 16:19:35 +08:00
bjdgyc
ea7d27b4f0 Update go.yml 2021-03-01 16:15:57 +08:00
bjdgyc
9f2e9de49a Update go.yml 2021-03-01 16:10:48 +08:00
bjdgyc
8709dbaba1 Update go.yml 2021-03-01 16:08:14 +08:00
bjdgyc
4928ad5f62 修改分支为main,添加otp开关 2021-03-01 16:02:00 +08:00
bjdgyc
0f91c779e3 更改目录结构 2021-03-01 15:46:08 +08:00
bjdgyc
3464d1d10e 修改图片地址 2021-02-25 14:17:09 +08:00
bjdgyc
1579e92ba1 修改图片地址 2021-02-25 14:15:53 +08:00
bjdgyc
48327fe8d3 修改jetbrains图片尺寸 2021-02-25 14:11:44 +08:00
bjdgyc
ef7723b03b 修复测试bug 2021-02-22 16:39:58 +08:00
bjdgyc
0baab68bb2 修改日志写入文件内 2021-02-22 14:35:29 +08:00
bjd
665732fc03 添加codecov配置文件 2021-02-04 15:19:51 +08:00
bjd
edb0fe2dc9 修改客户端分配的ip为CIDR格式,请注意原来network格式 2021-02-04 13:32:10 +08:00
bjd
1c6572f5e3 折叠截图文档 2021-02-03 15:20:19 +08:00
bjd
103329c3d0 增加测试覆盖率 2021-02-03 11:44:25 +08:00
bjdgyc
d40b753871 Update go.yml 2021-02-02 20:51:49 +08:00
bjdgyc
fa5a58e98d Update go.yml 2021-02-02 20:42:08 +08:00
bjdgyc
62f30c05ff Update .travis.yml 2021-02-02 20:34:22 +08:00
bjdgyc
c02ffc27c0 Update .travis.yml 2021-02-02 20:28:12 +08:00
bjdgyc
a4e09e7719 Create .travis.yml 2021-02-02 20:27:02 +08:00
bjd
631e49bd41 增加LinkAcl功能,可以限制访问端口 2021-02-01 17:36:59 +08:00
bjd
ef95b1f927 增加LinkAcl功能,可以限制访问端口 2021-02-01 17:34:56 +08:00
bjd
9e0da33c6a 增加重连功能,可动态修改连接参数 2021-01-25 16:03:33 +08:00
bjdgyc
3bb771971c Create CONTRIBUTING.md 2021-01-25 15:15:10 +08:00
bjd
dd83b330eb 发送邮件增加ssl功能 2021-01-22 10:55:49 +08:00
bjd
73d1edd62f 添加邮件发送错误信息 2021-01-12 19:43:41 +08:00
113 changed files with 29747 additions and 501 deletions

5
.codecov.yml Normal file
View File

@@ -0,0 +1,5 @@
ignore:
- "screenshot"
- "web"
- "server/conf"
- "server/files"

View File

@@ -2,9 +2,9 @@ name: Go
on: on:
push: push:
branches: [ master ] branches: [ main ]
pull_request: pull_request:
branches: [ master ] branches: [ main ]
jobs: jobs:
@@ -24,10 +24,21 @@ jobs:
- name: Get dependencies - name: Get dependencies
run: | run: |
cd server
go get -v -t -d ./... go get -v -t -d ./...
- name: Build - name: Build
run: go build -v . run: |
cd server
go build -v -o anylink -ldflags "-X main.COMMIT_ID=`git rev-parse HEAD`"
./anylink -rev
- name: Test - name: Test coverage
run: go test -v . run: |
cd server
go test -race -coverprofile=coverage.txt -covermode=atomic -v ./...
- name: Upload coverage to Codecov
run: |
cd server
bash <(curl -s https://codecov.io/bash)

20
.gitignore vendored
View File

@@ -1,19 +1,5 @@
# Binaries for programs and plugins # Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
vendor/
ui/
.idea/ .idea/
anylink anylink-deploy
ui

View File

@@ -1,6 +1,9 @@
# AnyLink # AnyLink
[![Go](https://github.com/bjdgyc/anylink/workflows/Go/badge.svg?branch=master)](https://github.com/bjdgyc/anylink/actions)
[![PkgGoDev](https://pkg.go.dev/badge/github.com/bjdgyc/anylink)](https://pkg.go.dev/github.com/bjdgyc/anylink) [![PkgGoDev](https://pkg.go.dev/badge/github.com/bjdgyc/anylink)](https://pkg.go.dev/github.com/bjdgyc/anylink)
[![Go Report Card](https://goreportcard.com/badge/github.com/bjdgyc/anylink)](https://goreportcard.com/report/github.com/bjdgyc/anylink)
[![codecov](https://codecov.io/gh/bjdgyc/anylink/branch/master/graph/badge.svg?token=JTFLIIIBQ0)](https://codecov.io/gh/bjdgyc/anylink)
AnyLink 是一个企业级远程办公ssl vpn软件可以支持多人同时在线使用。 AnyLink 是一个企业级远程办公ssl vpn软件可以支持多人同时在线使用。
@@ -17,11 +20,11 @@ AnyLink 基于 [ietf-openconnect](https://tools.ietf.org/html/draft-mavrogiannop
AnyLink 使用TLS/DTLS进行数据加密因此需要RSA或ECC证书可以通过 Let's Encrypt 和 TrustAsia 申请免费的SSL证书。 AnyLink 使用TLS/DTLS进行数据加密因此需要RSA或ECC证书可以通过 Let's Encrypt 和 TrustAsia 申请免费的SSL证书。
AnyLink 服务端仅在CentOS7测试通过如需要安装在其他系统需要服务端支持tun/tap功能、ip设置命令。 AnyLink 服务端仅在CentOS 7、Ubuntu 18.04测试通过如需要安装在其他系统需要服务端支持tun/tap功能、ip设置命令。
## Screenshot ## Screenshot
![online](https://gitee.com/bjdgyc/anylink/raw/master/screenshot/online.jpg) ![online](screenshot/online.jpg)
## Installation ## Installation
@@ -31,11 +34,16 @@ AnyLink 服务端仅在CentOS7测试通过如需要安装在其他系统
git clone https://github.com/bjdgyc/anylink.git git clone https://github.com/bjdgyc/anylink.git
cd anylink cd anylink
sh deploy.sh sh -x build.sh
# 注意使用root权限运行 # 注意使用root权限运行
cd anylink-deploy cd anylink-deploy
sudo ./anylink -conf="conf/server.toml" sudo ./anylink -conf="conf/server.toml"
# 默认管理后台访问地址
# http://host:8800
# 默认日志文件
# log/anylink.log
``` ```
## Feature ## Feature
@@ -49,10 +57,11 @@ sudo ./anylink -conf="conf/server.toml"
- [x] 用户组支持 - [x] 用户组支持
- [x] 多用户支持 - [x] 多用户支持
- [x] TOTP令牌支持 - [x] TOTP令牌支持
- [x] TOTP令牌开关
- [x] 流量控制 - [x] 流量控制
- [x] 后台管理界面 - [x] 后台管理界面
- [x] 访问权限管理
- [ ] 访问权限管理
- [ ] DTLS-UDP通道 - [ ] DTLS-UDP通道
## Config ## Config
@@ -67,7 +76,7 @@ sudo ./anylink -conf="conf/server.toml"
./anylink -secret ./anylink -secret
``` ```
[conf/server.toml](https://github.com/bjdgyc/anylink/blob/master/conf/server.toml) [conf/server.toml](server/conf/server.toml)
## Setting ## Setting
@@ -122,23 +131,40 @@ sh bridge-init.sh
## Soft ## Soft
相关软件下载: https://gitee.com/bjdgyc/anylink-soft 相关软件下载: QQ群共享文件: 567510628
## Discussion
![qq.png](screenshot/qq.png)
添加QQ群: 567510628
## Contribution
欢迎提交 PR、Issues感谢为AnyLink做出贡献
## Other Screenshot ## Other Screenshot
![system.jpg](https://gitee.com/bjdgyc/anylink/raw/master/screenshot/system.jpg) <details>
![setting.jpg](https://gitee.com/bjdgyc/anylink/raw/master/screenshot/setting.jpg) <summary>展开查看</summary>
![users.jpg](https://gitee.com/bjdgyc/anylink/raw/master/screenshot/users.jpg)
![ip_map.jpg](https://gitee.com/bjdgyc/anylink/raw/master/screenshot/ip_map.jpg) ![system.jpg](screenshot/system.jpg)
![group.jpg](https://gitee.com/bjdgyc/anylink/raw/master/screenshot/group.jpg) ![setting.jpg](screenshot/setting.jpg)
![users.jpg](screenshot/users.jpg)
![ip_map.jpg](screenshot/ip_map.jpg)
![group.jpg](screenshot/group.jpg)
</details>
## License ## License
本项目采用 MIT 开源授权许可证,完整的授权说明已放置在 LICENSE 文件中。 本项目采用 MIT 开源授权许可证,完整的授权说明已放置在 LICENSE 文件中。
## Thank
<a href="https://www.jetbrains.com">
<img src="screenshot/jetbrains.png" width="200" height="200" alt="jetbrains.png" />
</a>

View File

@@ -1,90 +0,0 @@
package base
import (
"fmt"
"log"
"os"
"strings"
)
const (
_Debug = iota
_Info
_Warn
_Error
_Fatal
)
var (
baseLog *log.Logger
baseLevel int
levels map[int]string
)
func initLog() {
baseLog = log.New(os.Stderr, "", log.LstdFlags|log.Lshortfile)
baseLevel = logLevel2Int(Cfg.LogLevel)
}
func logLevel2Int(l string) int {
levels = map[int]string{
_Debug: "Debug",
_Info: "Info",
_Warn: "Warn",
_Error: "Error",
_Fatal: "Fatal",
}
lvl := _Info
for k, v := range levels {
if strings.ToLower(l) == strings.ToLower(v) {
lvl = k
}
}
return lvl
}
func output(l int, s ...interface{}) {
lvl := fmt.Sprintf("[%s] ", levels[l])
baseLog.Output(3, lvl+fmt.Sprintln(s...))
}
func Debug(v ...interface{}) {
l := _Debug
if baseLevel > l {
return
}
output(l, v...)
}
func Info(v ...interface{}) {
l := _Info
if baseLevel > l {
return
}
output(l, v...)
}
func Warn(v ...interface{}) {
l := _Warn
if baseLevel > l {
return
}
output(l, v...)
}
func Error(v ...interface{}) {
l := _Error
if baseLevel > l {
return
}
output(l, v...)
}
func Fatal(v ...interface{}) {
l := _Fatal
if baseLevel > l {
return
}
output(l, v...)
os.Exit(1)
}

35
build.sh Normal file
View File

@@ -0,0 +1,35 @@
#!/usr/bin/env bash
#当前目录
cpath=$(pwd)
echo "编译二进制文件"
cd $cpath/server
go build -o anylink -ldflags "-X main.COMMIT_ID=$(git rev-parse HEAD)"
echo "编译前端项目"
cd $cpath/web
#国内可替换源加快速度
npm install --registry=https://registry.npm.taobao.org
npm run build --registry=https://registry.npm.taobao.org
#npm install
#npm run build
cd $cpath
echo "整理部署文件"
deploy="anylink-deploy"
rm -rf $deploy
mkdir $deploy
mkdir $deploy/log
cp -r server/anylink $deploy
cp -r server/conf $deploy
cp -r server/files $deploy
cp -r server/bridge-init.sh $deploy
cp -r web/ui $deploy
#注意使用root权限运行
#cd anylink-deploy
#sudo ./anylink -conf="conf/server.toml"

View File

@@ -1,28 +0,0 @@
#!/usr/bin/env bash
#编译二进制文件
go build -o anylink -ldflags "-X main.COMMIT_ID=`git rev-parse HEAD`"
#编译前端项目
git clone https://github.com/bjdgyc/anylink-web.git
cd anylink-web
#国内可替换源加快速度
#npm install --registry=https://registry.npm.taobao.org
#npm run build --registry=https://registry.npm.taobao.org
npm install
npm run build
cd ../
#整理部署文件
mkdir anylink-deploy
cp -r anylink anylink-deploy
cp -r anylink-web/ui anylink-deploy
cp -r conf anylink-deploy
cp -r down_files anylink-deploy
#注意使用root权限运行
#cd anylink-deploy
#sudo ./anylink -conf="conf/server.toml"

View File

@@ -1,10 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
</body>
</html>

View File

@@ -1,47 +0,0 @@
package handler
import "github.com/bjdgyc/anylink/sessdata"
func payloadIn(cSess *sessdata.ConnSession, lType sessdata.LType, pType byte, data []byte) bool {
payload := &sessdata.Payload{
LType: lType,
PType: pType,
Data: data,
}
return payloadInData(cSess, payload)
}
func payloadInData(cSess *sessdata.ConnSession, payload *sessdata.Payload) bool {
closed := false
select {
case cSess.PayloadIn <- payload:
case <-cSess.CloseChan:
closed = true
}
return closed
}
func payloadOut(cSess *sessdata.ConnSession, lType sessdata.LType, pType byte, data []byte) bool {
payload := &sessdata.Payload{
LType: lType,
PType: pType,
Data: data,
}
return payloadOutData(cSess, payload)
}
func payloadOutData(cSess *sessdata.ConnSession, payload *sessdata.Payload) bool {
closed := false
select {
case cSess.PayloadOut <- payload:
case <-cSess.CloseChan:
closed = true
}
return closed
}

BIN
screenshot/jetbrains.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

BIN
screenshot/qq.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

5
server/.codecov.yml Normal file
View File

@@ -0,0 +1,5 @@
ignore:
- "screenshot"
- "web"
- "server/conf"
- "server/files"

19
server/.gitignore vendored Normal file
View File

@@ -0,0 +1,19 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
vendor/
ui/
.idea/
anylink

View File

@@ -10,26 +10,26 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
) )
// 登陆接口 // Login 登陆接口
func Login(w http.ResponseWriter, r *http.Request) { func Login(w http.ResponseWriter, r *http.Request) {
// TODO 调试信息输出 // TODO 调试信息输出
// hd, _ := httputil.DumpRequest(r, true) // hd, _ := httputil.DumpRequest(r, true)
// fmt.Println("DumpRequest: ", string(hd)) // fmt.Println("DumpRequest: ", string(hd))
r.ParseForm() _ = r.ParseForm()
admin_user := r.PostFormValue("admin_user") adminUser := r.PostFormValue("admin_user")
admin_pass := r.PostFormValue("admin_pass") adminPass := r.PostFormValue("admin_pass")
// 认证错误 // 认证错误
if !(admin_user == base.Cfg.AdminUser && if !(adminUser == base.Cfg.AdminUser &&
utils.PasswordVerify(admin_pass, base.Cfg.AdminPass)) { utils.PasswordVerify(adminPass, base.Cfg.AdminPass)) {
RespError(w, RespUserOrPassErr) RespError(w, RespUserOrPassErr)
return return
} }
// token有效期 // token有效期
expiresAt := time.Now().Unix() + 3600*3 expiresAt := time.Now().Unix() + 3600*3
jwtData := map[string]interface{}{"admin_user": admin_user} jwtData := map[string]interface{}{"admin_user": adminUser}
tokenString, err := SetJwtData(jwtData, expiresAt) tokenString, err := SetJwtData(jwtData, expiresAt)
if err != nil { if err != nil {
RespError(w, 1, err) RespError(w, 1, err)
@@ -38,7 +38,7 @@ func Login(w http.ResponseWriter, r *http.Request) {
data := make(map[string]interface{}) data := make(map[string]interface{})
data["token"] = tokenString data["token"] = tokenString
data["admin_user"] = admin_user data["admin_user"] = adminUser
data["expires_at"] = expiresAt data["expires_at"] = expiresAt
RespSucess(w, data) RespSucess(w, data)

View File

@@ -10,7 +10,7 @@ import (
) )
func GroupList(w http.ResponseWriter, r *http.Request) { func GroupList(w http.ResponseWriter, r *http.Request) {
r.ParseForm() _ = r.ParseForm()
pageS := r.FormValue("page") pageS := r.FormValue("page")
page, _ := strconv.Atoi(pageS) page, _ := strconv.Atoi(pageS)
if page < 1 { if page < 1 {
@@ -48,7 +48,7 @@ func GroupNames(w http.ResponseWriter, r *http.Request) {
} }
func GroupDetail(w http.ResponseWriter, r *http.Request) { func GroupDetail(w http.ResponseWriter, r *http.Request) {
r.ParseForm() _ = r.ParseForm()
idS := r.FormValue("id") idS := r.FormValue("id")
id, _ := strconv.Atoi(idS) id, _ := strconv.Atoi(idS)
if id < 1 { if id < 1 {
@@ -90,7 +90,7 @@ func GroupSet(w http.ResponseWriter, r *http.Request) {
} }
func GroupDel(w http.ResponseWriter, r *http.Request) { func GroupDel(w http.ResponseWriter, r *http.Request) {
r.ParseForm() _ = r.ParseForm()
idS := r.FormValue("id") idS := r.FormValue("id")
id, _ := strconv.Atoi(idS) id, _ := strconv.Atoi(idS)
if id < 1 { if id < 1 {

View File

@@ -11,7 +11,7 @@ import (
) )
func UserIpMapList(w http.ResponseWriter, r *http.Request) { func UserIpMapList(w http.ResponseWriter, r *http.Request) {
r.ParseForm() _ = r.ParseForm()
pageS := r.FormValue("page") pageS := r.FormValue("page")
page, _ := strconv.Atoi(pageS) page, _ := strconv.Atoi(pageS)
if page < 1 { if page < 1 {
@@ -39,7 +39,7 @@ func UserIpMapList(w http.ResponseWriter, r *http.Request) {
} }
func UserIpMapDetail(w http.ResponseWriter, r *http.Request) { func UserIpMapDetail(w http.ResponseWriter, r *http.Request) {
r.ParseForm() _ = r.ParseForm()
idS := r.FormValue("id") idS := r.FormValue("id")
id, _ := strconv.Atoi(idS) id, _ := strconv.Atoi(idS)
if id < 1 { if id < 1 {
@@ -58,7 +58,7 @@ func UserIpMapDetail(w http.ResponseWriter, r *http.Request) {
} }
func UserIpMapSet(w http.ResponseWriter, r *http.Request) { func UserIpMapSet(w http.ResponseWriter, r *http.Request) {
r.ParseForm() _ = r.ParseForm()
body, err := ioutil.ReadAll(r.Body) body, err := ioutil.ReadAll(r.Body)
if err != nil { if err != nil {
@@ -92,7 +92,7 @@ func UserIpMapSet(w http.ResponseWriter, r *http.Request) {
} }
func UserIpMapDel(w http.ResponseWriter, r *http.Request) { func UserIpMapDel(w http.ResponseWriter, r *http.Request) {
r.ParseForm() _ = r.ParseForm()
idS := r.FormValue("id") idS := r.FormValue("id")
id, _ := strconv.Atoi(idS) id, _ := strconv.Atoi(idS)

View File

@@ -1,7 +1,6 @@
package admin package admin
import ( import (
"encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"runtime" "runtime"
@@ -84,9 +83,8 @@ func SetSystem(w http.ResponseWriter, r *http.Request) {
} }
func SetSoft(w http.ResponseWriter, r *http.Request) { func SetSoft(w http.ResponseWriter, r *http.Request) {
datas := base.ServerCfg2Slice() data := base.ServerCfg2Slice()
b, _ := json.Marshal(datas) RespSucess(w, data)
w.Write(b)
} }
func decimal(f float64) float64 { func decimal(f float64) float64 {

View File

@@ -19,7 +19,7 @@ import (
) )
func UserList(w http.ResponseWriter, r *http.Request) { func UserList(w http.ResponseWriter, r *http.Request) {
r.ParseForm() _ = r.ParseForm()
prefix := r.FormValue("prefix") prefix := r.FormValue("prefix")
pageS := r.FormValue("page") pageS := r.FormValue("page")
page, _ := strconv.Atoi(pageS) page, _ := strconv.Atoi(pageS)
@@ -58,7 +58,7 @@ func UserList(w http.ResponseWriter, r *http.Request) {
} }
func UserDetail(w http.ResponseWriter, r *http.Request) { func UserDetail(w http.ResponseWriter, r *http.Request) {
r.ParseForm() _ = r.ParseForm()
idS := r.FormValue("id") idS := r.FormValue("id")
id, _ := strconv.Atoi(idS) id, _ := strconv.Atoi(idS)
if id < 1 { if id < 1 {
@@ -77,7 +77,7 @@ func UserDetail(w http.ResponseWriter, r *http.Request) {
} }
func UserSet(w http.ResponseWriter, r *http.Request) { func UserSet(w http.ResponseWriter, r *http.Request) {
r.ParseForm() _ = r.ParseForm()
body, err := ioutil.ReadAll(r.Body) body, err := ioutil.ReadAll(r.Body)
if err != nil { if err != nil {
@@ -100,14 +100,18 @@ func UserSet(w http.ResponseWriter, r *http.Request) {
// 发送邮件 // 发送邮件
if data.SendEmail { if data.SendEmail {
userAccountMail(data) err = userAccountMail(data)
if err != nil {
RespError(w, RespInternalErr, err)
return
}
} }
RespSucess(w, nil) RespSucess(w, nil)
} }
func UserDel(w http.ResponseWriter, r *http.Request) { func UserDel(w http.ResponseWriter, r *http.Request) {
r.ParseForm() _ = r.ParseForm()
idS := r.FormValue("id") idS := r.FormValue("id")
id, _ := strconv.Atoi(idS) id, _ := strconv.Atoi(idS)
@@ -126,7 +130,7 @@ func UserDel(w http.ResponseWriter, r *http.Request) {
} }
func UserOtpQr(w http.ResponseWriter, r *http.Request) { func UserOtpQr(w http.ResponseWriter, r *http.Request) {
r.ParseForm() _ = r.ParseForm()
b64 := r.FormValue("b64") b64 := r.FormValue("b64")
idS := r.FormValue("id") idS := r.FormValue("id")
id, _ := strconv.Atoi(idS) id, _ := strconv.Atoi(idS)
@@ -144,11 +148,16 @@ func UserOtpQr(w http.ResponseWriter, r *http.Request) {
if b64 == "1" { if b64 == "1" {
data, _ := qr.PNG(300) data, _ := qr.PNG(300)
s := base64.StdEncoding.EncodeToString(data) s := base64.StdEncoding.EncodeToString(data)
fmt.Fprint(w, s) _, err = fmt.Fprint(w, s)
} else { if err != nil {
qr.Write(300, w) base.Error(err)
}
return
}
err = qr.Write(300, w)
if err != nil {
base.Error(err)
} }
} }
// 在线用户 // 在线用户
@@ -165,12 +174,19 @@ func UserOnline(w http.ResponseWriter, r *http.Request) {
} }
func UserOffline(w http.ResponseWriter, r *http.Request) { func UserOffline(w http.ResponseWriter, r *http.Request) {
r.ParseForm() _ = r.ParseForm()
token := r.FormValue("token") token := r.FormValue("token")
sessdata.CloseSess(token) sessdata.CloseSess(token)
RespSucess(w, nil) RespSucess(w, nil)
} }
func UserReline(w http.ResponseWriter, r *http.Request) {
_ = r.ParseForm()
token := r.FormValue("token")
sessdata.CloseCSess(token)
RespSucess(w, nil)
}
type userAccountMailData struct { type userAccountMailData struct {
Issuer string Issuer string
LinkAddr string LinkAddr string
@@ -220,7 +236,10 @@ func userAccountMail(user *dbdata.User) error {
} }
w := bytes.NewBufferString("") w := bytes.NewBufferString("")
t, _ := template.New("auth_complete").Parse(htmlBody) t, _ := template.New("auth_complete").Parse(htmlBody)
t.Execute(w, data) err = t.Execute(w, data)
if err != nil {
return err
}
// fmt.Println(w.String()) // fmt.Println(w.String())
return SendMail(base.Cfg.Issuer+"平台通知", user.Email, w.String()) return SendMail(base.Cfg.Issuer+"平台通知", user.Email, w.String())
} }

View File

@@ -8,8 +8,8 @@ import (
"github.com/bjdgyc/anylink/base" "github.com/bjdgyc/anylink/base"
"github.com/bjdgyc/anylink/dbdata" "github.com/bjdgyc/anylink/dbdata"
"github.com/dgrijalva/jwt-go" "github.com/dgrijalva/jwt-go"
"github.com/mojocn/base64Captcha"
mail "github.com/xhit/go-simple-mail/v2" mail "github.com/xhit/go-simple-mail/v2"
// "github.com/mojocn/base64Captcha"
) )
func SetJwtData(data map[string]interface{}, expiresAt int64) (string, error) { func SetJwtData(data map[string]interface{}, expiresAt int64) (string, error) {
@@ -43,15 +43,6 @@ func GetJwtData(jwtToken string) (map[string]interface{}, error) {
return claims, nil return claims, nil
} }
func createCaptcha() {
var store = base64Captcha.DefaultMemStore
var driver base64Captcha.Driver
driverString := &base64Captcha.DriverString{}
driver = driverString.ConvertFonts()
c := base64Captcha.NewCaptcha(driver, store)
_ = c
}
func SendMail(subject, to, htmlBody string) error { func SendMail(subject, to, htmlBody string) error {
dataSmtp := &dbdata.SettingSmtp{} dataSmtp := &dbdata.SettingSmtp{}
@@ -68,7 +59,9 @@ func SendMail(subject, to, htmlBody string) error {
server.Port = dataSmtp.Port server.Port = dataSmtp.Port
server.Username = dataSmtp.Username server.Username = dataSmtp.Username
server.Password = dataSmtp.Password server.Password = dataSmtp.Password
// server.Encryption = mail.EncryptionTLS if dataSmtp.UseSSl {
server.Encryption = mail.EncryptionSSL
}
// Since v2.3.0 you can specified authentication type: // Since v2.3.0 you can specified authentication type:
// - PLAIN (default) // - PLAIN (default)

View File

@@ -0,0 +1,23 @@
package admin
import (
"testing"
"time"
"github.com/bjdgyc/anylink/base"
"github.com/stretchr/testify/assert"
)
func TestJwtData(t *testing.T) {
assert := assert.New(t)
base.Cfg.JwtSecret = "dsfasfdfsadfasdfasd3sdaf"
data := map[string]interface{}{
"key": "value",
}
expiresAt := time.Now().Add(time.Minute).Unix()
token, err := SetJwtData(data, expiresAt)
assert.Nil(err)
dataN, err := GetJwtData(token)
assert.Nil(err)
assert.Equal(dataN["key"], "value")
}

View File

@@ -43,8 +43,10 @@ func respHttp(w http.ResponseWriter, respCode int, data interface{}, errS ...int
w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
w.Write(b) _, err = w.Write(b)
if err != nil {
base.Error(err)
}
// 记录返回数据 // 记录返回数据
// logger.Category("response").Debug(string(b)) // logger.Category("response").Debug(string(b))
} }

39
server/admin/resp_test.go Normal file
View File

@@ -0,0 +1,39 @@
package admin
import (
"encoding/json"
"io/ioutil"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestRespSucess(t *testing.T) {
assert := assert.New(t)
w := httptest.NewRecorder()
RespSucess(w, "data")
// fmt.Println(w)
assert.Equal(w.Code, 200)
body, _ := ioutil.ReadAll(w.Body)
res := Resp{}
err := json.Unmarshal(body, &res)
assert.Nil(err)
assert.Equal(res.Code, 0)
assert.Equal(res.Data, "data")
}
func TestRespError(t *testing.T) {
assert := assert.New(t)
w := httptest.NewRecorder()
RespError(w, 10, "err-msg")
// fmt.Println(w)
assert.Equal(w.Code, 200)
body, _ := ioutil.ReadAll(w.Body)
res := Resp{}
err := json.Unmarshal(body, &res)
assert.Nil(err)
assert.Equal(res.Code, 10)
assert.Equal(res.Msg, "err-msg")
}

View File

@@ -35,6 +35,7 @@ func StartAdmin() {
r.HandleFunc("/user/del", UserDel) r.HandleFunc("/user/del", UserDel)
r.HandleFunc("/user/online", UserOnline) r.HandleFunc("/user/online", UserOnline)
r.HandleFunc("/user/offline", UserOffline) r.HandleFunc("/user/offline", UserOffline)
r.HandleFunc("/user/reline", UserReline)
r.HandleFunc("/user/otp_qr", UserOtpQr) r.HandleFunc("/user/otp_qr", UserOtpQr)
r.HandleFunc("/user/ip_map/list", UserIpMapList) r.HandleFunc("/user/ip_map/list", UserIpMapList)
r.HandleFunc("/user/ip_map/detail", UserIpMapDetail) r.HandleFunc("/user/ip_map/detail", UserIpMapDetail)

View File

@@ -2,5 +2,5 @@ package base
const ( const (
APP_NAME = "AnyLink" APP_NAME = "AnyLink"
APP_VER = "0.0.8" APP_VER = "0.1.7"
) )

View File

@@ -40,7 +40,8 @@ type ServerConfig struct {
CertFile string `toml:"cert_file" info:"证书文件"` CertFile string `toml:"cert_file" info:"证书文件"`
CertKey string `toml:"cert_key" info:"证书密钥"` CertKey string `toml:"cert_key" info:"证书密钥"`
UiPath string `toml:"ui_path" info:"ui文件路径"` UiPath string `toml:"ui_path" info:"ui文件路径"`
DownFilesPath string `toml:"down_files_path" info:"外部下载文件路径"` FilesPath string `toml:"files_path" info:"外部下载文件路径"`
LogPath string `toml:"log_path" info:"日志文件路径"`
LogLevel string `toml:"log_level" info:"日志等级"` LogLevel string `toml:"log_level" info:"日志等级"`
Issuer string `toml:"issuer" info:"系统名称"` Issuer string `toml:"issuer" info:"系统名称"`
AdminUser string `toml:"admin_user" info:"管理用户名"` AdminUser string `toml:"admin_user" info:"管理用户名"`
@@ -48,8 +49,7 @@ type ServerConfig struct {
JwtSecret string `toml:"jwt_secret" info:"JWT密钥"` JwtSecret string `toml:"jwt_secret" info:"JWT密钥"`
LinkMode string `toml:"link_mode" info:"虚拟网络类型"` // tun tap LinkMode string `toml:"link_mode" info:"虚拟网络类型"` // tun tap
Ipv4Network string `toml:"ipv4_network" info:"ipv4_network"` // 192.168.1.0 Ipv4CIDR string `toml:"ipv4_cidr" info:"ip地址网段"` // 192.168.1.0/24
Ipv4Netmask string `toml:"ipv4_netmask" info:"ipv4_netmask"` // 255.255.255.0
Ipv4Gateway string `toml:"ipv4_gateway" info:"ipv4_gateway"` Ipv4Gateway string `toml:"ipv4_gateway" info:"ipv4_gateway"`
Ipv4Pool []string `toml:"ipv4_pool" info:"IPV4起止地址池"` // Pool[0]=192.168.1.100 Pool[1]=192.168.1.200 Ipv4Pool []string `toml:"ipv4_pool" info:"IPV4起止地址池"` // Pool[0]=192.168.1.100 Pool[1]=192.168.1.200
IpLease int `toml:"ip_lease" info:"IP租期(秒)"` IpLease int `toml:"ip_lease" info:"IP租期(秒)"`
@@ -84,7 +84,8 @@ func initServerCfg() {
Cfg.CertFile = getAbsPath(base, Cfg.CertFile) Cfg.CertFile = getAbsPath(base, Cfg.CertFile)
Cfg.CertKey = getAbsPath(base, Cfg.CertKey) Cfg.CertKey = getAbsPath(base, Cfg.CertKey)
Cfg.UiPath = getAbsPath(base, Cfg.UiPath) Cfg.UiPath = getAbsPath(base, Cfg.UiPath)
Cfg.DownFilesPath = getAbsPath(base, Cfg.DownFilesPath) Cfg.FilesPath = getAbsPath(base, Cfg.FilesPath)
Cfg.LogPath = getAbsPath(base, Cfg.LogPath)
if len(Cfg.JwtSecret) < 20 { if len(Cfg.JwtSecret) < 20 {
fmt.Println("请设置 jwt_secret 长度20位以上") fmt.Println("请设置 jwt_secret 长度20位以上")
@@ -95,6 +96,10 @@ func initServerCfg() {
} }
func getAbsPath(base, cfile string) string { func getAbsPath(base, cfile string) string {
if cfile == "" {
return ""
}
abs := filepath.IsAbs(cfile) abs := filepath.IsAbs(cfile)
if abs { if abs {
return cfile return cfile
@@ -102,16 +107,17 @@ func getAbsPath(base, cfile string) string {
return filepath.Join(base, cfile) return filepath.Join(base, cfile)
} }
func ServerCfg2Slice() interface{} { type SCfg struct {
ref := reflect.ValueOf(Cfg)
s := ref.Elem()
type cfg struct {
Name string `json:"name"` Name string `json:"name"`
Info string `json:"info"` Info string `json:"info"`
Data interface{} `json:"data"` Data interface{} `json:"data"`
} }
var datas []cfg
func ServerCfg2Slice() []SCfg {
ref := reflect.ValueOf(Cfg)
s := ref.Elem()
var datas []SCfg
typ := s.Type() typ := s.Type()
numFields := s.NumField() numFields := s.NumField()
@@ -122,7 +128,7 @@ func ServerCfg2Slice() interface{} {
tags := strings.Split(tag, ",") tags := strings.Split(tag, ",")
info := field.Tag.Get("info") info := field.Tag.Get("info")
datas = append(datas, cfg{Name: tags[0], Info: info, Data: value.Interface()}) datas = append(datas, SCfg{Name: tags[0], Info: info, Data: value.Interface()})
} }
return datas return datas

View File

@@ -26,7 +26,7 @@ var (
) )
func initFlag() { func initFlag() {
flag.StringVar(&serverFile, "conf", "./conf/server.toml", "server config file path") flag.StringVar(&serverFile, "conf", "./conf/server.toml", "server config files path")
flag.StringVar(&passwd, "passwd", "", "convert the password plaintext") flag.StringVar(&passwd, "passwd", "", "convert the password plaintext")
flag.BoolVar(&secret, "secret", false, "generate a random jwt secret") flag.BoolVar(&secret, "secret", false, "generate a random jwt secret")
flag.BoolVar(&rev, "rev", false, "display version info") flag.BoolVar(&rev, "rev", false, "display version info")

146
server/base/log.go Normal file
View File

@@ -0,0 +1,146 @@
package base
import (
"fmt"
"log"
"os"
"path"
"strings"
"time"
)
const (
_Debug = iota
_Info
_Warn
_Error
_Fatal
)
var (
baseLog *log.Logger
baseLevel int
levels map[int]string
dateFormat = "2006-01-02"
logName = "anylink.log"
)
// 实现 os.Writer 接口
type logWriter struct {
UseStdout bool
FileName string
File *os.File
NowDate string
}
// 实现日志文件的切割
func (lw *logWriter) Write(p []byte) (n int, err error) {
if !lw.UseStdout {
return lw.File.Write(p)
}
date := time.Now().Format(dateFormat)
if lw.NowDate != date {
_ = lw.File.Close()
_ = os.Rename(lw.FileName, lw.FileName+"."+lw.NowDate)
lw.NowDate = date
lw.newFile()
}
return lw.File.Write(p)
}
// 创建新文件
func (lw *logWriter) newFile() {
if lw.UseStdout {
lw.File = os.Stdout
return
}
f, err := os.OpenFile(lw.FileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
panic(err)
}
lw.File = f
}
func initLog() {
// 初始化 baseLog
baseLw := &logWriter{
UseStdout: Cfg.LogPath == "",
FileName: path.Join(Cfg.LogPath, logName),
NowDate: time.Now().Format(dateFormat),
}
baseLw.newFile()
baseLevel = logLevel2Int(Cfg.LogLevel)
baseLog = log.New(baseLw, "", log.LstdFlags|log.Lshortfile)
}
// 获取 log.Logger
func GetBaseLog() *log.Logger {
return baseLog
}
func logLevel2Int(l string) int {
levels = map[int]string{
_Debug: "Debug",
_Info: "Info",
_Warn: "Warn",
_Error: "Error",
_Fatal: "Fatal",
}
lvl := _Info
for k, v := range levels {
if strings.EqualFold(strings.ToLower(l), strings.ToLower(v)) {
lvl = k
}
}
return lvl
}
func output(l int, s ...interface{}) {
lvl := fmt.Sprintf("[%s] ", levels[l])
_ = baseLog.Output(3, lvl+fmt.Sprintln(s...))
}
func Debug(v ...interface{}) {
l := _Debug
if baseLevel > l {
return
}
output(l, v...)
}
func Info(v ...interface{}) {
l := _Info
if baseLevel > l {
return
}
output(l, v...)
}
func Warn(v ...interface{}) {
l := _Warn
if baseLevel > l {
return
}
output(l, v...)
}
func Error(v ...interface{}) {
l := _Error
if baseLevel > l {
return
}
output(l, v...)
}
func Fatal(v ...interface{}) {
l := _Fatal
if baseLevel > l {
return
}
output(l, v...)
os.Exit(1)
}

View File

@@ -5,3 +5,7 @@ func Start() {
initServerCfg() initServerCfg()
initLog() initLog()
} }
func Test() {
initLog()
}

View File

@@ -8,10 +8,6 @@
# Define Bridge Interface # Define Bridge Interface
br="anylink0" br="anylink0"
# Define list of TAP interfaces to be bridged,
# for example tap="tap0 tap1 tap2".
tap="tap0"
# Define physical ethernet interface to be bridged # Define physical ethernet interface to be bridged
# with TAP interface(s) above. # with TAP interface(s) above.

View File

@@ -9,8 +9,9 @@ db_file = "./data.db"
cert_file = "./vpn_cert.pem" cert_file = "./vpn_cert.pem"
cert_key = "./vpn_cert.key" cert_key = "./vpn_cert.key"
ui_path = "../ui" ui_path = "../ui"
down_files_path = "../down_files" files_path = "../files"
#日志目录,为空写入标准输出
log_path = "../log"
log_level = "info" log_level = "info"
#系统名称 #系统名称
@@ -35,8 +36,7 @@ proxy_protocol = false
link_mode = "tun" link_mode = "tun"
#客户端分配的ip地址池 #客户端分配的ip地址池
ipv4_network = "192.168.10.0" ipv4_cidr = "192.168.10.0/24"
ipv4_netmask = "255.255.255.0"
ipv4_gateway = "192.168.10.1" ipv4_gateway = "192.168.10.1"
ipv4_pool = ["192.168.10.100", "192.168.10.200"] ipv4_pool = ["192.168.10.100", "192.168.10.200"]

View File

@@ -43,28 +43,27 @@ func initData() {
return return
} }
defer Set(SettingBucket, Installed, true) defer func() {
_ = Set(SettingBucket, Installed, true)
}()
smtp := &SettingSmtp{ smtp := &SettingSmtp{
Host: "127.0.0.1", Host: "127.0.0.1",
Port: 25, Port: 25,
From: "vpn@xx.com", From: "vpn@xx.com",
} }
SettingSet(smtp) _ = SettingSet(smtp)
other := &SettingOther{ other := &SettingOther{
Banner: "您已接入公司网络,请按照公司规定使用。\n请勿进行非工作下载及视频行为", Banner: "您已接入公司网络,请按照公司规定使用。\n请勿进行非工作下载及视频行为",
AccountMail: accountMail, AccountMail: accountMail,
} }
SettingSet(other) _ = SettingSet(other)
} }
func CheckErrNotFound(err error) bool { func CheckErrNotFound(err error) bool {
if err == storm.ErrNotFound { return err == storm.ErrNotFound
return true
}
return false
} }
const accountMail = `<p>您好:</p> const accountMail = `<p>您好:</p>

View File

@@ -27,7 +27,7 @@ func TestDb(t *testing.T) {
defer closeIpdata() defer closeIpdata()
u := User{Username: "a"} u := User{Username: "a"}
Save(&u) _ = Save(&u)
assert.Equal(u.Id, 1) assert.Equal(u.Id, 1)
} }

View File

@@ -18,13 +18,15 @@ type GroupLinkAcl struct {
// 自上而下匹配 默认 allow * * // 自上而下匹配 默认 allow * *
Action string `json:"action"` // allow、deny Action string `json:"action"` // allow、deny
Val string `json:"val"` Val string `json:"val"`
Port uint8 `json:"port"` Port uint16 `json:"port"`
IpNet *net.IPNet `json:"ip_net"` IpNet *net.IPNet `json:"ip_net"`
Note string `json:"note"`
} }
type ValData struct { type ValData struct {
Val string `json:"val"` Val string `json:"val"`
IpMask string `json:"ip_mask"` IpMask string `json:"ip_mask"`
Note string `json:"note"`
} }
type Group struct { type Group struct {
@@ -81,8 +83,9 @@ func SetGroup(g *Group) error {
if err != nil { if err != nil {
return errors.New("RouteInclude 错误" + err.Error()) return errors.New("RouteInclude 错误" + err.Error())
} }
vn := ValData{Val: v.Val, IpMask: ipMask}
routeInclude = append(routeInclude, vn) v.IpMask = ipMask
routeInclude = append(routeInclude, v)
} }
} }
g.RouteInclude = routeInclude g.RouteInclude = routeInclude
@@ -93,8 +96,8 @@ func SetGroup(g *Group) error {
if err != nil { if err != nil {
return errors.New("RouteExclude 错误" + err.Error()) return errors.New("RouteExclude 错误" + err.Error())
} }
vn := ValData{Val: v.Val, IpMask: ipMask} v.IpMask = ipMask
routeExclude = append(routeExclude, vn) routeExclude = append(routeExclude, v)
} }
} }
g.RouteExclude = routeExclude g.RouteExclude = routeExclude
@@ -106,9 +109,8 @@ func SetGroup(g *Group) error {
if err != nil { if err != nil {
return errors.New("GroupLinkAcl 错误" + err.Error()) return errors.New("GroupLinkAcl 错误" + err.Error())
} }
vn := v v.IpNet = ipNet
vn.IpNet = ipNet linkAcl = append(linkAcl, v)
linkAcl = append(linkAcl, vn)
} }
} }
g.LinkAcl = linkAcl g.LinkAcl = linkAcl

View File

@@ -38,6 +38,7 @@ type SettingSmtp struct {
Username string `json:"username"` Username string `json:"username"`
Password string `json:"password"` Password string `json:"password"`
From string `json:"from"` From string `json:"from"`
UseSSl bool `json:"use_ssl"`
} }
type SettingOther struct { type SettingOther struct {

View File

@@ -18,6 +18,7 @@ type User struct {
// Password string `json:"password"` // Password string `json:"password"`
PinCode string `json:"pin_code"` PinCode string `json:"pin_code"`
OtpSecret string `json:"otp_secret"` OtpSecret string `json:"otp_secret"`
DisableOtp bool `json:"disable_otp"` // 禁用otp
Groups []string `json:"groups"` Groups []string `json:"groups"`
Status int8 `json:"status"` // 1正常 Status int8 `json:"status"` // 1正常
SendEmail bool `json:"send_email"` SendEmail bool `json:"send_email"`
@@ -39,7 +40,7 @@ func SetUser(v *User) error {
v.PinCode = planPass v.PinCode = planPass
if v.OtpSecret == "" { if v.OtpSecret == "" {
v.OtpSecret = gotp.RandomSecret(24) v.OtpSecret = gotp.RandomSecret(32)
} }
// 判断组是否有效 // 判断组是否有效
@@ -85,18 +86,16 @@ func CheckUser(name, pwd, group string) error {
} }
// 判断otp信息 // 判断otp信息
pinCode := pwd
if !v.DisableOtp {
pinCode = pwd[:pl-6]
otp := pwd[pl-6:] otp := pwd[pl-6:]
if !checkOtp(name, otp) { if !checkOtp(name, otp, v.OtpSecret) {
return fmt.Errorf("%s %s", name, "动态码错误") return fmt.Errorf("%s %s", name, "动态码错误")
} }
totp := gotp.NewDefaultTOTP(v.OtpSecret)
unix := time.Now().Unix()
verify := totp.Verify(otp, int(unix))
if !verify {
return fmt.Errorf("%s %s", name, "动态码错误")
} }
pinCode := pwd[:pl-6] // 判断用户密码
if pinCode != v.PinCode { if pinCode != v.PinCode {
return fmt.Errorf("%s %s", name, "密码错误") return fmt.Errorf("%s %s", name, "密码错误")
} }
@@ -126,18 +125,23 @@ func init() {
}() }()
} }
// 令牌只能使用一次 // 判断令牌信息
func checkOtp(username, otp string) bool { func checkOtp(name, otp, secret string) bool {
key := fmt.Sprintf("%s:%s", username, otp) key := fmt.Sprintf("%s:%s", name, otp)
userOtpMux.Lock() userOtpMux.Lock()
defer userOtpMux.Unlock() defer userOtpMux.Unlock()
// 令牌只能使用一次
if _, ok := userOtp[key]; ok { if _, ok := userOtp[key]; ok {
// 已经存在 // 已经存在
return false return false
} }
userOtp[key] = time.Now() userOtp[key] = time.Now()
return true
totp := gotp.NewDefaultTOTP(secret)
unix := time.Now().Unix()
verify := totp.Verify(otp, int(unix))
return verify
} }

0
server/files/index.html Normal file
View File

View File

@@ -6,21 +6,19 @@ require (
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
github.com/asdine/storm/v3 v3.2.1 github.com/asdine/storm/v3 v3.2.1
github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/go-ole/go-ole v1.2.4 // indirect github.com/go-ole/go-ole v1.2.5 // indirect
github.com/google/gopacket v1.1.19 github.com/google/gopacket v1.1.19
github.com/gorilla/mux v1.8.0 github.com/gorilla/mux v1.8.0
github.com/mojocn/base64Captcha v1.3.1
github.com/pelletier/go-toml v1.8.1 github.com/pelletier/go-toml v1.8.1
github.com/shirou/gopsutil v3.20.11+incompatible github.com/shirou/gopsutil v3.21.1+incompatible
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8
github.com/stretchr/testify v1.6.1 github.com/stretchr/testify v1.7.0
github.com/xhit/go-simple-mail/v2 v2.6.0 github.com/xhit/go-simple-mail/v2 v2.8.0
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119 github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119
go.etcd.io/bbolt v1.3.5 go.etcd.io/bbolt v1.3.5
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9 golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11 golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
) )

View File

@@ -11,10 +11,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY=
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -29,14 +27,12 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mojocn/base64Captcha v1.3.1 h1:2Wbkt8Oc8qjmNJ5GyOfSo4tgVQPsbKMftqASnq8GlT0=
github.com/mojocn/base64Captcha v1.3.1/go.mod h1:wAQCKEc5bDujxKRmbT6/vTnTt5CjStQ8bRfPWUuz/iY=
github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM= github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/shirou/gopsutil v3.20.11+incompatible h1:LJr4ZQK4mPpIV5gOa4jCOKOGb4ty4DZO54I4FGqIpto= github.com/shirou/gopsutil v3.21.1+incompatible h1:2LwXWdbjXwyDgq26Yy/OT4xozlpmssQfy/rtfhWb0bY=
github.com/shirou/gopsutil v3.20.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/gopsutil v3.21.1+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 h1:1zN6ImoqhSJhN8hGXFaJlSC8msLmIbX8bFqOfWLKw0w= github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 h1:1zN6ImoqhSJhN8hGXFaJlSC8msLmIbX8bFqOfWLKw0w=
@@ -45,12 +41,12 @@ github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 h1:TG/diQgUe0pntT/2D
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E= github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/xhit/go-simple-mail/v2 v2.6.0 h1:pvPmpDUUWy07cnTgwxwEe5fjdyYtETnxcvdGPQxtv/k= github.com/xhit/go-simple-mail/v2 v2.8.0 h1:w6ZDXvRk0EO+r78LRlQl14ngP2tiRDRRHhr9UaVJ0p4=
github.com/xhit/go-simple-mail/v2 v2.6.0/go.mod h1:kA1XbQfCI4JxQ9ccSN6VFyIEkkugOm7YiPkA5hKiQn4= github.com/xhit/go-simple-mail/v2 v2.8.0/go.mod h1:kA1XbQfCI4JxQ9ccSN6VFyIEkkugOm7YiPkA5hKiQn4=
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119 h1:YyPWX3jLOtYKulBR6AScGIs74lLrJcgeKRwcbAuQOG4= github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119 h1:YyPWX3jLOtYKulBR6AScGIs74lLrJcgeKRwcbAuQOG4=
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119/go.mod h1:/nuTSlK+okRfR/vnIPqR89fFKonnWPiZymN5ydRJkX8= github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119/go.mod h1:/nuTSlK+okRfR/vnIPqR89fFKonnWPiZymN5ydRJkX8=
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
@@ -58,21 +54,20 @@ go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9 h1:sYNJzB4J8toYPQTM6pAkcmBRgw9SnQKP9oXCHfgy604= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/image v0.0.0-20190501045829-6d32002ffd75 h1:TbGuee8sSq15Iguxu4deQ7+Bqq/d2rsQejGcEtADAMQ=
golang.org/x/image v0.0.0-20190501045829-6d32002ffd75/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191105084925-a882066a44e0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191105084925-a882066a44e0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11 h1:lwlPPsmjDKK0J6eG6xDWd5XPehI0R024zxjDnw3esPA= golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d h1:1aflnvSoWWLI2k/dMUAl5lvU1YO4Mb4hz0gh+1rjcxU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
@@ -82,13 +77,11 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -2,4 +2,5 @@ package handler
// 暂时没有实现 // 暂时没有实现
func startDtls() { func startDtls() {
} }

View File

@@ -17,10 +17,10 @@ import (
func LinkAuth(w http.ResponseWriter, r *http.Request) { func LinkAuth(w http.ResponseWriter, r *http.Request) {
// 判断anyconnect客户端 // 判断anyconnect客户端
userAgent := strings.ToLower(r.UserAgent()) userAgent := strings.ToLower(r.UserAgent())
x_Aggregate_Auth := r.Header.Get("X-Aggregate-Auth") xAggregateAuth := r.Header.Get("X-Aggregate-Auth")
x_Transcend_Version := r.Header.Get("X-Transcend-Version") xTranscendVersion := r.Header.Get("X-Transcend-Version")
if !(strings.Contains(userAgent, "anyconnect") && if !(strings.Contains(userAgent, "anyconnect") &&
x_Aggregate_Auth == "1" && x_Transcend_Version == "1") { xAggregateAuth == "1" && xTranscendVersion == "1") {
w.WriteHeader(http.StatusForbidden) w.WriteHeader(http.StatusForbidden)
fmt.Fprintf(w, "error request") fmt.Fprintf(w, "error request")
return return
@@ -67,7 +67,7 @@ func LinkAuth(w http.ResponseWriter, r *http.Request) {
// TODO 用户密码校验 // TODO 用户密码校验
err = dbdata.CheckUser(cr.Auth.Username, cr.Auth.Password, cr.GroupSelect) err = dbdata.CheckUser(cr.Auth.Username, cr.Auth.Password, cr.GroupSelect)
if err != nil { if err != nil {
base.Info(err) base.Warn(err)
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
data := RequestData{Group: cr.GroupSelect, Groups: dbdata.GetGroupNames(), Error: "用户名或密码错误"} data := RequestData{Group: cr.GroupSelect, Groups: dbdata.GetGroupNames(), Error: "用户名或密码错误"}
tplRequest(tpl_request, w, data) tplRequest(tpl_request, w, data)
@@ -87,11 +87,12 @@ func LinkAuth(w http.ResponseWriter, r *http.Request) {
sess.MacAddr = strings.ToLower(cr.MacAddressList.MacAddress) sess.MacAddr = strings.ToLower(cr.MacAddressList.MacAddress)
sess.UniqueIdGlobal = cr.DeviceId.UniqueIdGlobal sess.UniqueIdGlobal = cr.DeviceId.UniqueIdGlobal
other := &dbdata.SettingOther{} other := &dbdata.SettingOther{}
dbdata.SettingGet(other) _ = dbdata.SettingGet(other)
rd := RequestData{SessionId: sess.Sid, SessionToken: sess.Sid + "@" + sess.Token, rd := RequestData{SessionId: sess.Sid, SessionToken: sess.Sid + "@" + sess.Token,
Banner: other.Banner} Banner: other.Banner}
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
tplRequest(tpl_complete, w, rd) tplRequest(tpl_complete, w, rd)
base.Debug("login", cr.Auth.Username)
} }
const ( const (
@@ -102,7 +103,7 @@ const (
func tplRequest(typ int, w io.Writer, data RequestData) { func tplRequest(typ int, w io.Writer, data RequestData) {
if typ == tpl_request { if typ == tpl_request {
t, _ := template.New("auth_request").Parse(auth_request) t, _ := template.New("auth_request").Parse(auth_request)
t.Execute(w, data) _ = t.Execute(w, data)
return return
} }
@@ -111,7 +112,7 @@ func tplRequest(typ int, w io.Writer, data RequestData) {
data.Banner = strings.ReplaceAll(data.Banner, "\n", "&#x0A;") data.Banner = strings.ReplaceAll(data.Banner, "\n", "&#x0A;")
} }
t, _ := template.New("auth_complete").Parse(auth_complete) t, _ := template.New("auth_complete").Parse(auth_complete)
t.Execute(w, data) _ = t.Execute(w, data)
} }
// 设置输出信息 // 设置输出信息

View File

@@ -2,11 +2,9 @@ package handler
import ( import (
"encoding/xml" "encoding/xml"
"fmt"
"log" "log"
"net/http" "net/http"
"os/exec" "os/exec"
"strings"
) )
const BufferSize = 2048 const BufferSize = 2048
@@ -43,27 +41,6 @@ type macAddressList struct {
MacAddress string `xml:"mac-address"` MacAddress string `xml:"mac-address"`
} }
// 判断anyconnect客户端
func checkLinkClient(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// TODO 调试信息输出
// hd, _ := httputil.DumpRequest(r, true)
// fmt.Println("DumpRequest: ", string(hd))
// fmt.Println(r.RemoteAddr)
userAgent := strings.ToLower(r.UserAgent())
x_Aggregate_Auth := r.Header.Get("X-Aggregate-Auth")
x_Transcend_Version := r.Header.Get("X-Transcend-Version")
if strings.Contains(userAgent, "anyconnect") &&
x_Aggregate_Auth == "1" && x_Transcend_Version == "1" {
h(w, r)
} else {
w.WriteHeader(http.StatusForbidden)
fmt.Fprintf(w, "error request")
}
}
}
func setCommonHeader(w http.ResponseWriter) { func setCommonHeader(w http.ResponseWriter) {
// Content-Length Date 默认已经存在 // Content-Length Date 默认已经存在
w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Header().Set("Content-Type", "text/html; charset=utf-8")

View File

@@ -11,8 +11,8 @@ import (
func LinkCstp(conn net.Conn, cSess *sessdata.ConnSession) { func LinkCstp(conn net.Conn, cSess *sessdata.ConnSession) {
defer func() { defer func() {
// log.Println("LinkCstp return") base.Debug("LinkCstp return", cSess.IpAddr)
conn.Close() _ = conn.Close()
cSess.Close() cSess.Close()
}() }()
@@ -72,8 +72,8 @@ func LinkCstp(conn net.Conn, cSess *sessdata.ConnSession) {
func cstpWrite(conn net.Conn, cSess *sessdata.ConnSession) { func cstpWrite(conn net.Conn, cSess *sessdata.ConnSession) {
defer func() { defer func() {
// log.Println("cstpWrite return") base.Debug("cstpWrite return", cSess.IpAddr)
conn.Close() _ = conn.Close()
cSess.Close() cSess.Close()
}() }()

View File

@@ -26,7 +26,7 @@ func LinkHome(w http.ResponseWriter, r *http.Request) {
} }
func LinkOtpQr(w http.ResponseWriter, r *http.Request) { func LinkOtpQr(w http.ResponseWriter, r *http.Request) {
r.ParseForm() _ = r.ParseForm()
idS := r.FormValue("id") idS := r.FormValue("id")
jwtToken := r.FormValue("jwt") jwtToken := r.FormValue("jwt")
data, err := admin.GetJwtData(jwtToken) data, err := admin.GetJwtData(jwtToken)

View File

@@ -29,6 +29,9 @@ func checkTap() {
bridgeHw = brFace.HardwareAddr bridgeHw = brFace.HardwareAddr
addrs, err := brFace.Addrs() addrs, err := brFace.Addrs()
if err != nil {
base.Fatal("testTap err: ", err)
}
for _, addr := range addrs { for _, addr := range addrs {
ip, _, err := net.ParseCIDR(addr.String()) ip, _, err := net.ParseCIDR(addr.String())
if err != nil || ip.To4() == nil { if err != nil || ip.To4() == nil {
@@ -67,7 +70,7 @@ func LinkTap(cSess *sessdata.ConnSession) error {
err = execCmd(cmdStrs) err = execCmd(cmdStrs)
if err != nil { if err != nil {
base.Error(err) base.Error(err)
ifce.Close() _ = ifce.Close()
return err return err
} }
@@ -78,9 +81,9 @@ func LinkTap(cSess *sessdata.ConnSession) error {
func tapWrite(ifce *water.Interface, cSess *sessdata.ConnSession) { func tapWrite(ifce *water.Interface, cSess *sessdata.ConnSession) {
defer func() { defer func() {
// log.Println("LinkTap return") base.Debug("LinkTap return", cSess.IpAddr)
cSess.Close() cSess.Close()
ifce.Close() _ = ifce.Close()
}() }()
var ( var (
@@ -150,8 +153,8 @@ func tapWrite(ifce *water.Interface, cSess *sessdata.ConnSession) {
func tapRead(ifce *water.Interface, cSess *sessdata.ConnSession) { func tapRead(ifce *water.Interface, cSess *sessdata.ConnSession) {
defer func() { defer func() {
// log.Println("tapRead return") base.Debug("tapRead return", cSess.IpAddr)
ifce.Close() _ = ifce.Close()
}() }()
var ( var (

View File

@@ -51,7 +51,7 @@ func LinkTun(cSess *sessdata.ConnSession) error {
err = execCmd(cmdStrs) err = execCmd(cmdStrs)
if err != nil { if err != nil {
base.Error(err) base.Error(err)
ifce.Close() _ = ifce.Close()
return err return err
} }
@@ -62,9 +62,9 @@ func LinkTun(cSess *sessdata.ConnSession) error {
func tunWrite(ifce *water.Interface, cSess *sessdata.ConnSession) { func tunWrite(ifce *water.Interface, cSess *sessdata.ConnSession) {
defer func() { defer func() {
// log.Println("LinkTun return") base.Debug("LinkTun return", cSess.IpAddr)
cSess.Close() cSess.Close()
ifce.Close() _ = ifce.Close()
}() }()
var ( var (
@@ -89,8 +89,8 @@ func tunWrite(ifce *water.Interface, cSess *sessdata.ConnSession) {
func tunRead(ifce *water.Interface, cSess *sessdata.ConnSession) { func tunRead(ifce *water.Interface, cSess *sessdata.ConnSession) {
defer func() { defer func() {
// log.Println("tunRead return") base.Debug("tunRead return", cSess.IpAddr)
ifce.Close() _ = ifce.Close()
}() }()
var ( var (
err error err error

View File

@@ -74,7 +74,7 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-CSTP-Version", "1") w.Header().Set("X-CSTP-Version", "1")
w.Header().Set("X-CSTP-Protocol", "Copyright (c) 2004 Cisco Systems, Inc.") w.Header().Set("X-CSTP-Protocol", "Copyright (c) 2004 Cisco Systems, Inc.")
w.Header().Set("X-CSTP-Address", cSess.IpAddr.String()) // 分配的ip地址 w.Header().Set("X-CSTP-Address", cSess.IpAddr.String()) // 分配的ip地址
w.Header().Set("X-CSTP-Netmask", base.Cfg.Ipv4Netmask) // 子网掩码 w.Header().Set("X-CSTP-Netmask", sessdata.IpPool.Ipv4Mask.String()) // 子网掩码
w.Header().Set("X-CSTP-Hostname", hn) // 机器名称 w.Header().Set("X-CSTP-Hostname", hn) // 机器名称
// 允许本地LAN访问vpn网络必须放在路由的第一个 // 允许本地LAN访问vpn网络必须放在路由的第一个
@@ -131,15 +131,16 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) {
// w.Header().Set("X-CSTP-Post-Auth-XML", ``) // w.Header().Set("X-CSTP-Post-Auth-XML", ``)
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
h := w.Header().Clone() hClone := w.Header().Clone()
headers := make([]byte, 0) headers := make([]byte, 0)
buf := bytes.NewBuffer(headers) buf := bytes.NewBuffer(headers)
h.Write(buf) _ = hClone.Write(buf)
base.Debug(string(buf.Bytes())) base.Debug(buf.String())
hj := w.(http.Hijacker) hj := w.(http.Hijacker)
conn, _, err := hj.Hijack() conn, _, err := hj.Hijack()
if err != nil { if err != nil {
base.Error(err)
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
return return
} }

91
server/handler/payload.go Normal file
View File

@@ -0,0 +1,91 @@
package handler
import (
"github.com/bjdgyc/anylink/dbdata"
"github.com/bjdgyc/anylink/sessdata"
"github.com/songgao/water/waterutil"
)
func payloadIn(cSess *sessdata.ConnSession, lType sessdata.LType, pType byte, data []byte) bool {
payload := &sessdata.Payload{
LType: lType,
PType: pType,
Data: data,
}
return payloadInData(cSess, payload)
}
func payloadInData(cSess *sessdata.ConnSession, payload *sessdata.Payload) bool {
// 进行Acl规则判断
check := checkLinkAcl(cSess.Group, payload)
if !check {
// 校验不通过直接丢弃
return false
}
closed := false
select {
case cSess.PayloadIn <- payload:
case <-cSess.CloseChan:
closed = true
}
return closed
}
func payloadOut(cSess *sessdata.ConnSession, lType sessdata.LType, pType byte, data []byte) bool {
payload := &sessdata.Payload{
LType: lType,
PType: pType,
Data: data,
}
return payloadOutData(cSess, payload)
}
func payloadOutData(cSess *sessdata.ConnSession, payload *sessdata.Payload) bool {
closed := false
select {
case cSess.PayloadOut <- payload:
case <-cSess.CloseChan:
closed = true
}
return closed
}
// Acl规则校验
func checkLinkAcl(group *dbdata.Group, payload *sessdata.Payload) bool {
if payload.LType == sessdata.LTypeIPData && payload.PType == 0x00 && len(group.LinkAcl) > 0 {
} else {
return true
}
ip_dst := waterutil.IPv4Destination(payload.Data)
ip_port := waterutil.IPv4DestinationPort(payload.Data)
// fmt.Println("sent:", ip_dst, ip_port)
// 优先放行dns端口
for _, v := range group.ClientDns {
if v.Val == ip_dst.String() && ip_port == 53 {
return true
}
}
for _, v := range group.LinkAcl {
// 循环判断ip和端口
if v.IpNet.Contains(ip_dst) {
if v.Port == ip_port || v.Port == 0 {
if v.Action == dbdata.Allow {
return true
} else {
return false
}
}
}
}
return false
}

View File

@@ -6,7 +6,6 @@ import (
"log" "log"
"net" "net"
"net/http" "net/http"
"os"
"time" "time"
"github.com/bjdgyc/anylink/base" "github.com/bjdgyc/anylink/base"
@@ -14,22 +13,28 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
) )
func GetCertificate(*tls.ClientHelloInfo) (*tls.Certificate, error) {
cert, err := tls.LoadX509KeyPair(base.Cfg.CertFile, base.Cfg.CertKey)
return &cert, err
}
func startTls() { func startTls() {
addr := base.Cfg.ServerAddr addr := base.Cfg.ServerAddr
certFile := base.Cfg.CertFile certFile := base.Cfg.CertFile
keyFile := base.Cfg.CertKey keyFile := base.Cfg.CertKey
logger := log.New(os.Stdout, "[SERVER]", log.Lshortfile|log.Ldate)
// 设置tls信息 // 设置tls信息
tlsConfig := &tls.Config{ tlsConfig := &tls.Config{
NextProtos: []string{"http/1.1"}, NextProtos: []string{"http/1.1"},
MinVersion: tls.VersionTLS12, MinVersion: tls.VersionTLS12,
InsecureSkipVerify: true,
GetCertificate: GetCertificate,
} }
srv := &http.Server{ srv := &http.Server{
Addr: addr, Addr: addr,
Handler: initRoute(), Handler: initRoute(),
TLSConfig: tlsConfig, TLSConfig: tlsConfig,
ErrorLog: logger, ErrorLog: base.GetBaseLog(),
} }
var ln net.Listener var ln net.Listener
@@ -57,9 +62,9 @@ func initRoute() http.Handler {
r.HandleFunc("/", LinkAuth).Methods(http.MethodPost) r.HandleFunc("/", LinkAuth).Methods(http.MethodPost)
r.HandleFunc("/CSCOSSLC/tunnel", LinkTunnel).Methods(http.MethodConnect) r.HandleFunc("/CSCOSSLC/tunnel", LinkTunnel).Methods(http.MethodConnect)
r.HandleFunc("/otp_qr", LinkOtpQr).Methods(http.MethodGet) r.HandleFunc("/otp_qr", LinkOtpQr).Methods(http.MethodGet)
r.PathPrefix("/down_files/").Handler( r.PathPrefix("/files/").Handler(
http.StripPrefix("/down_files/", http.StripPrefix("/files/",
http.FileServer(http.Dir(base.Cfg.DownFilesPath)), http.FileServer(http.Dir(base.Cfg.FilesPath)),
), ),
) )
r.NotFoundHandler = http.HandlerFunc(notFound) r.NotFoundHandler = http.HandlerFunc(notFound)

View File

@@ -21,5 +21,5 @@ func Start() {
} }
func Stop() { func Stop() {
dbdata.Stop() _ = dbdata.Stop()
} }

View File

@@ -1,4 +1,7 @@
// AnyLink 是一个企业级远程办公vpn软件可以支持多人同时在线使用。 // AnyLink 是一个企业级远程办公vpn软件可以支持多人同时在线使用。
// +build linux
package main package main
import ( import (

View File

@@ -48,7 +48,7 @@ func tableLookup(ip net.IP) *Addr {
} }
// 判断老化过期时间 // 判断老化过期时间
tsub := time.Now().Sub(addr.disTime) tsub := time.Since(addr.disTime)
switch addr.Type { switch addr.Type {
case TypeNormal: case TypeNormal:
if tsub > StaleTimeNormal { if tsub > StaleTimeNormal {

View File

@@ -48,7 +48,7 @@ func doPing(ip string) error {
return err return err
} }
conn.SetReadDeadline(time.Now().Add(time.Second * 2)) _ = conn.SetReadDeadline(time.Now().Add(time.Second * 2))
for { for {
buf := make([]byte, 512) buf := make([]byte, 512)

View File

@@ -198,8 +198,10 @@ func (p *Conn) checkPrefixOnce() {
func (p *Conn) checkPrefix() error { func (p *Conn) checkPrefix() error {
if p.proxyHeaderTimeout != 0 { if p.proxyHeaderTimeout != 0 {
readDeadLine := time.Now().Add(p.proxyHeaderTimeout) readDeadLine := time.Now().Add(p.proxyHeaderTimeout)
p.conn.SetReadDeadline(readDeadLine) _ = p.conn.SetReadDeadline(readDeadLine)
defer p.conn.SetReadDeadline(time.Time{}) defer func() {
_ = p.conn.SetReadDeadline(time.Time{})
}()
} }
// Incrementally check each byte of the prefix // Incrementally check each byte of the prefix

View File

@@ -45,8 +45,6 @@ func CopyStruct(a interface{}, b interface{}, fields ...string) (err error) {
// a中有同名的字段并且类型一致才复制 // a中有同名的字段并且类型一致才复制
if f.IsValid() && f.Kind() == bValue.Kind() { if f.IsValid() && f.Kind() == bValue.Kind() {
f.Set(bValue) f.Set(bValue)
} else {
// fmt.Printf("no such field or different kind, fieldName: %s\n", name)
} }
} }
return return

View File

@@ -19,7 +19,8 @@ type ipPoolConfig struct {
mux sync.Mutex mux sync.Mutex
// 计算动态ip // 计算动态ip
Ipv4Gateway net.IP Ipv4Gateway net.IP
Ipv4IPNet net.IPNet Ipv4Mask net.IP
Ipv4IPNet *net.IPNet
IpLongMin uint32 IpLongMin uint32
IpLongMax uint32 IpLongMax uint32
} }
@@ -27,11 +28,12 @@ type ipPoolConfig struct {
func initIpPool() { func initIpPool() {
// 地址处理 // 地址处理
// ip地址 _, ipNet, err := net.ParseCIDR(base.Cfg.Ipv4CIDR)
ip := net.ParseIP(base.Cfg.Ipv4Network) if err != nil {
// 子网掩码 panic(err)
maskIp := net.ParseIP(base.Cfg.Ipv4Netmask).To4() }
IpPool.Ipv4IPNet = net.IPNet{IP: ip, Mask: net.IPMask(maskIp)} IpPool.Ipv4IPNet = ipNet
IpPool.Ipv4Mask = net.IP(ipNet.Mask)
IpPool.Ipv4Gateway = net.ParseIP(base.Cfg.Ipv4Gateway) IpPool.Ipv4Gateway = net.ParseIP(base.Cfg.Ipv4Gateway)
// 网络地址零值 // 网络地址零值
@@ -74,11 +76,11 @@ func AcquireIp(username, macAddr string) net.IP {
mi.Username = username mi.Username = username
mi.LastLogin = tNow mi.LastLogin = tNow
// 回写db数据 // 回写db数据
dbdata.Save(mi) _ = dbdata.Save(mi)
ipActive[ipStr] = true ipActive[ipStr] = true
return ip return ip
} else { } else {
dbdata.Del(mi) _ = dbdata.Del(mi)
} }
} }
@@ -92,7 +94,7 @@ func AcquireIp(username, macAddr string) net.IP {
if err != nil && dbdata.CheckErrNotFound(err) { if err != nil && dbdata.CheckErrNotFound(err) {
// 该ip没有被使用 // 该ip没有被使用
mi := &dbdata.IpMap{IpAddr: ip, MacAddr: macAddr, Username: username, LastLogin: tNow} mi := &dbdata.IpMap{IpAddr: ip, MacAddr: macAddr, Username: username, LastLogin: tNow}
dbdata.Save(mi) _ = dbdata.Save(mi)
ipActive[ipStr] = true ipActive[ipStr] = true
return ip return ip
} }
@@ -121,10 +123,10 @@ func AcquireIp(username, macAddr string) net.IP {
// 已经超过租期 // 已经超过租期
if tNow.Sub(v.LastLogin) > time.Duration(base.Cfg.IpLease)*time.Second { if tNow.Sub(v.LastLogin) > time.Duration(base.Cfg.IpLease)*time.Second {
dbdata.Del(v) _ = dbdata.Del(v)
mi := &dbdata.IpMap{IpAddr: ip, MacAddr: macAddr, Username: username, LastLogin: tNow} mi := &dbdata.IpMap{IpAddr: ip, MacAddr: macAddr, Username: username, LastLogin: tNow}
// 重写db数据 // 重写db数据
dbdata.Save(mi) _ = dbdata.Save(mi)
ipActive[ipStr] = true ipActive[ipStr] = true
return ip return ip
} }
@@ -145,7 +147,7 @@ func AcquireIp(username, macAddr string) net.IP {
ipStr := ip.String() ipStr := ip.String()
mi = &dbdata.IpMap{IpAddr: ip, MacAddr: macAddr, Username: username, LastLogin: tNow} mi = &dbdata.IpMap{IpAddr: ip, MacAddr: macAddr, Username: username, LastLogin: tNow}
// 回写db数据 // 回写db数据
dbdata.Save(mi) _ = dbdata.Save(mi)
ipActive[ipStr] = true ipActive[ipStr] = true
return ip return ip
} }
@@ -160,6 +162,6 @@ func ReleaseIp(ip net.IP, macAddr string) {
err := dbdata.One("IpAddr", ip, mi) err := dbdata.One("IpAddr", ip, mi)
if err == nil { if err == nil {
mi.LastLogin = time.Now() mi.LastLogin = time.Now()
dbdata.Save(mi) _ = dbdata.Save(mi)
} }
} }

View File

@@ -12,45 +12,53 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func preIpData() { func preData(tmpDir string) {
base.Cfg.Ipv4Network = "192.168.3.0" base.Test()
base.Cfg.Ipv4Netmask = "255.255.255.0" tmpDb := path.Join(tmpDir, "test.db")
base.Cfg.Ipv4Pool = []string{"192.168.3.1", "192.168.3.199"}
tmpDb := path.Join(os.TempDir(), "anylink_test.db")
base.Cfg.DbFile = tmpDb base.Cfg.DbFile = tmpDb
base.Cfg.Ipv4CIDR = "192.168.3.0/24"
base.Cfg.Ipv4Pool = []string{"192.168.3.1", "192.168.3.199"}
base.Cfg.MaxClient = 100
base.Cfg.MaxUserClient = 3
dbdata.Start() dbdata.Start()
group := dbdata.Group{
Name: "group1",
Bandwidth: 1000,
}
_ = dbdata.Save(&group)
initIpPool()
} }
func closeIpdata() { func cleardata(tmpDir string) {
dbdata.Stop() _ = dbdata.Stop()
tmpDb := path.Join(os.TempDir(), "anylink_test.db") tmpDb := path.Join(tmpDir, "test.db")
os.Remove(tmpDb) os.Remove(tmpDb)
} }
func TestIpPool(t *testing.T) { func TestIpPool(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
preIpData() tmp := t.TempDir()
defer closeIpdata() preData(tmp)
defer cleardata(tmp)
initIpPool()
var ip net.IP var ip net.IP
for i := 1; i <= 100; i++ { for i := 1; i <= 100; i++ {
ip = AcquireIp("user", fmt.Sprintf("mac-%d", i)) _ = AcquireIp("user", fmt.Sprintf("mac-%d", i))
} }
ip = AcquireIp("user", fmt.Sprintf("mac-new")) ip = AcquireIp("user", "mac-new")
assert.True(net.IPv4(192, 168, 3, 101).Equal(ip)) assert.True(net.IPv4(192, 168, 3, 101).Equal(ip))
for i := 102; i <= 199; i++ { for i := 102; i <= 199; i++ {
ip = AcquireIp("user", fmt.Sprintf("mac-%d", i)) ip = AcquireIp("user", fmt.Sprintf("mac-%d", i))
} }
assert.True(net.IPv4(192, 168, 3, 199).Equal(ip)) assert.True(net.IPv4(192, 168, 3, 199).Equal(ip))
ip = AcquireIp("user", fmt.Sprintf("mac-nil")) ip = AcquireIp("user", "mac-nil")
assert.Nil(ip) assert.Nil(ip)
ReleaseIp(net.IPv4(192, 168, 3, 88), "mac-88") ReleaseIp(net.IPv4(192, 168, 3, 88), "mac-88")
ReleaseIp(net.IPv4(192, 168, 3, 77), "mac-77") ReleaseIp(net.IPv4(192, 168, 3, 77), "mac-77")
// 最早过期的ip // 从头循环获取可用ip
ip = AcquireIp("user", "mac-release-new") ip = AcquireIp("user", "mac-release-new")
assert.True(net.IPv4(192, 168, 3, 88).Equal(ip)) assert.True(net.IPv4(192, 168, 3, 77).Equal(ip))
} }

View File

@@ -43,10 +43,11 @@ func TestLimitClient(t *testing.T) {
func TestLimitWait(t *testing.T) { func TestLimitWait(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
limit := NewLimitRater(1, 2) limit := NewLimitRater(1, 2)
limit.Wait(2)
start := time.Now()
err := limit.Wait(2) err := limit.Wait(2)
assert.Nil(err) assert.Nil(err)
start := time.Now()
err = limit.Wait(2)
assert.Nil(err)
err = limit.Wait(1) err = limit.Wait(1)
assert.Nil(err) assert.Nil(err)
end := time.Now() end := time.Now()

View File

@@ -34,10 +34,7 @@ func (o Onlines) Len() int {
} }
func (o Onlines) Less(i, j int) bool { func (o Onlines) Less(i, j int) bool {
if bytes.Compare(o[i].Ip, o[j].Ip) < 0 { return bytes.Compare(o[i].Ip, o[j].Ip) < 0
return true
}
return false
} }
func (o Onlines) Swap(i, j int) { func (o Onlines) Swap(i, j int) {

View File

@@ -3,7 +3,6 @@ package sessdata
import ( import (
"crypto/md5" "crypto/md5"
"fmt" "fmt"
"log"
"math/rand" "math/rand"
"net" "net"
"strconv" "strconv"
@@ -78,13 +77,13 @@ func checkSession() {
return return
} }
timeout := time.Duration(base.Cfg.SessionTimeout) * time.Second timeout := time.Duration(base.Cfg.SessionTimeout) * time.Second
tick := time.Tick(time.Second * 60) tick := time.NewTicker(time.Second * 60)
for range tick { for range tick.C {
sessMux.Lock() sessMux.Lock()
t := time.Now() t := time.Now()
for k, v := range sessions { for k, v := range sessions {
v.mux.Lock() v.mux.Lock()
if v.IsActive != true { if !v.IsActive {
if t.Sub(v.LastLogin) > timeout { if t.Sub(v.LastLogin) > timeout {
delete(sessions, k) delete(sessions, k)
} }
@@ -133,12 +132,12 @@ func (s *Session) NewConn() *ConnSession {
macAddr := s.MacAddr macAddr := s.MacAddr
username := s.Username username := s.Username
s.mux.Unlock() s.mux.Unlock()
if active == true { if active {
s.CSess.Close() s.CSess.Close()
} }
limit := LimitClient(username, false) limit := LimitClient(username, false)
if limit == false { if !limit {
return nil return nil
} }
// 获取客户端mac地址 // 获取客户端mac地址
@@ -191,7 +190,7 @@ func (s *Session) NewConn() *ConnSession {
func (cs *ConnSession) Close() { func (cs *ConnSession) Close() {
cs.closeOnce.Do(func() { cs.closeOnce.Do(func() {
log.Println("closeOnce:", cs.IpAddr) base.Info("closeOnce:", cs.IpAddr)
cs.Sess.mux.Lock() cs.Sess.mux.Lock()
defer cs.Sess.mux.Unlock() defer cs.Sess.mux.Unlock()
@@ -208,8 +207,10 @@ func (cs *ConnSession) Close() {
const BandwidthPeriodSec = 2 // 流量速率统计周期(秒) const BandwidthPeriodSec = 2 // 流量速率统计周期(秒)
func (cs *ConnSession) ratePeriod() { func (cs *ConnSession) ratePeriod() {
tick := time.Tick(time.Second * BandwidthPeriodSec) tick := time.NewTicker(time.Second * BandwidthPeriodSec)
for range tick { defer tick.Stop()
for range tick.C {
select { select {
case <-cs.CloseChan: case <-cs.CloseChan:
return return
@@ -296,6 +297,17 @@ func CloseSess(token string) {
sess.CSess.Close() sess.CSess.Close()
} }
func CloseCSess(token string) {
sessMux.Lock()
defer sessMux.Unlock()
sess, ok := sessions[token]
if !ok {
return
}
sess.CSess.Close()
}
func DelSessByStoken(stoken string) { func DelSessByStoken(stoken string) {
stoken = strings.TrimSpace(stoken) stoken = strings.TrimSpace(stoken)
sarr := strings.Split(stoken, "@") sarr := strings.Split(stoken, "@")

View File

@@ -0,0 +1,38 @@
package sessdata
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewSession(t *testing.T) {
ast := assert.New(t)
sessions = make(map[string]*Session)
sess := NewSession("")
token := sess.Token
v, ok := sessions[token]
ast.True(ok)
ast.Equal(sess, v)
}
func TestConnSession(t *testing.T) {
ast := assert.New(t)
tmp := t.TempDir()
preData(tmp)
defer cleardata(tmp)
sess := NewSession("")
sess.Group = "group1"
sess.MacAddr = "00:15:5d:50:14:43"
cSess := sess.NewConn()
err := cSess.RateLimit(100, true)
ast.Nil(err)
ast.Equal(cSess.BandwidthUp, uint32(100))
err = cSess.RateLimit(200, false)
ast.Nil(err)
ast.Equal(cSess.BandwidthDown, uint32(200))
cSess.Close()
}

View File

@@ -1,30 +0,0 @@
package sessdata
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewSession(t *testing.T) {
assert := assert.New(t)
sessions = make(map[string]*Session)
sess := NewSession("")
token := sess.Token
v, ok := sessions[token]
assert.True(ok)
assert.Equal(sess, v)
}
func TestConnSession(t *testing.T) {
assert := assert.New(t)
preIpData()
defer closeIpdata()
sess := NewSession("")
cSess := sess.NewConn()
cSess.RateLimit(100, true)
assert.Equal(cSess.BandwidthUp, uint32(100))
cSess.RateLimit(200, false)
assert.Equal(cSess.BandwidthDown, uint32(200))
cSess.Close()
}

13
systemd/anylink.service Normal file
View File

@@ -0,0 +1,13 @@
[Unit]
Description=VPN Server Service
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory= /usr/local/anylink-deploy
Restart=on-failure
RestartSec=5s
ExecStart=/usr/local/anylink-deploy/anylink -conf="conf/server.toml"
[Install]
WantedBy=multi-user.target

3
web/.browserslistrc Normal file
View File

@@ -0,0 +1,3 @@
> 1%
last 2 versions
not dead

17
web/.eslintrc.js Normal file
View File

@@ -0,0 +1,17 @@
module.exports = {
root: true,
env: {
node: true
},
'extends': [
'plugin:vue/essential',
'eslint:recommended'
],
parserOptions: {
parser: 'babel-eslint'
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
}
}

24
web/.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
.DS_Store
node_modules
/dist
/ui
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

21
web/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 bjdgyc
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.

30
web/README.md Normal file
View File

@@ -0,0 +1,30 @@
# anylink-web
## Repo
> github: https://github.com/bjdgyc/anylink-web
> gitee: https://gitee.com/bjdgyc/anylink-web
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Lints and fixes files
```
npm run lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

5
web/babel.config.js Normal file
View File

@@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

26204
web/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

29
web/package.json Normal file
View File

@@ -0,0 +1,29 @@
{
"name": "anylink-web",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"axios": "^0.20.0",
"core-js": "^3.6.5",
"echarts": "^4.9.0",
"element-ui": "^2.4.5",
"vue": "^2.6.11",
"vue-count-to": "^1.0.13",
"vue-router": "^3.4.6"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2",
"vue-cli-plugin-element": "~1.0.1",
"vue-template-compiler": "^2.6.11"
}
}

BIN
web/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

17
web/public/index.html Normal file
View File

@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>AnyLink</title>
</head>
<body>
<noscript>
<strong>We're sorry but AnyLink doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

84
web/src/App.vue Normal file
View File

@@ -0,0 +1,84 @@
<template>
<div id="app">
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'app',
components: {},
created() {
const token = sessionStorage.getItem('jwtToken');
console.log("App created", token)
if (token) {
this.$root.isLogin = true
}
},
mounted() {
console.log("App mounted")
},
data() {
return {}
},
computed: {
currentComponent: function () {
var isLogin = this.$root.isLogin
console.log("App isLogin", isLogin)
if (isLogin) {
return "layout";
}
return "login";
},
},
}
</script>
<style>
html, body {
height: 100%;
margin: 0;
}
#app {
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
/*color: #2c3e50;*/
/*border: 1px solid red;*/
height: 100%;
/*width:100%;*/
/*box-sizing: border-box;*/
/*padding: 4px;*/
}
.hide {
display: none;
}
/*space vertical*/
.sh-10 {
height: 10px;
}
.sh-20 {
height: 20px;
}
/*space horizontal*/
.sw-10 {
height: 1px;
width: 10px;
}
.sw-20 {
height: 1px;
width: 20px;
}
.m-left-10 {
margin-left: 10px;
}
</style>

BIN
web/src/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -0,0 +1,37 @@
<template>
<div>
<div class="monitor">
<div class="monitor-left">{{ left }}</div>
<div class="monitor-right">{{ right }}</div>
</div>
<el-divider v-if="divider"></el-divider>
</div>
</template>
<script>
export default {
name: "Cell",
props: {
left: {},
right: {},
divider: {type: Boolean}
},
}
</script>
<style scoped>
.monitor {
display: flex;
justify-content: space-between;
align-items: center;
}
.monitor-left {
font-size: 14px;
}
.monitor-right {
font-size: 12px;
color: #909399;
}
</style>

View File

@@ -0,0 +1,82 @@
<template>
<div id="line-chart" :style="{height:height,width:width}"/>
</template>
<script>
import echarts from 'echarts'
export default {
name: 'LineChart',
props: {
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '350px'
},
// title,xAxis,series
chartData: {
type: Object,
required: true
}
},
data() {
return {}
},
mounted() {
this.initChart()
},
beforeDestroy() {
},
methods: {
initChart() {
let chart = echarts.init(this.$el)
const option = {
title: {
text: this.chartData.title || '折线图'
},
tooltip: {
trigger: 'axis'
},
legend: {},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
// toolbox: {
// feature: {
// saveAsImage: {}
// }
// },
xAxis: {
type: 'category',
boundaryGap: false,
data: this.chartData.xname
},
yAxis: {
type: 'value'
},
series: [],
};
let xdata = this.chartData.xdata
for (let key in xdata) {
// window.console.log(key);
let a = {
name: key,
type: 'line',
data: xdata[key]
};
option.series.push(a)
}
chart.setOption(option)
},
}
}
</script>

63
web/src/layout/Layout.vue Normal file
View File

@@ -0,0 +1,63 @@
<template>
<el-container style="height: 100%;">
<!--侧边栏菜单-->
<el-aside :width="is_active?'200':'64'">
<LayoutAside :is_active="is_active" :route_path="route_path"/>
</el-aside>
<el-container>
<!--正文头部内容-->
<el-header>
<!--监听子组件的变量事件-->
<LayoutHeader :is_active.sync="is_active" :route_name="route_name"/>
</el-header>
<!--正文内容-->
<!--style="background-color: rgb(240, 242, 245);"-->
<el-main style="background-color: #fbfbfb">
<!-- 对应的组件内容渲染到router-view中 -->
<!--子组件上报route信息-->
<router-view :route_path.sync="route_path" :route_name.sync="route_name"></router-view>
</el-main>
</el-container>
</el-container>
</template>
<script>
import LayoutAside from "@/layout/LayoutAside";
import LayoutHeader from "@/layout/LayoutHeader";
export default {
name: "Layout",
components: {LayoutHeader, LayoutAside},
data() {
return {
is_active: true,
route_path: '/index',
route_name: ['首页'],
}
},
watch: {
route_path: function (val) {
// var w = document.getElementById('layout-menu').clientWidth;
window.console.log('is_active', val)
},
},
created() {
window.console.log('layout-route', this.$route)
},
}
</script>
<style>
.el-header {
background-color: #fff;
/*box-shadow: 0 1px 4px rgba(0, 21, 41, .08);*/
color: #333;
line-height: 60px;
/*width: 100%;*/
border-bottom: 1px solid #d8dce5;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);
}
</style>

View File

@@ -0,0 +1,78 @@
<template>
<!--background-color="#304156"-->
<!--text-color="#bfcbd9"-->
<!--active-text-color="#409EFF"-->
<!--:unique-opened="false"-->
<!--<div class="layout-aside" :style="aside_style">-->
<el-menu :collapse="!is_active"
:default-active="route_path"
:style="is_active?'width:200px':''"
router
class="layout-menu"
:collapse-transition="false"
background-color="#545c64"
text-color="#fff"
active-text-color="#ffd04b"
>
<el-menu-item index="/admin/home">
<i class="el-icon-s-home"></i>
<span slot="title">首页</span>
</el-menu-item>
<el-submenu index="1">
<template slot="title">
<i class="el-icon-menu"></i>
<span slot="title">基础信息</span>
</template>
<el-menu-item index="/admin/set/system">系统信息</el-menu-item>
<el-menu-item index="/admin/set/soft">软件配置</el-menu-item>
<el-menu-item index="/admin/set/other">其他设置</el-menu-item>
</el-submenu>
<el-submenu index="2">
<template slot="title">
<i class="el-icon-s-custom"></i>
<span slot="title">用户信息</span>
</template>
<el-menu-item index="/admin/user/list">用户列表</el-menu-item>
<el-menu-item index="/admin/user/online">在线用户</el-menu-item>
<el-menu-item index="/admin/user/ip_map">IP映射</el-menu-item>
</el-submenu>
<el-submenu index="3">
<template slot="title">
<i class="el-icon-s-order"></i>
<span slot="title">用户组信息</span>
</template>
<el-menu-item index="/admin/group/list">用户组列表</el-menu-item>
</el-submenu>
</el-menu>
</template>
<script>
export default {
name: "LayoutAside",
data() {
return {}
},
props: ['is_active', 'route_path'],
mounted() {
}
}
</script>
<style scoped>
.layout-menu {
height: 100%;
}
</style>

View File

@@ -0,0 +1,82 @@
<template>
<div class="layout-header">
<div>
<i @click="toggleClick" :class="is_active ? 'el-icon-s-fold' : 'el-icon-s-unfold'" class="toggle-icon"
style="font-size: 26px;"></i>
<el-breadcrumb separator="/" class="app-breadcrumb">
<el-breadcrumb-item v-for="(item, index) in route_name" :key="index">{{ item }}</el-breadcrumb-item>
</el-breadcrumb>
</div>
<el-dropdown trigger="click" @command="handleCommand">
<i class="el-icon-setting" style="margin-right: 15px"></i>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="logout">退出</el-dropdown-item>
</el-dropdown-menu>
<span style="font-size: 12px;">{{ admin_user }}</span>
</el-dropdown>
</div>
</template>
<script>
import {getUser, removeToken} from "@/plugins/token";
export default {
name: "Layoutheader",
props: ['route_name'],
data() {
return {
is_active: true
}
},
computed: {
admin_user() {
return getUser();
},
},
methods: {
// 菜单栏开关按钮
toggleClick() {
this.is_active = !this.is_active
// 触发事件,抛出到上层
this.$emit('update:is_active', this.is_active)
},
handleCommand() {
console.log("handleCommand")
// 退出 删除登录信息
removeToken()
this.$router.push("/login");
},
}
}
</script>
<style scoped>
.layout-header {
display: flex;
justify-content: space-between;
align-items: center
}
.toggle-icon {
cursor: pointer;
transition: background .3s;
-webkit-tap-highlight-color: transparent;
}
.toggle-icon:hover {
background: rgba(0, 0, 0, .025)
}
.app-breadcrumb {
display: inline-block;
font-size: 14px;
/*line-height: 20;*/
margin-left: 20px;
}
</style>

22
web/src/main.js Normal file
View File

@@ -0,0 +1,22 @@
import Vue from 'vue'
import App from './App.vue'
import './plugins/element'
import "./plugins/mixin";
import request from './plugins/request'
import router from "./plugins/router";
//TODO
Vue.config.productionTip = false
const vm = new Vue({
data: {
// 判断是否登录
isLogin: false,
},
router,
render: h => h(App),
}).$mount('#app')
request(vm)

160
web/src/pages/Home.vue Normal file
View File

@@ -0,0 +1,160 @@
<template>
<div class="home">
<el-row :gutter="40" class="panel-group">
<el-col :span="6" class="card-panel-col">
<div class="card-panel">
<i class="el-icon-user-solid" style="font-size:50px;color: #f4516c;"></i>
<div class="card-panel-description">
<div class="card-panel-text">在线数</div>
<countTo :startVal='0' :endVal='counts.online' :duration='2000' class="panel-num"></countTo>
</div>
</div>
</el-col>
<el-col :span="6" class="card-panel-col">
<div class="card-panel">
<i class="el-icon-user-solid" style="font-size:50px;color: #36a3f7"></i>
<div class="card-panel-description">
<div class="card-panel-text">用户数</div>
<countTo :startVal='0' :endVal='counts.user' :duration='2000' class="panel-num"></countTo>
</div>
</div>
</el-col>
<el-col :span="6" class="card-panel-col">
<div class="card-panel">
<i class="el-icon-wallet" style="font-size:50px;color:#34bfa3"></i>
<div class="card-panel-description">
<div class="card-panel-text">用户组数</div>
<countTo :startVal='0' :endVal='counts.group' :duration='2000' class="panel-num"></countTo>
</div>
</div>
</el-col>
<el-col :span="6" class="card-panel-col">
<div class="card-panel">
<i class="el-icon-s-order" style="font-size:50px;color:#40c9c6"></i>
<div class="card-panel-description">
<div class="card-panel-text">IP映射数</div>
<countTo :startVal='0' :endVal='counts.ip_map' :duration='2000' class="panel-num"></countTo>
</div>
</div>
</el-col>
</el-row>
<el-row class="line-chart">
<LineChart :chart-data="lineChartUser"/>
</el-row>
<el-row class="line-chart">
<LineChart :chart-data="lineChartOrder"/>
</el-row>
</div>
</template>
<script>
import countTo from 'vue-count-to';
import LineChart from "@/components/LineChart";
import axios from "axios";
const lineChartUser = {
title: '每日在线统计',
xname: ['2019-12-13', '2019-12-14', '2019-12-15', '2019-12-16', '2019-12-17', '2019-12-18', '2019-12-19'],
xdata: {
'test1': [10, 120, 11, 134, 105, 10, 15],
'test2': [10, 82, 91, 14, 162, 10, 15]
}
}
const lineChartOrder = {
title: '每日流量统计',
xname: ['2019-12-13', '2019-12-14', '2019-12-15', '2019-12-16', '2019-12-17', '2019-12-18', '2019-12-19'],
xdata: {
'test1': [100, 120, 161, 134, 105, 160, 165],
'test2': [120, 82, 91, 154, 162, 140, 145]
}
}
export default {
name: "Home",
components: {
LineChart,
countTo,
},
data() {
return {
counts: {
online: 0,
user: 0,
group: 0,
ip_map: 0,
},
lineChartUser: lineChartUser,
lineChartOrder: lineChartOrder,
}
},
created() {
this.$emit('update:route_path', this.$route.path)
this.$emit('update:route_name', ['首页'])
},
mounted() {
this.getData()
},
methods: {
getData() {
axios.get('/set/home').then(resp => {
var data = resp.data.data
console.log(data);
this.counts = data.counts
}).catch(error => {
this.$message.error('哦,请求出错');
console.log(error);
});
},
},
}
</script>
<style scoped>
.card-panel {
display: flex;
justify-content: space-around;
border: 1px solid red;
padding: 30px 0;
color: #666;
background: #fff;
/*box-shadow: 4px 4px 40px rgba(0, 0, 0, .05);*/
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);
border-color: rgba(0, 0, 0, .05);
}
.card-panel-description {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
}
.card-panel-text {
line-height: 18px;
color: rgba(0, 0, 0, .45);
font-size: 16px;
}
.panel-num {
font-size: 20px;
font-weight: 700;
}
.line-chart {
background: #fff;
padding: 0 16px;
margin-top: 40px;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);
border-color: rgba(0, 0, 0, .05);
}
</style>

118
web/src/pages/Login.vue Normal file
View File

@@ -0,0 +1,118 @@
<template>
<div class="login">
<el-card style="width: 550px;">
<div class="issuer">AnyLink SSL VPN管理后台</div>
<el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm" label-width="100px" class="ruleForm">
<el-form-item label="管理用户名" prop="admin_user">
<el-input v-model="ruleForm.admin_user"></el-input>
</el-form-item>
<el-form-item label="管理密码" prop="admin_pass">
<el-input type="password" v-model="ruleForm.admin_pass" autocomplete="off"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="isLoading" @click="submitForm('ruleForm')">登录</el-button>
<el-button @click="resetForm('ruleForm')">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</template>
<script>
import axios from "axios";
import qs from "qs";
import {setToken, setUser} from "@/plugins/token";
export default {
name: "Login",
mounted() {
// 进入login删除登录信息
console.log("login created")
//绑定事件
window.addEventListener('keydown', this.keyDown);
},
destroyed(){
window.removeEventListener('keydown',this.keyDown,false);
},
data() {
return {
ruleForm: {},
rules: {
admin_user: [
{required: true, message: '请输入用户名', trigger: 'blur'},
{max: 50, message: '长度小于 50 个字符', trigger: 'blur'}
],
admin_pass: [
{required: true, message: '请输入密码', trigger: 'blur'},
{min: 6, message: '长度大于 6 个字符', trigger: 'blur'}
],
},
}
},
methods: {
keyDown(e) {
//如果是回车则执行登录方法
if (e.keyCode === 13) {
this.submitForm('ruleForm');
}
},
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (!valid) {
console.log('error submit!!');
return false;
}
this.isLoading = true
// alert('submit!');
axios.post('/base/login', qs.stringify(this.ruleForm)).then(resp => {
var rdata = resp.data
if (rdata.code === 0) {
this.$message.success(rdata.msg);
setToken(rdata.data.token)
setUser(rdata.data.admin_user)
this.$router.push("/home");
} else {
this.$message.error(rdata.msg);
}
console.log(rdata);
}).catch(error => {
this.$message.error('哦,请求出错');
console.log(error);
}).finally(() => {
this.isLoading = false
}
);
});
},
resetForm(formName) {
this.$refs[formName].resetFields();
},
},
}
</script>
<style scoped>
.login {
/*border: 1px solid red;*/
height: 100%;
/*margin: 0 auto;*/
text-align: center;
display: flex;
justify-content: center;
align-items: center;
}
.issuer {
font-size: 26px;
font-weight: bold;
margin-bottom: 50px;
}
</style>

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