diff --git a/anylink/Dockerfile b/anylink/Dockerfile index 34cfe13..5bbaa7d 100644 --- a/anylink/Dockerfile +++ b/anylink/Dockerfile @@ -1,6 +1,6 @@ # web FROM node:16.17.1-alpine3.15 as builder_node -ENV VERSION 0.9.1 +ENV VERSION 0.9.3 WORKDIR /web COPY ./web /web RUN yarn install \ @@ -8,7 +8,7 @@ RUN yarn install \ && ls /web/ui # server -FROM golang:1.18-alpine as builder_golang +FROM golang:1.19-alpine as builder_golang #TODO 本地打包时使用镜像 ENV GOPROXY=https://goproxy.io ENV GOOS=linux @@ -26,13 +26,13 @@ RUN cd /anylink/server;go mod tidy;go build -o anylink -ldflags "-X main.CommitI FROM alpine LABEL maintainer="github.com/bjdgyc" -ENV IPV4_CIDR="192.168.10.0/24" +#ENV IPV4_CIDR="192.168.10.0/24" WORKDIR /app COPY --from=builder_golang /anylink/server/anylink /app/ COPY docker_entrypoint.sh /app/ -COPY ./server/bridge-init.sh /app/ +#COPY ./server/bridge-init.sh /app/ COPY ./server/conf /app/conf COPY ./LICENSE /app/LICENSE diff --git a/anylink/README.md b/anylink/README.md index 2f00b3f..0b04917 100644 --- a/anylink/README.md +++ b/anylink/README.md @@ -7,17 +7,6 @@ Docker [stilleshan/anylink](https://hub.docker.com/r/stilleshan/anylink) ## 简介 基于 [bjdgyc/anylink](https://github.com/bjdgyc/anylink) 项目的 docker 镜像. -## 更新 -- **2022-11-10** 更新`0.9.1-beta1`版 docker 镜像. -- **2022-07-04** 更新`0.8.1`版 docker 镜像. -- **2022-04-07** 更新`0.7.4`版 docker 镜像. -- **2022-02-16** 更新`0.7.3`版 docker 镜像. -- **2021-12-31** 更新`0.7.2`版 docker 镜像. -- **2021-12-29** 更新`0.7.1`版 docker 镜像. -- **2021-08-26** 更新`0.6.2`版 docker 镜像. -- **2021-08-02** 更新`0.5.1`版 docker 镜像. -- **2021-07-05** 更新`0.4.2`版 docker 镜像. -- **2021-06-09** 更新`0.3.3`版 docker 镜像,新增同时支持 X86 和 ARM 架构. ## 部署 ### docker diff --git a/anylink/build.sh b/anylink/build.sh index 59c1456..e8d2ce3 100644 --- a/anylink/build.sh +++ b/anylink/build.sh @@ -20,9 +20,10 @@ cd $cpath/web #npm install #npm run build -yarn install +yarn install --registry=https://registry.npmmirror.com yarn run build + RETVAL $? echo "编译二进制文件" @@ -43,7 +44,7 @@ rm -rf $deploy ${deploy}.tar.gz mkdir $deploy cp -r server/anylink $deploy -cp -r server/bridge-init.sh $deploy +#cp -r server/bridge-init.sh $deploy cp -r server/conf $deploy cp -r systemd $deploy diff --git a/anylink/build_docker.sh b/anylink/build_docker.sh index fad70ff..ec94d0f 100644 --- a/anylink/build_docker.sh +++ b/anylink/build_docker.sh @@ -5,7 +5,8 @@ echo $ver #docker login -u bjdgyc -docker build -t bjdgyc/anylink . +#docker build -t bjdgyc/anylink . +docker build -t bjdgyc/anylink -f docker/Dockerfile . docker tag bjdgyc/anylink:latest bjdgyc/anylink:$ver diff --git a/anylink/doc/README.md b/anylink/doc/README.md deleted file mode 100644 index 66c8c19..0000000 --- a/anylink/doc/README.md +++ /dev/null @@ -1,31 +0,0 @@ -## Donate - -> 如果您觉得 AnyLink 对你有帮助,欢迎给我们打赏,也是帮助 AnyLink 更好的发展。 - -

- -

- -## Donator - -> 感谢以下同学的打赏,AnyLink 有你更美好! - -| 昵称 | 主页 | -| -------------- | ---------------------------- | -| 代码 oo8 | | -| 甘磊 | https://github.com/ganlei333 | -| Oo@ | https://github.com/chooop | -| 虚极静笃 | | -| 请喝可乐 | | -| 加油加油 | | -| 李建 | | -| lanbin | | -| 乐在东途 | | -| 孤鸿 | | -| 刘国华 | | -| 改名好无聊 | | -| 全能互联网专家 | | - - - - diff --git a/anylink/doc/question.md b/anylink/doc/question.md deleted file mode 100644 index 9ff77f5..0000000 --- a/anylink/doc/question.md +++ /dev/null @@ -1,42 +0,0 @@ -# 常见问题 - -### anyconnect 客户端问题 -> 客户端请使用群共享文件的版本,其他版本没有测试过,不保证使用正常 -> -> 添加QQ群: 567510628 - -### OTP 动态码 -> 请使用手机安装 freeotp ,然后扫描otp二维码,生成的数字即是动态码 - -### 远程桌面连接 -> 本软件已经支持远程桌面里面连接anyconnect。 - -### 私有证书问题 -> anylink 默认不支持私有证书 -> -> 其他使用私有证书的问题,请自行解决 - -### dpd timeout 设置问题 -``` -#客户端失效检测时间(秒) dpd > keepalive -cstp_keepalive = 20 -cstp_dpd = 30 -mobile_keepalive = 40 -mobile_dpd = 50 -``` -> 以上dpd参数为客户端的超时检测时间, 如一段时间内,没有数据传输,防火墙会主动关闭连接 -> -> 如经常出现 timeout 的错误信息,应根据当前防火墙的设置,适当减小dpd数值 - -### 性能问题 -``` -内网环境测试数据 -虚拟服务器: centos7 4C8G -anylink: tun模式 tcp传输 -客户端文件下载速度:240Mb/s -客户端网卡下载速度:270Mb/s -服务端网卡上传速度:280Mb/s -``` -> 客户端tls加密协议、隧道header头都会占用一定带宽 - - diff --git a/anylink/doc/screenshot/group.jpg b/anylink/doc/screenshot/group.jpg deleted file mode 100644 index 1099a0b..0000000 Binary files a/anylink/doc/screenshot/group.jpg and /dev/null differ diff --git a/anylink/doc/screenshot/ip_map.jpg b/anylink/doc/screenshot/ip_map.jpg deleted file mode 100644 index 6db4ce6..0000000 Binary files a/anylink/doc/screenshot/ip_map.jpg and /dev/null differ diff --git a/anylink/doc/screenshot/jetbrains.png b/anylink/doc/screenshot/jetbrains.png deleted file mode 100644 index ccceb95..0000000 Binary files a/anylink/doc/screenshot/jetbrains.png and /dev/null differ diff --git a/anylink/doc/screenshot/online.jpg b/anylink/doc/screenshot/online.jpg deleted file mode 100644 index 684777b..0000000 Binary files a/anylink/doc/screenshot/online.jpg and /dev/null differ diff --git a/anylink/doc/screenshot/qq.png b/anylink/doc/screenshot/qq.png deleted file mode 100644 index 50e1fac..0000000 Binary files a/anylink/doc/screenshot/qq.png and /dev/null differ diff --git a/anylink/doc/screenshot/setting.jpg b/anylink/doc/screenshot/setting.jpg deleted file mode 100644 index 2e45a0f..0000000 Binary files a/anylink/doc/screenshot/setting.jpg and /dev/null differ diff --git a/anylink/doc/screenshot/system.jpg b/anylink/doc/screenshot/system.jpg deleted file mode 100644 index 70274f2..0000000 Binary files a/anylink/doc/screenshot/system.jpg and /dev/null differ diff --git a/anylink/doc/screenshot/users.jpg b/anylink/doc/screenshot/users.jpg deleted file mode 100644 index 2cd193c..0000000 Binary files a/anylink/doc/screenshot/users.jpg and /dev/null differ diff --git a/anylink/doc/screenshot/wxpay.png b/anylink/doc/screenshot/wxpay.png deleted file mode 100644 index f473173..0000000 Binary files a/anylink/doc/screenshot/wxpay.png and /dev/null differ diff --git a/anylink/doc/screenshot/wxpay2.png b/anylink/doc/screenshot/wxpay2.png deleted file mode 100644 index 7e8749d..0000000 Binary files a/anylink/doc/screenshot/wxpay2.png and /dev/null differ diff --git a/anylink/docker/Dockerfile b/anylink/docker/Dockerfile deleted file mode 100644 index b240e3c..0000000 --- a/anylink/docker/Dockerfile +++ /dev/null @@ -1,6 +0,0 @@ -FROM ubuntu:18.04 -WORKDIR / -COPY docker_entrypoint.sh docker_entrypoint.sh -RUN mkdir /anylink && apt update && apt install -y wget iptables tar iproute2 -ENTRYPOINT ["/docker_entrypoint.sh"] -#CMD ["/anylink/anylink","-conf=/anylink/conf/server.toml"] diff --git a/anylink/docker/docker_entrypoint.sh b/anylink/docker/docker_entrypoint.sh deleted file mode 100644 index 1ddfe54..0000000 --- a/anylink/docker/docker_entrypoint.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/sh -USER="admin" -MM=$(pwgen -1s) -CREATE_USER=1 -CONFIG_FILE='/app/conf/server.toml' - -if [ $CREATE_USER -eq 1 ]; then - if [ ! -e $CREATE_USER ]; then - MM=$(pwgen -1s) - touch $CREATE_USER - bash /app/generate-certs.sh - cd /app/conf/ && cp *.crt /usr/local/share/ca-certificates/ - update-ca-certificates --fresh - userpass=$(/app/anylink -passwd "${MM}"| cut -d : -f2) - echo "${userpass}" - jwttoken=$(/app/anylink -secret | cut -d : -f2) - echo "-- First container startup --user:${USER} pwd:${MM}" - sed -i "s/admin/${USER}/g" /app/server-example.toml - sed -i "s/123456/${MM}/g" /app/server-example.toml - sed -i "s#usertoken#${userpass}#g" /app/server-example.toml - sed -i "s/jwttoken/${jwttoken}/g" /app/server-example.toml - else - echo "-- Not first container startup --" - fi - -else - echo "user switch not create" - -fi - -if [ ! -f $CONFIG_FILE ]; then -echo "#####Generating configuration file#####" -cp /app/server-example.toml /app/conf/server.toml -else - echo "#####Configuration file already exists#####" -fi - -rtaddr=$(grep "cidr" /app/conf/server.toml |awk -F \" '{print $2}') -sysctl -w net.ipv4.ip_forward=1 -iptables -t nat -A POSTROUTING -s "${rtaddr}" -o eth0+ -j MASQUERADE -/app/anylink -conf="/app/conf/server.toml" diff --git a/anylink/docker/docker_entrypoint_fix.sh b/anylink/docker/docker_entrypoint_fix.sh deleted file mode 100644 index 7ddf805..0000000 --- a/anylink/docker/docker_entrypoint_fix.sh +++ /dev/null @@ -1,37 +0,0 @@ -#! /bin/bash -version=(`wget -qO- -t1 -T2 "https://api.github.com/repos/bjdgyc/anylink/releases/latest" | grep "tag_name" | head -n 1 | awk -F ":" '{print $2}' | sed 's/\"//g;s/,//g;s/ //g'`) -count=(`ls anylink | wc -w `) -wget https://github.com/bjdgyc/anylink/releases/download/${version}/anylink-deploy.tar.gz -tar xf anylink-deploy.tar.gz -rm -rf anylink-deploy.tar.gz -if [ ${count} -eq 0 ]; then - echo "init anylink" - mv anylink-deploy/* anylink/ -else - if [ ! -d "/anylink/log" ]; then - mv anylink-deploy/log anylink/ - fi - if [ ! -d "/anylink/conf" ]; then - mv anylink-deploy/conf anylink/ - fi - echo "update anylink" - rm -rf anylink/ui anylink/anylink anylink/files - mv anylink-deploy/ui anylink/ - mv anylink-deploy/anylink anylink/ - mv anylink-deploy/files anylink/ -fi -rm -rf anylink-deploy -sysctl -w net.ipv4.ip_forward=1 -if [[ ${mode} == pro ]];then - iptables -t nat -A POSTROUTING -s ${iproute} -o eth0 -j MASQUERADE - iptables -L -n -t nat - /anylink/anylink -conf=/anylink/conf/server.toml -elif [[ ${mode} == password ]];then - if [ -z ${password} ];then - echo "invalid password" - else - /anylink/anylink -passwd ${password} - fi -elif [[ ${mode} -eq jwt ]];then - /anylink/anylink -secret -fi \ No newline at end of file diff --git a/anylink/docker/generate-certs.sh b/anylink/docker/generate-certs.sh deleted file mode 100644 index 24ed85d..0000000 --- a/anylink/docker/generate-certs.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/sh - -mkdir -p /ssl - -OUTPUT_FILENAME="vpn.xx.com" - -printf "[req] -prompt = no -default_bits = 4096 -default_md = sha256 -encrypt_key = no -string_mask = utf8only - -distinguished_name = cert_distinguished_name -req_extensions = req_x509v3_extensions -x509_extensions = req_x509v3_extensions - -[ cert_distinguished_name ] -C = CN -ST = BJ -L = BJ -O = xx.com -OU = xx.com -CN = xx.com - -[req_x509v3_extensions] -basicConstraints = critical,CA:true -subjectKeyIdentifier = hash -keyUsage = critical,digitalSignature,keyCertSign,cRLSign #,keyEncipherment -extendedKeyUsage = critical,serverAuth #, clientAuth -subjectAltName = @alt_names - -[alt_names] -DNS.1 = xx.com -DNS.2 = *.xx.com - -">/ssl/${OUTPUT_FILENAME}.conf - -openssl req -x509 -newkey rsa:2048 -keyout /ssl/test_vpn_key.pem -out /ssl/test_vpn_cert.pem \ --days 3600 -nodes -config /ssl/${OUTPUT_FILENAME}.conf - diff --git a/anylink/docker_entrypoint.sh b/anylink/docker_entrypoint.sh index ea23118..3a7c5b3 100644 --- a/anylink/docker_entrypoint.sh +++ b/anylink/docker_entrypoint.sh @@ -15,8 +15,8 @@ case $var1 in *) sysctl -w net.ipv4.ip_forward=1 - iptables -t nat -A POSTROUTING -s "${IPV4_CIDR}" -o eth0+ -j MASQUERADE - iptables -nL -t nat + #iptables -t nat -A POSTROUTING -s "${IPV4_CIDR}" -o eth0+ -j MASQUERADE + #iptables -nL -t nat exec /app/anylink "$@" ;; diff --git a/anylink/server/admin/api_cert.go b/anylink/server/admin/api_cert.go new file mode 100644 index 0000000..664bbfd --- /dev/null +++ b/anylink/server/admin/api_cert.go @@ -0,0 +1,99 @@ +package admin + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "os" + + "github.com/bjdgyc/anylink/base" + "github.com/bjdgyc/anylink/dbdata" +) + +func CustomCert(w http.ResponseWriter, r *http.Request) { + cert, _, err := r.FormFile("cert") + if err != nil { + RespError(w, RespInternalErr, err) + return + } + key, _, err := r.FormFile("key") + if err != nil { + RespError(w, RespInternalErr, err) + return + } + certFile, err := os.OpenFile(base.Cfg.CertFile, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0600) + if err != nil { + RespError(w, RespInternalErr, err) + return + } + defer certFile.Close() + if _, err := io.Copy(certFile, cert); err != nil { + RespError(w, RespInternalErr, err) + return + } + keyFile, err := os.OpenFile(base.Cfg.CertKey, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0600) + if err != nil { + RespError(w, RespInternalErr, err) + return + } + defer keyFile.Close() + if _, err := io.Copy(keyFile, key); err != nil { + RespError(w, RespInternalErr, err) + return + } + if tlscert, _, err := dbdata.ParseCert(); err != nil { + RespError(w, RespInternalErr, fmt.Sprintf("证书不合法,请重新上传:%v", err)) + return + } else { + dbdata.LoadCertificate(tlscert) + } + RespSucess(w, "上传成功") +} +func GetCertSetting(w http.ResponseWriter, r *http.Request) { + sess := dbdata.GetXdb().NewSession() + defer sess.Close() + data := &dbdata.SettingLetsEncrypt{} + if err := dbdata.SettingGet(data); err != nil { + dbdata.SettingSessAdd(sess, data) + RespError(w, RespInternalErr, err) + } + userData := &dbdata.LegoUserData{} + if err := dbdata.SettingGet(userData); err != nil { + dbdata.SettingSessAdd(sess, userData) + } + RespSucess(w, data) +} +func CreatCert(w http.ResponseWriter, r *http.Request) { + if err := r.ParseForm(); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + body, err := io.ReadAll(r.Body) + if err != nil { + RespError(w, RespInternalErr, err) + return + } + defer r.Body.Close() + config := &dbdata.SettingLetsEncrypt{} + if err := json.Unmarshal(body, config); err != nil { + RespError(w, RespInternalErr, err) + return + } + if err := dbdata.SettingSet(config); err != nil { + RespError(w, RespInternalErr, err) + return + } + client := dbdata.LeGoClient{} + if err := client.NewClient(config); err != nil { + base.Error(err) + RespError(w, RespInternalErr, fmt.Sprintf("获取证书失败:%v", err)) + return + } + if err := client.GetCert(config.Domain); err != nil { + base.Error(err) + RespError(w, RespInternalErr, fmt.Sprintf("获取证书失败:%v", err)) + return + } + RespSucess(w, "生成证书成功") +} diff --git a/anylink/server/admin/api_group.go b/anylink/server/admin/api_group.go index f5975c1..44522a9 100644 --- a/anylink/server/admin/api_group.go +++ b/anylink/server/admin/api_group.go @@ -2,7 +2,7 @@ package admin import ( "encoding/json" - "io/ioutil" + "io" "net/http" "strconv" @@ -79,7 +79,7 @@ func GroupDetail(w http.ResponseWriter, r *http.Request) { } func GroupSet(w http.ResponseWriter, r *http.Request) { - body, err := ioutil.ReadAll(r.Body) + body, err := io.ReadAll(r.Body) if err != nil { RespError(w, RespInternalErr, err) return @@ -118,3 +118,30 @@ func GroupDel(w http.ResponseWriter, r *http.Request) { } RespSucess(w, nil) } + +func GroupAuthLogin(w http.ResponseWriter, r *http.Request) { + type AuthLoginData struct { + Name string `json:"name"` + Pwd string `json:"pwd"` + Auth map[string]interface{} `json:"auth"` + } + + body, err := io.ReadAll(r.Body) + if err != nil { + RespError(w, RespInternalErr, err) + return + } + defer r.Body.Close() + v := &AuthLoginData{} + err = json.Unmarshal(body, &v) + if err != nil { + RespError(w, RespInternalErr, err) + return + } + err = dbdata.GroupAuthLogin(v.Name, v.Pwd, v.Auth) + if err != nil { + RespError(w, RespInternalErr, err) + return + } + RespSucess(w, "ok") +} diff --git a/anylink/server/admin/api_ip_map.go b/anylink/server/admin/api_ip_map.go index 49aeb85..a7a9aba 100644 --- a/anylink/server/admin/api_ip_map.go +++ b/anylink/server/admin/api_ip_map.go @@ -2,7 +2,7 @@ package admin import ( "encoding/json" - "io/ioutil" + "io" "net/http" "strconv" @@ -59,7 +59,7 @@ func UserIpMapDetail(w http.ResponseWriter, r *http.Request) { func UserIpMapSet(w http.ResponseWriter, r *http.Request) { _ = r.ParseForm() - body, err := ioutil.ReadAll(r.Body) + body, err := io.ReadAll(r.Body) if err != nil { RespError(w, RespInternalErr, err) return @@ -80,6 +80,8 @@ func UserIpMapSet(w http.ResponseWriter, r *http.Request) { return } + // sessdata.IpAllSet(v) + RespSucess(w, nil) } @@ -93,11 +95,20 @@ func UserIpMapDel(w http.ResponseWriter, r *http.Request) { return } - data := dbdata.IpMap{Id: id} - err := dbdata.Del(&data) + var data dbdata.IpMap + err := dbdata.One("Id", id, &data) if err != nil { RespError(w, RespInternalErr, err) return } + + err = dbdata.Del(&data) + if err != nil { + RespError(w, RespInternalErr, err) + return + } + + // sessdata.IpAllDel(&data) + RespSucess(w, nil) } diff --git a/anylink/server/admin/api_other.go b/anylink/server/admin/api_other.go index 557b27a..6f12da2 100644 --- a/anylink/server/admin/api_other.go +++ b/anylink/server/admin/api_other.go @@ -3,10 +3,11 @@ package admin import ( "encoding/json" "errors" - "io/ioutil" + "io" "net/http" "regexp" + "github.com/bjdgyc/anylink/base" "github.com/bjdgyc/anylink/dbdata" ) @@ -25,7 +26,7 @@ func setOtherGet(data interface{}, w http.ResponseWriter) { } func setOtherEdit(data interface{}, w http.ResponseWriter, r *http.Request) { - body, err := ioutil.ReadAll(r.Body) + body, err := io.ReadAll(r.Body) if err != nil { RespError(w, RespInternalErr, err) return @@ -82,11 +83,12 @@ func SetOtherAuditLog(w http.ResponseWriter, r *http.Request) { RespError(w, RespInternalErr, err) return } + data.AuditInterval = base.Cfg.AuditInterval RespSucess(w, data) } func SetOtherAuditLogEdit(w http.ResponseWriter, r *http.Request) { - body, err := ioutil.ReadAll(r.Body) + body, err := io.ReadAll(r.Body) if err != nil { RespError(w, RespInternalErr, err) return diff --git a/anylink/server/admin/api_policy.go b/anylink/server/admin/api_policy.go index a4934c9..2adc512 100644 --- a/anylink/server/admin/api_policy.go +++ b/anylink/server/admin/api_policy.go @@ -2,7 +2,7 @@ package admin import ( "encoding/json" - "io/ioutil" + "io" "net/http" "strconv" @@ -57,7 +57,7 @@ func PolicyDetail(w http.ResponseWriter, r *http.Request) { } func PolicySet(w http.ResponseWriter, r *http.Request) { - body, err := ioutil.ReadAll(r.Body) + body, err := io.ReadAll(r.Body) if err != nil { RespError(w, RespInternalErr, err) return diff --git a/anylink/server/admin/api_set_audit.go b/anylink/server/admin/api_set_audit.go index 872c7a6..1b423e8 100644 --- a/anylink/server/admin/api_set_audit.go +++ b/anylink/server/admin/api_set_audit.go @@ -51,3 +51,29 @@ func SetAuditExport(w http.ResponseWriter, r *http.Request) { gocsv.Marshal(datas, w) } + +func UserActLogList(w http.ResponseWriter, r *http.Request) { + _ = r.ParseForm() + pageS := r.FormValue("page") + page, _ := strconv.Atoi(pageS) + if page < 1 { + page = 1 + } + var datas []dbdata.UserActLog + session := dbdata.UserActLogIns.GetSession(r.Form) + count, err := dbdata.FindAndCount(session, &datas, dbdata.PageSize, page) + if err != nil && !dbdata.CheckErrNotFound(err) { + RespError(w, RespInternalErr, err) + return + } + data := map[string]interface{}{ + "count": count, + "page_size": dbdata.PageSize, + "datas": datas, + "statusOps": dbdata.UserActLogIns.GetStatusOpsWithTag(), + "osOps": dbdata.UserActLogIns.OsOps, + "clientOps": dbdata.UserActLogIns.ClientOps, + } + + RespSucess(w, data) +} diff --git a/anylink/server/admin/api_uploaduser.go b/anylink/server/admin/api_uploaduser.go new file mode 100644 index 0000000..43b4650 --- /dev/null +++ b/anylink/server/admin/api_uploaduser.go @@ -0,0 +1,114 @@ +package admin + +import ( + "fmt" + "io" + "net/http" + "os" + "strconv" + "strings" + "time" + + "github.com/bjdgyc/anylink/base" + "github.com/bjdgyc/anylink/dbdata" + "github.com/bjdgyc/anylink/pkg/utils" + mapset "github.com/deckarep/golang-set" + "github.com/spf13/cast" + "github.com/xuri/excelize/v2" +) + +func UserUpload(w http.ResponseWriter, r *http.Request) { + r.ParseMultipartForm(8 << 20) + file, header, err := r.FormFile("file") + if err != nil || !strings.Contains(header.Filename, ".xlsx") || !strings.Contains(header.Filename, ".xls") { + RespError(w, RespInternalErr, "文件解析失败:仅支持xlsx或xls文件") + return + } + defer file.Close() + newFile, err := os.Create(base.Cfg.FilesPath + header.Filename) + if err != nil { + RespError(w, RespInternalErr, "创建文件失败:", err) + return + } + defer newFile.Close() + io.Copy(newFile, file) + if err = UploadUser(newFile.Name()); err != nil { + RespError(w, RespInternalErr, err) + os.Remove(base.Cfg.FilesPath + header.Filename) + return + } + os.Remove(base.Cfg.FilesPath + header.Filename) + RespSucess(w, "批量添加成功") +} +func UploadUser(file string) error { + f, err := excelize.OpenFile(file) + if err != nil { + return err + } + defer func() { + if err := f.Close(); err != nil { + return + } + }() + rows, err := f.GetRows("Sheet1") + if err != nil { + return err + } + if rows[0][0] != "id" || rows[0][1] != "username" || rows[0][2] != "nickname" || rows[0][3] != "email" || rows[0][4] != "pin_code" || rows[0][5] != "limittime" || rows[0][6] != "otp_secret" || rows[0][7] != "disable_otp" || rows[0][8] != "groups" || rows[0][9] != "status" || rows[0][10] != "send_email" { + return fmt.Errorf("批量添加失败,表格格式不正确") + } + var k []interface{} + for _, v := range dbdata.GetGroupNames() { + k = append(k, v) + } + for index, row := range rows { + if index == 0 { + continue + } + id, _ := strconv.Atoi(row[0]) + if len(row[4]) < 6 { + row[4] = utils.RandomRunes(8) + } + limittime, _ := time.ParseInLocation("2006-01-02 15:04:05", row[5], time.Local) + disableOtp, _ := strconv.ParseBool(row[7]) + var group []string + if row[8] == "" { + return fmt.Errorf("第%d行数据错误,用户组不允许为空", index) + } + for _, v := range strings.Split(row[8], ",") { + if s := mapset.NewSetFromSlice(k); s.Contains(v) { + group = append(group, v) + } else { + return fmt.Errorf("用户组【%s】不存在,请检查第%d行数据", v, index) + } + } + status := cast.ToInt8(row[9]) + sendmail, _ := strconv.ParseBool(row[10]) + // createdAt, _ := time.ParseInLocation("2006-01-02 15:04:05", row[11], time.Local) + // updatedAt, _ := time.ParseInLocation("2006-01-02 15:04:05", row[12], time.Local) + user := &dbdata.User{ + Id: id, + Username: row[1], + Nickname: row[2], + Email: row[3], + PinCode: row[4], + LimitTime: &limittime, + OtpSecret: row[6], + DisableOtp: disableOtp, + Groups: group, + Status: status, + SendEmail: sendmail, + // CreatedAt: createdAt, + // UpdatedAt: updatedAt, + } + if err := dbdata.AddBatch(user); err != nil { + return fmt.Errorf("请检查第%d行数据是否导入有重复用户", index) + } + if user.SendEmail { + if err := userAccountMail(user); err != nil { + return err + } + } + } + return nil +} diff --git a/anylink/server/admin/api_user.go b/anylink/server/admin/api_user.go index 516d3ae..b93ea5d 100644 --- a/anylink/server/admin/api_user.go +++ b/anylink/server/admin/api_user.go @@ -5,7 +5,7 @@ import ( "encoding/base64" "encoding/json" "fmt" - "io/ioutil" + "io" "net/http" "net/url" "strconv" @@ -80,7 +80,7 @@ func UserDetail(w http.ResponseWriter, r *http.Request) { func UserSet(w http.ResponseWriter, r *http.Request) { _ = r.ParseForm() - body, err := ioutil.ReadAll(r.Body) + body, err := io.ReadAll(r.Body) if err != nil { RespError(w, RespInternalErr, err) return @@ -107,7 +107,8 @@ func UserSet(w http.ResponseWriter, r *http.Request) { return } } - + //修改用户资料后执行过期用户检测 + sessdata.CloseUserLimittimeSession() RespSucess(w, nil) } @@ -132,33 +133,44 @@ func UserDel(w http.ResponseWriter, r *http.Request) { func UserOtpQr(w http.ResponseWriter, r *http.Request) { _ = r.ParseForm() - b64 := r.FormValue("b64") + b64S := r.FormValue("b64") idS := r.FormValue("id") id, _ := strconv.Atoi(idS) - var user dbdata.User - err := dbdata.One("Id", id, &user) + + var b64 bool + if b64S == "1" { + b64 = true + } + data, err := userOtpQr(id, b64) if err != nil { - RespError(w, RespInternalErr, err) - return + base.Error(err) + } + io.WriteString(w, data) +} + +func userOtpQr(uid int, b64 bool) (string, error) { + var user dbdata.User + err := dbdata.One("Id", uid, &user) + if err != nil { + return "", err } issuer := url.QueryEscape(base.Cfg.Issuer) qrstr := fmt.Sprintf("otpauth://totp/%s:%s?issuer=%s&secret=%s", issuer, user.Email, issuer, user.OtpSecret) qr, _ := qrcode.New(qrstr, qrcode.High) - if b64 == "1" { - data, _ := qr.PNG(300) - s := base64.StdEncoding.EncodeToString(data) - _, err = fmt.Fprint(w, s) + if b64 { + data, err := qr.PNG(300) if err != nil { - base.Error(err) + return "", err } - return - } - err = qr.Write(300, w) - if err != nil { - base.Error(err) + s := base64.StdEncoding.EncodeToString(data) + return s, nil } + + buf := bytes.NewBuffer(nil) + err = qr.Write(300, buf) + return buf.String(), err } // 在线用户 @@ -177,7 +189,7 @@ func UserOnline(w http.ResponseWriter, r *http.Request) { func UserOffline(w http.ResponseWriter, r *http.Request) { _ = r.ParseForm() token := r.FormValue("token") - sessdata.CloseSess(token) + sessdata.CloseSess(token, dbdata.UserLogoutAdmin) RespSucess(w, nil) } @@ -189,12 +201,13 @@ func UserReline(w http.ResponseWriter, r *http.Request) { } type userAccountMailData struct { - Issuer string - LinkAddr string - Group string - Username string - PinCode string - OtpImg string + Issuer string + LinkAddr string + Group string + Username string + PinCode string + OtpImg string + OtpImgBase64 string } func userAccountMail(user *dbdata.User) error { @@ -235,12 +248,15 @@ func userAccountMail(user *dbdata.User) error { return err } + otpData, _ := userOtpQr(user.Id, true) + data := userAccountMailData{ - LinkAddr: setting.LinkAddr, - Group: strings.Join(user.Groups, ","), - Username: user.Username, - PinCode: user.PinCode, - OtpImg: fmt.Sprintf("https://%s/otp_qr?id=%d&jwt=%s", setting.LinkAddr, user.Id, tokenString), + LinkAddr: setting.LinkAddr, + Group: strings.Join(user.Groups, ","), + Username: user.Username, + PinCode: user.PinCode, + OtpImg: fmt.Sprintf("https://%s/otp_qr?id=%d&jwt=%s", setting.LinkAddr, user.Id, tokenString), + OtpImgBase64: "data:image/png;base64," + otpData, } w := bytes.NewBufferString("") t, _ := template.New("auth_complete").Parse(htmlBody) diff --git a/anylink/server/admin/resp_test.go b/anylink/server/admin/resp_test.go index 4d3dd60..b4dbd53 100644 --- a/anylink/server/admin/resp_test.go +++ b/anylink/server/admin/resp_test.go @@ -2,7 +2,7 @@ package admin import ( "encoding/json" - "io/ioutil" + "io" "net/http/httptest" "testing" @@ -15,7 +15,7 @@ func TestRespSucess(t *testing.T) { RespSucess(w, "data") // fmt.Println(w) assert.Equal(w.Code, 200) - body, _ := ioutil.ReadAll(w.Body) + body, _ := io.ReadAll(w.Body) res := Resp{} err := json.Unmarshal(body, &res) assert.Nil(err) @@ -30,7 +30,7 @@ func TestRespError(t *testing.T) { RespError(w, 10, "err-msg") // fmt.Println(w) assert.Equal(w.Code, 200) - body, _ := ioutil.ReadAll(w.Body) + body, _ := io.ReadAll(w.Body) res := Resp{} err := json.Unmarshal(body, &res) assert.Nil(err) diff --git a/anylink/server/admin/server.go b/anylink/server/admin/server.go index d4939e0..3cabb99 100644 --- a/anylink/server/admin/server.go +++ b/anylink/server/admin/server.go @@ -9,6 +9,7 @@ import ( "github.com/arl/statsviz" "github.com/bjdgyc/anylink/base" + "github.com/bjdgyc/anylink/dbdata" "github.com/gorilla/handlers" "github.com/gorilla/mux" ) @@ -45,10 +46,15 @@ func StartAdmin() { r.HandleFunc("/set/other/audit_log/edit", SetOtherAuditLogEdit) r.HandleFunc("/set/audit/list", SetAuditList) r.HandleFunc("/set/audit/export", SetAuditExport) + r.HandleFunc("/set/audit/act_log_list", UserActLogList) + r.HandleFunc("/set/other/createcert", CreatCert) + r.HandleFunc("/set/other/getcertset", GetCertSetting) + r.HandleFunc("/set/other/customcert", CustomCert) r.HandleFunc("/user/list", UserList) r.HandleFunc("/user/detail", UserDetail) r.HandleFunc("/user/set", UserSet) + r.HandleFunc("/user/uploaduser", UserUpload).Methods(http.MethodPost) r.HandleFunc("/user/del", UserDel) r.HandleFunc("/user/online", UserOnline) r.HandleFunc("/user/offline", UserOffline) @@ -69,6 +75,7 @@ func StartAdmin() { r.HandleFunc("/group/detail", GroupDetail) r.HandleFunc("/group/set", GroupSet) r.HandleFunc("/group/del", GroupDel) + r.HandleFunc("/group/auth_login", GroupAuthLogin) r.HandleFunc("/statsinfo/list", StatsInfoList) @@ -93,18 +100,28 @@ func StartAdmin() { for _, s := range cipherSuites { selectedCipherSuites = append(selectedCipherSuites, s.ID) } + + if tlscert, _, err := dbdata.ParseCert(); err != nil { + base.Fatal("证书加载失败", err) + } else { + dbdata.LoadCertificate(tlscert) + } + // 设置tls信息 tlsConfig := &tls.Config{ NextProtos: []string{"http/1.1"}, MinVersion: tls.VersionTLS12, CipherSuites: selectedCipherSuites, + GetCertificate: func(chi *tls.ClientHelloInfo) (*tls.Certificate, error) { + return dbdata.GetCertificateBySNI(chi.ServerName) + }, } srv := &http.Server{ Addr: base.Cfg.AdminAddr, Handler: r, TLSConfig: tlsConfig, } - err := srv.ListenAndServeTLS(base.Cfg.CertFile, base.Cfg.CertKey) + err := srv.ListenAndServeTLS("", "") if err != nil { base.Fatal(err) } diff --git a/anylink/server/base/app_ver.go b/anylink/server/base/app_ver.go index 2666b77..93599e2 100644 --- a/anylink/server/base/app_ver.go +++ b/anylink/server/base/app_ver.go @@ -3,5 +3,5 @@ package base const ( APP_NAME = "AnyLink" // app版本号 - APP_VER = "0.9.1-beta1" + APP_VER = "0.9.3" ) diff --git a/anylink/server/base/cfg.go b/anylink/server/base/cfg.go index 3df1113..48d2de8 100644 --- a/anylink/server/base/cfg.go +++ b/anylink/server/base/cfg.go @@ -73,7 +73,12 @@ type ServerConfig struct { // AuthTimeout int `json:"auth_timeout"` // in seconds AuditInterval int `json:"audit_interval"` // in seconds - ShowSQL bool `json:"show_sql"` // bool + ShowSQL bool `json:"show_sql"` // bool + IptablesNat bool `json:"iptables_nat"` + Compression bool `json:"compression"` // bool + NoCompressLimit int `json:"no_compress_limit"` // int + + DisplayError bool `json:"display_error"` } func initServerCfg() { diff --git a/anylink/server/base/cmd.go b/anylink/server/base/cmd.go index 75d5f25..ca4d095 100644 --- a/anylink/server/base/cmd.go +++ b/anylink/server/base/cmd.go @@ -69,6 +69,11 @@ func initCmd() { Run: func(cmd *cobra.Command, args []string) { // fmt.Println("cmd:", cmd.Use, args) runSrv = true + + if rev { + printVersion() + os.Exit(0) + } }, } @@ -92,6 +97,7 @@ func initCmd() { // viper.SetDefault(v.Name, v.Value) } + rootCmd.Flags().BoolVarP(&rev, "version", "v", false, "display version info") rootCmd.AddCommand(initToolCmd()) cobra.OnInitialize(func() { @@ -127,8 +133,7 @@ func initToolCmd() *cobra.Command { toolCmd.Run = func(cmd *cobra.Command, args []string) { switch { case rev: - fmt.Printf("%s v%s build on %s [%s, %s] commit_id(%s) \n", - APP_NAME, APP_VER, runtime.Version(), runtime.GOOS, runtime.GOARCH, CommitId) + printVersion() case secret: s, _ := utils.RandSecret(40, 60) s = strings.Trim(s, "=") @@ -145,3 +150,8 @@ func initToolCmd() *cobra.Command { return toolCmd } + +func printVersion() { + fmt.Printf("%s v%s build on %s [%s, %s] commit_id(%s) \n", + APP_NAME, APP_VER, runtime.Version(), runtime.GOOS, runtime.GOARCH, CommitId) +} diff --git a/anylink/server/base/config.go b/anylink/server/base/config.go index 50f9e28..f237b18 100644 --- a/anylink/server/base/config.go +++ b/anylink/server/base/config.go @@ -24,7 +24,7 @@ var configs = []config{ {Typ: cfgStr, Name: "profile", Usage: "profile.xml file", ValStr: "./conf/profile.xml"}, {Typ: cfgStr, Name: "server_addr", Usage: "服务监听地址", ValStr: ":443"}, {Typ: cfgBool, Name: "server_dtls", Usage: "开启DTLS", ValBool: false}, - {Typ: cfgStr, Name: "server_dtls_addr", Usage: "DTLS监听地址", ValStr: ":4433"}, + {Typ: cfgStr, Name: "server_dtls_addr", Usage: "DTLS监听地址", ValStr: ":443"}, {Typ: cfgStr, Name: "admin_addr", Usage: "后台服务监听地址", ValStr: ":8800"}, {Typ: cfgBool, Name: "proxy_protocol", Usage: "TCP代理协议", ValBool: false}, {Typ: cfgStr, Name: "db_type", Usage: "数据库类型 [sqlite3 mysql postgres]", ValStr: "sqlite3"}, @@ -33,7 +33,7 @@ var configs = []config{ {Typ: cfgStr, Name: "cert_key", Usage: "证书密钥", ValStr: "./conf/vpn_cert.key"}, {Typ: cfgStr, Name: "files_path", Usage: "外部下载文件路径", ValStr: "./conf/files"}, {Typ: cfgStr, Name: "log_path", Usage: "日志文件路径,默认标准输出", ValStr: ""}, - {Typ: cfgStr, Name: "log_level", Usage: "日志等级 [debug info warn error]", ValStr: "info"}, + {Typ: cfgStr, Name: "log_level", Usage: "日志等级 [debug info warn error]", ValStr: "debug"}, {Typ: cfgBool, Name: "pprof", Usage: "开启pprof", ValBool: false}, {Typ: cfgStr, Name: "issuer", Usage: "系统名称", ValStr: "XX公司VPN"}, {Typ: cfgStr, Name: "admin_user", Usage: "管理用户名", ValStr: "admin"}, @@ -41,26 +41,31 @@ var configs = []config{ {Typ: cfgStr, Name: "jwt_secret", Usage: "JWT密钥", ValStr: defaultJwt}, {Typ: cfgStr, Name: "link_mode", Usage: "虚拟网络类型[tun tap macvtap ipvtap]", ValStr: "tun"}, {Typ: cfgStr, Name: "ipv4_master", Usage: "ipv4主网卡名称", ValStr: "eth0"}, - {Typ: cfgStr, Name: "ipv4_cidr", Usage: "ip地址网段", ValStr: "192.168.10.0/24"}, - {Typ: cfgStr, Name: "ipv4_gateway", Usage: "ipv4_gateway", ValStr: "192.168.10.1"}, - {Typ: cfgStr, Name: "ipv4_start", Usage: "IPV4开始地址", ValStr: "192.168.10.100"}, - {Typ: cfgStr, Name: "ipv4_end", Usage: "IPV4结束", ValStr: "192.168.10.200"}, + {Typ: cfgStr, Name: "ipv4_cidr", Usage: "ip地址网段", ValStr: "192.168.90.0/24"}, + {Typ: cfgStr, Name: "ipv4_gateway", Usage: "ipv4_gateway", ValStr: "192.168.90.1"}, + {Typ: cfgStr, Name: "ipv4_start", Usage: "IPV4开始地址", ValStr: "192.168.90.100"}, + {Typ: cfgStr, Name: "ipv4_end", Usage: "IPV4结束", ValStr: "192.168.90.200"}, {Typ: cfgStr, Name: "default_group", Usage: "默认用户组", ValStr: "one"}, {Typ: cfgStr, Name: "default_domain", Usage: "要发布的默认域", ValStr: ""}, - {Typ: cfgInt, Name: "ip_lease", Usage: "IP租期(秒)", ValInt: 1209600}, - {Typ: cfgInt, Name: "max_client", Usage: "最大用户连接", ValInt: 100}, + {Typ: cfgInt, Name: "ip_lease", Usage: "IP租期(秒)", ValInt: 86400}, + {Typ: cfgInt, Name: "max_client", Usage: "最大用户连接", ValInt: 200}, {Typ: cfgInt, Name: "max_user_client", Usage: "最大单用户连接", ValInt: 3}, - {Typ: cfgInt, Name: "cstp_keepalive", Usage: "keepalive时间(秒)", ValInt: 20}, - {Typ: cfgInt, Name: "cstp_dpd", Usage: "死链接检测时间(秒)", ValInt: 30}, - {Typ: cfgInt, Name: "mobile_keepalive", Usage: "移动端keepalive接检测时间(秒)", ValInt: 50}, - {Typ: cfgInt, Name: "mobile_dpd", Usage: "移动端死链接检测时间(秒)", ValInt: 60}, + {Typ: cfgInt, Name: "cstp_keepalive", Usage: "keepalive时间(秒)", ValInt: 4}, + {Typ: cfgInt, Name: "cstp_dpd", Usage: "死链接检测时间(秒)", ValInt: 10}, + {Typ: cfgInt, Name: "mobile_keepalive", Usage: "移动端keepalive接检测时间(秒)", ValInt: 7}, + {Typ: cfgInt, Name: "mobile_dpd", Usage: "移动端死链接检测时间(秒)", ValInt: 15}, {Typ: cfgInt, Name: "mtu", Usage: "最大传输单元MTU", ValInt: 1460}, - {Typ: cfgInt, Name: "session_timeout", Usage: "session过期时间(秒)", ValInt: 3600}, + {Typ: cfgInt, Name: "session_timeout", Usage: "session过期时间(秒)-用于断线重连,0永不过期", ValInt: 3600}, // {Typ: cfgInt, Name: "auth_timeout", Usage: "auth_timeout", ValInt: 0}, {Typ: cfgInt, Name: "audit_interval", Usage: "审计去重间隔(秒),-1关闭", ValInt: -1}, {Typ: cfgBool, Name: "show_sql", Usage: "显示sql语句,用于调试", ValBool: false}, + {Typ: cfgBool, Name: "iptables_nat", Usage: "是否自动添加NAT", ValBool: true}, + {Typ: cfgBool, Name: "compression", Usage: "启用压缩", ValBool: false}, + {Typ: cfgInt, Name: "no_compress_limit", Usage: "低于及等于多少字节不压缩", ValInt: 256}, + + {Typ: cfgBool, Name: "display_error", Usage: "客户端显示详细错误信息(线上环境慎开启)", ValBool: false}, } var envs = map[string]string{} diff --git a/anylink/server/base/log.go b/anylink/server/base/log.go index 36bb927..debb283 100644 --- a/anylink/server/base/log.go +++ b/anylink/server/base/log.go @@ -10,11 +10,12 @@ import ( ) const ( - _Debug = iota - _Info - _Warn - _Error - _Fatal + LogLevelTrace = iota + LogLevelDebug + LogLevelInfo + LogLevelWarn + LogLevelError + LogLevelFatal ) var ( @@ -87,15 +88,20 @@ func GetBaseLog() *log.Logger { return baseLog } +func GetLogLevel() int { + return baseLevel +} + func logLevel2Int(l string) int { levels = map[int]string{ - _Debug: "Debug", - _Info: "Info", - _Warn: "Warn", - _Error: "Error", - _Fatal: "Fatal", + LogLevelTrace: "Trace", + LogLevelDebug: "Debug", + LogLevelInfo: "Info", + LogLevelWarn: "Warn", + LogLevelError: "Error", + LogLevelFatal: "Fatal", } - lvl := _Info + lvl := LogLevelInfo for k, v := range levels { if strings.EqualFold(strings.ToLower(l), strings.ToLower(v)) { lvl = k @@ -109,8 +115,16 @@ func output(l int, s ...interface{}) { _ = baseLog.Output(3, lvl+fmt.Sprintln(s...)) } +func Trace(v ...interface{}) { + l := LogLevelTrace + if baseLevel > l { + return + } + output(l, v...) +} + func Debug(v ...interface{}) { - l := _Debug + l := LogLevelDebug if baseLevel > l { return } @@ -118,7 +132,7 @@ func Debug(v ...interface{}) { } func Info(v ...interface{}) { - l := _Info + l := LogLevelInfo if baseLevel > l { return } @@ -126,7 +140,7 @@ func Info(v ...interface{}) { } func Warn(v ...interface{}) { - l := _Warn + l := LogLevelWarn if baseLevel > l { return } @@ -134,7 +148,7 @@ func Warn(v ...interface{}) { } func Error(v ...interface{}) { - l := _Error + l := LogLevelError if baseLevel > l { return } @@ -142,7 +156,7 @@ func Error(v ...interface{}) { } func Fatal(v ...interface{}) { - l := _Fatal + l := LogLevelFatal if baseLevel > l { return } diff --git a/anylink/server/conf/server-sample.toml b/anylink/server/conf/server-sample.toml index 785e901..d83bf5a 100644 --- a/anylink/server/conf/server-sample.toml +++ b/anylink/server/conf/server-sample.toml @@ -30,7 +30,7 @@ jwt_secret = "abcdef.0123456789.abcdef" server_addr = ":443" #开启 DTLS, 默认关闭 server_dtls = false -server_dtls_addr = ":4433" +server_dtls_addr = ":443" #后台服务监听地址 admin_addr = ":8800" #开启tcp proxy protocol协议 @@ -40,26 +40,26 @@ link_mode = "tun" #客户端分配的ip地址池 ipv4_master = "eth0" -ipv4_cidr = "192.168.10.0/24" -ipv4_gateway = "192.168.10.1" -ipv4_start = "192.168.10.100" -ipv4_end = "192.168.10.200" +ipv4_cidr = "192.168.90.0/24" +ipv4_gateway = "192.168.90.1" +ipv4_start = "192.168.90.100" +ipv4_end = "192.168.90.200" #最大客户端数量 max_client = 100 #单个用户同时在线数量 max_user_client = 3 #IP租期(秒) -ip_lease = 1209600 +ip_lease = 86400 #默认选择的组 default_group = "one" #客户端失效检测时间(秒) dpd > keepalive -cstp_keepalive = 20 -cstp_dpd = 30 -mobile_keepalive = 40 -mobile_dpd = 50 +cstp_keepalive = 6 +cstp_dpd = 10 +mobile_keepalive = 15 +mobile_dpd = 20 #设置最大传输单元 mtu = 1460 @@ -75,5 +75,15 @@ audit_interval = -1 show_sql = false +#是否自动添加nat +iptables_nat = true + +#启用压缩 +compression = false +#低于及等于多少字节不压缩 +no_compress_limit = 256 + +#客户端显示详细错误信息(线上环境慎开启) +display_error = false diff --git a/anylink/server/conf/server.toml b/anylink/server/conf/server.toml index e246b10..b46aa1a 100644 --- a/anylink/server/conf/server.toml +++ b/anylink/server/conf/server.toml @@ -26,5 +26,18 @@ server_addr = ":443" admin_addr = ":8800" +#客户端分配的ip地址池 +ipv4_master = "eth0" +ipv4_cidr = "192.168.90.0/24" +ipv4_gateway = "192.168.90.1" +ipv4_start = "192.168.90.100" +ipv4_end = "192.168.90.200" + +#是否自动添加nat +iptables_nat = true + + +#客户端显示详细错误信息(线上环境慎开启) +display_error = false diff --git a/anylink/server/conf/vpn_cert.crt b/anylink/server/conf/vpn_cert.crt index e2e701b..04f451e 100644 --- a/anylink/server/conf/vpn_cert.crt +++ b/anylink/server/conf/vpn_cert.crt @@ -1,36 +1,36 @@ -----BEGIN CERTIFICATE----- -MIIF9zCCBN+gAwIBAgIQBNH+cm5YH1O2NhfT+zB+ATANBgkqhkiG9w0BAQsFADBu +MIIF9jCCBN6gAwIBAgIQAuUy6Rv6Bo3nDXn5FackbDANBgkqhkiG9w0BAQsFADBu MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 d3cuZGlnaWNlcnQuY29tMS0wKwYDVQQDEyRFbmNyeXB0aW9uIEV2ZXJ5d2hlcmUg -RFYgVExTIENBIC0gRzEwHhcNMjExMjEyMDAwMDAwWhcNMjIxMjEzMjM1OTU5WjAc +RFYgVExTIENBIC0gRzEwHhcNMjMwMTAzMDAwMDAwWhcNMjQwMTAzMjM1OTU5WjAc MRowGAYDVQQDExF2cG4udGVzdC52cWlsdS5jbjCCASIwDQYJKoZIhvcNAQEBBQAD -ggEPADCCAQoCggEBAK2XO6Na//i0sMiV0nF+aDTbDibGiTLr+LFlhTIi1KX9IAU2 -Xboz1B8cxDro3g+CzgrGg0YMI4CxBiY56UT3jUTsLYBNpWPkbhlH+mpf0J7fgH29 -V1LAZKm2qR28y/krKHIbcGrfMAbXi6iVkVHhc+edvGCdAiDSyJgVSZbYV/s0LXLF -0B0BokagwtvGIx7ik5uG4exuRCUKE3z0n6RXdN0eWBvKKHFhWEeaBIGzHjoDgAx/ -4VJ8XsW0tcwByiVRqpMFa1eG3HLMvi34M1qLzNv7dGPIkr1zjvlvTqhDpimXOi9C -4N5ZOfZfNAyR8zU5+tBqSCvByavxLJwC//F7VQcCAwEAAaOCAuEwggLdMB8GA1Ud -IwQYMBaAFFV0T7JyT/VgulDR1+ZRXJoBhxrXMB0GA1UdDgQWBBQKyNOGPzBPyqY9 -nxahHC+B6xT83TAcBgNVHREEFTATghF2cG4udGVzdC52cWlsdS5jbjAOBgNVHQ8B +ggEPADCCAQoCggEBANJAJPYBvOP/7v8SgMIkVLIulN/ziPALvFcEwVnQDImUIky8 +4udy0fmvJ2E3E3NL6Qv14ZHDGtH7CafukimNWTT2BVmQBYiO1ZlUkHcHUX4IoYEh +egdy2xw0WwknJWTOyvkRkeDhtT9QUpA/zeemS4q1TG95zRDf5htUR4OMZXsZpkQ2 +bkSgnLtdyUmw2nhfSWgsD9fbwr6WnOx/swsUe52N3sIDZ6JTgn3N7xeT3/lVJKVN +wyYkZldialmRzrs6btr3mmnqpWObcc4FvKr/CLmoOSXl0I1wWsr+HnQ4X9hHsJUk +jk3EZKfhH3mM37HF8apqztb6WjC3R96Zam6Z8bMCAwEAAaOCAuAwggLcMB8GA1Ud +IwQYMBaAFFV0T7JyT/VgulDR1+ZRXJoBhxrXMB0GA1UdDgQWBBSUpPcW3emC2l0o +q6qRBOBDMjQ2rDAcBgNVHREEFTATghF2cG4udGVzdC52cWlsdS5jbjAOBgNVHQ8B Af8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMD4GA1UdIAQ3 MDUwMwYGZ4EMAQIBMCkwJwYIKwYBBQUHAgEWG2h0dHA6Ly93d3cuZGlnaWNlcnQu Y29tL0NQUzCBgAYIKwYBBQUHAQEEdDByMCQGCCsGAQUFBzABhhhodHRwOi8vb2Nz cC5kaWdpY2VydC5jb20wSgYIKwYBBQUHMAKGPmh0dHA6Ly9jYWNlcnRzLmRpZ2lj ZXJ0LmNvbS9FbmNyeXB0aW9uRXZlcnl3aGVyZURWVExTQ0EtRzEuY3J0MAkGA1Ud -EwQCMAAwggF+BgorBgEEAdZ5AgQCBIIBbgSCAWoBaAB2ACl5vvCeOTkh8FZzn2Ol -d+W+V32cYAr4+U1dJlwlXceEAAABfa0lBgAAAAQDAEcwRQIgEQ4wS5gyLMK30aeD -xF3kWvsUhkd94HKIl13ckYnukGMCIQD1/6fFUAPjdw2k8f/ctJ7STUHeA1WoBy5H -O/iXBRCkWgB2AFGjsPX9AXmcVm24N3iPDKR6zBsny/eeiEKaDf7UiwXlAAABfa0l -BmYAAAQDAEcwRQIgOoguGrrlpwoxGiJHJNcEWbuH2AOJCDSDiun80DX9hUwCIQCJ -cFCOe5E5VbgHrTWbQ0OUFS0epDgUiG8y9kjfkN1M5QB2AEHIyrHfIkZKEMahOglC -h15OMYsbA+vrS8do8JBilgb2AAABfa0lBfoAAAQDAEcwRQIhAIHCUjXv+M3/jFOU -AzjjMCISczShjqQ5FKqsIYNTUN46AiAom+II914ifwdFiS2xWI0ncSj8cxH6f+WZ -UUQj9RczMDANBgkqhkiG9w0BAQsFAAOCAQEALj5oEwyU+gxVKhLFrBBtkoi9F0HQ -jjSQZvOcKApSXjKS11VdmLGKuy85FSocw7VvDtZ4o43OhO79GMAMiPXroTnPIS5O -ZNxfuusF6HpS+2Dq9UidnlxQmIaJ4A7PkX+NqAI4V6yr839SXKyHJROfXf9hNoJZ -PJeZ94oMwXdeNjFkOismFpvaZcYq7t51xi5tkH/NaJHV5FEU8Or4zk/OoaPe3r+b -2hpltIIaapoNVYLWLW7YS7hlvhjfwPypsR3ev4bTRWvT1tu9+AE+TG0OZqeWGucP -6MjZI5gecOnkQVmBovkRi2lr26PDWrwnAlyoMI3ioU1XaTftIrBL2YalfQ== +EwQCMAAwggF9BgorBgEEAdZ5AgQCBIIBbQSCAWkBZwB2AO7N0GTV2xrOxVy3nbTN +E6Iyh0Z8vOzew1FIWUZxH7WbAAABhXXkyXMAAAQDAEcwRQIgVhLvLOPcW0V1xhBv +5KSeqGHbAnRVhew3kutV3Bu1x+ICIQCbYjRtmkDo1hx6p0YNdNfkZ3N5u+syVjwH +Al3a9NpVxgB1AEiw42vapkc0D+VqAvqdMOscUgHLVt0sgdm7v6s52IRzAAABhXXk +yY4AAAQDAEYwRAIgcuscG2kkSGNvAsVH9CAtXjNUwk9UJriY0+3OtQ4WVrMCIAsC +CkqEI1Ek5M26yrWt0Q7+u+UZ8rXhfYu3kcMMq7PVAHYAO1N3dT4tuYBOizBbBv5A +O2fYT8P0x70ADS1yb+H61BcAAAGFdeTJkAAABAMARzBFAiAEJbJTN8hrRUZ6UaaD +2TlyDQfzUvTkex0XGT6PGKHkagIhAJ+Kg6tdt/csKde2vdweu+dT01fzg/fq4q3o +mjfPhFm1MA0GCSqGSIb3DQEBCwUAA4IBAQAKFTUHbpgKsXARCBIIfEZGqkOvaafm +QaoNodc6cj0+LJCbuMzrTlkzmII0X/U52MBG8JCEIO8BPe5R4NIFqqaE066zQANq +HOsROOJi2A+WTTZcSEHbH3uhdVwcEQHvDzaOEEJc9Ilz6pdYsrv+trOmeR5PeIxv +t1jQacSwN1z6z0N4CRjBpePV/9nwETkEaKjQuXSoYlN+pczK/4nX2W9+E/OnwtZs +ScyFffPtTLHf1u4eSYuBT/AdwaKHXetxWzh98GP9LRfQhm63Gs+/WcloYl489dG/ +FOFjch2TdmrPcUwxxGEbbPt3zXRxSVlzvIaf4gTUl2+PsKwbKy/w4OLS -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIEqjCCA5KgAwIBAgIQAnmsRYvBskWr+YBTzSybsTANBgkqhkiG9w0BAQsFADBh diff --git a/anylink/server/conf/vpn_cert.key b/anylink/server/conf/vpn_cert.key index 6026ff9..8f5e285 100644 --- a/anylink/server/conf/vpn_cert.key +++ b/anylink/server/conf/vpn_cert.key @@ -1,27 +1,27 @@ -----BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEArZc7o1r/+LSwyJXScX5oNNsOJsaJMuv4sWWFMiLUpf0gBTZd -ujPUHxzEOujeD4LOCsaDRgwjgLEGJjnpRPeNROwtgE2lY+RuGUf6al/Qnt+Afb1X -UsBkqbapHbzL+Ssochtwat8wBteLqJWRUeFz5528YJ0CINLImBVJlthX+zQtcsXQ -HQGiRqDC28YjHuKTm4bh7G5EJQoTfPSfpFd03R5YG8oocWFYR5oEgbMeOgOADH/h -UnxexbS1zAHKJVGqkwVrV4bccsy+LfgzWovM2/t0Y8iSvXOO+W9OqEOmKZc6L0Lg -3lk59l80DJHzNTn60GpIK8HJq/EsnAL/8XtVBwIDAQABAoIBACXjPEELO5Ms3Ojq -ymO7E0N2DECqVIeouT7+yXOH5qHT/YkltI9PgJzJyoqRCOaZxh7T9RL000rjWFQ/ -j4pd/ZdtdQDr8Y077kvWSfGtt/r1DTZkfQqys0XXeFHlQx+/K7S8CG1LCVB0+yZw -fqdAbeu/ob30huJjHyUSgF1MGufYvuII6x0CGORwzruWWFniXkg2z+9SP4x4RSfm -exMUE4T4tlzR63QaW02xWEDTWCSQw/FgjpCWwryDVCmnLf63UhI+4hITqZLL+ROd -sG/8Yp284q7BYBKk4/N1HD4W1vU+dls3glxZ22NCQKx+2RVtqTrRUd/d4AnxOmMR -dnfh4AECgYEA7cl9NIRrtQdW+KFcoSdyP2F+SU74nSAh6Uolzwr9lHB+NbMJ5g79 -eU1zp3RAvSFg249L4cnceaFL1LTPcNN0xhpaJ7v5FQWk5tkddSmy2T3CAh8VwLXF -487pgakO1SpS6uz+BtwsAFOS8k/GjYeSbPR4e9F/FbYAvGYwOLNj2ocCgYEAuuL8 -xnFnt95TwWptu4T97YXTeZRB17jiH1BhX+QawsSafagsWlSKihKMxYhfCHiwztS/ -KsCnkS6cH9slU3y4gvCiT1S4z1Qkw93ljUQXCzRIVEd9SxXoQMeRi+/5c239Fhnu -aoxESAFWNXJZ5r9Jp3qukHvEtYn2FoE1Zkmu0YECgYApULgDdvqr4pGW85p/mbX9 -Ezh5DlKeImYh/bMiDTvQHdegBvKyWWprOCzfLJDPC8yjeXtqyMMZExB07dGZPfRt -M0j03HFD2M41GgZHRC6CFnvuGG6UJEE0+s+Rqskb+pWbof/lOz4d9Gd02K2cC7FC -YxvID7dwE0Z/dZXtVCYGYwKBgQCjckPKtoIUcBBmV1NzLiP66REEAuL27Q5ufpk7 -CT9SWioXfc6Ujd3AVeriE5uxyAQyUCSFGosy0UXgIoRpmOmyMwxxP1KGmTuyRc4u -l39j4Czl8MQmuBkxFpk3fwB2sJopCzLV4qkRJIImKkVwJpofLI+hc22dq/QayJRQ -Sl7ngQKBgQCkfcbQDvhkL6QKUC/K7MDGw9JMICLUpRyp6D3ibeL7i6WO6dkKde2t -O/oLz2XvG0NR0nulhThpWUdyUWco3FZ038jiuY8ZZum5wdVBDOcDcnuBisE3Kzh8 -p7WycoWItAVxmyTKzHJIZ7pFQULYjap7gFSUPE9uBQZu09VKBtGPHA== +MIIEpQIBAAKCAQEA0kAk9gG84//u/xKAwiRUsi6U3/OI8Au8VwTBWdAMiZQiTLzi +53LR+a8nYTcTc0vpC/XhkcMa0fsJp+6SKY1ZNPYFWZAFiI7VmVSQdwdRfgihgSF6 +B3LbHDRbCSclZM7K+RGR4OG1P1BSkD/N56ZLirVMb3nNEN/mG1RHg4xlexmmRDZu +RKCcu13JSbDaeF9JaCwP19vCvpac7H+zCxR7nY3ewgNnolOCfc3vF5Pf+VUkpU3D +JiRmV2JqWZHOuzpu2veaaeqlY5txzgW8qv8Iuag5JeXQjXBayv4edDhf2EewlSSO +TcRkp+EfeYzfscXxqmrO1vpaMLdH3plqbpnxswIDAQABAoIBAGKKzugAi4Q/Vch2 +ZyPXPF0hCQToE3QSxAzy/R53rRCkfekClMTO44xHpEjjs/mTiCBjd3xGeiEVrIJp +hlb0WW3Bq2M9ZeKJs6JAaM9o/jB4oh2wT44DLqALB+oDz3puk+Jl8j34++a3YmMa +jIq4veo+rBsJduwkTKjdeQE2ge/ODZEQ6bUmSjYo1P9LNGEyO2wmcVk+jHx0zBi7 +8fR3oY03Io+byuN0494Di1m3IpIdj3ma0MV5zJf31urLXqqYtOApouWL/yhZOIuo +YW+mcuS7ZgK5FsqrUm0vGBcf24GcKhhlBlUu0mfLrCRrWLDsqJDQ/8alvuNP0IVm +gqz0H5UCgYEA8yaKzMfkeRXSTERt6NZHo/8ShIn26Yf+pMDpVdYKfVBL254vGTeq +B+LQhDpxZV1iMr1FNvkhHtNGQ74ZbWOj4+5Xjsllaw1ao2iGp1w/chuJ6FG/Go4q +9FaY3eGiCqRJOQNivBxU/D7sN0y4b48HAEQdmp516pItlGtG+C8TY38CgYEA3VyD +6EczHdAmPO7bdbYn/irfe78so1lHT04P0FiVg2W77ZKuQINTNDK9w/alYyZ2tH1b +N2JznulJ6UDcl4xw43xJixxhme2jWPaYzmQUuQHviZ0D0tCgmOkN8bUnc9LSvEGA +SnaiKbOtUfP8Z3c/mF997wuFNfdmhww8LBpbO80CgYEAmdRKf9/+5bQuhd3NAz99 +t31KQ9vdAEXvjmAVvx5ZKIrCU0EyXuvegHq4nM80qoJ3+83Omkbm80+K5pTAFXqy +VyOU9Vro9N9P9o3MktlDsndFulrtYmmLN2YJ9GYpVD43rQA9WPE7uxI784hwLvP3 +4+00JXwW8b5lY76y+ZUe2RUCgYEAt6BQN/YgPCH4JmHKIWp64If2HbQntlWQJwRN +b/qcBIT3EQu1iwSll85jxtSqu4YjwHOgoGAGI5PIYTsSApFY8AyhAUoI2OTdtSXS ++prg6dvmNhTPICk6n73seE5bLOR9NfdsEdk5ijhnlW09OyMb2S2VzR+UYIEbRvnq +THeMqR0CgYEAwQ9aAIGD4t4DXjHHtz6Wqpbq6jj6mmYBsL9jqRgu8ASj1/THgWWn +iUrlCbFIXWu2vnP1h0SBV56GA7MSTqAt0ZdTzpI1PRkMOIO9z1dN4Q2R1SEya1eF +7/LPLqJIoLbILGvy6U3DLYdMckPZaoTPf5BNKD52paZcNbjkXTlVLSY= -----END RSA PRIVATE KEY----- diff --git a/anylink/server/cron/clear_statsinfo.go b/anylink/server/cron/clear_statsinfo.go index 11ecbd3..75a7b0b 100644 --- a/anylink/server/cron/clear_statsinfo.go +++ b/anylink/server/cron/clear_statsinfo.go @@ -43,10 +43,5 @@ func getTimeAgo(days int) string { ts := time.Now().AddDate(0, 0, -days) tsZero := time.Date(ts.Year(), ts.Month(), ts.Day(), 0, 0, 0, 0, time.Local) timeS = tsZero.Format(dbdata.LayoutTimeFormat) - // UTC - switch base.Cfg.DbType { - case "sqlite3", "postgres": - timeS = tsZero.UTC().Format(dbdata.LayoutTimeFormat) - } return timeS } diff --git a/anylink/server/cron/clear_user_act_log.go b/anylink/server/cron/clear_user_act_log.go new file mode 100644 index 0000000..b407d8c --- /dev/null +++ b/anylink/server/cron/clear_user_act_log.go @@ -0,0 +1,20 @@ +package cron + +import ( + "github.com/bjdgyc/anylink/base" + "github.com/bjdgyc/anylink/dbdata" +) + +// 清除用户活动日志 +func ClearUserActLog() { + lifeDay, timesUp := isClearTime() + if !timesUp { + return + } + // 当审计日志永久保存时,则退出 + if lifeDay <= 0 { + return + } + affected, err := dbdata.UserActLogIns.ClearUserActLog(getTimeAgo(lifeDay)) + base.Info("Cron ClearUserActLog: ", affected, err) +} diff --git a/anylink/server/cron/start.go b/anylink/server/cron/start.go index 2692cfe..a2ff4c2 100644 --- a/anylink/server/cron/start.go +++ b/anylink/server/cron/start.go @@ -3,6 +3,8 @@ package cron import ( "time" + "github.com/bjdgyc/anylink/dbdata" + "github.com/bjdgyc/anylink/sessdata" "github.com/go-co-op/gocron" ) @@ -10,5 +12,8 @@ func Start() { s := gocron.NewScheduler(time.Local) s.Cron("0 * * * *").Do(ClearAudit) s.Cron("0 * * * *").Do(ClearStatsInfo) + s.Cron("0 * * * *").Do(ClearUserActLog) + s.Every(1).Day().At("00:00").Do(sessdata.CloseUserLimittimeSession) + s.Every(1).Day().At("00:00").Do(dbdata.ReNewCert) s.StartAsync() } diff --git a/anylink/server/dbdata/audit.go b/anylink/server/dbdata/audit.go index d54f315..603b061 100644 --- a/anylink/server/dbdata/audit.go +++ b/anylink/server/dbdata/audit.go @@ -14,6 +14,7 @@ type SearchCon struct { AccessProto string `json:"access_proto"` Date []string `json:"date"` Info string `json:"info"` + Sort int `json:"sort"` } func GetAuditSession(search string) *xorm.Session { @@ -47,6 +48,11 @@ func GetAuditSession(search string) *xorm.Session { if searchData.Info != "" { session.And("info LIKE ?", "%"+searchData.Info+"%") } + if searchData.Sort == 1 { + session.OrderBy("id desc") + } else { + session.OrderBy("id asc") + } return session } diff --git a/anylink/server/dbdata/audit_test.go b/anylink/server/dbdata/audit_test.go index a2ca9aa..02a3b6b 100644 --- a/anylink/server/dbdata/audit_test.go +++ b/anylink/server/dbdata/audit_test.go @@ -15,7 +15,7 @@ func TestSearchAudit(t *testing.T) { defer closeIpdata() currDateVal := "2022-07-24 00:00:00" - CreatedAt, _ := time.Parse("2006-01-02 15:04:05", currDateVal) + CreatedAt, _ := time.ParseInLocation("2006-01-02 15:04:05", currDateVal, time.Local) dataTest := AccessAudit{ Username: "Test", diff --git a/anylink/server/dbdata/cert.go b/anylink/server/dbdata/cert.go new file mode 100644 index 0000000..b9e93af --- /dev/null +++ b/anylink/server/dbdata/cert.go @@ -0,0 +1,412 @@ +package dbdata + +import ( + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "errors" + "fmt" + "math/big" + "net" + "os" + "strings" + "sync" + "time" + + "github.com/pion/dtls/v2/pkg/crypto/selfsign" + + "github.com/bjdgyc/anylink/base" + "github.com/go-acme/lego/v4/certcrypto" + "github.com/go-acme/lego/v4/certificate" + "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/lego" + "github.com/go-acme/lego/v4/providers/dns/alidns" + "github.com/go-acme/lego/v4/providers/dns/cloudflare" + "github.com/go-acme/lego/v4/providers/dns/tencentcloud" + "github.com/go-acme/lego/v4/registration" +) + +var ( + // nameToCertificate mutex + ntcMux sync.RWMutex + nameToCertificate = make(map[string]*tls.Certificate) + tempCert *tls.Certificate +) + +func init() { + c, _ := selfsign.GenerateSelfSignedWithDNS("localhost") + tempCert = &c +} + +type SettingLetsEncrypt struct { + Domain string `json:"domain"` + Legomail string `json:"legomail"` + Name string `json:"name"` + Renew bool `json:"renew"` + DNSProvider +} + +type DNSProvider struct { + AliYun struct { + APIKey string `json:"apiKey"` + SecretKey string `json:"secretKey"` + } `json:"aliyun"` + + TXCloud struct { + SecretID string `json:"secretId"` + SecretKey string `json:"secretKey"` + } `json:"txcloud"` + CfCloud struct { + AuthEmail string `json:"authEmail"` + AuthKey string `json:"authKey"` + } `json:"cfcloud"` +} +type LegoUserData struct { + Email string `json:"email"` + Registration *registration.Resource `json:"registration"` + Key []byte `json:"key"` +} +type LegoUser struct { + Email string + Registration *registration.Resource + Key *ecdsa.PrivateKey +} + +type LeGoClient struct { + mutex sync.Mutex + Client *lego.Client + Cert *certificate.Resource + LegoUserData +} + +func GetDNSProvider(l *SettingLetsEncrypt) (Provider challenge.Provider, err error) { + switch l.Name { + case "aliyun": + if Provider, err = alidns.NewDNSProviderConfig(&alidns.Config{APIKey: l.DNSProvider.AliYun.APIKey, SecretKey: l.DNSProvider.AliYun.SecretKey, TTL: 600}); err != nil { + return + } + case "txcloud": + if Provider, err = tencentcloud.NewDNSProviderConfig(&tencentcloud.Config{SecretID: l.DNSProvider.TXCloud.SecretID, SecretKey: l.DNSProvider.TXCloud.SecretKey, TTL: 600}); err != nil { + return + } + case "cloudflare": + if Provider, err = cloudflare.NewDNSProviderConfig(&cloudflare.Config{AuthEmail: l.DNSProvider.CfCloud.AuthEmail, AuthKey: l.DNSProvider.CfCloud.AuthKey, TTL: 600}); err != nil { + return + } + } + return +} +func (u *LegoUser) GetEmail() string { + return u.Email +} +func (u LegoUser) GetRegistration() *registration.Resource { + return u.Registration +} +func (u *LegoUser) GetPrivateKey() crypto.PrivateKey { + return u.Key +} + +func (l *LegoUserData) SaveUserData(u *LegoUser) error { + key, err := x509.MarshalECPrivateKey(u.Key) + if err != nil { + return err + } + l.Email = u.Email + l.Registration = u.Registration + l.Key = key + if err := SettingSet(l); err != nil { + return err + } + return nil +} + +func (l *LegoUserData) GetUserData(d *SettingLetsEncrypt) (*LegoUser, error) { + if err := SettingGet(l); err != nil { + return nil, err + } + if l.Email != "" { + key, err := x509.ParseECPrivateKey(l.Key) + if err != nil { + return nil, err + } + return &LegoUser{ + Email: l.Email, + Registration: l.Registration, + Key: key, + }, nil + } + privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, err + } + return &LegoUser{ + Email: d.Legomail, + Key: privateKey, + }, nil +} +func ReNewCert() { + _, certtime, err := ParseCert() + if err != nil { + base.Error(err) + return + } + if certtime.AddDate(0, 0, -7).Before(time.Now()) { + config := &SettingLetsEncrypt{} + if err := SettingGet(config); err != nil { + base.Error(err) + return + } + if config.Renew { + client := &LeGoClient{} + if err := client.NewClient(config); err != nil { + base.Error(err) + return + } + if err := client.RenewCert(base.Cfg.CertFile, base.Cfg.CertKey); err != nil { + base.Error(err) + return + } + base.Info("证书续期成功") + } + } else { + base.Info(fmt.Sprintf("证书过期时间:%s", certtime.Local().Format("2006-1-2 15:04:05"))) + } +} + +func (c *LeGoClient) NewClient(l *SettingLetsEncrypt) error { + c.mutex.Lock() + defer c.mutex.Unlock() + legouser, err := c.GetUserData(l) + if err != nil { + return err + } + config := lego.NewConfig(legouser) + config.CADirURL = lego.LEDirectoryProduction + config.Certificate.KeyType = certcrypto.RSA2048 + + client, err := lego.NewClient(config) + if err != nil { + return err + } + Provider, err := GetDNSProvider(l) + if err != nil { + return err + } + if err := client.Challenge.SetDNS01Provider(Provider, dns01.AddRecursiveNameservers([]string{"114.114.114.114", "114.114.115.115"})); err != nil { + return err + } + if legouser.Registration == nil { + reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) + if err != nil { + return err + } + legouser.Registration = reg + c.SaveUserData(legouser) + } + c.Client = client + return nil +} + +func (c *LeGoClient) GetCert(domain string) error { + // 申请证书 + certificates, err := c.Client.Certificate.Obtain( + certificate.ObtainRequest{ + Domains: []string{domain}, + Bundle: true, + }) + if err != nil { + return err + } + c.Cert = certificates + // 保存证书 + if err := c.SaveCert(); err != nil { + return err + } + return nil +} + +func (c *LeGoClient) RenewCert(certFile, keyFile string) error { + cert, err := os.ReadFile(certFile) + if err != nil { + return err + } + key, err := os.ReadFile(keyFile) + if err != nil { + return err + } + // 续期证书 + renewcert, err := c.Client.Certificate.Renew(certificate.Resource{ + Certificate: cert, + PrivateKey: key, + }, true, false, "") + if err != nil { + return err + } + c.Cert = renewcert + // 保存更新证书 + if err := c.SaveCert(); err != nil { + return err + } + return nil +} + +func (c *LeGoClient) SaveCert() error { + err := os.WriteFile(base.Cfg.CertFile, c.Cert.Certificate, 0600) + if err != nil { + return err + } + err = os.WriteFile(base.Cfg.CertKey, c.Cert.PrivateKey, 0600) + if err != nil { + return err + } + if tlscert, _, err := ParseCert(); err != nil { + return err + } else { + LoadCertificate(tlscert) + } + return nil +} + +func ParseCert() (*tls.Certificate, *time.Time, error) { + _, errCert := os.Stat(base.Cfg.CertFile) + _, errKey := os.Stat(base.Cfg.CertKey) + if os.IsNotExist(errCert) || os.IsNotExist(errKey) { + err := PrivateCert() + if err != nil { + return nil, nil, err + } + } + cert, err := tls.LoadX509KeyPair(base.Cfg.CertFile, base.Cfg.CertKey) + if err != nil || errors.Is(err, os.ErrNotExist) { + PrivateCert() + return nil, nil, err + } + parseCert, err := x509.ParseCertificate(cert.Certificate[0]) + if err != nil { + return nil, nil, err + } + return &cert, &parseCert.NotAfter, nil +} + +func PrivateCert() error { + // 创建一个RSA密钥对 + priv, _ := rsa.GenerateKey(rand.Reader, 2048) + pub := &priv.PublicKey + + // 生成一个自签名证书 + template := x509.Certificate{ + SerialNumber: big.NewInt(1658), + Subject: pkix.Name{CommonName: "localhost"}, + NotBefore: time.Now(), + NotAfter: time.Now().Add(time.Hour * 24 * 365), + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + IPAddresses: []net.IP{}, + } + + derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, pub, priv) + if err != nil { + return err + } + + // 将证书编码为PEM格式并将其写入文件 + certOut, _ := os.OpenFile(base.Cfg.CertFile, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0600) + pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) + certOut.Close() + + // 将私钥编码为PEM格式并将其写入文件 + keyOut, _ := os.OpenFile(base.Cfg.CertKey, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}) + keyOut.Close() + cert, err := tls.LoadX509KeyPair(base.Cfg.CertFile, base.Cfg.CertKey) + if err != nil { + return err + } + LoadCertificate(&cert) + return nil +} + +func getTempCertificate() (*tls.Certificate, error) { + var err error + var cert tls.Certificate + if tempCert == nil { + cert, err = selfsign.GenerateSelfSignedWithDNS("localhost") + tempCert = &cert + } + return tempCert, err +} + +func GetCertificateBySNI(commonName string) (*tls.Certificate, error) { + ntcMux.RLock() + defer ntcMux.RUnlock() + + // Copy from tls.Config getCertificate() + name := strings.ToLower(commonName) + if cert, ok := nameToCertificate[name]; ok { + return cert, nil + } + if len(name) > 0 { + labels := strings.Split(name, ".") + labels[0] = "*" + wildcardName := strings.Join(labels, ".") + if cert, ok := nameToCertificate[wildcardName]; ok { + return cert, nil + } + } + // TODO 默认证书 兼容不支持 SNI 的客户端 + if cert, ok := nameToCertificate["default"]; ok { + return cert, nil + } + + return getTempCertificate() +} + +func LoadCertificate(cert *tls.Certificate) { + buildNameToCertificate(cert) +} + +// Copy from tls.Config BuildNameToCertificate() +func buildNameToCertificate(cert *tls.Certificate) { + ntcMux.Lock() + defer ntcMux.Unlock() + + // TODO 设置默认证书 + nameToCertificate["default"] = cert + + x509Cert, err := x509.ParseCertificate(cert.Certificate[0]) + if err != nil { + return + } + startTime := x509Cert.NotBefore.String() + expiredTime := x509Cert.NotAfter.String() + if x509Cert.Subject.CommonName != "" && len(x509Cert.DNSNames) == 0 { + commonName := x509Cert.Subject.CommonName + fmt.Printf("┏ Load Certificate: %s\n", commonName) + fmt.Printf("┠╌╌ Start Time: %s\n", startTime) + fmt.Printf("┖╌╌ Expired Time: %s\n", expiredTime) + nameToCertificate[commonName] = cert + } + for _, san := range x509Cert.DNSNames { + fmt.Printf("┏ Load Certificate: %s\n", san) + fmt.Printf("┠╌╌ Start Time: %s\n", startTime) + fmt.Printf("┖╌╌ Expired Time: %s\n", expiredTime) + nameToCertificate[san] = cert + } +} + +// func Scrypt(passwd string) string { +// salt := []byte{0xc8, 0x28, 0xf2, 0x58, 0xa7, 0x6a, 0xad, 0x7b} +// hashPasswd, err := scrypt.Key([]byte(passwd), salt, 1<<15, 8, 1, 32) +// if err != nil { +// return err.Error() +// } +// return base64.StdEncoding.EncodeToString(hashPasswd) +// } diff --git a/anylink/server/dbdata/db.go b/anylink/server/dbdata/db.go index 64f1ecc..293e9b8 100644 --- a/anylink/server/dbdata/db.go +++ b/anylink/server/dbdata/db.go @@ -1,6 +1,8 @@ package dbdata import ( + "time" + "github.com/bjdgyc/anylink/base" _ "github.com/go-sql-driver/mysql" _ "github.com/lib/pq" @@ -19,6 +21,9 @@ func GetXdb() *xorm.Engine { func initDb() { var err error xdb, err = xorm.NewEngine(base.Cfg.DbType, base.Cfg.DbSource) + // 初始化xorm时区 + xdb.DatabaseTZ = time.Local + xdb.TZLocation = time.Local if err != nil { base.Fatal(err) } @@ -28,7 +33,7 @@ func initDb() { } // 初始化数据库 - err = xdb.Sync2(&User{}, &Setting{}, &Group{}, &IpMap{}, &AccessAudit{}, &Policy{}, &StatsNetwork{}, &StatsCpu{}, &StatsMem{}, &StatsOnline{}) + err = xdb.Sync2(&User{}, &Setting{}, &Group{}, &IpMap{}, &AccessAudit{}, &Policy{}, &StatsNetwork{}, &StatsCpu{}, &StatsMem{}, &StatsOnline{}, &UserActLog{}) if err != nil { base.Fatal(err) } @@ -94,6 +99,36 @@ func addInitData() error { return err } + // SettingDnsProvider + provider := &SettingLetsEncrypt{ + Domain: "vpn.xxx.com", + Legomail: "legomail", + Name: "aliyun", + Renew: false, + DNSProvider: DNSProvider{ + AliYun: struct { + APIKey string `json:"apiKey"` + SecretKey string `json:"secretKey"` + }{APIKey: "", SecretKey: ""}, + TXCloud: struct { + SecretID string `json:"secretId"` + SecretKey string `json:"secretKey"` + }{SecretID: "", SecretKey: ""}, + CfCloud: struct { + AuthEmail string `json:"authEmail"` + AuthKey string `json:"authKey"` + }{AuthEmail: "", AuthKey: ""}}, + } + err = SettingSessAdd(sess, provider) + if err != nil { + return err + } + // LegoUser + legouser := &LegoUserData{} + err = SettingSessAdd(sess, legouser) + if err != nil { + return err + } // SettingOther other := &SettingOther{ LinkAddr: "vpn.xx.com", @@ -123,6 +158,7 @@ func addInitData() error { AllowLan: true, ClientDns: []ValData{{Val: "114.114.114.114"}}, RouteInclude: []ValData{{Val: All}}, + Status: 1, } err = SetGroup(&g1) if err != nil { @@ -143,8 +179,12 @@ const accountMail = `

您好:

用户组: {{.Group}}
用户名: {{.Username}}
用户PIN码: {{.PinCode}}
+ + 用户动态码(请妥善保存):
+

使用说明: diff --git a/anylink/server/dbdata/group.go b/anylink/server/dbdata/group.go index ee66ac0..e664aeb 100644 --- a/anylink/server/dbdata/group.go +++ b/anylink/server/dbdata/group.go @@ -74,6 +74,20 @@ func GetGroupNames() []string { return names } +func GetGroupNamesNormal() []string { + var datas []Group + err := FindWhere(&datas, 0, 0, "status=1") + if err != nil { + base.Error(err) + return nil + } + var names []string + for _, v := range datas { + names = append(names, v.Name) + } + return names +} + func GetGroupNamesIds() []GroupNameId { var datas []Group err := Find(&datas, 0, 0) @@ -211,6 +225,21 @@ func SetGroup(g *Group) error { return err } +func GroupAuthLogin(name, pwd string, authData map[string]interface{}) error { + g := &Group{Auth: authData} + authType := g.Auth["type"].(string) + if _, ok := authRegistry[authType]; !ok { + return errors.New("未知的认证方式: " + authType) + } + auth := makeInstance(authType).(IUserAuth) + err := auth.checkData(g.Auth) + if err != nil { + return err + } + err = auth.checkUser(name, pwd, g) + return err +} + func parseIpNet(s string) (string, *net.IPNet, error) { ip, ipNet, err := net.ParseCIDR(s) if err != nil { diff --git a/anylink/server/dbdata/group_test.go b/anylink/server/dbdata/group_test.go index b4b26f9..0d64c86 100644 --- a/anylink/server/dbdata/group_test.go +++ b/anylink/server/dbdata/group_test.go @@ -46,13 +46,14 @@ func TestGetGroupNames(t *testing.T) { authData = map[string]interface{}{ "type": "ldap", "ldap": map[string]interface{}{ - "addr": "192.168.8.12:389", - "tls": true, - "bind_name": "userfind@abc.com", - "bind_pwd": "afdbfdsafds", - "base_dn": "dc=abc,dc=com", - "search_attr": "sAMAccountName", - "member_of": "cn=vpn,cn=user,dc=abc,dc=com", + "addr": "192.168.8.12:389", + "tls": true, + "bind_name": "userfind@abc.com", + "bind_pwd": "afdbfdsafds", + "base_dn": "dc=abc,dc=com", + "object_class": "person", + "search_attr": "sAMAccountName", + "member_of": "cn=vpn,cn=user,dc=abc,dc=com", }, } g7 := Group{Name: "g7", ClientDns: []ValData{{Val: "114.114.114.114"}}, Auth: authData} diff --git a/anylink/server/dbdata/ip_map.go b/anylink/server/dbdata/ip_map.go index 98c955a..e9474dc 100644 --- a/anylink/server/dbdata/ip_map.go +++ b/anylink/server/dbdata/ip_map.go @@ -2,20 +2,22 @@ package dbdata import ( "errors" + "net" "time" ) -// type IpMap struct { -// Id int `json:"id" xorm:"pk autoincr not null"` -// IpAddr string `json:"ip_addr" xorm:"not null unique"` -// MacAddr string `json:"mac_addr" xorm:"not null unique"` -// Username string `json:"username"` -// Keep bool `json:"keep"` // 保留 ip-mac 绑定 -// KeepTime time.Time `json:"keep_time"` -// Note string `json:"note"` // 备注 -// LastLogin time.Time `json:"last_login"` -// UpdatedAt time.Time `json:"updated_at"` -// } +type IpMap struct { + Id int `json:"id" xorm:"pk autoincr not null"` + IpAddr string `json:"ip_addr" xorm:"varchar(32) not null unique"` + MacAddr string `json:"mac_addr" xorm:"varchar(32) not null unique"` + UniqueMac bool `json:"unique_mac" xorm:"Bool index"` + Username string `json:"username" xorm:"varchar(60)"` + Keep bool `json:"keep" xorm:"Bool"` // 保留 ip-mac 绑定 + KeepTime time.Time `json:"keep_time" xorm:"DateTime"` + Note string `json:"note" xorm:"varchar(255)"` // 备注 + LastLogin time.Time `json:"last_login" xorm:"DateTime"` + UpdatedAt time.Time `json:"updated_at" xorm:"DateTime updated"` +} func SetIpMap(v *IpMap) error { var err error @@ -24,6 +26,13 @@ func SetIpMap(v *IpMap) error { return errors.New("IP或MAC错误") } + macHw, err := net.ParseMAC(v.MacAddr) + if err != nil { + return errors.New("MAC错误") + } + // 统一macAddr的格式 + v.MacAddr = macHw.String() + v.UpdatedAt = time.Now() if v.Id > 0 { err = Set(v) diff --git a/anylink/server/dbdata/setting.go b/anylink/server/dbdata/setting.go index 964cb72..c7e0a36 100644 --- a/anylink/server/dbdata/setting.go +++ b/anylink/server/dbdata/setting.go @@ -21,8 +21,9 @@ type SettingSmtp struct { } type SettingAuditLog struct { - LifeDay int `json:"life_day"` - ClearTime string `json:"clear_time"` + AuditInterval int `json:"audit_interval"` + LifeDay int `json:"life_day"` + ClearTime string `json:"clear_time"` } type SettingOther struct { @@ -48,7 +49,6 @@ func SettingSessAdd(sess *xorm.Session, data interface{}) error { v, _ := json.Marshal(data) s := &Setting{Name: name, Data: v} _, err := sess.InsertOne(s) - return err } diff --git a/anylink/server/dbdata/statsinfo.go b/anylink/server/dbdata/statsinfo.go index cef60be..0ec53e4 100644 --- a/anylink/server/dbdata/statsinfo.go +++ b/anylink/server/dbdata/statsinfo.go @@ -199,12 +199,6 @@ func (s *StatsInfo) getScopeDetail(scope string) (sd *ScopeDetail) { } sd.fsTime = sd.sTime.Format(LayoutTimeFormat) sd.feTime = sd.eTime.Format(LayoutTimeFormat) - // UTC - switch base.Cfg.DbType { - case "sqlite3", "postgres": - sd.fsTime = sd.sTime.UTC().Format(LayoutTimeFormat) - sd.feTime = sd.eTime.UTC().Format(LayoutTimeFormat) - } return } diff --git a/anylink/server/dbdata/tables.go b/anylink/server/dbdata/tables.go index e0da7e0..b70e45a 100644 --- a/anylink/server/dbdata/tables.go +++ b/anylink/server/dbdata/tables.go @@ -29,26 +29,31 @@ type User struct { Nickname string `json:"nickname" xorm:"varchar(255)"` Email string `json:"email" xorm:"varchar(255)"` // Password string `json:"password"` - PinCode string `json:"pin_code" xorm:"varchar(32)"` - OtpSecret string `json:"otp_secret" xorm:"varchar(255)"` - DisableOtp bool `json:"disable_otp" xorm:"Bool"` // 禁用otp - Groups []string `json:"groups" xorm:"Text"` - Status int8 `json:"status" xorm:"Int"` // 1正常 - SendEmail bool `json:"send_email" xorm:"Bool"` - CreatedAt time.Time `json:"created_at" xorm:"DateTime created"` - UpdatedAt time.Time `json:"updated_at" xorm:"DateTime updated"` + PinCode string `json:"pin_code" xorm:"varchar(32)"` + LimitTime *time.Time `json:"limittime,omitempty" xorm:"Datetime limittime"` // 值为null时,前端不显示 + OtpSecret string `json:"otp_secret" xorm:"varchar(255)"` + DisableOtp bool `json:"disable_otp" xorm:"Bool"` // 禁用otp + Groups []string `json:"groups" xorm:"Text"` + Status int8 `json:"status" xorm:"Int"` // 1正常 + SendEmail bool `json:"send_email" xorm:"Bool"` + CreatedAt time.Time `json:"created_at" xorm:"DateTime created"` + UpdatedAt time.Time `json:"updated_at" xorm:"DateTime updated"` } -type IpMap struct { - Id int `json:"id" xorm:"pk autoincr not null"` - IpAddr string `json:"ip_addr" xorm:"varchar(32) not null unique"` - MacAddr string `json:"mac_addr" xorm:"varchar(32) not null unique"` - Username string `json:"username" xorm:"varchar(60)"` - Keep bool `json:"keep" xorm:"Bool"` // 保留 ip-mac 绑定 - KeepTime time.Time `json:"keep_time" xorm:"DateTime"` - Note string `json:"note" xorm:"varchar(255)"` // 备注 - LastLogin time.Time `json:"last_login" xorm:"DateTime"` - UpdatedAt time.Time `json:"updated_at" xorm:"DateTime updated"` +type UserActLog struct { + Id int `json:"id" xorm:"pk autoincr not null"` + Username string `json:"username" xorm:"varchar(60)"` + GroupName string `json:"group_name" xorm:"varchar(60)"` + IpAddr string `json:"ip_addr" xorm:"varchar(32)"` + RemoteAddr string `json:"remote_addr" xorm:"varchar(32)"` + Os uint8 `json:"os" xorm:"not null default 0 Int"` + Client uint8 `json:"client" xorm:"not null default 0 Int"` + Version string `json:"version" xorm:"varchar(15)"` + DeviceType string `json:"device_type" xorm:"varchar(128) not null default ''"` + PlatformVersion string `json:"platform_version" xorm:"varchar(128) not null default ''"` + Status uint8 `json:"status" xorm:"not null default 0 Int"` + Info string `json:"info" xorm:"varchar(255) not null default ''"` // 详情 + CreatedAt time.Time `json:"created_at" xorm:"DateTime created"` } type Setting struct { diff --git a/anylink/server/dbdata/user.go b/anylink/server/dbdata/user.go index 7834013..e05ab4d 100644 --- a/anylink/server/dbdata/user.go +++ b/anylink/server/dbdata/user.go @@ -104,7 +104,12 @@ func checkLocalUser(name, pwd, group string) error { v := &User{} err := One("Username", name, v) if err != nil || v.Status != 1 { - return fmt.Errorf("%s %s", name, "用户名错误") + switch v.Status { + case 0: + return fmt.Errorf("%s %s", name, "用户不存在或用户已停用") + case 2: + return fmt.Errorf("%s %s", name, "用户已过期") + } } // 判断用户组信息 if !utils.InArrStr(v.Groups, group) { @@ -128,6 +133,21 @@ func checkLocalUser(name, pwd, group string) error { return nil } +// 用户过期时间到达后,更新用户状态,并返回一个状态为过期的用户切片 +func CheckUserlimittime() (limitUser []interface{}) { + if _, err := xdb.Where("limittime <= ?", time.Now()).And("status = ?", 1).Update(&User{Status: 2}); err != nil { + return + } + user := make(map[int64]User) + if err := xdb.Where("status != ?", 1).Find(user); err != nil { + return + } + for _, v := range user { + limitUser = append(limitUser, v.Username) + } + return +} + var ( userOtpMux = sync.Mutex{} userOtp = map[string]time.Time{} diff --git a/anylink/server/dbdata/user_act_log.go b/anylink/server/dbdata/user_act_log.go new file mode 100644 index 0000000..ac9c506 --- /dev/null +++ b/anylink/server/dbdata/user_act_log.go @@ -0,0 +1,210 @@ +package dbdata + +import ( + "net/url" + "regexp" + "strings" + + "github.com/bjdgyc/anylink/base" + "github.com/ivpusic/grpool" + "github.com/spf13/cast" + "xorm.io/xorm" +) + +const ( + UserAuthFail = 0 // 认证失败 + UserAuthSuccess = 1 // 认证成功 + UserConnected = 2 // 连线成功 + UserLogout = 3 // 用户登出 + UserLogoutLose = 0 // 用户掉线 + UserLogoutBanner = 1 // 用户banner弹窗取消 + UserLogoutClient = 2 // 用户主动登出 + UserLogoutTimeout = 3 // 用户超时登出 + UserLogoutAdmin = 4 // 账号被管理员踢下线 + UserLogoutExpire = 5 // 账号过期被踢下线 +) + +type UserActLogProcess struct { + Pool *grpool.Pool + StatusOps []string + OsOps []string + ClientOps []string + InfoOps []string +} + +var ( + UserActLogIns = &UserActLogProcess{ + Pool: grpool.NewPool(1, 100), + StatusOps: []string{ // 操作类型 + UserAuthFail: "认证失败", + UserAuthSuccess: "认证成功", + UserConnected: "连接成功", + UserLogout: "用户登出", + }, + OsOps: []string{ // 操作系统 + 0: "Unknown", + 1: "Windows", + 2: "macOS", + 3: "Linux", + 4: "Android", + 5: "iOS", + }, + ClientOps: []string{ // 客户端 + 0: "Unknown", + 1: "AnyConnect", + 2: "OpenConnect", + 3: "AnyLink", + }, + InfoOps: []string{ // 信息 + UserLogoutLose: "用户掉线", + UserLogoutBanner: "用户取消弹窗/客户端发起的logout", + UserLogoutClient: "用户/客户端主动断开", + UserLogoutTimeout: "Session过期被踢下线", + UserLogoutAdmin: "账号被管理员踢下线", + UserLogoutExpire: "账号过期被踢下线", + }, + } +) + +// 异步写入用户操作日志 +func (ua *UserActLogProcess) Add(u UserActLog, userAgent string) { + // os, client, ver + os_idx, client_idx, ver := ua.ParseUserAgent(userAgent) + u.Os = os_idx + u.Client = client_idx + u.Version = ver + u.RemoteAddr = strings.Split(u.RemoteAddr, ":")[0] + // remove extra characters + infoSlice := strings.Split(u.Info, " ") + infoLen := len(infoSlice) + if infoLen > 1 { + if u.Username == infoSlice[0] { + u.Info = strings.Join(infoSlice[1:], " ") + } + // delete - char + if infoLen > 2 && infoSlice[1] == "-" { + u.Info = u.Info[2:] + } + } + // limit the max length of char + u.Version = substr(u.Version, 0, 15) + u.DeviceType = substr(u.DeviceType, 0, 128) + u.PlatformVersion = substr(u.PlatformVersion, 0, 128) + u.Info = substr(u.Info, 0, 255) + + UserActLogIns.Pool.JobQueue <- func() { + err := Add(u) + if err != nil { + base.Error("Add UserActLog error: ", err) + } + } +} + +// 转义操作类型, 方便vue显示 +func (ua *UserActLogProcess) GetStatusOpsWithTag() interface{} { + type StatusTag struct { + Key int `json:"key"` + Value string `json:"value"` + Tag string `json:"tag"` + } + var res []StatusTag + for k, v := range ua.StatusOps { + tag := "info" + switch k { + case UserAuthFail: + tag = "danger" + case UserAuthSuccess: + tag = "success" + case UserConnected: + tag = "" + } + res = append(res, StatusTag{k, v, tag}) + } + return res +} + +func (ua *UserActLogProcess) GetInfoOpsById(id uint8) string { + return ua.InfoOps[id] +} + +// 解析user agent +func (ua *UserActLogProcess) ParseUserAgent(userAgent string) (os_idx, client_idx uint8, ver string) { + // Unknown + if len(userAgent) == 0 { + return 0, 0, "" + } + // OS + os_idx = 0 + if strings.Contains(userAgent, "windows") { + os_idx = 1 + } else if strings.Contains(userAgent, "mac os") || strings.Contains(userAgent, "darwin_i386") { + os_idx = 2 + } else if strings.Contains(userAgent, "darwin_arm") || strings.Contains(userAgent, "apple") { + os_idx = 5 + } else if strings.Contains(userAgent, "android") { + os_idx = 4 + } else if strings.Contains(userAgent, "linux") { + os_idx = 3 + } + // Client + client_idx = 0 + if strings.Contains(userAgent, "anyconnect") { + client_idx = 1 + } else if strings.Contains(userAgent, "openconnect") { + client_idx = 2 + } else if strings.Contains(userAgent, "anylink") { + client_idx = 3 + } + // Version + uaSlice := strings.Split(userAgent, " ") + ver = uaSlice[len(uaSlice)-1] + if ver[0] == 'v' { + ver = ver[1:] + } + if !regexp.MustCompile(`^(\d+\.?)+$`).MatchString(ver) { + ver = "" + } + return +} + +// 清除用户操作日志 +func (ua *UserActLogProcess) ClearUserActLog(ts string) (int64, error) { + affected, err := xdb.Where("created_at < '" + ts + "'").Delete(&UserActLog{}) + return affected, err +} + +// 后台筛选用户操作日志 +func (ua *UserActLogProcess) GetSession(values url.Values) *xorm.Session { + session := xdb.Where("1=1") + if values.Get("username") != "" { + session.And("username = ?", values.Get("username")) + } + if values.Get("sdate") != "" { + session.And("created_at >= ?", values.Get("sdate")+" 00:00:00'") + } + if values.Get("edate") != "" { + session.And("created_at <= ?", values.Get("edate")+" 23:59:59'") + } + if values.Get("status") != "" { + session.And("status = ?", cast.ToUint8(values.Get("status"))-1) + } + if values.Get("os") != "" { + session.And("os = ?", cast.ToUint8(values.Get("os"))-1) + } + if values.Get("sort") == "1" { + session.OrderBy("id desc") + } else { + session.OrderBy("id asc") + } + return session +} + +// 截取字符串 +func substr(s string, pos, length int) string { + runes := []rune(s) + l := pos + length + if l > len(runes) { + l = len(runes) + } + return string(runes[pos:l]) +} diff --git a/anylink/server/dbdata/user_act_log_test.go b/anylink/server/dbdata/user_act_log_test.go new file mode 100644 index 0000000..1e2b25f --- /dev/null +++ b/anylink/server/dbdata/user_act_log_test.go @@ -0,0 +1,82 @@ +package dbdata + +import "testing" + +func TestParseUserAgent(t *testing.T) { + type args struct { + userAgent string + } + type res struct { + os_idx uint8 + client_idx uint8 + ver string + } + tests := []struct { + name string + args args + want res + }{ + { + name: "mac os 1", + args: args{userAgent: "cisco anyconnect vpn agent for mac os x 4.10.05085"}, + want: res{os_idx: 2, client_idx: 1, ver: "4.10.05085"}, + }, + { + name: "mac os 2", + args: args{userAgent: "anyconnect darwin_i386 4.10.05085"}, + want: res{os_idx: 2, client_idx: 1, ver: "4.10.05085"}, + }, + { + name: "windows", + args: args{userAgent: "cisco anyconnect vpn agent for windows 4.8.02042"}, + want: res{os_idx: 1, client_idx: 1, ver: "4.8.02042"}, + }, + { + name: "iPad", + args: args{userAgent: "anyconnect applesslvpn_darwin_arm (ipad) 4.10.04060"}, + want: res{os_idx: 5, client_idx: 1, ver: "4.10.04060"}, + }, + { + name: "iPhone", + args: args{userAgent: "cisco anyconnect vpn agent for apple iphone 4.10.04060"}, + want: res{os_idx: 5, client_idx: 1, ver: "4.10.04060"}, + }, + { + name: "android", + args: args{userAgent: "anyconnect android 4.10.05096"}, + want: res{os_idx: 4, client_idx: 1, ver: "4.10.05096"}, + }, + { + name: "linux", + args: args{userAgent: "cisco anyconnect vpn agent for linux v7.08"}, + want: res{os_idx: 3, client_idx: 1, ver: "7.08"}, + }, + { + name: "openconnect", + args: args{userAgent: "openconnect-gui 1.5.3 v7.08"}, + want: res{os_idx: 0, client_idx: 2, ver: "7.08"}, + }, + { + name: "unknown", + args: args{userAgent: "unknown 1.4.3 aabcd"}, + want: res{os_idx: 0, client_idx: 0, ver: ""}, + }, + { + name: "unknown 2", + args: args{userAgent: ""}, + want: res{os_idx: 0, client_idx: 0, ver: ""}, + }, + { + name: "anylink", + args: args{userAgent: "anylink vpn agent for linux v1.0"}, + want: res{os_idx: 3, client_idx: 3, ver: "1.0"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if os_idx, client_idx, ver := UserActLogIns.ParseUserAgent(tt.args.userAgent); os_idx != tt.want.os_idx || client_idx != tt.want.client_idx || ver != tt.want.ver { + t.Errorf("ParseUserAgent() = %v, %v, %v, want %v, %v, %v", os_idx, client_idx, ver, tt.want.os_idx, tt.want.client_idx, tt.want.ver) + } + }) + } +} diff --git a/anylink/server/dbdata/user_test.go b/anylink/server/dbdata/user_test.go index d9f3a69..fea4735 100644 --- a/anylink/server/dbdata/user_test.go +++ b/anylink/server/dbdata/user_test.go @@ -70,13 +70,14 @@ func TestCheckUser(t *testing.T) { authData = map[string]interface{}{ "type": "ldap", "ldap": map[string]interface{}{ - "addr": "192.168.8.12:389", - "tls": true, - "bind_name": "userfind@abc.com", - "bind_pwd": "afdbfdsafds", - "base_dn": "dc=abc,dc=com", - "search_attr": "sAMAccountName", - "member_of": "cn=vpn,cn=user,dc=abc,dc=com", + "addr": "192.168.8.12:389", + "tls": true, + "bind_name": "userfind@abc.com", + "bind_pwd": "afdbfdsafds", + "base_dn": "dc=abc,dc=com", + "object_class": "person", + "search_attr": "sAMAccountName", + "member_of": "cn=vpn,cn=user,dc=abc,dc=com", }, } g3 := Group{Name: group3, Status: 1, ClientDns: dns, RouteInclude: route, Auth: authData} diff --git a/anylink/server/dbdata/userauth_ldap.go b/anylink/server/dbdata/userauth_ldap.go index 1ade05f..e7a5f1a 100644 --- a/anylink/server/dbdata/userauth_ldap.go +++ b/anylink/server/dbdata/userauth_ldap.go @@ -8,19 +8,21 @@ import ( "net" "reflect" "regexp" + "strconv" "time" "github.com/go-ldap/ldap" ) type AuthLdap struct { - Addr string `json:"addr"` - Tls bool `json:"tls"` - BindName string `json:"bind_name"` - BindPwd string `json:"bind_pwd"` - BaseDn string `json:"base_dn"` - SearchAttr string `json:"search_attr"` - MemberOf string `json:"member_of"` + Addr string `json:"addr"` + Tls bool `json:"tls"` + BindName string `json:"bind_name"` + BindPwd string `json:"bind_pwd"` + BaseDn string `json:"base_dn"` + ObjectClass string `json:"object_class"` + SearchAttr string `json:"search_attr"` + MemberOf string `json:"member_of"` } func init() { @@ -39,7 +41,7 @@ func (auth AuthLdap) checkData(authData map[string]interface{}) error { return errors.New("LDAP的服务器地址(含端口)填写有误") } if auth.BindName == "" { - return errors.New("LDAP的管理员账号不能为空") + return errors.New("LDAP的管理员 DN不能为空") } if auth.BindPwd == "" { return errors.New("LDAP的管理员密码不能为空") @@ -47,6 +49,9 @@ func (auth AuthLdap) checkData(authData map[string]interface{}) error { if auth.BaseDn == "" || !ValidateDN(auth.BaseDn) { return errors.New("LDAP的Base DN填写有误") } + if auth.ObjectClass == "" { + return errors.New("LDAP的用户对象类填写有误") + } if auth.SearchAttr == "" { return errors.New("LDAP的用户唯一ID不能为空") } @@ -93,9 +98,12 @@ func (auth AuthLdap) checkUser(name, pwd string, g *Group) error { } err = l.Bind(auth.BindName, auth.BindPwd) if err != nil { - return fmt.Errorf("%s LDAP 管理员账号或密码填写有误 %s", name, err.Error()) + return fmt.Errorf("%s LDAP 管理员 DN或密码填写有误 %s", name, err.Error()) } - filterAttr := "(objectClass=person)" + if auth.ObjectClass == "" { + auth.ObjectClass = "person" + } + filterAttr := "(objectClass=" + auth.ObjectClass + ")" filterAttr += "(" + auth.SearchAttr + "=" + name + ")" if auth.MemberOf != "" { filterAttr += "(memberOf:=" + auth.MemberOf + ")" @@ -117,6 +125,10 @@ func (auth AuthLdap) checkUser(name, pwd string, g *Group) error { } return fmt.Errorf("LDAP发现 %s 用户,存在多个账号", name) } + err = parseEntries(sr) + if err != nil { + return fmt.Errorf("LDAP %s 用户 %s", name, err.Error()) + } userDN := sr.Entries[0].DN err = l.Bind(userDN, pwd) if err != nil { @@ -125,6 +137,32 @@ func (auth AuthLdap) checkUser(name, pwd string, g *Group) error { return nil } +func parseEntries(sr *ldap.SearchResult) error { + for _, attr := range sr.Entries[0].Attributes { + switch attr.Name { + case "shadowExpire": + // -1 启用, 1 停用, >1 从1970-01-01至到期日的天数 + val, _ := strconv.ParseInt(attr.Values[0], 10, 64) + if val == -1 { + return nil + } + if val == 1 { + return fmt.Errorf("账号已停用") + } + if val > 1 { + expireTime := time.Unix(val*86400, 0) + t := time.Date(expireTime.Year(), expireTime.Month(), expireTime.Day(), 23, 59, 59, 0, time.Local) + if t.Before(time.Now()) { + return fmt.Errorf("账号已过期(过期日期: %s)", t.Format("2006-01-02")) + } + return nil + } + return fmt.Errorf("账号shadowExpire值异常: %d", val) + } + } + return nil +} + func ValidateDomainPort(addr string) bool { re := regexp.MustCompile(`^([a-zA-Z0-9][-a-zA-Z0-9]{0,62}\.)+[A-Za-z]{2,18}\:([0-9]|[1-9]\d{1,3}|[1-5]\d{4}|6[0-5]{2}[0-3][0-5])$`) return re.MatchString(addr) diff --git a/anylink/server/go.mod b/anylink/server/go.mod index acc9e92..ac5cd6c 100644 --- a/anylink/server/go.mod +++ b/anylink/server/go.mod @@ -1,75 +1,104 @@ module github.com/bjdgyc/anylink -go 1.18 +go 1.19 require ( github.com/arl/statsviz v0.5.1 + github.com/deckarep/golang-set v1.8.0 + github.com/go-acme/lego/v4 v4.10.2 github.com/go-co-op/gocron v1.17.0 github.com/go-ldap/ldap v3.0.3+incompatible github.com/go-sql-driver/mysql v1.6.0 github.com/gocarina/gocsv v0.0.0-20220712153207-8b2118da4570 - github.com/golang-jwt/jwt/v4 v4.0.0 + github.com/golang-jwt/jwt/v4 v4.2.0 github.com/google/gopacket v1.1.19 github.com/gorilla/handlers v1.5.1 github.com/gorilla/mux v1.8.0 github.com/ivpusic/grpool v1.0.0 + github.com/lanrenwo/lzsgo v0.0.2 github.com/lib/pq v1.10.2 - github.com/mattn/go-sqlite3 v1.14.8 + github.com/mattn/go-sqlite3 v1.14.9 github.com/orcaman/concurrent-map v1.0.0 - github.com/pion/dtls/v2 v2.1.5 + github.com/pion/dtls/v2 v2.2.6 github.com/pion/logging v0.2.2 + github.com/pires/go-proxyproto v0.6.2 github.com/shirou/gopsutil v3.21.7+incompatible github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 + github.com/spf13/cast v1.3.1 github.com/spf13/cobra v1.2.1 github.com/spf13/viper v1.8.1 - github.com/stretchr/testify v1.8.0 + github.com/stretchr/testify v1.8.1 github.com/xhit/go-simple-mail/v2 v2.10.0 github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119 + github.com/xuri/excelize/v2 v2.6.1 go.uber.org/atomic v1.10.0 - golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f - golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 - golang.org/x/text v0.3.7 - golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac + golang.org/x/crypto v0.5.0 + golang.org/x/net v0.7.0 + golang.org/x/text v0.7.0 + golang.org/x/time v0.3.0 layeh.com/radius v0.0.0-20210819152912-ad72663a72ab - xorm.io/xorm v1.2.2 + xorm.io/xorm v1.3.2 +) + +require ( + github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 // indirect + github.com/cenkalti/backoff/v4 v4.2.0 // indirect + github.com/cloudflare/cloudflare-go v0.49.0 // indirect + github.com/felixge/httpsnoop v1.0.1 // indirect + github.com/go-jose/go-jose/v3 v3.0.0 // indirect + github.com/google/go-querystring v1.1.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-retryablehttp v0.7.1 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/miekg/dns v1.1.50 // indirect + github.com/pion/transport/v2 v2.0.2 // indirect + github.com/pion/udp/v2 v2.0.1 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 // indirect + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 // indirect + golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect + golang.org/x/tools v0.1.12 // indirect ) require ( github.com/StackExchange/wmi v1.2.1 // indirect + github.com/coreos/go-iptables v0.6.0 github.com/davecgh/go-spew v1.1.1 // indirect - github.com/felixge/httpsnoop v1.0.1 // indirect - github.com/fsnotify/fsnotify v1.4.9 // indirect + github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/go-ole/go-ole v1.2.5 // indirect - github.com/goccy/go-json v0.7.4 // indirect - github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect + github.com/goccy/go-json v0.8.1 // indirect + github.com/golang/snappy v0.0.4 // indirect github.com/gorilla/websocket v1.4.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect - github.com/json-iterator/go v1.1.11 // indirect + github.com/json-iterator/go v1.1.12 // indirect github.com/magiconair/properties v1.8.5 // indirect - github.com/mitchellh/mapstructure v1.4.1 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/pelletier/go-toml v1.9.3 // indirect - github.com/pion/transport v0.13.0 // indirect - github.com/pion/udp v0.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/richardlehane/mscfb v1.0.4 // indirect + github.com/richardlehane/msoleps v1.0.3 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect github.com/spf13/afero v1.6.0 // indirect - github.com/spf13/cast v1.3.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.2.0 // indirect github.com/syndtr/goleveldb v1.0.0 // indirect github.com/tklauser/go-sysconf v0.3.7 // indirect github.com/tklauser/numcpus v0.2.3 // indirect - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect - golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect + github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 // indirect + github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 // indirect + golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect + golang.org/x/sys v0.5.0 // indirect gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect - gopkg.in/ini.v1 v1.62.0 // indirect + gopkg.in/ini.v1 v1.66.6 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - xorm.io/builder v0.3.9 // indirect + xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978 // indirect ) diff --git a/anylink/server/go.sum b/anylink/server/go.sum index aa2f83a..89e6dfb 100644 --- a/anylink/server/go.sum +++ b/anylink/server/go.sum @@ -39,6 +39,7 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU= +gitee.com/travelliu/dm v1.8.11192/go.mod h1:DHTzyhCrM843x9VdKVbZ+GKXGRbKM2sJ4LxihRxShkE= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= @@ -53,6 +54,8 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 h1:J45/QHgrzUdqe/Vco/Vxk0wRvdS2nKUxmf/zLgvfass= +github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755/go.mod h1:RcDobYh8k5VP6TNybz9m++gL3ijVI5wueVr0EM10VsU= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= @@ -72,6 +75,8 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4= +github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -79,12 +84,16 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudflare/cloudflare-go v0.49.0 h1:KqJYk/YQ5ZhmyYz1oa4kGDskfF1gVuZfqesaJ/XDLto= +github.com/cloudflare/cloudflare-go v0.49.0/go.mod h1:h0QgcIZ3qEXwFiwfBO8sQxjVdYsLX+PfD7NFEnANaKg= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/coreos/go-iptables v0.6.0 h1:is9qnZMPYjLd8LYqmm/qlE+wwEgJIkTYdhV3rfZo4jk= +github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= @@ -95,9 +104,12 @@ github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfc github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4= +github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= @@ -115,19 +127,25 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-acme/lego/v4 v4.10.2 h1:5eW3qmda5v/LP21v1Hj70edKY1jeFZQwO617tdkwp6Q= +github.com/go-acme/lego/v4 v4.10.2/go.mod h1:EMbf0Jmqwv94nJ5WL9qWnSXIBZnvsS9gNypansHGc6U= github.com/go-co-op/gocron v1.17.0 h1:IixLXsti+Qo0wMvmn6Kmjp2csk2ykpkcL+EmHmST18w= github.com/go-co-op/gocron v1.17.0/go.mod h1:IpDBSaJOVfFw7hXZuTag3SCSkqazXBBUkbQ1m1aesBs= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo= +github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= @@ -144,8 +162,8 @@ github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gocarina/gocsv v0.0.0-20220712153207-8b2118da4570 h1:n4E8KiBgNvYdtjgJbAqKov2IFv7tDkULV/2Ld3wj5Hg= github.com/gocarina/gocsv v0.0.0-20220712153207-8b2118da4570/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI= -github.com/goccy/go-json v0.7.4 h1:B44qRUFwz/vxPKPISQ1KhvzRi9kZ28RAf6YtjriBZ5k= -github.com/goccy/go-json v0.7.4/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.8.1 h1:4/Wjm0JIJaTDm8K1KcGrLHJoa8EsJ13YWeX+6Kfq6uI= +github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= @@ -154,8 +172,9 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v4 v4.0.0 h1:RAqyYixv1p7uEnocuy8P1nru5wprCh/MH2BIlW5z5/o= -github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= +github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU= +github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -187,8 +206,10 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -202,6 +223,9 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= @@ -222,9 +246,10 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= @@ -246,9 +271,15 @@ github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyN github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ= +github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= @@ -322,15 +353,20 @@ github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dv github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= @@ -342,12 +378,15 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= -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.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lanrenwo/lzsgo v0.0.2 h1:FA30LAaJFYLoaM17b+H32gA+5H+abjoomNLSA9HCbrI= +github.com/lanrenwo/lzsgo v0.0.2/go.mod h1:oxDZy2vgi6VBGIdvL80ayRMtIyXV+TbjavVuINXZY2k= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -363,20 +402,22 @@ github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaO github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxzIU= -github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA= +github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= +github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= @@ -384,14 +425,18 @@ github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS4 github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= @@ -427,17 +472,20 @@ github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCko github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pion/dtls/v2 v2.1.5 h1:jlh2vtIyUBShchoTDqpCCqiYCyRFJ/lvf/gQ8TALs+c= -github.com/pion/dtls/v2 v2.1.5/go.mod h1:BqCE7xPZbPSubGasRoDFJeTsyJtdD1FanJYL0JGheqY= +github.com/pion/dtls/v2 v2.2.6 h1:yXMxKr0Skd+Ub6A8UqXTRLSywskx93ooMRHsQUtd+Z4= +github.com/pion/dtls/v2 v2.2.6/go.mod h1:t8fWJCIquY5rlQZwA2yWxUS1+OCrAdXrhVKXB5oD/wY= github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= -github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q= -github.com/pion/transport v0.13.0 h1:KWTA5ZrQogizzYwPEciGtHPLwpAjE91FgXnyu+Hv2uY= -github.com/pion/transport v0.13.0/go.mod h1:yxm9uXpK9bpBBWkITk13cLo1y5/ur5VQpG22ny6EP7g= -github.com/pion/udp v0.1.1 h1:8UAPvyqmsxK8oOjloDk4wUt63TzFe9WEJkg5lChlj7o= -github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M= +github.com/pion/transport/v2 v2.0.2 h1:St+8o+1PEzPT51O9bv+tH/KYYLMNR5Vwm5Z3Qkjsywg= +github.com/pion/transport/v2 v2.0.2/go.mod h1:vrz6bUbFr/cjdwbnxq8OdDDzHf7JJfGsIRkxfpZoTA0= +github.com/pion/udp/v2 v2.0.1 h1:xP0z6WNux1zWEjhC7onRA3EwwSliXqu1ElUZAQhUP54= +github.com/pion/udp/v2 v2.0.1/go.mod h1:B7uvTMP00lzWdyMr/1PVZXtV3wpPIxBRd4Wl6AksXn8= +github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8= +github.com/pires/go-proxyproto v0.6.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -462,11 +510,17 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM= +github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk= +github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= +github.com/richardlehane/msoleps v1.0.3 h1:aznSZzrwYRl3rLKRT3gUk9am7T/mLNSnJINvN0AQoVM= +github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= @@ -486,9 +540,7 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 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/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 h1:1zN6ImoqhSJhN8hGXFaJlSC8msLmIbX8bFqOfWLKw0w= @@ -517,6 +569,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -524,12 +577,17 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 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/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 h1:mmz27tVi2r70JYnm5y0Zk8w0Qzsx+vfUw3oqSyrEfP8= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 h1:g9SWTaTy/rEuhMErC2jWq9Qt5ci+jBYSvXnJsLq4adg= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490/go.mod h1:l9q4vc1QiawUB1m3RU+87yLvrrxe54jc0w/kEl4DbSQ= github.com/tklauser/go-sysconf v0.3.7 h1:HT7h4+536gjqeq1ZIJPgOl1rg1XFatQGVZWp7Py53eg= github.com/tklauser/go-sysconf v0.3.7/go.mod h1:JZIdXh4RmBvZDBZ41ld2bGxRV3n4daiiqA3skYhAoQ4= github.com/tklauser/numcpus v0.2.3 h1:nQ0QYpiritP6ViFhrKYsiv6VVxOpum2Gks5GhnJbS/8= @@ -542,11 +600,18 @@ github.com/xhit/go-simple-mail/v2 v2.10.0/go.mod h1:kA1XbQfCI4JxQ9ccSN6VFyIEkkug github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 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/xuri/efp v0.0.0-20220603152613-6918739fd470 h1:6932x8ltq1w4utjmfMPVj09jdMlkY0aiA6+Skbtl3/c= +github.com/xuri/efp v0.0.0-20220603152613-6918739fd470/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= +github.com/xuri/excelize/v2 v2.6.1 h1:ICBdtw803rmhLN3zfvyEGH3cwSmZv+kde7LhTDT659k= +github.com/xuri/excelize/v2 v2.6.1/go.mod h1:tL+0m6DNwSXj/sILHbQTYsLi9IF4TW59H2EF3Yrx1AU= +github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 h1:OAmKAfT06//esDdpi/DZ8Qsdt4+M5+ltca05dA5bG2M= +github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= @@ -596,8 +661,10 @@ golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f h1:OeJjE6G4dgCY4PIXvIRQbE8+RX+uXZyGhUy/ksMGJoc= -golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= +golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -610,6 +677,8 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9 h1:LRtI4W37N+KFebI/qV0OFiLUv4GLOWeEW5hn/KEJvxE= +golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -632,8 +701,9 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -671,16 +741,18 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA= -golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -703,8 +775,9 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -754,7 +827,6 @@ golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -765,11 +837,20 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/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.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -778,14 +859,16 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -844,14 +927,15 @@ golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/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-20191204190536-9bdfabe68543/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/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= @@ -967,16 +1051,18 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM= gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= -gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.66.6 h1:LATuAqN/shcYAOkv3wl2L4rkaKqkcgTBQjOyYDvcPKI= +gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= @@ -990,6 +1076,7 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1004,37 +1091,119 @@ layeh.com/radius v0.0.0-20210819152912-ad72663a72ab h1:05KeMI4s7jEdIfHb7QCjUr5X2 layeh.com/radius v0.0.0-20210819152912-ad72663a72ab/go.mod h1:pFWM9De99EY9TPVyHIyA56QmoRViVck/x41WFkUlc9A= lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU= lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= -modernc.org/cc/v3 v3.33.6 h1:r63dgSzVzRxUpAJFPQWHy1QeZeY1ydNENUDaBx1GqYc= modernc.org/cc/v3 v3.33.6/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= -modernc.org/ccgo/v3 v3.9.5 h1:dEuUSf8WN51rDkprFuAqjfchKEzN0WttP/Py3enBwjk= +modernc.org/cc/v3 v3.33.9/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.33.11/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.34.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.4/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.5/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.7/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.8/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.10/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.15/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.16/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.17/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.18 h1:rMZhRcWrba0y3nVmdiQ7kxAgOOSq2m2f2VzjHLgEs6U= +modernc.org/cc/v3 v3.35.18/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= modernc.org/ccgo/v3 v3.9.5/go.mod h1:umuo2EP2oDSBnD3ckjaVUXMrmeAw8C8OSICVa0iFf60= +modernc.org/ccgo/v3 v3.10.0/go.mod h1:c0yBmkRFi7uW4J7fwx/JiijwOjeAeR2NoSaRVFPmjMw= +modernc.org/ccgo/v3 v3.11.0/go.mod h1:dGNposbDp9TOZ/1KBxghxtUp/bzErD0/0QW4hhSaBMI= +modernc.org/ccgo/v3 v3.11.1/go.mod h1:lWHxfsn13L3f7hgGsGlU28D9eUOf6y3ZYHKoPaKU0ag= +modernc.org/ccgo/v3 v3.11.3/go.mod h1:0oHunRBMBiXOKdaglfMlRPBALQqsfrCKXgw9okQ3GEw= +modernc.org/ccgo/v3 v3.12.4/go.mod h1:Bk+m6m2tsooJchP/Yk5ji56cClmN6R1cqc9o/YtbgBQ= +modernc.org/ccgo/v3 v3.12.6/go.mod h1:0Ji3ruvpFPpz+yu+1m0wk68pdr/LENABhTrDkMDWH6c= +modernc.org/ccgo/v3 v3.12.8/go.mod h1:Hq9keM4ZfjCDuDXxaHptpv9N24JhgBZmUG5q60iLgUo= +modernc.org/ccgo/v3 v3.12.11/go.mod h1:0jVcmyDwDKDGWbcrzQ+xwJjbhZruHtouiBEvDfoIsdg= +modernc.org/ccgo/v3 v3.12.14/go.mod h1:GhTu1k0YCpJSuWwtRAEHAol5W7g1/RRfS4/9hc9vF5I= +modernc.org/ccgo/v3 v3.12.18/go.mod h1:jvg/xVdWWmZACSgOiAhpWpwHWylbJaSzayCqNOJKIhs= +modernc.org/ccgo/v3 v3.12.20/go.mod h1:aKEdssiu7gVgSy/jjMastnv/q6wWGRbszbheXgWRHc8= +modernc.org/ccgo/v3 v3.12.21/go.mod h1:ydgg2tEprnyMn159ZO/N4pLBqpL7NOkJ88GT5zNU2dE= +modernc.org/ccgo/v3 v3.12.22/go.mod h1:nyDVFMmMWhMsgQw+5JH6B6o4MnZ+UQNw1pp52XYFPRk= +modernc.org/ccgo/v3 v3.12.25/go.mod h1:UaLyWI26TwyIT4+ZFNjkyTbsPsY3plAEB6E7L/vZV3w= +modernc.org/ccgo/v3 v3.12.29/go.mod h1:FXVjG7YLf9FetsS2OOYcwNhcdOLGt8S9bQ48+OP75cE= +modernc.org/ccgo/v3 v3.12.36/go.mod h1:uP3/Fiezp/Ga8onfvMLpREq+KUjUmYMxXPO8tETHtA8= +modernc.org/ccgo/v3 v3.12.38/go.mod h1:93O0G7baRST1vNj4wnZ49b1kLxt0xCW5Hsa2qRaZPqc= +modernc.org/ccgo/v3 v3.12.43/go.mod h1:k+DqGXd3o7W+inNujK15S5ZYuPoWYLpF5PYougCmthU= +modernc.org/ccgo/v3 v3.12.46/go.mod h1:UZe6EvMSqOxaJ4sznY7b23/k13R8XNlyWsO5bAmSgOE= +modernc.org/ccgo/v3 v3.12.47/go.mod h1:m8d6p0zNps187fhBwzY/ii6gxfjob1VxWb919Nk1HUk= +modernc.org/ccgo/v3 v3.12.50/go.mod h1:bu9YIwtg+HXQxBhsRDE+cJjQRuINuT9PUK4orOco/JI= +modernc.org/ccgo/v3 v3.12.51/go.mod h1:gaIIlx4YpmGO2bLye04/yeblmvWEmE4BBBls4aJXFiE= +modernc.org/ccgo/v3 v3.12.53/go.mod h1:8xWGGTFkdFEWBEsUmi+DBjwu/WLy3SSOrqEmKUjMeEg= +modernc.org/ccgo/v3 v3.12.54/go.mod h1:yANKFTm9llTFVX1FqNKHE0aMcQb1fuPJx6p8AcUx+74= +modernc.org/ccgo/v3 v3.12.55/go.mod h1:rsXiIyJi9psOwiBkplOaHye5L4MOOaCjHg1Fxkj7IeU= +modernc.org/ccgo/v3 v3.12.56/go.mod h1:ljeFks3faDseCkr60JMpeDb2GSO3TKAmrzm7q9YOcMU= +modernc.org/ccgo/v3 v3.12.57/go.mod h1:hNSF4DNVgBl8wYHpMvPqQWDQx8luqxDnNGCMM4NFNMc= +modernc.org/ccgo/v3 v3.12.60/go.mod h1:k/Nn0zdO1xHVWjPYVshDeWKqbRWIfif5dtsIOCUVMqM= +modernc.org/ccgo/v3 v3.12.65/go.mod h1:D6hQtKxPNZiY6wDBtehSGKFKmyXn53F8nGTpH+POmS4= +modernc.org/ccgo/v3 v3.12.66/go.mod h1:jUuxlCFZTUZLMV08s7B1ekHX5+LIAurKTTaugUr/EhQ= +modernc.org/ccgo/v3 v3.12.67/go.mod h1:Bll3KwKvGROizP2Xj17GEGOTrlvB1XcVaBrC90ORO84= +modernc.org/ccgo/v3 v3.12.73/go.mod h1:hngkB+nUUqzOf3iqsM48Gf1FZhY599qzVg1iX+BT3cQ= +modernc.org/ccgo/v3 v3.12.81/go.mod h1:p2A1duHoBBg1mFtYvnhAnQyI6vL0uw5PGYLSIgF6rYY= +modernc.org/ccgo/v3 v3.12.82 h1:wudcnJyjLj1aQQCXF3IM9Gz2X6UNjw+afIghzdtn0v8= +modernc.org/ccgo/v3 v3.12.82/go.mod h1:ApbflUfa5BKadjHynCficldU1ghjen84tuM5jRynB7w= +modernc.org/ccorpus v1.11.1/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= -modernc.org/libc v1.7.13-0.20210308123627-12f642a52bb8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w= modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w= -modernc.org/libc v1.9.11 h1:QUxZMs48Ahg2F7SN41aERvMfGLY2HU/ADnB9DC4Yts8= modernc.org/libc v1.9.11/go.mod h1:NyF3tsA5ArIjJ83XB0JlqhjTabTCHm9aX4XMPHyQn0Q= +modernc.org/libc v1.11.0/go.mod h1:2lOfPmj7cz+g1MrPNmX65QCzVxgNq2C5o0jdLY2gAYg= +modernc.org/libc v1.11.2/go.mod h1:ioIyrl3ETkugDO3SGZ+6EOKvlP3zSOycUETe4XM4n8M= +modernc.org/libc v1.11.5/go.mod h1:k3HDCP95A6U111Q5TmG3nAyUcp3kR5YFZTeDS9v8vSU= +modernc.org/libc v1.11.6/go.mod h1:ddqmzR6p5i4jIGK1d/EiSw97LBcE3dK24QEwCFvgNgE= +modernc.org/libc v1.11.11/go.mod h1:lXEp9QOOk4qAYOtL3BmMve99S5Owz7Qyowzvg6LiZso= +modernc.org/libc v1.11.13/go.mod h1:ZYawJWlXIzXy2Pzghaf7YfM8OKacP3eZQI81PDLFdY8= +modernc.org/libc v1.11.16/go.mod h1:+DJquzYi+DMRUtWI1YNxrlQO6TcA5+dRRiq8HWBWRC8= +modernc.org/libc v1.11.19/go.mod h1:e0dgEame6mkydy19KKaVPBeEnyJB4LGNb0bBH1EtQ3I= +modernc.org/libc v1.11.24/go.mod h1:FOSzE0UwookyT1TtCJrRkvsOrX2k38HoInhw+cSCUGk= +modernc.org/libc v1.11.26/go.mod h1:SFjnYi9OSd2W7f4ct622o/PAYqk7KHv6GS8NZULIjKY= +modernc.org/libc v1.11.27/go.mod h1:zmWm6kcFXt/jpzeCgfvUNswM0qke8qVwxqZrnddlDiE= +modernc.org/libc v1.11.28/go.mod h1:Ii4V0fTFcbq3qrv3CNn+OGHAvzqMBvC7dBNyC4vHZlg= +modernc.org/libc v1.11.31/go.mod h1:FpBncUkEAtopRNJj8aRo29qUiyx5AvAlAxzlx9GNaVM= +modernc.org/libc v1.11.34/go.mod h1:+Tzc4hnb1iaX/SKAutJmfzES6awxfU1BPvrrJO0pYLg= +modernc.org/libc v1.11.37/go.mod h1:dCQebOwoO1046yTrfUE5nX1f3YpGZQKNcITUYWlrAWo= +modernc.org/libc v1.11.39/go.mod h1:mV8lJMo2S5A31uD0k1cMu7vrJbSA3J3waQJxpV4iqx8= +modernc.org/libc v1.11.42/go.mod h1:yzrLDU+sSjLE+D4bIhS7q1L5UwXDOw99PLSX0BlZvSQ= +modernc.org/libc v1.11.44/go.mod h1:KFq33jsma7F5WXiYelU8quMJasCCTnHK0mkri4yPHgA= +modernc.org/libc v1.11.45/go.mod h1:Y192orvfVQQYFzCNsn+Xt0Hxt4DiO4USpLNXBlXg/tM= +modernc.org/libc v1.11.47/go.mod h1:tPkE4PzCTW27E6AIKIR5IwHAQKCAtudEIeAV1/SiyBg= +modernc.org/libc v1.11.49/go.mod h1:9JrJuK5WTtoTWIFQ7QjX2Mb/bagYdZdscI3xrvHbXjE= +modernc.org/libc v1.11.51/go.mod h1:R9I8u9TS+meaWLdbfQhq2kFknTW0O3aw3kEMqDDxMaM= +modernc.org/libc v1.11.53/go.mod h1:5ip5vWYPAoMulkQ5XlSJTy12Sz5U6blOQiYasilVPsU= +modernc.org/libc v1.11.54/go.mod h1:S/FVnskbzVUrjfBqlGFIPA5m7UwB3n9fojHhCNfSsnw= +modernc.org/libc v1.11.55/go.mod h1:j2A5YBRm6HjNkoSs/fzZrSxCuwWqcMYTDPLNx0URn3M= +modernc.org/libc v1.11.56/go.mod h1:pakHkg5JdMLt2OgRadpPOTnyRXm/uzu+Yyg/LSLdi18= +modernc.org/libc v1.11.58/go.mod h1:ns94Rxv0OWyoQrDqMFfWwka2BcaF6/61CqJRK9LP7S8= +modernc.org/libc v1.11.70/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw= +modernc.org/libc v1.11.71/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw= +modernc.org/libc v1.11.75/go.mod h1:dGRVugT6edz361wmD9gk6ax1AbDSe0x5vji0dGJiPT0= +modernc.org/libc v1.11.82/go.mod h1:NF+Ek1BOl2jeC7lw3a7Jj5PWyHPwWD4aq3wVKxqV1fI= +modernc.org/libc v1.11.86/go.mod h1:ePuYgoQLmvxdNT06RpGnaDKJmDNEkV7ZPKI2jnsvZoE= +modernc.org/libc v1.11.87 h1:PzIzOqtlzMDDcCzJ5cUP6h/Ku6Fa9iyflP2ccTY64aE= +modernc.org/libc v1.11.87/go.mod h1:Qvd5iXTeLhI5PS0XSyqMY99282y+3euapQFxM7jYnpY= modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/mathutil v1.4.0 h1:GCjoRaBew8ECCKINQA2nYjzvufFW9YiEuuB+rQ9bn2E= modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/memory v1.0.4 h1:utMBrFcpnQDdNsmM6asmyH/FM9TqLPS7XF7otpJmrwM= +modernc.org/mathutil v1.4.1 h1:ij3fYGe8zBF4Vu+g0oT7mB06r8sqGWKuJu1yXeR4by8= +modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc= +modernc.org/memory v1.0.5 h1:XRch8trV7GgvTec2i7jc33YlUI0RKVDBvZ5eZ5m8y14= +modernc.org/memory v1.0.5/go.mod h1:B7OYswTRnfGg+4tDH1t1OeUNnsy2viGTdME4tzd+IjM= modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A= modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/sqlite v1.11.2 h1:ShWQpeD3ag/bmx6TqidBlIWonWmQaSQKls3aenCbt+w= -modernc.org/sqlite v1.11.2/go.mod h1:+mhs/P1ONd+6G7hcAs6irwDi/bjTQ7nLW6LHRBsEa3A= +modernc.org/sqlite v1.14.2 h1:ohsW2+e+Qe2To1W6GNezzKGwjXwSax6R+CrhRxVaFbE= +modernc.org/sqlite v1.14.2/go.mod h1:yqfn85u8wVOE6ub5UT8VI9JjhrwBUUCNyTACN0h6Sx8= modernc.org/strutil v1.1.1 h1:xv+J1BXY3Opl2ALrBwyfEikFAj8pmqcpnfmuwUwcozs= modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= -modernc.org/tcl v1.5.5/go.mod h1:ADkaTUuwukkrlhqwERyq0SM8OvyXo7+TjFz7yAF56EI= +modernc.org/tcl v1.8.13/go.mod h1:V+q/Ef0IJaNUSECieLU4o+8IScapxnMyFV6i/7uQlAY= modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk= modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -modernc.org/z v1.0.1/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA= +modernc.org/z v1.2.19/go.mod h1:+ZpP0pc4zz97eukOzW3xagV/lS82IpPN9NGG5pNF9vY= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= -xorm.io/builder v0.3.9 h1:Sd65/LdWyO7LR8+Cbd+e7mm3sK/7U9k0jS3999IDHMc= -xorm.io/builder v0.3.9/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= -xorm.io/xorm v1.2.2 h1:FFBOEvJ++8fYBA9cywf2sxDVmFktl1SpJzTAG1ab06Y= -xorm.io/xorm v1.2.2/go.mod h1:fTG8tSjk6O1BYxwuohZUK+S1glnRycsCF05L1qQyEU0= +xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978 h1:bvLlAPW1ZMTWA32LuZMBEGHAUOcATZjzHcotf3SWweM= +xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= +xorm.io/xorm v1.3.2 h1:uTRRKF2jYzbZ5nsofXVUx6ncMaek+SHjWYtCXyZo1oM= +xorm.io/xorm v1.3.2/go.mod h1:9NbjqdnjX6eyjRRhh01GHm64r6N9shTb/8Ak3YRt8Nw= diff --git a/anylink/server/handler/dtls.go b/anylink/server/handler/dtls.go index 7501246..56e54a9 100644 --- a/anylink/server/handler/dtls.go +++ b/anylink/server/handler/dtls.go @@ -66,9 +66,13 @@ func startDtls() { go func() { // time.Sleep(1 * time.Second) cc := conn.(*dtls.Conn) - sessid := hex.EncodeToString(cc.ConnectionState().SessionID) - sess := sessdata.Dtls2Sess(sessid) - LinkDtls(conn, sess.CSess) + did := hex.EncodeToString(cc.ConnectionState().SessionID) + cSess := sessdata.Dtls2CSess(did) + if cSess == nil { + conn.Close() + return + } + LinkDtls(conn, cSess) }() } } diff --git a/anylink/server/handler/link_auth.go b/anylink/server/handler/link_auth.go index 5a8138d..2e9bf8f 100644 --- a/anylink/server/handler/link_auth.go +++ b/anylink/server/handler/link_auth.go @@ -7,6 +7,7 @@ import ( "io" "net" "net/http" + "net/http/httputil" "strings" "text/template" @@ -18,11 +19,16 @@ import ( var profileHash = "" func LinkAuth(w http.ResponseWriter, r *http.Request) { + // TODO 调试信息输出 + if base.GetLogLevel() == base.LogLevelTrace { + hd, _ := httputil.DumpRequest(r, true) + base.Trace("LinkAuth: ", string(hd)) + } // 判断anyconnect客户端 userAgent := strings.ToLower(r.UserAgent()) xAggregateAuth := r.Header.Get("X-Aggregate-Auth") xTranscendVersion := r.Header.Get("X-Transcend-Version") - if !((strings.Contains(userAgent, "anyconnect") || strings.Contains(userAgent, "openconnect")) && + if !((strings.Contains(userAgent, "anyconnect") || strings.Contains(userAgent, "openconnect") || strings.Contains(userAgent, "anylink")) && xAggregateAuth == "1" && xTranscendVersion == "1") { w.WriteHeader(http.StatusForbidden) fmt.Fprintf(w, "error request") @@ -43,7 +49,6 @@ func LinkAuth(w http.ResponseWriter, r *http.Request) { return } // fmt.Printf("%+v \n", cr) - setCommonHeader(w) if cr.Type == "logout" { // 退出删除session信息 @@ -56,7 +61,7 @@ func LinkAuth(w http.ResponseWriter, r *http.Request) { if cr.Type == "init" { w.WriteHeader(http.StatusOK) - data := RequestData{Group: cr.GroupSelect, Groups: dbdata.GetGroupNames()} + data := RequestData{Group: cr.GroupSelect, Groups: dbdata.GetGroupNamesNormal()} tplRequest(tpl_request, w, data) return } @@ -66,16 +71,32 @@ func LinkAuth(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusBadRequest) return } - + // 用户活动日志 + ua := dbdata.UserActLog{ + Username: cr.Auth.Username, + GroupName: cr.GroupSelect, + RemoteAddr: r.RemoteAddr, + Status: dbdata.UserAuthSuccess, + DeviceType: cr.DeviceId.DeviceType, + PlatformVersion: cr.DeviceId.PlatformVersion, + } // TODO 用户密码校验 err = dbdata.CheckUser(cr.Auth.Username, cr.Auth.Password, cr.GroupSelect) if err != nil { base.Warn(err) + ua.Info = err.Error() + ua.Status = dbdata.UserAuthFail + dbdata.UserActLogIns.Add(ua, userAgent) + w.WriteHeader(http.StatusOK) - data := RequestData{Group: cr.GroupSelect, Groups: dbdata.GetGroupNames(), Error: "用户名或密码错误"} + data := RequestData{Group: cr.GroupSelect, Groups: dbdata.GetGroupNamesNormal(), Error: "用户名或密码错误"} + if base.Cfg.DisplayError { + data.Error = err.Error() + } tplRequest(tpl_request, w, data) return } + dbdata.UserActLogIns.Add(ua, userAgent) // if !ok { // w.WriteHeader(http.StatusOK) // data := RequestData{Group: cr.GroupSelect, Groups: base.Cfg.UserGroups, Error: "请先激活用户"} @@ -87,29 +108,38 @@ func LinkAuth(w http.ResponseWriter, r *http.Request) { sess := sessdata.NewSession("") sess.Username = cr.Auth.Username sess.Group = cr.GroupSelect - sess.MacAddr = strings.ToLower(cr.MacAddressList.MacAddress) + oriMac := cr.MacAddressList.MacAddress sess.UniqueIdGlobal = cr.DeviceId.UniqueIdGlobal + sess.UserAgent = userAgent + sess.DeviceType = ua.DeviceType + sess.PlatformVersion = ua.PlatformVersion + sess.RemoteAddr = r.RemoteAddr // 获取客户端mac地址 - macHw, err := net.ParseMAC(sess.MacAddr) + sess.UniqueMac = true + macHw, err := net.ParseMAC(oriMac) if err != nil { var sum [16]byte if sess.UniqueIdGlobal != "" { sum = md5.Sum([]byte(sess.UniqueIdGlobal)) } else { sum = md5.Sum([]byte(sess.Token)) + sess.UniqueMac = false } macHw = sum[0:5] // 5个byte macHw = append([]byte{0x02}, macHw...) sess.MacAddr = macHw.String() } sess.MacHw = macHw + // 统一macAddr的格式 + sess.MacAddr = macHw.String() + other := &dbdata.SettingOther{} _ = dbdata.SettingGet(other) rd := RequestData{SessionId: sess.Sid, SessionToken: sess.Sid + "@" + sess.Token, Banner: other.Banner, ProfileHash: profileHash} w.WriteHeader(http.StatusOK) tplRequest(tpl_complete, w, rd) - base.Debug("login", cr.Auth.Username) + base.Debug("login", cr.Auth.Username, userAgent) } const ( diff --git a/anylink/server/handler/link_base.go b/anylink/server/handler/link_base.go index 17efbb3..1afcc92 100644 --- a/anylink/server/handler/link_base.go +++ b/anylink/server/handler/link_base.go @@ -44,6 +44,7 @@ type macAddressList struct { func setCommonHeader(w http.ResponseWriter) { // Content-Length Date 默认已经存在 + w.Header().Set("Server", "AnyLink") w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Header().Set("Cache-Control", "no-store,no-cache") w.Header().Set("Pragma", "no-cache") diff --git a/anylink/server/handler/link_cstp.go b/anylink/server/handler/link_cstp.go index 97a9fb3..ba947e7 100644 --- a/anylink/server/handler/link_cstp.go +++ b/anylink/server/handler/link_cstp.go @@ -7,6 +7,7 @@ import ( "time" "github.com/bjdgyc/anylink/base" + "github.com/bjdgyc/anylink/dbdata" "github.com/bjdgyc/anylink/pkg/utils" "github.com/bjdgyc/anylink/sessdata" ) @@ -14,7 +15,7 @@ import ( func LinkCstp(conn net.Conn, bufRW *bufio.ReadWriter, cSess *sessdata.ConnSession) { base.Debug("LinkCstp connect ip:", cSess.IpAddr, "user:", cSess.Username, "rip:", conn.RemoteAddr()) defer func() { - base.Debug("LinkCstp return", cSess.IpAddr) + base.Debug("LinkCstp return", cSess.Username, cSess.IpAddr) _ = conn.Close() cSess.Close() }() @@ -33,14 +34,14 @@ func LinkCstp(conn net.Conn, bufRW *bufio.ReadWriter, cSess *sessdata.ConnSessio // 设置超时限制 err = conn.SetReadDeadline(utils.NowSec().Add(dead)) if err != nil { - base.Error("SetDeadline: ", err) + base.Error("SetDeadline: ", cSess.Username, err) return } // hdata := make([]byte, BufferSize) pl := getPayload() n, err = bufRW.Read(pl.Data) if err != nil { - base.Error("read hdata: ", err) + base.Error("read hdata: ", cSess.Username, err) return } @@ -55,7 +56,8 @@ func LinkCstp(conn net.Conn, bufRW *bufio.ReadWriter, cSess *sessdata.ConnSessio // do nothing // base.Debug("recv keepalive", cSess.IpAddr) case 0x05: // DISCONNECT - base.Debug("DISCONNECT", cSess.IpAddr) + cSess.UserLogoutCode = dbdata.UserLogoutClient + base.Debug("DISCONNECT", cSess.Username, cSess.IpAddr) return case 0x03: // DPD-REQ // base.Debug("recv DPD-REQ", cSess.IpAddr) @@ -64,13 +66,28 @@ func LinkCstp(conn net.Conn, bufRW *bufio.ReadWriter, cSess *sessdata.ConnSessio return } case 0x04: - // log.Println("recv DPD-RESP") + // log.Println("recv DPD-RESP") + case 0x08: // decompress + if cSess.CstpPickCmp == nil { + continue + } + dst := getByteFull() + nn, err := cSess.CstpPickCmp.Uncompress(pl.Data[8:], *dst) + if err != nil { + putByte(dst) + base.Error("cstp decompress error", err, nn) + continue + } + binary.BigEndian.PutUint16(pl.Data[4:6], uint16(nn)) + pl.Data = append(pl.Data[:8], (*dst)[:nn]...) + putByte(dst) + fallthrough case 0x00: // DATA // 获取数据长度 dataLen = binary.BigEndian.Uint16(pl.Data[4:6]) // 4,5 // 修复 cstp 数据长度溢出报错 if 8+dataLen > BufferSize { - base.Error("recv error dataLen", dataLen) + base.Error("recv error dataLen", cSess.Username, dataLen) continue } // 去除数据头 @@ -87,7 +104,7 @@ func LinkCstp(conn net.Conn, bufRW *bufio.ReadWriter, cSess *sessdata.ConnSessio func cstpWrite(conn net.Conn, bufRW *bufio.ReadWriter, cSess *sessdata.ConnSession) { defer func() { - base.Debug("cstpWrite return", cSess.IpAddr) + base.Debug("cstpWrite return", cSess.Username, cSess.IpAddr) _ = conn.Close() cSess.Close() }() @@ -110,16 +127,31 @@ func cstpWrite(conn net.Conn, bufRW *bufio.ReadWriter, cSess *sessdata.ConnSessi } if pl.PType == 0x00 { - // 获取数据长度 - l := len(pl.Data) - // 先扩容 +8 - pl.Data = pl.Data[:l+8] - // 数据后移 - copy(pl.Data[8:], pl.Data) - // 添加头信息 - copy(pl.Data[:8], plHeader) - // 更新头长度 - binary.BigEndian.PutUint16(pl.Data[4:6], uint16(l)) + isCompress := false + if cSess.CstpPickCmp != nil && len(pl.Data) > base.Cfg.NoCompressLimit { + dst := getByteFull() + size, err := cSess.CstpPickCmp.Compress(pl.Data, (*dst)[8:]) + if err == nil && size < len(pl.Data) { + copy((*dst)[:8], plHeader) + binary.BigEndian.PutUint16((*dst)[4:6], uint16(size)) + (*dst)[6] = 0x08 + pl.Data = append(pl.Data[:0], (*dst)[:size+8]...) + isCompress = true + } + putByte(dst) + } + if !isCompress { + // 获取数据长度 + l := len(pl.Data) + // 先扩容 +8 + pl.Data = pl.Data[:l+8] + // 数据后移 + copy(pl.Data[8:], pl.Data) + // 添加头信息 + copy(pl.Data[:8], plHeader) + // 更新头长度 + binary.BigEndian.PutUint16(pl.Data[4:6], uint16(l)) + } } else { pl.Data = append(pl.Data[:0], plHeader...) // 设置头类型 @@ -128,7 +160,7 @@ func cstpWrite(conn net.Conn, bufRW *bufio.ReadWriter, cSess *sessdata.ConnSessi n, err = conn.Write(pl.Data) if err != nil { - base.Error("write err", err) + base.Error("write err", cSess.Username, err) return } diff --git a/anylink/server/handler/link_dtls.go b/anylink/server/handler/link_dtls.go index 23c6e86..34fe578 100644 --- a/anylink/server/handler/link_dtls.go +++ b/anylink/server/handler/link_dtls.go @@ -5,6 +5,7 @@ import ( "time" "github.com/bjdgyc/anylink/base" + "github.com/bjdgyc/anylink/dbdata" "github.com/bjdgyc/anylink/pkg/utils" "github.com/bjdgyc/anylink/sessdata" ) @@ -19,7 +20,7 @@ func LinkDtls(conn net.Conn, cSess *sessdata.ConnSession) { } defer func() { - base.Debug("LinkDtls return", cSess.IpAddr) + base.Debug("LinkDtls return", cSess.Username, cSess.IpAddr) _ = conn.Close() dSess.Close() }() @@ -35,14 +36,14 @@ func LinkDtls(conn net.Conn, cSess *sessdata.ConnSession) { for { err = conn.SetReadDeadline(utils.NowSec().Add(dead)) if err != nil { - base.Error("SetDeadline: ", err) + base.Error("SetDeadline: ", cSess.Username, err) return } pl := getPayload() n, err = conn.Read(pl.Data) if err != nil { - base.Error("read hdata: ", err) + base.Error("read hdata: ", cSess.Username, err) return } @@ -57,7 +58,8 @@ func LinkDtls(conn net.Conn, cSess *sessdata.ConnSession) { // do nothing // base.Debug("recv keepalive", cSess.IpAddr) case 0x05: // DISCONNECT - base.Debug("DISCONNECT DTLS", cSess.IpAddr) + cSess.UserLogoutCode = dbdata.UserLogoutClient + base.Debug("DISCONNECT DTLS", cSess.Username, cSess.IpAddr) return case 0x03: // DPD-REQ // base.Debug("recv DPD-REQ", cSess.IpAddr) @@ -66,7 +68,22 @@ func LinkDtls(conn net.Conn, cSess *sessdata.ConnSession) { return } case 0x04: - // base.Debug("recv DPD-RESP", cSess.IpAddr) + // base.Debug("recv DPD-RESP", cSess.IpAddr) + case 0x08: // decompress + if cSess.DtlsPickCmp == nil { + continue + } + dst := getByteFull() + nn, err := cSess.DtlsPickCmp.Uncompress(pl.Data[1:], *dst) + if err != nil { + putByte(dst) + base.Error("dtls decompress error", err, n) + continue + } + pl.Data = append(pl.Data[:1], (*dst)[:nn]...) + putByte(dst) + n = nn + 1 + fallthrough case 0x00: // DATA // 去除数据头 // copy(pl.Data, pl.Data[1:n]) @@ -83,7 +100,7 @@ func LinkDtls(conn net.Conn, cSess *sessdata.ConnSession) { func dtlsWrite(conn net.Conn, dSess *sessdata.DtlsSession, cSess *sessdata.ConnSession) { defer func() { - base.Debug("dtlsWrite return", cSess.IpAddr) + base.Debug("dtlsWrite return", cSess.Username, cSess.IpAddr) _ = conn.Close() dSess.Close() }() @@ -106,21 +123,35 @@ func dtlsWrite(conn net.Conn, dSess *sessdata.DtlsSession, cSess *sessdata.ConnS // header = []byte{payload.PType} if pl.PType == 0x00 { // data - // 获取数据长度 - l := len(pl.Data) - // 先扩容 +1 - pl.Data = pl.Data[:l+1] - // 数据后移 - copy(pl.Data[1:], pl.Data) - // 添加头信息 - pl.Data[0] = pl.PType + isCompress := false + if cSess.DtlsPickCmp != nil && len(pl.Data) > base.Cfg.NoCompressLimit { + dst := getByteFull() + size, err := cSess.DtlsPickCmp.Compress(pl.Data, (*dst)[1:]) + if err == nil && size < len(pl.Data) { + (*dst)[0] = 0x08 + pl.Data = append(pl.Data[:0], (*dst)[:size+1]...) + isCompress = true + } + putByte(dst) + } + // 未压缩 + if !isCompress { + // 获取数据长度 + l := len(pl.Data) + // 先扩容 +1 + pl.Data = pl.Data[:l+1] + // 数据后移 + copy(pl.Data[1:], pl.Data) + // 添加头信息 + pl.Data[0] = pl.PType + } } else { // 设置头类型 pl.Data = append(pl.Data[:0], pl.PType) } n, err := conn.Write(pl.Data) if err != nil { - base.Error("write err", err) + base.Error("write err", cSess.Username, err) return } diff --git a/anylink/server/handler/link_home.go b/anylink/server/handler/link_home.go index 9669d26..a2dc30b 100644 --- a/anylink/server/handler/link_home.go +++ b/anylink/server/handler/link_home.go @@ -13,6 +13,7 @@ func LinkHome(w http.ResponseWriter, r *http.Request) { // fmt.Println(r.RemoteAddr) // hu, _ := httputil.DumpRequest(r, true) // fmt.Println("DumpHome: ", string(hu)) + w.Header().Set("Server", "AnyLinkOpenSource") connection := strings.ToLower(r.Header.Get("Connection")) userAgent := strings.ToLower(r.UserAgent()) if connection == "close" && (strings.Contains(userAgent, "anyconnect") || strings.Contains(userAgent, "openconnect")) { @@ -21,11 +22,13 @@ func LinkHome(w http.ResponseWriter, r *http.Request) { return } index := &dbdata.SettingOther{} - dbdata.SettingGet(index) + if err := dbdata.SettingGet(index); err != nil { + return + } w.WriteHeader(http.StatusOK) if index.Homeindex == "" { index.Homeindex = "AnyLink 是一个企业级远程办公 SSL VPN 软件,可以支持多人同时在线使用。" - } + } fmt.Fprintln(w, index.Homeindex) } diff --git a/anylink/server/handler/link_tap.go b/anylink/server/handler/link_tap.go index 2e3062b..67972ff 100644 --- a/anylink/server/handler/link_tap.go +++ b/anylink/server/handler/link_tap.go @@ -175,7 +175,7 @@ func allTapWrite(ifce LinkDriver, cSess *sessdata.ConnSession) { return } - putPayload(pl) + putPayloadInBefore(cSess, pl) } } diff --git a/anylink/server/handler/link_tun.go b/anylink/server/handler/link_tun.go index 2f0045c..0ba7c76 100644 --- a/anylink/server/handler/link_tun.go +++ b/anylink/server/handler/link_tun.go @@ -5,6 +5,7 @@ import ( "github.com/bjdgyc/anylink/base" "github.com/bjdgyc/anylink/sessdata" + "github.com/coreos/go-iptables/iptables" "github.com/songgao/water" ) @@ -26,6 +27,28 @@ func checkTun() { if err != nil { base.Fatal("testTun err: ", err) } + //开启服务器转发 + if err := execCmd([]string{"sysctl -w net.ipv4.ip_forward=1"}); err != nil { + base.Error(err) + } + if base.Cfg.IptablesNat { + //添加NAT转发规则 + ipt, err := iptables.New() + if err != nil { + base.Error(err) + return + } + natRule := []string{"-s", base.Cfg.Ipv4CIDR, "-o", base.Cfg.Ipv4Master, "-j", "MASQUERADE"} + forwardRule := []string{"-j", "ACCEPT"} + if natExists, _ := ipt.Exists("nat", "POSTROUTING", natRule...); !natExists { + ipt.Insert("nat", "POSTROUTING", 1, natRule...) + } + if forwardExists, _ := ipt.Exists("filter", "FORWARD", forwardRule...); !forwardExists { + ipt.Insert("filter", "FORWARD", 1, forwardRule...) + } + base.Info(ipt.List("nat", "POSTROUTING")) + base.Info(ipt.List("filter", "FORWARD")) + } } // 创建tun网卡 @@ -85,7 +108,7 @@ func tunWrite(ifce *water.Interface, cSess *sessdata.ConnSession) { return } - putPayload(pl) + putPayloadInBefore(cSess, pl) } } diff --git a/anylink/server/handler/link_tunnel.go b/anylink/server/handler/link_tunnel.go index 16373b2..4550854 100644 --- a/anylink/server/handler/link_tunnel.go +++ b/anylink/server/handler/link_tunnel.go @@ -6,6 +6,7 @@ import ( "log" "net" "net/http" + "net/http/httputil" "os" "strings" "text/template" @@ -34,9 +35,10 @@ func HttpAddHeader(w http.ResponseWriter, key string, value string) { func LinkTunnel(w http.ResponseWriter, r *http.Request) { // TODO 调试信息输出 - // hd, _ := httputil.DumpRequest(r, true) - // fmt.Println("DumpRequest: ", string(hd)) - // fmt.Println("LinkTunnel", r.RemoteAddr) + if base.GetLogLevel() == base.LogLevelTrace { + hd, _ := httputil.DumpRequest(r, true) + base.Trace("LinkTunnel: ", string(hd)) + } // 判断session-token的值 cookie, err := r.Cookie("webvpn") @@ -69,6 +71,7 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) { cSess.SetMtu(cstpMtu) cSess.MasterSecret = masterSecret cSess.RemoteAddr = r.RemoteAddr + cSess.UserAgent = strings.ToLower(r.UserAgent()) cSess.LocalIp = net.ParseIP(localIp) cstpKeepalive := base.Cfg.CstpKeepalive cstpDpd := base.Cfg.CstpDpd @@ -89,6 +92,14 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) { base.Debug(cSess.IpAddr, cSess.MacHw, sess.Username, mobile) + // 压缩 + if cmpName, ok := cSess.SetPickCmp("cstp", r.Header.Get("X-Cstp-Accept-Encoding")); ok { + HttpSetHeader(w, "X-CSTP-Content-Encoding", cmpName) + } + if cmpName, ok := cSess.SetPickCmp("dtls", r.Header.Get("X-Dtls-Accept-Encoding")); ok { + HttpSetHeader(w, "X-DTLS-Content-Encoding", cmpName) + } + // 返回客户端数据 HttpSetHeader(w, "Server", fmt.Sprintf("%s %s", base.APP_NAME, base.APP_VER)) HttpSetHeader(w, "X-CSTP-Version", "1") @@ -125,7 +136,8 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) { for _, v := range cSess.Group.RouteExclude { HttpAddHeader(w, "X-CSTP-Split-Exclude", v.IpMask) } - HttpSetHeader(w, "X-CSTP-Lease-Duration", fmt.Sprintf("%d", base.Cfg.IpLease)) // ip地址租期 + + HttpSetHeader(w, "X-CSTP-Lease-Duration", "1209600") // ip地址租期 HttpSetHeader(w, "X-CSTP-Session-Timeout", "none") HttpSetHeader(w, "X-CSTP-Session-Timeout-Alert-Interval", "60") HttpSetHeader(w, "X-CSTP-Session-Timeout-Remaining", "none") @@ -134,8 +146,10 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) { HttpSetHeader(w, "X-CSTP-Keep", "true") HttpSetHeader(w, "X-CSTP-Tunnel-All-DNS", "false") - HttpSetHeader(w, "X-CSTP-Rekey-Time", "172800") + HttpSetHeader(w, "X-CSTP-Rekey-Time", "43200") // 172800 HttpSetHeader(w, "X-CSTP-Rekey-Method", "new-tunnel") + HttpSetHeader(w, "X-DTLS-Rekey-Time", "43200") + HttpSetHeader(w, "X-DTLS-Rekey-Method", "new-tunnel") HttpSetHeader(w, "X-CSTP-DPD", fmt.Sprintf("%d", cstpDpd)) HttpSetHeader(w, "X-CSTP-Keepalive", fmt.Sprintf("%d", cstpKeepalive)) @@ -150,7 +164,6 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) { HttpSetHeader(w, "X-DTLS-Port", dtlsPort) HttpSetHeader(w, "X-DTLS-DPD", fmt.Sprintf("%d", cstpDpd)) HttpSetHeader(w, "X-DTLS-Keepalive", fmt.Sprintf("%d", cstpKeepalive)) - HttpSetHeader(w, "X-DTLS-Rekey-Time", "5400") HttpSetHeader(w, "X-DTLS12-CipherSuite", "ECDHE-ECDSA-AES128-GCM-SHA256") HttpSetHeader(w, "X-CSTP-License", "accept") @@ -194,6 +207,15 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) { base.Error(err) return } + dbdata.UserActLogIns.Add(dbdata.UserActLog{ + Username: sess.Username, + GroupName: sess.Group, + IpAddr: cSess.IpAddr.String(), + RemoteAddr: cSess.RemoteAddr, + DeviceType: sess.DeviceType, + PlatformVersion: sess.PlatformVersion, + Status: dbdata.UserConnected, + }, cSess.UserAgent) go LinkCstp(conn, bufRW, cSess) } diff --git a/anylink/server/handler/payload.go b/anylink/server/handler/payload.go index 626ae6e..e5ed545 100644 --- a/anylink/server/handler/payload.go +++ b/anylink/server/handler/payload.go @@ -15,11 +15,6 @@ func payloadIn(cSess *sessdata.ConnSession, pl *sessdata.Payload) bool { // 校验不通过直接丢弃 return false } - if base.Cfg.AuditInterval >= 0 { - cSess.IpAuditPool.JobQueue <- func() { - logAudit(cSess, pl) - } - } } closed := false @@ -32,6 +27,15 @@ func payloadIn(cSess *sessdata.ConnSession, pl *sessdata.Payload) bool { return closed } +func putPayloadInBefore(cSess *sessdata.ConnSession, pl *sessdata.Payload) { + // 异步审计日志 + if base.Cfg.AuditInterval >= 0 { + auditPayload.Add(cSess.Username, pl) + return + } + putPayload(pl) +} + func payloadOut(cSess *sessdata.ConnSession, pl *sessdata.Payload) bool { dSess := cSess.GetDtlsSession() if dSess == nil { diff --git a/anylink/server/handler/payload_access_audit.go b/anylink/server/handler/payload_access_audit.go index 0ad9ea4..4384352 100644 --- a/anylink/server/handler/payload_access_audit.go +++ b/anylink/server/handler/payload_access_audit.go @@ -3,13 +3,13 @@ package handler import ( "crypto/md5" "encoding/binary" - "encoding/hex" "time" "github.com/bjdgyc/anylink/base" "github.com/bjdgyc/anylink/dbdata" "github.com/bjdgyc/anylink/pkg/utils" "github.com/bjdgyc/anylink/sessdata" + "github.com/ivpusic/grpool" "github.com/songgao/water/waterutil" ) @@ -20,73 +20,92 @@ const ( acc_proto_http ) -// 保存批量的审计日志 +var ( + auditPayload *AuditPayload + logBatch *LogBatch +) + +// 分析审计日志 +type AuditPayload struct { + Pool *grpool.Pool + IpAuditMap utils.IMaps +} + +// 保存审计日志 type LogBatch struct { - Logs []dbdata.AccessAudit + Logs []dbdata.AccessAudit + LogChan chan dbdata.AccessAudit } -// 日志池 -type LogSink struct { - logChan chan dbdata.AccessAudit - autoCommitChan chan *LogBatch // 超时通知 +// 异步写入pool +func (p *AuditPayload) Add(userName string, pl *sessdata.Payload) { + select { + case p.Pool.JobQueue <- func() { + logAudit(userName, pl) + }: + default: + putPayload(pl) + base.Error("AccessAudit: AuditPayload channel is full") + } } -var logAuditSink *LogSink - -// 写入日志通道 -func logAuditWrite(aa dbdata.AccessAudit) { - logAuditSink.logChan <- aa +// 数据落盘 +func (l *LogBatch) Write() { + if len(l.Logs) == 0 { + return + } + _ = dbdata.AddBatch(l.Logs) + l.Reset() } -// 批量写入数据表 +// 清空数据 +func (l *LogBatch) Reset() { + l.Logs = []dbdata.AccessAudit{} +} + +// 开启批量写入数据功能 func logAuditBatch() { if base.Cfg.AuditInterval < 0 { return } - logAuditSink = &LogSink{ - logChan: make(chan dbdata.AccessAudit, 1000), - autoCommitChan: make(chan *LogBatch, 10), + auditPayload = &AuditPayload{ + Pool: grpool.NewPool(10, 10240), + IpAuditMap: utils.NewMap("cmap", 0), + } + logBatch = &LogBatch{ + LogChan: make(chan dbdata.AccessAudit, 10240), } var ( - limit = 100 // 超过上限批量写入数据表 - logAudit dbdata.AccessAudit - logBatch *LogBatch - commitTimer *time.Timer // 超时自动提交 - timeOutBatch *LogBatch + limit = 100 // 超过上限批量写入数据表 + outTime = time.NewTimer(time.Second) + accessAudit = dbdata.AccessAudit{} ) + for { + // 重置超时 时间 + outTime.Reset(time.Second * 1) select { - case logAudit = <-logAuditSink.logChan: - if logBatch == nil { - logBatch = &LogBatch{} - commitTimer = time.AfterFunc( - 1*time.Second, func(logBatch *LogBatch) func() { - return func() { - logAuditSink.autoCommitChan <- logBatch - } - }(logBatch), - ) - } - logBatch.Logs = append(logBatch.Logs, logAudit) + case accessAudit = <-logBatch.LogChan: + logBatch.Logs = append(logBatch.Logs, accessAudit) if len(logBatch.Logs) >= limit { - commitTimer.Stop() - _ = dbdata.AddBatch(logBatch.Logs) - logBatch = nil + if !outTime.Stop() { + <-outTime.C + } + logBatch.Write() } - case timeOutBatch = <-logAuditSink.autoCommitChan: - if timeOutBatch != logBatch { - continue - } - if logBatch != nil { - _ = dbdata.AddBatch(logBatch.Logs) - } - logBatch = nil + case <-outTime.C: + logBatch.Write() } } } // 解析IP包的数据 -func logAudit(cSess *sessdata.ConnSession, pl *sessdata.Payload) { +func logAudit(userName string, pl *sessdata.Payload) { + defer putPayload(pl) + + if !(pl.LType == sessdata.LTypeIPData && pl.PType == 0x00) { + return + } ipProto := waterutil.IPv4Protocol(pl.Data) // 访问协议 var accessProto uint8 @@ -109,79 +128,48 @@ func logAudit(cSess *sessdata.ConnSession, pl *sessdata.Payload) { copy(key[:16], ipSrc) copy(key[16:32], ipDst) binary.BigEndian.PutUint16(key[32:34], ipPort) + key[34] = byte(accessProto) + copy(key[35:51], []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}) info := "" nu := utils.NowSec().Unix() if ipProto == waterutil.TCP { - plData := waterutil.IPv4Payload(pl.Data) - if len(plData) < 14 { + tcpPlData := waterutil.IPv4Payload(pl.Data) + // 24 (ACK PSH) + if len(tcpPlData) < 14 || tcpPlData[13] != 24 { return } - flags := plData[13] - switch flags { - case flags & 0x20: - // URG - return - case flags & 0x14: - // RST ACK - return - case flags & 0x12: - // SYN ACK - return - case flags & 0x11: - // Client FIN - return - case flags & 0x10: - // ACK - return - case flags & 0x08: - // PSH - return - case flags & 0x04: - // RST - return - case flags & 0x02: - // SYN - return - case flags & 0x01: - // FIN - return - case flags & 0x18: - // PSH ACK - accessProto, info = onTCP(plData) + accessProto, info = onTCP(tcpPlData) + // HTTPS or HTTP + if accessProto != acc_proto_tcp { + // 提前存储只含ip数据的key, 避免即记录域名又记录一笔IP数据的记录 + ipKey := make([]byte, 51) + copy(ipKey, key) + ipS := utils.BytesToString(ipKey) + auditPayload.IpAuditMap.Set(ipS, nu) + + key[34] = byte(accessProto) + // 存储含域名的key if info != "" { - // 提前存储只含ip数据的key, 避免即记录域名又记录一笔IP数据的记录 - ipKey := make([]byte, 51) - copy(ipKey, key) - ipS := utils.BytesToString(ipKey) - cSess.IpAuditMap.Set(ipS, nu) - // 存储含域名的key - key[34] = byte(accessProto) md5Sum := md5.Sum([]byte(info)) - copy(key[35:51], hex.EncodeToString(md5Sum[:])) + copy(key[35:51], md5Sum[:]) } - case flags & 0x19: - // URG - return - case flags & 0xC2: - // SYN-ECE-CWR - return } } s := utils.BytesToString(key) // 判断已经存在,并且没有过期 - v, ok := cSess.IpAuditMap.Get(s) + v, ok := auditPayload.IpAuditMap.Get(s) if ok && nu-v.(int64) < int64(base.Cfg.AuditInterval) { // 回收byte对象 putByte51(b) return } - cSess.IpAuditMap.Set(s, nu) + auditPayload.IpAuditMap.Set(s, nu) audit := dbdata.AccessAudit{ - Username: cSess.Username, + Username: userName, Protocol: uint8(ipProto), Src: ipSrc.String(), Dst: ipDst.String(), @@ -190,5 +178,11 @@ func logAudit(cSess *sessdata.ConnSession, pl *sessdata.Payload) { AccessProto: accessProto, Info: info, } - logAuditWrite(audit) + + select { + case logBatch.LogChan <- audit: + default: + base.Error("AccessAudit: LogChan channel is full") + return + } } diff --git a/anylink/server/handler/payload_tcp_parser.go b/anylink/server/handler/payload_tcp_parser.go index bece94d..6d3c4cc 100644 --- a/anylink/server/handler/payload_tcp_parser.go +++ b/anylink/server/handler/payload_tcp_parser.go @@ -21,7 +21,7 @@ func onTCP(payload []byte) (uint8, string) { } data := payload[ihl:] for _, parser := range tcpParsers { - if proto, info := parser(data); info != "" { + if proto, info := parser(data); proto != acc_proto_tcp { return proto, info } } @@ -29,8 +29,7 @@ func onTCP(payload []byte) (uint8, string) { } func sniNewParser(b []byte) (uint8, string) { - dataSize := len(b) - if dataSize < 2 || b[0] != 0x16 || b[1] != 0x03 { + if len(b) < 2 || b[0] != 0x16 || b[1] != 0x03 { return acc_proto_tcp, "" } rest := b[5:] @@ -51,27 +50,27 @@ func sniNewParser(b []byte) (uint8, string) { // Skip over random number current += 4 + 28 if current >= restLen { - return acc_proto_tcp, "" + return acc_proto_https, "" } // Skip over session ID sessionIDLength := int(rest[current]) current += 1 current += sessionIDLength - if current >= restLen { - return acc_proto_tcp, "" + if current+1 >= restLen { + return acc_proto_https, "" } cipherSuiteLength := (int(rest[current]) << 8) + int(rest[current+1]) current += 2 current += cipherSuiteLength if current >= restLen { - return acc_proto_tcp, "" + return acc_proto_https, "" } compressionMethodLength := int(rest[current]) current += 1 current += compressionMethodLength if current >= restLen { - return acc_proto_tcp, "" + return acc_proto_https, "" } current += 2 hostname := "" @@ -84,27 +83,30 @@ func sniNewParser(b []byte) (uint8, string) { // Skip over number of names as we're assuming there's just one current += 2 if current >= restLen { - return acc_proto_tcp, "" + return acc_proto_https, "" } nameType := rest[current] current += 1 if nameType != 0 { - return acc_proto_tcp, "" + return acc_proto_https, "" } if current+1 >= restLen { - return acc_proto_tcp, "" + return acc_proto_https, "" } nameLen := (int(rest[current]) << 8) + int(rest[current+1]) current += 2 if current+nameLen >= restLen { - return acc_proto_tcp, "" + return acc_proto_https, "" } hostname = string(rest[current : current+nameLen]) } current += extensionDataLength } if hostname == "" { - return acc_proto_tcp, "" + return acc_proto_https, "" + } + if !validDomainChar(hostname) { + return acc_proto_https, "" } return acc_proto_https, hostname } @@ -150,8 +152,7 @@ func httpNewParser(data []byte) (uint8, string) { } func sniParser(data []byte) (uint8, string) { - dataSize := len(data) - if dataSize < 2 || data[0] != 0x16 || data[1] != 0x03 { + if len(data) < 2 || data[0] != 0x16 || data[1] != 0x03 { return acc_proto_tcp, "" } sniRe := regexp.MustCompile("\x00\x00.{4}\x00.{2}([a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,6})\x00") @@ -169,3 +170,15 @@ func httpParser(data []byte) (uint8, string) { } return acc_proto_tcp, "" } + +// 校验域名的合法字符, 处理乱码问题 +func validDomainChar(addr string) bool { + // Allow a-z A-Z . - 0-9 + for i := 0; i < len(addr); i++ { + c := addr[i] + if !((c >= 97 && c <= 122) || (c >= 65 && c <= 90) || (c >= 45 && c <= 46) || (c >= 48 && c <= 57)) { + return false + } + } + return true +} diff --git a/anylink/server/handler/payload_test.go b/anylink/server/handler/payload_test.go index f36a08c..fc5358b 100644 --- a/anylink/server/handler/payload_test.go +++ b/anylink/server/handler/payload_test.go @@ -51,22 +51,26 @@ func BenchmarkNewHttpParser(b *testing.B) { func TestNewSniParser(t *testing.T) { ast := assert.New(t) data := handlerTcpPayload(httpsPacket) - _, sni := sniNewParser(data) + proto, sni := sniNewParser(data) ast.Equal(sni, httpsSni) + ast.Equal(int(proto), acc_proto_https) } func TestNewHttpParser(t *testing.T) { ast := assert.New(t) // Host data := handlerTcpPayload(httpPacket) - _, hostname := httpNewParser(data) + proto, hostname := httpNewParser(data) ast.Equal(hostname, httpHost) + ast.Equal(int(proto), acc_proto_http) // HOST data = handlerTcpPayload(httpPacket2) - _, hostname = httpNewParser(data) + proto, hostname = httpNewParser(data) ast.Equal(hostname, httpHost) + ast.Equal(int(proto), acc_proto_http) // GET http://www.google.com/index.html HTTP/1.1 data = handlerTcpPayload(httpPacket3) - _, hostname = httpNewParser(data) + proto, hostname = httpNewParser(data) ast.Equal(hostname, httpHost) + ast.Equal(int(proto), acc_proto_http) } diff --git a/anylink/server/handler/server.go b/anylink/server/handler/server.go index 6cfd4cc..34fd016 100644 --- a/anylink/server/handler/server.go +++ b/anylink/server/handler/server.go @@ -3,6 +3,7 @@ package handler import ( "crypto/tls" "fmt" + "io" "log" "net" "net/http" @@ -10,8 +11,9 @@ import ( "time" "github.com/bjdgyc/anylink/base" - "github.com/bjdgyc/anylink/pkg/proxyproto" + "github.com/bjdgyc/anylink/dbdata" "github.com/gorilla/mux" + "github.com/pires/go-proxyproto" ) func startTls() { @@ -47,13 +49,19 @@ func startTls() { NextProtos: []string{"http/1.1"}, MinVersion: tls.VersionTLS12, CipherSuites: selectedCipherSuites, + GetCertificate: func(chi *tls.ClientHelloInfo) (*tls.Certificate, error) { + base.Trace("GetCertificate", chi.ServerName) + return dbdata.GetCertificateBySNI(chi.ServerName) + }, // InsecureSkipVerify: true, } srv := &http.Server{ - Addr: addr, - Handler: initRoute(), - TLSConfig: tlsConfig, - ErrorLog: base.GetBaseLog(), + Addr: addr, + Handler: initRoute(), + TLSConfig: tlsConfig, + ErrorLog: base.GetBaseLog(), + ReadTimeout: 60 * time.Second, + WriteTimeout: 60 * time.Second, } ln, err = net.Listen("tcp", addr) @@ -63,11 +71,14 @@ func startTls() { defer ln.Close() if base.Cfg.ProxyProtocol { - ln = &proxyproto.Listener{Listener: ln, ProxyHeaderTimeout: time.Second * 5} + ln = &proxyproto.Listener{ + Listener: ln, + ReadHeaderTimeout: 30 * time.Second, + } } base.Info("listen server", addr) - err = srv.ServeTLS(ln, base.Cfg.CertFile, base.Cfg.CertKey) + err = srv.ServeTLS(ln, "", "") if err != nil { base.Fatal(err) } @@ -88,6 +99,10 @@ func initRoute() http.Handler { http.FileServer(http.Dir(base.Cfg.FilesPath)), ), ) + // 健康检测 + r.HandleFunc("/status", func(w http.ResponseWriter, r *http.Request) { + io.WriteString(w, "ok") + }).Methods(http.MethodGet) r.NotFoundHandler = http.HandlerFunc(notFound) return r } diff --git a/anylink/server/main.go b/anylink/server/main.go index c52ddcf..a25efc5 100644 --- a/anylink/server/main.go +++ b/anylink/server/main.go @@ -1,5 +1,6 @@ // AnyLink 是一个企业级远程办公vpn软件,可以支持多人同时在线使用。 +//go:build !windows // +build !windows package main diff --git a/anylink/server/pkg/proxyproto/protocol.go b/anylink/server/pkg/proxyproto/protocol.go deleted file mode 100644 index f91f0b0..0000000 --- a/anylink/server/pkg/proxyproto/protocol.go +++ /dev/null @@ -1,290 +0,0 @@ -// copy from: https://github.com/armon/go-proxyproto/blob/master/protocol.go -// design: http://www.haproxy.org/download/2.2/doc/proxy-protocol.txt - -// HAProxy proxy proto v1 -package proxyproto - -import ( - "bufio" - "bytes" - "errors" - "fmt" - "io" - "log" - "net" - "strconv" - "strings" - "sync" - "time" -) - -var ( - // prefix is the string we look for at the start of a connection - // to check if this connection is using the proxy protocol - prefix = []byte("PROXY ") - prefixLen = len(prefix) - - ErrInvalidUpstream = errors.New("upstream connection address not trusted for PROXY information") -) - -// SourceChecker can be used to decide whether to trust the PROXY info or pass -// the original connection address through. If set, the connecting address is -// passed in as an argument. If the function returns an error due to the source -// being disallowed, it should return ErrInvalidUpstream. -// -// If error is not nil, the call to Accept() will fail. If the reason for -// triggering this failure is due to a disallowed source, it should return -// ErrInvalidUpstream. -// -// If bool is true, the PROXY-set address is used. -// -// If bool is false, the connection's remote address is used, rather than the -// address claimed in the PROXY info. -type SourceChecker func(net.Addr) (bool, error) - -// Listener is used to wrap an underlying listener, -// whose connections may be using the HAProxy Proxy Protocol (version 1). -// If the connection is using the protocol, the RemoteAddr() will return -// the correct client address. -// -// Optionally define ProxyHeaderTimeout to set a maximum time to -// receive the Proxy Protocol Header. Zero means no timeout. -type Listener struct { - Listener net.Listener - ProxyHeaderTimeout time.Duration - SourceCheck SourceChecker - UnknownOK bool // allow PROXY UNKNOWN -} - -// Conn is used to wrap and underlying connection which -// may be speaking the Proxy Protocol. If it is, the RemoteAddr() will -// return the address of the client instead of the proxy address. -type Conn struct { - bufReader *bufio.Reader - conn net.Conn - dstAddr *net.TCPAddr - srcAddr *net.TCPAddr - useConnAddr bool - once sync.Once - proxyHeaderTimeout time.Duration - unknownOK bool -} - -// Accept waits for and returns the next connection to the listener. -func (p *Listener) Accept() (net.Conn, error) { - // Get the underlying connection - conn, err := p.Listener.Accept() - if err != nil { - return nil, err - } - var useConnAddr bool - if p.SourceCheck != nil { - allowed, err := p.SourceCheck(conn.RemoteAddr()) - if err != nil { - return nil, err - } - if !allowed { - useConnAddr = true - } - } - newConn := NewConn(conn, p.ProxyHeaderTimeout) - newConn.useConnAddr = useConnAddr - newConn.unknownOK = p.UnknownOK - return newConn, nil -} - -// Close closes the underlying listener. -func (p *Listener) Close() error { - return p.Listener.Close() -} - -// Addr returns the underlying listener's network address. -func (p *Listener) Addr() net.Addr { - return p.Listener.Addr() -} - -// NewConn is used to wrap a net.Conn that may be speaking -// the proxy protocol into a proxyproto.Conn -func NewConn(conn net.Conn, timeout time.Duration) *Conn { - pConn := &Conn{ - bufReader: bufio.NewReader(conn), - conn: conn, - proxyHeaderTimeout: timeout, - } - return pConn -} - -// Read is check for the proxy protocol header when doing -// the initial scan. If there is an error parsing the header, -// it is returned and the socket is closed. -func (p *Conn) Read(b []byte) (int, error) { - var err error - p.once.Do(func() { err = p.checkPrefix() }) - if err != nil { - return 0, err - } - return p.bufReader.Read(b) -} - -func (p *Conn) ReadFrom(r io.Reader) (int64, error) { - if rf, ok := p.conn.(io.ReaderFrom); ok { - return rf.ReadFrom(r) - } - return io.Copy(p.conn, r) -} - -func (p *Conn) WriteTo(w io.Writer) (int64, error) { - var err error - p.once.Do(func() { err = p.checkPrefix() }) - if err != nil { - return 0, err - } - return p.bufReader.WriteTo(w) -} - -func (p *Conn) Write(b []byte) (int, error) { - return p.conn.Write(b) -} - -func (p *Conn) Close() error { - return p.conn.Close() -} - -func (p *Conn) LocalAddr() net.Addr { - p.checkPrefixOnce() - if p.dstAddr != nil && !p.useConnAddr { - return p.dstAddr - } - return p.conn.LocalAddr() -} - -// RemoteAddr returns the address of the client if the proxy -// protocol is being used, otherwise just returns the address of -// the socket peer. If there is an error parsing the header, the -// address of the client is not returned, and the socket is closed. -// Once implication of this is that the call could block if the -// client is slow. Using a Deadline is recommended if this is called -// before Read() -func (p *Conn) RemoteAddr() net.Addr { - p.checkPrefixOnce() - if p.srcAddr != nil && !p.useConnAddr { - return p.srcAddr - } - return p.conn.RemoteAddr() -} - -func (p *Conn) SetDeadline(t time.Time) error { - return p.conn.SetDeadline(t) -} - -func (p *Conn) SetReadDeadline(t time.Time) error { - return p.conn.SetReadDeadline(t) -} - -func (p *Conn) SetWriteDeadline(t time.Time) error { - return p.conn.SetWriteDeadline(t) -} - -func (p *Conn) checkPrefixOnce() { - p.once.Do(func() { - if err := p.checkPrefix(); err != nil && err != io.EOF { - log.Printf("[ERR] Failed to read proxy prefix: %v", err) - p.Close() - p.bufReader = bufio.NewReader(p.conn) - } - }) -} - -func (p *Conn) checkPrefix() error { - if p.proxyHeaderTimeout != 0 { - readDeadLine := time.Now().Add(p.proxyHeaderTimeout) - _ = p.conn.SetReadDeadline(readDeadLine) - defer func() { - _ = p.conn.SetReadDeadline(time.Time{}) - }() - } - - // Incrementally check each byte of the prefix - for i := 1; i <= prefixLen; i++ { - inp, err := p.bufReader.Peek(i) - - if err != nil { - if neterr, ok := err.(net.Error); ok && neterr.Timeout() { - return nil - } else { - return err - } - } - - // Check for a prefix mis-match, quit early - if !bytes.Equal(inp, prefix[:i]) { - return nil - } - } - - // Read the header line - header, err := p.bufReader.ReadString('\n') - if err != nil { - p.conn.Close() - return err - } - - // Strip the carriage return and new line - header = header[:len(header)-2] - - // Split on spaces, should be (PROXY ) - parts := strings.Split(header, " ") - if len(parts) < 2 { - p.conn.Close() - return fmt.Errorf("Invalid header line: %s", header) - } - - // Verify the type is known - switch parts[1] { - case "UNKNOWN": - if !p.unknownOK || len(parts) != 2 { - p.conn.Close() - return fmt.Errorf("Invalid UNKNOWN header line: %s", header) - } - p.useConnAddr = true - return nil - case "TCP4": - case "TCP6": - default: - p.conn.Close() - return fmt.Errorf("Unhandled address type: %s", parts[1]) - } - - if len(parts) != 6 { - p.conn.Close() - return fmt.Errorf("Invalid header line: %s", header) - } - - // Parse out the source address - ip := net.ParseIP(parts[2]) - if ip == nil { - p.conn.Close() - return fmt.Errorf("Invalid source ip: %s", parts[2]) - } - port, err := strconv.Atoi(parts[4]) - if err != nil { - p.conn.Close() - return fmt.Errorf("Invalid source port: %s", parts[4]) - } - p.srcAddr = &net.TCPAddr{IP: ip, Port: port} - - // Parse out the destination address - ip = net.ParseIP(parts[3]) - if ip == nil { - p.conn.Close() - return fmt.Errorf("Invalid destination ip: %s", parts[3]) - } - port, err = strconv.Atoi(parts[5]) - if err != nil { - p.conn.Close() - return fmt.Errorf("Invalid destination port: %s", parts[5]) - } - p.dstAddr = &net.TCPAddr{IP: ip, Port: port} - - return nil -} diff --git a/anylink/server/pkg/proxyproto/protocol_test.go b/anylink/server/pkg/proxyproto/protocol_test.go deleted file mode 100644 index 1ad37aa..0000000 --- a/anylink/server/pkg/proxyproto/protocol_test.go +++ /dev/null @@ -1,486 +0,0 @@ -// copy from: https://github.com/armon/go-proxyproto/blob/master/protocol_test.go -package proxyproto - -import ( - "bytes" - "io" - "net" - "testing" - "time" -) - -const ( - goodAddr = "127.0.0.1" - badAddr = "127.0.0.2" - errAddr = "9999.0.0.2" -) - -var ( - checkAddr string -) - -func TestPassthrough(t *testing.T) { - l, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - t.Fatalf("err: %v", err) - } - - pl := &Listener{Listener: l} - - go func() { - conn, err := net.Dial("tcp", pl.Addr().String()) - if err != nil { - t.Fatalf("err: %v", err) - } - defer conn.Close() - - conn.Write([]byte("ping")) - recv := make([]byte, 4) - _, err = conn.Read(recv) - if err != nil { - t.Fatalf("err: %v", err) - } - if !bytes.Equal(recv, []byte("pong")) { - t.Fatalf("bad: %v", recv) - } - }() - - conn, err := pl.Accept() - if err != nil { - t.Fatalf("err: %v", err) - } - defer conn.Close() - - recv := make([]byte, 4) - _, err = conn.Read(recv) - if err != nil { - t.Fatalf("err: %v", err) - } - if !bytes.Equal(recv, []byte("ping")) { - t.Fatalf("bad: %v", recv) - } - - if _, err := conn.Write([]byte("pong")); err != nil { - t.Fatalf("err: %v", err) - } -} - -func TestTimeout(t *testing.T) { - l, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - t.Fatalf("err: %v", err) - } - - clientWriteDelay := 200 * time.Millisecond - proxyHeaderTimeout := 50 * time.Millisecond - pl := &Listener{Listener: l, ProxyHeaderTimeout: proxyHeaderTimeout} - - go func() { - conn, err := net.Dial("tcp", pl.Addr().String()) - if err != nil { - t.Fatalf("err: %v", err) - } - defer conn.Close() - - // Do not send data for a while - time.Sleep(clientWriteDelay) - - conn.Write([]byte("ping")) - recv := make([]byte, 4) - _, err = conn.Read(recv) - if err != nil { - t.Fatalf("err: %v", err) - } - if !bytes.Equal(recv, []byte("pong")) { - t.Fatalf("bad: %v", recv) - } - }() - - conn, err := pl.Accept() - if err != nil { - t.Fatalf("err: %v", err) - } - defer conn.Close() - - // Check the remote addr is the original 127.0.0.1 - remoteAddrStartTime := time.Now() - addr := conn.RemoteAddr().(*net.TCPAddr) - if addr.IP.String() != "127.0.0.1" { - t.Fatalf("bad: %v", addr) - } - remoteAddrDuration := time.Since(remoteAddrStartTime) - - // Check RemoteAddr() call did timeout - if remoteAddrDuration >= clientWriteDelay { - t.Fatalf("RemoteAddr() took longer than the specified timeout: %v < %v", proxyHeaderTimeout, remoteAddrDuration) - } - - recv := make([]byte, 4) - _, err = conn.Read(recv) - if err != nil { - t.Fatalf("err: %v", err) - } - if !bytes.Equal(recv, []byte("ping")) { - t.Fatalf("bad: %v", recv) - } - - if _, err := conn.Write([]byte("pong")); err != nil { - t.Fatalf("err: %v", err) - } -} - -func TestParse_ipv4(t *testing.T) { - l, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - t.Fatalf("err: %v", err) - } - - pl := &Listener{Listener: l} - - go func() { - conn, err := net.Dial("tcp", pl.Addr().String()) - if err != nil { - t.Fatalf("err: %v", err) - } - defer conn.Close() - - // Write out the header! - header := "PROXY TCP4 10.1.1.1 20.2.2.2 1000 2000\r\n" - conn.Write([]byte(header)) - - conn.Write([]byte("ping")) - recv := make([]byte, 4) - _, err = conn.Read(recv) - if err != nil { - t.Fatalf("err: %v", err) - } - if !bytes.Equal(recv, []byte("pong")) { - t.Fatalf("bad: %v", recv) - } - }() - - conn, err := pl.Accept() - if err != nil { - t.Fatalf("err: %v", err) - } - defer conn.Close() - - recv := make([]byte, 4) - _, err = conn.Read(recv) - if err != nil { - t.Fatalf("err: %v", err) - } - if !bytes.Equal(recv, []byte("ping")) { - t.Fatalf("bad: %v", recv) - } - - if _, err := conn.Write([]byte("pong")); err != nil { - t.Fatalf("err: %v", err) - } - - // Check the remote addr - addr := conn.RemoteAddr().(*net.TCPAddr) - if addr.IP.String() != "10.1.1.1" { - t.Fatalf("bad: %v", addr) - } - if addr.Port != 1000 { - t.Fatalf("bad: %v", addr) - } -} - -func TestParse_ipv6(t *testing.T) { - l, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - t.Fatalf("err: %v", err) - } - - pl := &Listener{Listener: l} - - go func() { - conn, err := net.Dial("tcp", pl.Addr().String()) - if err != nil { - t.Fatalf("err: %v", err) - } - defer conn.Close() - - // Write out the header! - header := "PROXY TCP6 ffff::ffff ffff::ffff 1000 2000\r\n" - conn.Write([]byte(header)) - - conn.Write([]byte("ping")) - recv := make([]byte, 4) - _, err = conn.Read(recv) - if err != nil { - t.Fatalf("err: %v", err) - } - if !bytes.Equal(recv, []byte("pong")) { - t.Fatalf("bad: %v", recv) - } - }() - - conn, err := pl.Accept() - if err != nil { - t.Fatalf("err: %v", err) - } - defer conn.Close() - - recv := make([]byte, 4) - _, err = conn.Read(recv) - if err != nil { - t.Fatalf("err: %v", err) - } - if !bytes.Equal(recv, []byte("ping")) { - t.Fatalf("bad: %v", recv) - } - - if _, err := conn.Write([]byte("pong")); err != nil { - t.Fatalf("err: %v", err) - } - - // Check the remote addr - addr := conn.RemoteAddr().(*net.TCPAddr) - if addr.IP.String() != "ffff::ffff" { - t.Fatalf("bad: %v", addr) - } - if addr.Port != 1000 { - t.Fatalf("bad: %v", addr) - } -} - -func TestParse_Unknown(t *testing.T) { - l, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - t.Fatalf("err: %v", err) - } - - pl := &Listener{Listener: l, UnknownOK: true} - - go func() { - conn, err := net.Dial("tcp", pl.Addr().String()) - if err != nil { - t.Fatalf("err: %v", err) - } - defer conn.Close() - - // Write out the header! - header := "PROXY UNKNOWN\r\n" - conn.Write([]byte(header)) - - conn.Write([]byte("ping")) - recv := make([]byte, 4) - _, err = conn.Read(recv) - if err != nil { - t.Fatalf("err: %v", err) - } - if !bytes.Equal(recv, []byte("pong")) { - t.Fatalf("bad: %v", recv) - } - }() - - conn, err := pl.Accept() - if err != nil { - t.Fatalf("err: %v", err) - } - defer conn.Close() - - recv := make([]byte, 4) - _, err = conn.Read(recv) - if err != nil { - t.Fatalf("err: %v", err) - } - if !bytes.Equal(recv, []byte("ping")) { - t.Fatalf("bad: %v", recv) - } - - if _, err := conn.Write([]byte("pong")); err != nil { - t.Fatalf("err: %v", err) - } - -} - -func TestParse_BadHeader(t *testing.T) { - l, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - t.Fatalf("err: %v", err) - } - - pl := &Listener{Listener: l} - - go func() { - conn, err := net.Dial("tcp", pl.Addr().String()) - if err != nil { - t.Fatalf("err: %v", err) - } - defer conn.Close() - - // Write out the header! - header := "PROXY TCP4 what 127.0.0.1 1000 2000\r\n" - conn.Write([]byte(header)) - - conn.Write([]byte("ping")) - - recv := make([]byte, 4) - _, err = conn.Read(recv) - if err == nil { - t.Fatalf("err: %v", err) - } - }() - - conn, err := pl.Accept() - if err != nil { - t.Fatalf("err: %v", err) - } - defer conn.Close() - - // Check the remote addr, should be the local addr - addr := conn.RemoteAddr().(*net.TCPAddr) - if addr.IP.String() != "127.0.0.1" { - t.Fatalf("bad: %v", addr) - } - - // Read should fail - recv := make([]byte, 4) - _, err = conn.Read(recv) - if err == nil { - t.Fatalf("err: %v", err) - } -} - -func TestParse_ipv4_checkfunc(t *testing.T) { - checkAddr = goodAddr - testParse_ipv4_checkfunc(t) - checkAddr = badAddr - testParse_ipv4_checkfunc(t) - checkAddr = errAddr - testParse_ipv4_checkfunc(t) -} - -func testParse_ipv4_checkfunc(t *testing.T) { - l, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - t.Fatalf("err: %v", err) - } - - checkFunc := func(addr net.Addr) (bool, error) { - tcpAddr := addr.(*net.TCPAddr) - if tcpAddr.IP.String() == checkAddr { - return true, nil - } - return false, nil - } - - pl := &Listener{Listener: l, SourceCheck: checkFunc} - - go func() { - conn, err := net.Dial("tcp", pl.Addr().String()) - if err != nil { - t.Fatalf("err: %v", err) - } - defer conn.Close() - - // Write out the header! - header := "PROXY TCP4 10.1.1.1 20.2.2.2 1000 2000\r\n" - conn.Write([]byte(header)) - - conn.Write([]byte("ping")) - recv := make([]byte, 4) - _, err = conn.Read(recv) - if err != nil { - t.Fatalf("err: %v", err) - } - if !bytes.Equal(recv, []byte("pong")) { - t.Fatalf("bad: %v", recv) - } - }() - - conn, err := pl.Accept() - if err != nil { - if checkAddr == badAddr { - return - } - t.Fatalf("err: %v", err) - } - defer conn.Close() - - recv := make([]byte, 4) - _, err = conn.Read(recv) - if err != nil { - t.Fatalf("err: %v", err) - } - if !bytes.Equal(recv, []byte("ping")) { - t.Fatalf("bad: %v", recv) - } - - if _, err := conn.Write([]byte("pong")); err != nil { - t.Fatalf("err: %v", err) - } - - // Check the remote addr - addr := conn.RemoteAddr().(*net.TCPAddr) - switch checkAddr { - case goodAddr: - if addr.IP.String() != "10.1.1.1" { - t.Fatalf("bad: %v", addr) - } - if addr.Port != 1000 { - t.Fatalf("bad: %v", addr) - } - case badAddr: - if addr.IP.String() != "127.0.0.1" { - t.Fatalf("bad: %v", addr) - } - if addr.Port == 1000 { - t.Fatalf("bad: %v", addr) - } - } -} - -type testConn struct { - readFromCalledWith io.Reader - net.Conn // nil; crash on any unexpected use -} - -func (c *testConn) ReadFrom(r io.Reader) (int64, error) { - c.readFromCalledWith = r - return 0, nil -} -func (c *testConn) Write(p []byte) (int, error) { - return len(p), nil -} -func (c *testConn) Read(p []byte) (int, error) { - return 1, nil -} - -func TestCopyToWrappedConnection(t *testing.T) { - innerConn := &testConn{} - wrappedConn := NewConn(innerConn, 0) - dummySrc := &testConn{} - - io.Copy(wrappedConn, dummySrc) - if innerConn.readFromCalledWith != dummySrc { - t.Error("Expected io.Copy to delegate to ReadFrom function of inner destination connection") - } -} - -func TestCopyFromWrappedConnection(t *testing.T) { - wrappedConn := NewConn(&testConn{}, 0) - dummyDst := &testConn{} - - io.Copy(dummyDst, wrappedConn) - if dummyDst.readFromCalledWith != wrappedConn.conn { - t.Errorf("Expected io.Copy to pass inner source connection to ReadFrom method of destination") - } -} - -func TestCopyFromWrappedConnectionToWrappedConnection(t *testing.T) { - innerConn1 := &testConn{} - wrappedConn1 := NewConn(innerConn1, 0) - innerConn2 := &testConn{} - wrappedConn2 := NewConn(innerConn2, 0) - - io.Copy(wrappedConn1, wrappedConn2) - if innerConn1.readFromCalledWith != innerConn2 { - t.Errorf("Expected io.Copy to pass inner source connection to ReadFrom of inner destination connection") - } - -} diff --git a/anylink/server/sessdata/compress.go b/anylink/server/sessdata/compress.go new file mode 100644 index 0000000..7156f89 --- /dev/null +++ b/anylink/server/sessdata/compress.go @@ -0,0 +1,35 @@ +package sessdata + +import ( + "github.com/lanrenwo/lzsgo" +) + +type CmpEncoding interface { + Compress(src []byte, dst []byte) (int, error) + Uncompress(src []byte, dst []byte) (int, error) +} + +type LzsgoCmp struct { +} + +func (l LzsgoCmp) Compress(src []byte, dst []byte) (int, error) { + n, err := lzsgo.Compress(src, dst) + return n, err +} + +func (l LzsgoCmp) Uncompress(src []byte, dst []byte) (int, error) { + n, err := lzsgo.Uncompress(src, dst) + return n, err +} + +// type Lz4Cmp struct { +// c lz4.Compressor +// } + +// func (l Lz4Cmp) Compress(src []byte, dst []byte) (int, error) { +// return l.c.CompressBlock(src, dst) +// } + +// func (l Lz4Cmp) Uncompress(src []byte, dst []byte) (int, error) { +// return lz4.UncompressBlock(src, dst) +// } diff --git a/anylink/server/sessdata/compress_test.go b/anylink/server/sessdata/compress_test.go new file mode 100644 index 0000000..ce3a317 --- /dev/null +++ b/anylink/server/sessdata/compress_test.go @@ -0,0 +1,28 @@ +package sessdata + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestLzsCompress(t *testing.T) { + var ( + n int + err error + ) + assert := assert.New(t) + c := LzsgoCmp{} + s := "hello anylink, you are best!" + src := []byte(strings.Repeat(s, 50)) + + comprBuf := make([]byte, 2048) + n, err = c.Compress(src, comprBuf) + assert.Nil(err) + + unprBuf := make([]byte, 2048) + n, err = c.Uncompress(comprBuf[:n], unprBuf) + assert.Nil(err) + assert.Equal(src, unprBuf[:n]) +} diff --git a/anylink/server/sessdata/ip_pool.go b/anylink/server/sessdata/ip_pool.go index 1ab5ae2..41dc773 100644 --- a/anylink/server/sessdata/ip_pool.go +++ b/anylink/server/sessdata/ip_pool.go @@ -14,8 +14,10 @@ var ( IpPool = &ipPoolConfig{} ipActive = map[string]bool{} // ipKeep and ipLease ipAddr => type - ipLease = map[string]bool{} + // ipLease = map[string]bool{} ipPoolMux sync.Mutex + // 记录循环点 + loopCurIp uint32 ) type ipPoolConfig struct { @@ -36,7 +38,19 @@ func initIpPool() { } IpPool.Ipv4IPNet = ipNet IpPool.Ipv4Mask = net.IP(ipNet.Mask) - IpPool.Ipv4Gateway = net.ParseIP(base.Cfg.Ipv4Gateway) + + ipv4Gateway := net.ParseIP(base.Cfg.Ipv4Gateway) + ipStart := net.ParseIP(base.Cfg.Ipv4Start) + ipEnd := net.ParseIP(base.Cfg.Ipv4End) + if !ipNet.Contains(ipv4Gateway) || !ipNet.Contains(ipStart) || !ipNet.Contains(ipEnd) { + panic("ip段 设置错误") + } + // ip地址池 + IpPool.Ipv4Gateway = ipv4Gateway + IpPool.IpLongMin = utils.Ip2long(ipStart) + IpPool.IpLongMax = utils.Ip2long(ipEnd) + + loopCurIp = IpPool.IpLongMin // 网络地址零值 // zero := binary.BigEndian.Uint32(ip.Mask(mask)) @@ -44,71 +58,160 @@ func initIpPool() { // one, _ := ipNet.Mask.Size() // max := min | uint32(math.Pow(2, float64(32-one))-1) - // ip地址池 - IpPool.IpLongMin = utils.Ip2long(net.ParseIP(base.Cfg.Ipv4Start)) - IpPool.IpLongMax = utils.Ip2long(net.ParseIP(base.Cfg.Ipv4End)) - // 获取IpLease数据 - go cronIpLease() + // go cronIpLease() } -func cronIpLease() { - getIpLease() - tick := time.NewTicker(time.Minute * 30) - for range tick.C { - getIpLease() - } -} - -func getIpLease() { - xdb := dbdata.GetXdb() - keepIpMaps := []dbdata.IpMap{} - sNow := time.Now().Add(-1 * time.Duration(base.Cfg.IpLease) * time.Second) - err := xdb.Cols("ip_addr").Where("keep=?", true).Or("last_login>?", sNow).Find(&keepIpMaps) - if err != nil { - base.Error(err) - } - // fmt.Println(keepIpMaps) - ipPoolMux.Lock() - ipLease = map[string]bool{} - for _, v := range keepIpMaps { - ipLease[v.IpAddr] = true - } - ipPoolMux.Unlock() -} +// func cronIpLease() { +// getIpLease() +// tick := time.NewTicker(time.Minute * 30) +// for range tick.C { +// getIpLease() +// } +// } +// +// func getIpLease() { +// xdb := dbdata.GetXdb() +// keepIpMaps := []dbdata.IpMap{} +// sNow := time.Now().Add(-1 * time.Duration(base.Cfg.IpLease) * time.Second) +// err := xdb.Cols("ip_addr").Where("keep=?", true). +// Or("unique_mac=? and last_login>?", true, sNow).Find(&keepIpMaps) +// if err != nil { +// base.Error(err) +// } +// // fmt.Println(keepIpMaps) +// ipPoolMux.Lock() +// ipLease = map[string]bool{} +// for _, v := range keepIpMaps { +// ipLease[v.IpAddr] = true +// } +// ipPoolMux.Unlock() +// } // AcquireIp 获取动态ip -func AcquireIp(username, macAddr string) net.IP { +func AcquireIp(username, macAddr string, uniqueMac bool) net.IP { + base.Trace("AcquireIp:", username, macAddr, uniqueMac) ipPoolMux.Lock() defer ipPoolMux.Unlock() - tNow := time.Now() + var ( + err error + tNow = time.Now() + ) - // 判断是否已经分配过 - mi := &dbdata.IpMap{} - err := dbdata.One("mac_addr", macAddr, mi) - // 存在ip记录 - if err == nil { + if uniqueMac { + // 判断是否已经分配过 + mi := &dbdata.IpMap{} + err = dbdata.One("mac_addr", macAddr, mi) + if err != nil { + // 没有查询到数据 + if dbdata.CheckErrNotFound(err) { + return loopIp(username, macAddr, uniqueMac) + } + // 查询报错 + base.Error(err) + return nil + } + + // 存在ip记录 + base.Trace("uniqueMac:", username, mi) ipStr := mi.IpAddr ip := net.ParseIP(ipStr) // 跳过活跃连接 _, ok := ipActive[ipStr] // 检测原有ip是否在新的ip池内 - if IpPool.Ipv4IPNet.Contains(ip) && !ok && + // IpPool.Ipv4IPNet.Contains(ip) && + if !ok && utils.Ip2long(ip) >= IpPool.IpLongMin && utils.Ip2long(ip) <= IpPool.IpLongMax { mi.Username = username mi.LastLogin = tNow + mi.UniqueMac = uniqueMac // 回写db数据 _ = dbdata.Set(mi) ipActive[ipStr] = true return ip } + // 删除当前macAddr + mi = &dbdata.IpMap{MacAddr: macAddr} _ = dbdata.Del(mi) + + } else { + // 没有获取到mac的情况 + ipMaps := []dbdata.IpMap{} + err = dbdata.FindWhere(&ipMaps, 50, 1, "username=? and unique_mac=?", username, false) + if err != nil { + // 没有查询到数据 + if dbdata.CheckErrNotFound(err) { + return loopIp(username, macAddr, uniqueMac) + } + // 查询报错 + base.Error(err) + return nil + } + + // 遍历mac记录 + for _, mi := range ipMaps { + ipStr := mi.IpAddr + ip := net.ParseIP(ipStr) + + // 跳过活跃连接 + if _, ok := ipActive[ipStr]; ok { + continue + } + // 跳过保留ip + if mi.Keep { + continue + } + // 没有mac的 不需要验证租期 + // mi.LastLogin.Before(leaseTime) && + if utils.Ip2long(ip) >= IpPool.IpLongMin && + utils.Ip2long(ip) <= IpPool.IpLongMax { + mi.LastLogin = tNow + mi.MacAddr = macAddr + mi.UniqueMac = uniqueMac + // 回写db数据 + _ = dbdata.Set(mi) + ipActive[ipStr] = true + return ip + } + } } + return loopIp(username, macAddr, uniqueMac) +} + +func loopIp(username, macAddr string, uniqueMac bool) net.IP { + var ( + i uint32 + ip net.IP + ) + + i, ip = loopLong(loopCurIp, IpPool.IpLongMax, username, macAddr, uniqueMac) + if ip != nil { + loopCurIp = i + return ip + } + + i, ip = loopLong(IpPool.IpLongMin, loopCurIp, username, macAddr, uniqueMac) + if ip != nil { + loopCurIp = i + return ip + } + + base.Warn("no ip available, please see ip_map table row", username, macAddr) + return nil +} + +func loopLong(start, end uint32, username, macAddr string, uniqueMac bool) (uint32, net.IP) { + var ( + err error + tNow = time.Now() + leaseTime = time.Now().Add(-1 * time.Duration(base.Cfg.IpLease) * time.Second) + ) + // 全局遍历超过租期和未保留的ip - for i := IpPool.IpLongMin; i <= IpPool.IpLongMax; i++ { + for i := start; i <= end; i++ { ip := utils.Long2ip(i) ipStr := ip.String() @@ -116,32 +219,42 @@ func AcquireIp(username, macAddr string) net.IP { if _, ok := ipActive[ipStr]; ok { continue } - // 跳过ip租期内数据 - if _, ok := ipLease[ipStr]; ok { - continue + + mi := &dbdata.IpMap{} + err = dbdata.One("ip_addr", ipStr, mi) + if err != nil { + // 没有查询到数据 + if dbdata.CheckErrNotFound(err) { + // 该ip没有被使用 + mi = &dbdata.IpMap{IpAddr: ipStr, MacAddr: macAddr, UniqueMac: uniqueMac, Username: username, LastLogin: tNow} + _ = dbdata.Add(mi) + ipActive[ipStr] = true + return i, ip + } + // 查询报错 + base.Error(err) + return 0, nil } - v := &dbdata.IpMap{} - err = dbdata.One("ip_addr", ipStr, v) - if err == nil { - // 存在记录直接跳过 + // 查询到已经使用的ip + // 跳过保留ip + if mi.Keep { continue } - - if dbdata.CheckErrNotFound(err) { - // 该ip没有被使用 - mi = &dbdata.IpMap{IpAddr: ipStr, MacAddr: macAddr, Username: username, LastLogin: tNow} - _ = dbdata.Add(mi) + // 判断租期 + if mi.LastLogin.Before(leaseTime) { + // 存在记录,说明已经超过租期,可以直接使用 + mi.LastLogin = tNow + mi.MacAddr = macAddr + mi.UniqueMac = uniqueMac + // 回写db数据 + _ = dbdata.Set(mi) ipActive[ipStr] = true - return ip + return i, ip } - // 查询报错 - base.Error(err) - return nil } - base.Warn("no ip available, please see ip_map table row") - return nil + return 0, nil } // 回收ip diff --git a/anylink/server/sessdata/ip_pool_test.go b/anylink/server/sessdata/ip_pool_test.go index 5db01cd..d103112 100644 --- a/anylink/server/sessdata/ip_pool_test.go +++ b/anylink/server/sessdata/ip_pool_test.go @@ -6,6 +6,7 @@ import ( "os" "path" "testing" + "time" "github.com/bjdgyc/anylink/base" "github.com/bjdgyc/anylink/dbdata" @@ -18,10 +19,12 @@ func preData(tmpDir string) { base.Cfg.DbType = "sqlite3" base.Cfg.DbSource = tmpDb base.Cfg.Ipv4CIDR = "192.168.3.0/24" - base.Cfg.Ipv4Start = "192.168.3.1" - base.Cfg.Ipv4End = "192.168.3.199" + base.Cfg.Ipv4Gateway = "192.168.3.1" + base.Cfg.Ipv4Start = "192.168.3.100" + base.Cfg.Ipv4End = "192.168.3.150" base.Cfg.MaxClient = 100 base.Cfg.MaxUserClient = 3 + base.Cfg.IpLease = 5 dbdata.Start() group := dbdata.Group{ @@ -46,22 +49,34 @@ func TestIpPool(t *testing.T) { var ip net.IP - for i := 1; i <= 100; i++ { - _ = AcquireIp("user", fmt.Sprintf("mac-%d", i)) + for i := 100; i <= 150; i++ { + _ = AcquireIp(getTestUser(i), getTestMacAddr(i), true) } - ip = AcquireIp("user", "mac-new") - assert.True(net.IPv4(192, 168, 3, 101).Equal(ip)) - for i := 102; i <= 199; i++ { - ip = AcquireIp("user", fmt.Sprintf("mac-%d", i)) - } - assert.True(net.IPv4(192, 168, 3, 199).Equal(ip)) - ip = AcquireIp("user", "mac-nil") - assert.Nil(ip) - ReleaseIp(net.IPv4(192, 168, 3, 88), "mac-88") - ReleaseIp(net.IPv4(192, 168, 3, 188), "mac-188") + // 回收 + ReleaseIp(net.IPv4(192, 168, 3, 140), getTestMacAddr(140)) + time.Sleep(time.Second * 6) + // 从头循环获取可用ip - ip = AcquireIp("user", "mac-188") - t.Log("mac-188", ip) - assert.True(net.IPv4(192, 168, 3, 188).Equal(ip)) + user_new := getTestUser(210) + mac_new := getTestMacAddr(210) + ip = AcquireIp(user_new, mac_new, true) + t.Log("mac_new", ip) + assert.NotNil(ip) + assert.True(net.IPv4(192, 168, 3, 140).Equal(ip)) + + // 回收全部 + for i := 100; i <= 150; i++ { + ReleaseIp(net.IPv4(192, 168, 3, byte(i)), getTestMacAddr(i)) + } +} + +func getTestUser(i int) string { + return fmt.Sprintf("user-%d", i) +} + +func getTestMacAddr(i int) string { + // 前缀mac + macAddr := "02:00:00:00:00" + return fmt.Sprintf("%s:%x", macAddr, i) } diff --git a/anylink/server/sessdata/online.go b/anylink/server/sessdata/online.go index e8dff7a..7ac526d 100644 --- a/anylink/server/sessdata/online.go +++ b/anylink/server/sessdata/online.go @@ -14,6 +14,7 @@ type Online struct { Username string `json:"username"` Group string `json:"group"` MacAddr string `json:"mac_addr"` + UniqueMac bool `json:"unique_mac"` Ip net.IP `json:"ip"` RemoteAddr string `json:"remote_addr"` TunName string `json:"tun_name"` @@ -52,6 +53,7 @@ func OnlineSess() []Online { Username: v.Username, Group: v.Group, MacAddr: v.MacAddr, + UniqueMac: v.UniqueMac, RemoteAddr: v.CSess.RemoteAddr, TunName: v.CSess.IfName, Mtu: v.CSess.Mtu, diff --git a/anylink/server/sessdata/session.go b/anylink/server/sessdata/session.go index 70f3e7e..fc38928 100644 --- a/anylink/server/sessdata/session.go +++ b/anylink/server/sessdata/session.go @@ -12,8 +12,7 @@ import ( "github.com/bjdgyc/anylink/base" "github.com/bjdgyc/anylink/dbdata" - "github.com/bjdgyc/anylink/pkg/utils" - "github.com/ivpusic/grpool" + mapset "github.com/deckarep/golang-set" atomic2 "go.uber.org/atomic" ) @@ -37,6 +36,8 @@ type ConnSession struct { Mtu int IfName string Client string // 客户端 mobile pc + UserAgent string // 客户端信息 + UserLogoutCode uint8 // 用户/客户端主动登出 CstpDpd int Group *dbdata.Group Limit *LimitRater @@ -51,10 +52,11 @@ type ConnSession struct { PayloadIn chan *Payload PayloadOutCstp chan *Payload // Cstp的数据 PayloadOutDtls chan *Payload // Dtls的数据 - IpAuditMap utils.IMaps // 审计的ip数据 - IpAuditPool *grpool.Pool // 审计的IP包解析池 // dSess *DtlsSession dSess *atomic.Value + // compress + CstpPickCmp CmpEncoding + DtlsPickCmp CmpEncoding } type DtlsSession struct { @@ -65,17 +67,22 @@ type DtlsSession struct { } type Session struct { - mux sync.RWMutex - Sid string // auth返回的 session-id - Token string // session信息的唯一token - DtlsSid string // dtls协议的 session_id - MacAddr string // 客户端mac地址 - UniqueIdGlobal string // 客户端唯一标示 - MacHw net.HardwareAddr - Username string // 用户名 - Group string - AuthStep string - AuthPass string + mux sync.RWMutex + Sid string // auth返回的 session-id + Token string // session信息的唯一token + DtlsSid string // dtls协议的 session_id + MacAddr string // 客户端mac地址 + UniqueIdGlobal string // 客户端唯一标示 + MacHw net.HardwareAddr + UniqueMac bool // 客户端获取到真实设备mac + Username string // 用户名 + Group string + AuthStep string + AuthPass string + RemoteAddr string + UserAgent string + DeviceType string + PlatformVersion string LastLogin time.Time IsActive bool @@ -97,22 +104,46 @@ func checkSession() { timeout := time.Duration(base.Cfg.SessionTimeout) * time.Second tick := time.NewTicker(time.Second * 60) for range tick.C { - sessMux.Lock() + outToken := []string{} + sessMux.RLock() t := time.Now() for k, v := range sessions { - v.mux.Lock() + v.mux.RLock() if !v.IsActive { if t.Sub(v.LastLogin) > timeout { - delete(sessions, k) + outToken = append(outToken, k) } } - v.mux.Unlock() + v.mux.RUnlock() + } + sessMux.RUnlock() + + // 删除过期session + for _, v := range outToken { + CloseSess(v, dbdata.UserLogoutTimeout) } - sessMux.Unlock() } }() } +// 状态为过期的用户踢下线 +func CloseUserLimittimeSession() { + s := mapset.NewSetFromSlice(dbdata.CheckUserlimittime()) + limitTimeToken := []string{} + sessMux.RLock() + for _, v := range sessions { + v.mux.RLock() + if v.IsActive && s.Contains(v.Username) { + limitTimeToken = append(limitTimeToken, v.Token) + } + v.mux.RUnlock() + } + sessMux.RUnlock() + for _, v := range limitTimeToken { + CloseSess(v, dbdata.UserLogoutExpire) + } +} + func GenToken() string { // 生成32位的 token bToken := make([]byte, 32) @@ -151,6 +182,7 @@ func (s *Session) NewConn() *ConnSession { macAddr := s.MacAddr macHw := s.MacHw username := s.Username + uniqueMac := s.UniqueMac s.mux.RUnlock() if active { s.CSess.Close() @@ -158,9 +190,10 @@ func (s *Session) NewConn() *ConnSession { limit := LimitClient(username, false) if !limit { + base.Warn("limit is full", username) return nil } - ip := AcquireIp(username, macAddr) + ip := AcquireIp(username, macAddr, uniqueMac) if ip == nil { LimitClient(username, true) return nil @@ -187,12 +220,6 @@ func (s *Session) NewConn() *ConnSession { dSess: &atomic.Value{}, } - // ip 审计 - if base.Cfg.AuditInterval >= 0 { - cSess.IpAuditMap = utils.NewMap("cmap", 0) - cSess.IpAuditPool = grpool.NewPool(1, 600) - } - dSess := &DtlsSession{ isActive: -1, } @@ -232,6 +259,7 @@ func (cs *ConnSession) Close() { ReleaseIp(cs.IpAddr, cs.Sess.MacAddr) LimitClient(cs.Username, true) + AddUserActLog(cs) }) } @@ -335,6 +363,30 @@ func (cs *ConnSession) RateLimit(byt int, isUp bool) error { return cs.Limit.Wait(byt) } +func (cs *ConnSession) SetPickCmp(cate, encoding string) (string, bool) { + var cmpName string + if !base.Cfg.Compression { + return cmpName, false + } + var cmp CmpEncoding + switch { + // case strings.Contains(encoding, "oc-lz4"): + // cmpName = "oc-lz4" + // cmp = Lz4Cmp{} + case strings.Contains(encoding, "lzs"): + cmpName = "lzs" + cmp = LzsgoCmp{} + default: + return cmpName, false + } + if cate == "cstp" { + cs.CstpPickCmp = cmp + } else { + cs.DtlsPickCmp = cmp + } + return cmpName, true +} + func SToken2Sess(stoken string) *Session { stoken = strings.TrimSpace(stoken) sarr := strings.Split(stoken, "@") @@ -356,6 +408,20 @@ func Dtls2Sess(did string) *Session { return sessions[token] } +func Dtls2CSess(did string) *ConnSession { + sessMux.RLock() + defer sessMux.RUnlock() + token := dtlsIds[did] + sess := sessions[token] + if sess == nil { + return nil + } + + sess.mux.RLock() + defer sess.mux.RUnlock() + return sess.CSess +} + func Dtls2MasterSecret(did string) string { sessMux.RLock() token := dtlsIds[did] @@ -378,7 +444,7 @@ func DelSess(token string) { // sessions.Delete(token) } -func CloseSess(token string) { +func CloseSess(token string, code ...uint8) { sessMux.Lock() defer sessMux.Unlock() sess, ok := sessions[token] @@ -387,7 +453,16 @@ func CloseSess(token string) { } delete(sessions, token) - sess.CSess.Close() + delete(dtlsIds, sess.DtlsSid) + + if sess.CSess != nil { + if len(code) > 0 { + sess.CSess.UserLogoutCode = code[0] + } + sess.CSess.Close() + return + } + AddUserActLogBySess(sess) } func CloseCSess(token string) { @@ -398,14 +473,42 @@ func CloseCSess(token string) { return } - sess.CSess.Close() + if sess.CSess != nil { + sess.CSess.Close() + } } func DelSessByStoken(stoken string) { stoken = strings.TrimSpace(stoken) sarr := strings.Split(stoken, "@") token := sarr[1] - sessMux.Lock() - delete(sessions, token) - sessMux.Unlock() + CloseSess(token, dbdata.UserLogoutBanner) +} + +func AddUserActLog(cs *ConnSession) { + ua := dbdata.UserActLog{ + Username: cs.Sess.Username, + GroupName: cs.Sess.Group, + IpAddr: cs.IpAddr.String(), + RemoteAddr: cs.RemoteAddr, + DeviceType: cs.Sess.DeviceType, + PlatformVersion: cs.Sess.PlatformVersion, + Status: dbdata.UserLogout, + } + ua.Info = dbdata.UserActLogIns.GetInfoOpsById(cs.UserLogoutCode) + dbdata.UserActLogIns.Add(ua, cs.UserAgent) +} + +func AddUserActLogBySess(sess *Session) { + ua := dbdata.UserActLog{ + Username: sess.Username, + GroupName: sess.Group, + IpAddr: "", + RemoteAddr: sess.RemoteAddr, + DeviceType: sess.DeviceType, + PlatformVersion: sess.PlatformVersion, + Status: dbdata.UserLogout, + } + ua.Info = dbdata.UserActLogIns.GetInfoOpsById(dbdata.UserLogoutBanner) + dbdata.UserActLogIns.Add(ua, sess.UserAgent) } diff --git a/anylink/server/sessdata/session_test.go b/anylink/server/sessdata/session_test.go index c9219b2..6719f61 100644 --- a/anylink/server/sessdata/session_test.go +++ b/anylink/server/sessdata/session_test.go @@ -1,8 +1,11 @@ package sessdata import ( + "fmt" "testing" + "time" + "github.com/bjdgyc/anylink/base" "github.com/stretchr/testify/assert" ) @@ -22,11 +25,15 @@ func TestConnSession(t *testing.T) { preData(tmp) defer cleardata(tmp) + time.Sleep(time.Second * 10) + sess := NewSession("") + sess.Username = "user-test" sess.Group = "group1" sess.MacAddr = "00:15:5d:50:14:43" cSess := sess.NewConn() + base.Info("cSess", cSess) err := cSess.RateLimit(100, true) ast.Nil(err) @@ -34,5 +41,23 @@ func TestConnSession(t *testing.T) { err = cSess.RateLimit(200, false) ast.Nil(err) ast.Equal(cSess.BandwidthDown.Load(), uint32(200)) + + var ( + cmpName string + ok bool + ) + base.Cfg.Compression = true + + cmpName, ok = cSess.SetPickCmp("cstp", "oc-lz4,lzs") + fmt.Println(cmpName, ok) + ast.True(ok) + ast.Equal(cmpName, "lzs") + cmpName, ok = cSess.SetPickCmp("dtls", "lzs") + ast.True(ok) + ast.Equal(cmpName, "lzs") + cmpName, ok = cSess.SetPickCmp("dtls", "test") + ast.False(ok) + ast.Equal(cmpName, "") + cSess.Close() } diff --git a/anylink/server/sessdata/start.go b/anylink/server/sessdata/start.go index 1d4243c..7862574 100644 --- a/anylink/server/sessdata/start.go +++ b/anylink/server/sessdata/start.go @@ -4,4 +4,5 @@ func Start() { initIpPool() checkSession() saveStatsInfo() + CloseUserLimittimeSession() } diff --git a/anylink/systemd/anylink.service b/anylink/systemd/anylink.service index 9f1fe68..72c3297 100644 --- a/anylink/systemd/anylink.service +++ b/anylink/systemd/anylink.service @@ -11,5 +11,12 @@ Restart=on-failure RestartSec=5s ExecStart=/usr/local/anylink-deploy/anylink --conf=/usr/local/anylink-deploy/conf/server.toml +# systemd older than v236 +# ExecStart=/bin/bash -c 'exec /usr/local/anylink-deploy/anylink --conf=/usr/local/anylink-deploy/conf/server.toml >> /usr/local/anylink-deploy/log/anylink.log 2>&1' + + +StandardOutput=file:/usr/local/anylink-deploy/log/anylink.log +StandardError=file:/usr/local/anylink-deploy/log/anylink.log + [Install] WantedBy=multi-user.target diff --git a/anylink/web/package.json b/anylink/web/package.json index 11ad5f9..73983b4 100644 --- a/anylink/web/package.json +++ b/anylink/web/package.json @@ -12,6 +12,7 @@ "core-js": "^3.6.5", "echarts": "^4.9.0", "element-ui": "^2.4.5", + "qs": "^6.11.1", "vue": "^2.6.11", "vue-count-to": "^1.0.13", "vue-router": "^3.5.2" diff --git a/anylink/web/public/批量添加用户模版.xlsx b/anylink/web/public/批量添加用户模版.xlsx new file mode 100644 index 0000000..5405de7 Binary files /dev/null and b/anylink/web/public/批量添加用户模版.xlsx differ diff --git a/anylink/web/src/components/audit/Access.vue b/anylink/web/src/components/audit/Access.vue new file mode 100644 index 0000000..bdab61b --- /dev/null +++ b/anylink/web/src/components/audit/Access.vue @@ -0,0 +1,321 @@ + + + + + \ No newline at end of file diff --git a/anylink/web/src/components/audit/ActLog.vue b/anylink/web/src/components/audit/ActLog.vue new file mode 100644 index 0000000..ab0e79e --- /dev/null +++ b/anylink/web/src/components/audit/ActLog.vue @@ -0,0 +1,263 @@ + + + + + \ No newline at end of file diff --git a/anylink/web/src/layout/LayoutAside.vue b/anylink/web/src/layout/LayoutAside.vue index 332190a..cfbbc59 100644 --- a/anylink/web/src/layout/LayoutAside.vue +++ b/anylink/web/src/layout/LayoutAside.vue @@ -56,7 +56,7 @@ 用户组列表 - + @@ -399,6 +426,7 @@ export default { addr:"", tls:false, base_dn:"", + object_class:"person", search_attr:"sAMAccountName", member_of:"", bind_name:"", @@ -415,6 +443,21 @@ export default { link_acl: [], auth : {}, }, + authLoginDialog : false, + authLoginLoading : false, + authLoginForm : { + name : "", + pwd : "", + }, + authLoginRules: { + name: [ + {required: true, message: '请输入账号', trigger: 'blur'}, + ], + pwd: [ + {required: true, message: '请输入密码', trigger: 'blur'}, + {min: 6, message: '长度至少 6 个字符', trigger: 'blur'} + ], + }, rules: { name: [ {required: true, message: '请输入组名', trigger: 'blur'}, @@ -437,7 +480,7 @@ export default { {required: true, message: '请输入服务器地址(含端口)', trigger: 'blur'} ], "auth.ldap.bind_name": [ - {required: true, message: '请输入管理员账号', trigger: 'blur'} + {required: true, message: '请输入管理员 DN', trigger: 'blur'} ], "auth.ldap.bind_pwd": [ {required: true, message: '请输入管理员密码', trigger: 'blur'} @@ -445,6 +488,9 @@ export default { "auth.ldap.base_dn": [ {required: true, message: '请输入Base DN值', trigger: 'blur'} ], + "auth.ldap.object_class": [ + {required: true, message: '请输入用户对象类', trigger: 'blur'} + ], "auth.ldap.search_attr": [ {required: true, message: '请输入用户唯一ID', trigger: 'blur'} ], @@ -457,6 +503,9 @@ export default { this.ruleForm.auth = JSON.parse(JSON.stringify(this.defAuth)); return ; } + if (row.auth.type == "ldap" && ! row.auth.ldap.object_class) { + row.auth.ldap.object_class = this.defAuth.ldap.object_class; + } this.ruleForm.auth = Object.assign(JSON.parse(JSON.stringify(this.defAuth)), row.auth); }, handleDel(row) { @@ -549,6 +598,44 @@ export default { }); }); }, + testAuthLogin() { + this.$refs["authLoginForm"].validate((valid) => { + if (!valid) { + console.log('error submit!!'); + return false; + } + this.authLoginLoading = true; + axios.post('/group/auth_login', {name:this.authLoginForm.name, + pwd:this.authLoginForm.pwd, + auth:this.ruleForm.auth}).then(resp => { + const rdata = resp.data; + if (rdata.code === 0) { + this.$message.success("登录成功"); + } else { + this.$message.error(rdata.msg); + } + this.authLoginLoading = false; + console.log(rdata); + }).catch(error => { + this.$message.error('哦,请求出错'); + console.log(error); + this.authLoginLoading = false; + }); + }); + }, + openAuthLoginDialog() { + this.$refs["ruleForm"].validate((valid) => { + if (!valid) { + console.log('error submit!!'); + return false; + } + this.authLoginDialog = true; + // set authLoginFormName focus + this.$nextTick(() => { + this.$refs['authLoginFormName'].focus(); + }); + }); + }, resetForm(formName) { this.$refs[formName].resetFields(); }, @@ -598,4 +685,20 @@ export default { .el-select { width: 80px; } + +::v-deep .valgin-dialog{ + display: flex; + flex-direction: column; + margin:0 !important; + position:absolute; + top:50%; + left:50%; + transform:translate(-50%,-50%); + max-height:calc(100% - 30px); + max-width:calc(100% - 30px); +} +::v-deep .valgin-dialog .el-dialog__body{ + flex:1; + overflow: auto; +} diff --git a/anylink/web/src/pages/set/Audit.vue b/anylink/web/src/pages/set/Audit.vue index 4e3a19e..e85de33 100644 --- a/anylink/web/src/pages/set/Audit.vue +++ b/anylink/web/src/pages/set/Audit.vue @@ -1,300 +1,61 @@ - - diff --git a/anylink/web/src/pages/set/Other.vue b/anylink/web/src/pages/set/Other.vue index 43f96fa..04a0e9b 100644 --- a/anylink/web/src/pages/set/Other.vue +++ b/anylink/web/src/pages/set/Other.vue @@ -2,7 +2,13 @@ - + @@ -13,7 +19,11 @@ - + @@ -26,90 +36,252 @@ - 保存 + 保存 重置 - + + + + 秒 +

+ 请手动修改配置文件中的 audit_interval 参数后,再重启服务, + -1 代表关闭审计日志 +

+
- 天 -

范围: 0 ~ 365天 , 0 代表永久保存

+ + 天 +

+ 范围: 0 ~ 365天 , + 0 代表永久保存 +

- - + v-model="dataAuditLog.clear_time" + :picker-options="{ + start: '00:00', + step: '01:00', + end: '23:00', + }" + editable="false," + size="small" + placeholder="请选择" + style="width: 130px" + > + + - 保存 + 保存 重置 - +
-
+ + + + + + + + 证书文件 + + + + + + + + 私钥文件 + + + + + + + 上传 + + + + + + + + + + + + + + 阿里云 + 腾讯云 + cloudflare + + + + + + + + + + + 申请 + 重置 + + + + + - - + - + + type="textarea" + :rows="5" + placeholder="请输入内容" + v-model="dataOther.banner" + > + type="textarea" + :rows="5" + placeholder="请输入内容" + v-model="dataOther.homeindex" + > + type="textarea" + :rows="10" + placeholder="请输入内容" + v-model="dataOther.account_mail" + > - 保存 + 保存 重置 -
@@ -120,25 +292,130 @@ import axios from "axios"; export default { name: "Other", created() { - this.$emit('update:route_path', this.$route.path) - this.$emit('update:route_name', ['基础信息', '其他设置']) + this.$emit("update:route_path", this.$route.path); + this.$emit("update:route_name", ["基础信息", "其他设置"]); }, mounted() { - this.getSmtp() + this.getSmtp(); }, data() { return { - activeName: 'dataSmtp', + activeName: "dataSmtp", + datacertManage: "customCert", dataSmtp: {}, dataAuditLog: {}, + letsCert: { + domain: ``, + legomail: ``, + name: "", + renew: "", + aliyun: { + apiKey: "", + secretKey: "", + }, + txcloud: { + secretId: "", + secretKey: "", + }, + cfcloud: { + authEmail: "", + authKey: "", + }, + }, + customCert: { cert: "", key: "" }, dataOther: {}, rules: { - host: {required: true, message: '请输入服务器地址', trigger: 'blur'}, + host: { required: true, message: "请输入服务器地址", trigger: "blur" }, port: [ - {required: true, message: '请输入服务器端口', trigger: 'blur'}, - {type: 'number', message: '请输入正确的服务器端口', trigger: ['blur', 'change']} + { required: true, message: "请输入服务器端口", trigger: "blur" }, + { + type: "number", + message: "请输入正确的服务器端口", + trigger: ["blur", "change"], + }, + ], + issuer: { required: true, message: "请输入系统名称", trigger: "blur" }, + domain: { + required: true, + message: "请输入需要申请证书的域名", + trigger: "blur", + }, + legomail: { + required: true, + message: "请输入申请证书的邮箱地址", + trigger: "blur", + }, + name: { required: true, message: "请选择域名服务商", trigger: "blur" }, + }, + certUpload: "/set/other/customcert", + dnsProvider: { + aliyun: [ + { + label: "APIKey", + prop: "apiKey", + component: "el-input", + type: "password", + rules: { + required: true, + message: "请输入正确的APIKey", + trigger: "blur", + }, + }, + { + label: "SecretKey", + prop: "secretKey", + component: "el-input", + type: "password", + rules: { + required: true, + message: "请输入正确的SecretKey", + trigger: "blur", + }, + }, + ], + txcloud: [ + { + label: "SecretID", + prop: "secretId", + component: "el-input", + type: "password", + rules: { + required: true, + message: "请输入正确的APIKey", + trigger: "blur", + }, + }, + { + label: "SecretKey", + prop: "secretKey", + component: "el-input", + type: "password", + rules: { + required: true, + message: "请输入正确的APIKey", + trigger: "blur", + }, + }, + ], + cfcloud: [ + { + label: "Email", + prop: "email", + component: "el-input", + type: "text", + }, + { + label: "AuthKey", + prop: "authKey", + component: "el-input", + type: "password", + rules: { + required: true, + message: "请输入正确的APIKey", + trigger: "blur", + }, + }, ], - issuer: {required: true, message: '请输入系统名称', trigger: 'blur'}, }, }; }, @@ -147,118 +424,185 @@ export default { window.console.log(tab.name, event); switch (tab.name) { case "dataSmtp": - this.getSmtp() - break + this.getSmtp(); + break; case "dataAuditLog": - this.getAuditLog() - break + this.getAuditLog(); + break; + case "letsCert": + this.getletsCert(); + break; case "dataOther": - this.getOther() - break + this.getOther(); + break; } }, + beforeCertUpload(file) { + // if (file.type !== 'application/x-pem-file') { + // this.$message.error('只能上传 .pem 格式的证书文件') + // return false + // } + this.customCert.cert = file; + }, + beforeKeyUpload(file) { + // if (file.type !== 'application/x-pem-file') { + // this.$message.error('只能上传 .pem 格式的私钥文件') + // return false + // } + this.customCert.key = file; + }, getSmtp() { - axios.get('/set/other/smtp').then(resp => { - let rdata = resp.data - console.log(rdata) - if (rdata.code !== 0) { - this.$message.error(rdata.msg); - return; - } - this.dataSmtp = rdata.data - }).catch(error => { - this.$message.error('哦,请求出错'); - console.log(error); - }); + axios + .get("/set/other/smtp") + .then((resp) => { + let rdata = resp.data; + console.log(rdata); + if (rdata.code !== 0) { + this.$message.error(rdata.msg); + return; + } + this.dataSmtp = rdata.data; + }) + .catch((error) => { + this.$message.error("哦,请求出错"); + console.log(error); + }); }, getAuditLog() { - axios.get('/set/other/audit_log').then(resp => { - let rdata = resp.data - console.log(rdata) - if (rdata.code !== 0) { - this.$message.error(rdata.msg); - return; - } - this.dataAuditLog = rdata.data - }).catch(error => { - this.$message.error('哦,请求出错'); - console.log(error); - }); - }, + axios + .get("/set/other/audit_log") + .then((resp) => { + let rdata = resp.data; + console.log(rdata); + if (rdata.code !== 0) { + this.$message.error(rdata.msg); + return; + } + this.dataAuditLog = rdata.data; + }) + .catch((error) => { + this.$message.error("哦,请求出错"); + console.log(error); + }); + }, + getletsCert() { + axios + .get("/set/other/getcertset") + .then((resp) => { + let rdata = resp.data; + console.log(rdata); + if (rdata.code !== 0) { + this.$message.error(rdata.msg); + return; + } + this.letsCert = Object.assign({}, this.letsCert, rdata.data); + }) + .catch((error) => { + this.$message.error("哦,请求出错"); + console.log(error); + }); + }, getOther() { - axios.get('/set/other').then(resp => { - let rdata = resp.data - console.log(rdata) - if (rdata.code !== 0) { - this.$message.error(rdata.msg); - return; - } - this.dataOther = rdata.data - }).catch(error => { - this.$message.error('哦,请求出错'); - console.log(error); - }); + axios + .get("/set/other") + .then((resp) => { + let rdata = resp.data; + console.log(rdata); + if (rdata.code !== 0) { + this.$message.error(rdata.msg); + return; + } + this.dataOther = rdata.data; + }) + .catch((error) => { + this.$message.error("哦,请求出错"); + console.log(error); + }); }, submitForm(formName) { this.$refs[formName].validate((valid) => { if (!valid) { - alert('error submit!'); + alert("error submit!"); } switch (formName) { case "dataSmtp": - axios.post('/set/other/smtp/edit', this.dataSmtp).then(resp => { - var rdata = resp.data + axios.post("/set/other/smtp/edit", this.dataSmtp).then((resp) => { + var rdata = resp.data; console.log(rdata); if (rdata.code === 0) { this.$message.success(rdata.msg); } else { this.$message.error(rdata.msg); } - - }) + }); break; case "dataAuditLog": - axios.post('/set/other/audit_log/edit', this.dataAuditLog).then(resp => { - var rdata = resp.data + axios + .post("/set/other/audit_log/edit", this.dataAuditLog) + .then((resp) => { + var rdata = resp.data; + console.log(rdata); + if (rdata.code === 0) { + this.$message.success(rdata.msg); + } else { + this.$message.error(rdata.msg); + } + }); + break; + case "letsCert": + axios.post("/set/other/createcert", this.letsCert).then((resp) => { + var rdata = resp.data; console.log(rdata); if (rdata.code === 0) { this.$message.success(rdata.msg); } else { this.$message.error(rdata.msg); } - }) + }); + break; + case "customCert": + var formData = new FormData(); + formData.append("cert", this.customCert.cert); + formData.append("key", this.customCert.key); + axios.post(this.certUpload, formData).then((resp) => { + var rdata = resp.data; + console.log(rdata); + if (rdata.code === 0) { + this.$message.success(rdata.msg); + } else { + this.$message.error(rdata.msg); + } + }); break; case "dataOther": - axios.post('/set/other/edit', this.dataOther).then(resp => { - var rdata = resp.data + axios.post("/set/other/edit", this.dataOther).then((resp) => { + var rdata = resp.data; console.log(rdata); if (rdata.code === 0) { this.$message.success(rdata.msg); } else { this.$message.error(rdata.msg); } - }) + }); break; } - }); }, resetForm(formName) { this.$refs[formName].resetFields(); - } + }, }, -} +}; diff --git a/anylink/web/src/pages/user/IpMap.vue b/anylink/web/src/pages/user/IpMap.vue index b6f6076..5b4755a 100644 --- a/anylink/web/src/pages/user/IpMap.vue +++ b/anylink/web/src/pages/user/IpMap.vue @@ -1,269 +1,285 @@ diff --git a/anylink/web/src/pages/user/List.vue b/anylink/web/src/pages/user/List.vue index 45b3805..303de1e 100644 --- a/anylink/web/src/pages/user/List.vue +++ b/anylink/web/src/pages/user/List.vue @@ -10,7 +10,24 @@ @click="handleEdit('')">添加 - + + + + 批量添加 + + + + 下载模版 + + + + @@ -87,7 +104,8 @@ width="70"> @@ -182,6 +200,18 @@ + + + + + @@ -208,6 +238,7 @@ 启用 停用 + 过期 @@ -245,6 +276,11 @@ export default { grouNames: [], tableData: [], count: 10, + pickerOptions: { + disabledDate(time) { + return time.getTime() < Date.now(); + } + }, searchData: '', otpImgData: {visible: false, username: '', nickname: '', base64Img: ''}, ruleForm: { @@ -264,7 +300,6 @@ export default { {required: true, message: '请输入用户邮箱', trigger: 'blur'}, {type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change']} ], - password: [ {min: 6, message: '长度大于 6 个字符', trigger: 'blur'} ], @@ -285,6 +320,24 @@ export default { }, methods: { + upLoadUser(item) { + const formData = new FormData(); + formData.append("file", item.file); + axios.post('/user/uploaduser', formData, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }).then(resp => { + if (resp.data.code === 0) { + this.$message.success(resp.data.data); + this.getData(1); + } else { + this.$message.error(resp.data.msg); + this.getData(1); + } + console.log(resp.data); + }) + }, getOtpImg(row) { // this.base64Img = Buffer.from(data).toString('base64'); this.otpImgData.visible = true diff --git a/anylink/web/src/pages/user/Online.vue b/anylink/web/src/pages/user/Online.vue index f9041cb..9b4f141 100644 --- a/anylink/web/src/pages/user/Online.vue +++ b/anylink/web/src/pages/user/Online.vue @@ -27,6 +27,15 @@ prop="mac_addr" label="MAC地址"> + + + + +