1.新增自动颁发证书

2.更新Go到1.23
This commit is contained in:
兔子 2025-06-13 13:05:50 +08:00
parent db851fbcb1
commit f77fc6dddf
38 changed files with 2921 additions and 197 deletions

View File

@ -16,14 +16,16 @@ go build -o .\bin\b612_x86_64 -ldflags "-w -s" .
upx -9 .\bin\b612_x86_64
set GOARCH=386
go build -o .\bin\b612_x86 -ldflags "-w -s" .
#upx -9 .\bin\b612_x86
upx -9 .\bin\b612_x86
set GOARCH=arm64
go build -o .\bin\b612_aarch64 -ldflags "-w -s" .
upx -9 .\bin\b612_aarch64
set GOARCH=mips
go build -o .\bin\b612_mips -ldflags "-w -s" .
upx -9 .\bin\b612_mips
set GOARCH=mipsle
go build -o .\bin\b612_mipsle -ldflags "-w -s" .
upx -9 .\bin\b612_mipsle
set GOARCH=mips64
go build -o .\bin\b612_mips64 -ldflags "-w -s" .
set GOARCH=mips64le

View File

@ -1,13 +1,13 @@
package cert
import (
"b612.me/starcrypto"
"b612.me/stario"
"b612.me/starlog"
"crypto"
"crypto/x509"
"fmt"
"github.com/spf13/cobra"
"math/big"
"os"
"path/filepath"
"time"
@ -26,10 +26,11 @@ var maxPathLen int
var caKey string
var caCert string
var csr string
var pubKey string
var caKeyPwd string
var passwd string
var enPasswd string
var keyUsage int
var extKeyUsage []int
var Cmd = &cobra.Command{
Use: "cert",
@ -66,16 +67,6 @@ var CmdCsr = &cobra.Command{
dnsName = stario.MessageBox("请输入dns名称用逗号分割", "").MustSliceString(",")
}
}
start, err = time.Parse(time.RFC3339, startStr)
if err != nil {
starlog.Errorln("开始时间格式错误,格式:2006-01-02T15:04:05Z07:00", err)
os.Exit(1)
}
end, err = time.Parse(time.RFC3339, endStr)
if err != nil {
starlog.Errorln("结束时间格式错误,格式:2006-01-02T15:04:05Z07:00", err)
os.Exit(1)
}
key, err := LoadPriv(caKey, caKeyPwd)
if err != nil {
starlog.Errorln("加载Key错误", err)
@ -108,10 +99,6 @@ var CmdGen = &cobra.Command{
starlog.Errorln("证书请求不能为空")
os.Exit(1)
}
if pubKey == "" {
starlog.Errorln("证书公钥不能为空")
os.Exit(1)
}
var caKeyRaw crypto.PrivateKey
var caCertRaw *x509.Certificate
var err error
@ -133,24 +120,68 @@ var CmdGen = &cobra.Command{
starlog.Errorln("加载证书请求错误", err)
os.Exit(1)
}
pubKeyByte, err := os.ReadFile(pubKey)
start, err = time.Parse(time.RFC3339, startStr)
if err != nil {
starlog.Errorln("加载公钥错误", err)
starlog.Errorln("开始时间格式错误,格式:2006-01-02T15:04:05Z07:00", err)
os.Exit(1)
}
pubKeyRaw, err := starcrypto.DecodePublicKey(pubKeyByte)
end, err = time.Parse(time.RFC3339, endStr)
if err != nil {
starlog.Errorln("解析公钥错误", err)
starlog.Errorln("结束时间格式错误,格式:2006-01-02T15:04:05Z07:00", err)
os.Exit(1)
}
pubKeyRaw := csrRaw.PublicKey
certReq := &x509.Certificate{
SerialNumber: big.NewInt(time.Now().UnixNano()),
Subject: csrRaw.Subject,
IsCA: isCa,
NotBefore: start,
NotAfter: end,
MaxPathLen: maxPathLen,
MaxPathLenZero: maxPathLenZero,
DNSNames: csrRaw.DNSNames,
IPAddresses: csrRaw.IPAddresses,
}
if !isCa {
if keyUsage == 0 {
certReq.KeyUsage = x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment
}
if len(extKeyUsage) == 0 {
certReq.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}
}
} else {
if len(extKeyUsage) == 0 {
certReq.ExtKeyUsage = []x509.ExtKeyUsage{
x509.ExtKeyUsageAny,
x509.ExtKeyUsageServerAuth,
x509.ExtKeyUsageClientAuth,
x509.ExtKeyUsageCodeSigning,
x509.ExtKeyUsageEmailProtection,
x509.ExtKeyUsageIPSECEndSystem,
x509.ExtKeyUsageIPSECTunnel,
x509.ExtKeyUsageIPSECUser,
x509.ExtKeyUsageTimeStamping,
x509.ExtKeyUsageOCSPSigning,
x509.ExtKeyUsageMicrosoftServerGatedCrypto,
x509.ExtKeyUsageNetscapeServerGatedCrypto,
x509.ExtKeyUsageMicrosoftCommercialCodeSigning,
x509.ExtKeyUsageMicrosoftKernelCodeSigning,
}
}
if keyUsage == 0 {
certReq.KeyUsage = x509.KeyUsageCertSign | x509.KeyUsageCRLSign | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement | x509.KeyUsageDigitalSignature
}
}
if keyUsage != 0 {
certReq.KeyUsage = x509.KeyUsage(keyUsage)
}
if len(extKeyUsage) > 0 {
certReq.ExtKeyUsage = make([]x509.ExtKeyUsage, len(extKeyUsage))
for i, v := range extKeyUsage {
certReq.ExtKeyUsage[i] = x509.ExtKeyUsage(v)
}
}
certReq.Subject.SerialNumber = fmt.Sprint(time.Now().UnixNano())
if isCa {
caCertRaw = certReq
}
@ -169,6 +200,15 @@ var CmdGen = &cobra.Command{
},
}
var CmdFastGen = &cobra.Command{
Use: "fastgen",
Short: "快速生成证书",
Long: "快速生成证书",
Run: func(cmd *cobra.Command, args []string) {
return
},
}
var CmdParse = &cobra.Command{
Use: "parse",
Short: "解析证书",
@ -208,11 +248,11 @@ func init() {
//CmdCsr.Flags().StringVarP(&endStr, "end", "E", time.Now().AddDate(1, 0, 0).Format(time.RFC3339), "结束时间,格式:2006-01-02T15:04:05Z07:00")
//CmdCsr.Flags().BoolVarP(&maxPathLenZero, "maxPathLenZero", "z", false, "允许最大路径长度为0")
//CmdCsr.Flags().IntVarP(&maxPathLen, "maxPathLen", "m", 0, "最大路径长度")
CmdGen.Flags().IntVarP(&keyUsage, "keyUsage", "u", 0, "证书使用类型默认数字00表示数字签名和密钥加密1表示证书签名2表示CRL签名4表示密钥协商8表示数据加密")
CmdGen.Flags().IntSliceVarP(&extKeyUsage, "extKeyUsage", "e", nil, "扩展证书使用类型默认数字00表示服务器认证1表示客户端认证2表示代码签名3表示电子邮件保护4表示IPSEC终端系统5表示IPSEC隧道6表示IPSEC用户7表示时间戳8表示OCSP签名9表示Microsoft服务器网关加密10表示Netscape服务器网关加密11表示Microsoft商业代码签名12表示Microsoft内核代码签名")
CmdGen.Flags().StringVarP(&caKey, "caKey", "k", "", "CA私钥")
CmdGen.Flags().StringVarP(&caCert, "caCert", "C", "", "CA证书")
CmdGen.Flags().StringVarP(&csr, "csr", "r", "", "证书请求")
CmdGen.Flags().StringVarP(&pubKey, "pubKey", "P", "", "证书公钥")
CmdGen.Flags().StringVarP(&savefolder, "savefolder", "s", "./", "保存文件夹")
CmdGen.Flags().StringVarP(&caKeyPwd, "caKeyPwd", "p", "", "CA私钥密码")
CmdGen.Flags().BoolVarP(&isCa, "isCa", "A", false, "是否是CA")
@ -248,6 +288,22 @@ func init() {
CmdOpenssh.Flags().StringVarP(&enPasswd, "en-passwd", "P", "", "加密密码")
Cmd.AddCommand(CmdOpenssh)
CmdFastGen.Flags().BoolVarP(&promptMode, "prompt", "P", false, "是否交互模式")
CmdFastGen.Flags().StringVarP(&country, "country", "c", "CN", "国家")
CmdFastGen.Flags().StringVarP(&province, "province", "p", "B612", "省份")
CmdFastGen.Flags().StringVarP(&city, "city", "t", "B612", "城市")
CmdFastGen.Flags().StringVarP(&org, "org", "o", "", "组织")
CmdFastGen.Flags().StringVarP(&orgUnit, "orgUnit", "u", "", "组织单位")
CmdFastGen.Flags().StringVarP(&name, "name", "n", "Starainrt", "通用名称")
CmdFastGen.Flags().StringSliceVarP(&dnsName, "dnsName", "d", nil, "dns名称")
CmdFastGen.Flags().StringVarP(&savefolder, "savefolder", "s", "./", "保存文件夹")
CmdFastGen.Flags().IntVarP(&keyUsage, "keyUsage", "U", 0, "证书使用类型默认数字00表示数字签名和密钥加密1表示证书签名2表示CRL签名4表示密钥协商8表示数据加密")
CmdFastGen.Flags().IntSliceVarP(&extKeyUsage, "extKeyUsage", "e", nil, "扩展证书使用类型默认数字00表示服务器认证1表示客户端认证2表示代码签名3表示电子邮件保护4表示IPSEC终端系统5表示IPSEC隧道6表示IPSEC用户7表示时间戳8表示OCSP签名9表示Microsoft服务器网关加密10表示Netscape服务器网关加密11表示Microsoft商业代码签名12表示Microsoft内核代码签名")
CmdFastGen.Flags().BoolVarP(&isCa, "isCa", "A", false, "是否是CA")
CmdFastGen.Flags().StringVarP(&startStr, "start", "S", time.Now().Format(time.RFC3339), "开始时间,格式:2006-01-02T15:04:05Z07:00")
CmdFastGen.Flags().StringVarP(&endStr, "end", "E", time.Now().AddDate(1, 0, 0).Format(time.RFC3339), "结束时间,格式:2006-01-02T15:04:05Z07:00")
CmdFastGen.Flags().BoolVarP(&maxPathLenZero, "maxPathLenZero", "z", false, "允许最大路径长度为0")
CmdFastGen.Flags().IntVarP(&maxPathLen, "maxPathLen", "m", 0, "最大路径长度")
}
var CmdPkcs8 = &cobra.Command{

204
filedate/cmd.go Normal file
View File

@ -0,0 +1,204 @@
package filedate
import (
"b612.me/staros"
"fmt"
"github.com/spf13/cobra"
"os"
"path/filepath"
"regexp"
"strconv"
"time"
)
var aTime, mTime, cTime, allTime string // "2006-01-02 15:04:05" format
var fileRegexp string // 正则表达式匹配文件名
var recursive bool // 是否递归子目录
var dateFromName bool // 是否从文件名中提取日期
var dateFromNameReg string // 从文件名提取日期的正则表达式
var dateFromNameReplace string // 从文件名提取日期正则替代
var dateFromNameFormat string
var isTimestamp bool // 文件名是否是时间戳
var onlyModifyAccess bool // 只修改访问时间
var onlyModifyCreate bool // 只修改创建时间
var onlyModifyModify bool // 只修改修改时间
var excludeFolder bool // 是否排除目录本身
var Cmd = &cobra.Command{
Use: "filedate",
Short: "修改文件的创建/修改/访问时间",
Long: `修改文件的创建/修改/访问时间
可以通过 --access, --modify, --create, --all 来设置不同的时间
例如
b612 filedate file.txt --access "2023-10-01 12:00:00" --modify "2023-10-01 12:00:00"
b612 filedate dir/ --all "2023-10-01 12:00:00" -r
也可以通过 --regexp 来匹配文件名或者通过 --date-from-name 来从文件名中提取日期
比如文件名为IMG_20160711_161625.jpg,可以通过如下命令来设置时间
b612 filedate -D -y '$1$2' -x 20060102150405 -z '.*?(\d+)_(\d+).+' .\IMG_20160711_161625.jpg
`,
Run: run,
}
func init() {
Cmd.Flags().StringVarP(&aTime, "access", "a", "", "设置访问时间,格式: 2006-01-02 15:04:05")
Cmd.Flags().StringVarP(&mTime, "modify", "m", "", "设置修改时间,格式: 2006-01-02 15:04:05")
Cmd.Flags().StringVarP(&cTime, "create", "c", "", "设置创建时间,格式: 2006-01-02 15:04:05")
Cmd.Flags().StringVarP(&allTime, "all", "l", "", "设置所有时间,格式: 2006-01-02 15:04:05")
Cmd.Flags().StringVarP(&fileRegexp, "regexp", "R", "", "正则表达式匹配文件名")
Cmd.Flags().BoolVarP(&recursive, "recursive", "r", false, "递归子目录")
Cmd.Flags().BoolVarP(&dateFromName, "date-from-name", "D", false, "从文件名中提取日期")
Cmd.Flags().StringVarP(&dateFromNameReg, "date-from-name-regex", "z", "", "从文件名提取日期的正则表达式")
Cmd.Flags().StringVarP(&dateFromNameReplace, "date-from-name-replace", "y", "", "从文件名提取日期的替换表达式")
Cmd.Flags().StringVarP(&dateFromNameFormat, "date-from-name-format", "x", "2006-01-02 15:04:05", "从文件名提取日期的格式")
Cmd.Flags().BoolVarP(&isTimestamp, "timestamp", "t", false, "文件名是否是时间戳")
Cmd.Flags().BoolVarP(&onlyModifyAccess, "only-modify-access", "A", false, "只修改访问时间")
Cmd.Flags().BoolVarP(&onlyModifyCreate, "only-modify-create", "C", false, "只修改创建时间")
Cmd.Flags().BoolVarP(&onlyModifyModify, "only-modify-modify", "M", false, "只修改修改时间")
Cmd.Flags().BoolVarP(&excludeFolder, "exclude-folder", "e", false, "排除目录本身,不修改目录的时间")
}
func run(cmd *cobra.Command, args []string) {
var aDate, mDate, cDate, allDate time.Time
var err error
if aTime != "" && (onlyModifyAccess || !(onlyModifyCreate || onlyModifyModify)) {
aDate, err = time.ParseInLocation("2006-01-02 15:04:05", aTime, time.Local)
if err != nil {
cmd.PrintErrf("解析访问时间失败: %v\n", err)
return
}
}
if mTime != "" && (onlyModifyCreate || !(onlyModifyAccess || onlyModifyModify)) {
mDate, err = time.ParseInLocation("2006-01-02 15:04:05", mTime, time.Local)
if err != nil {
cmd.PrintErrf("解析修改时间失败: %v\n", err)
return
}
}
if cTime != "" && (onlyModifyCreate || !(onlyModifyAccess || onlyModifyModify)) {
cDate, err = time.ParseInLocation("2006-01-02 15:04:05", cTime, time.Local)
if err != nil {
cmd.PrintErrf("解析创建时间失败: %v\n", err)
return
}
}
if allTime != "" {
allDate, err = time.ParseInLocation("2006-01-02 15:04:05", allTime, time.Local)
if err != nil {
cmd.PrintErrf("解析所有时间失败: %v\n", err)
return
}
if mDate.IsZero() && (onlyModifyCreate || !(onlyModifyAccess || onlyModifyModify)) {
mDate = allDate
}
if cDate.IsZero() && (onlyModifyCreate || !(onlyModifyAccess || onlyModifyModify)) {
cDate = allDate
}
if aDate.IsZero() && (onlyModifyAccess || !(onlyModifyCreate || onlyModifyModify)) {
aDate = allDate
}
}
var reg, namereg *regexp.Regexp
if dateFromName {
if dateFromNameReg == "" {
cmd.PrintErrf("未指定从文件名提取日期的正则表达式\n")
return
}
if dateFromNameReplace == "" {
cmd.PrintErrf("未指定从文件名提取日期的替换表达式\n")
return
}
if dateFromNameFormat == "" {
cmd.PrintErrf("未指定从文件名提取日期的格式\n")
return
}
namereg, err = regexp.Compile(dateFromNameReg)
if err != nil {
cmd.PrintErrf("解析正则表达式失败: %v\n", err)
return
}
}
if fileRegexp != "" {
reg, err = regexp.Compile(fileRegexp)
if err != nil {
cmd.PrintErrf("解析正则表达式失败: %v\n", err)
return
}
}
for _, path := range args {
SetFileTimes(path, reg, namereg, cDate, aDate, mDate)
}
}
func SetFileTimes(path string, reg, namereg *regexp.Regexp, ctime, atime, mtime time.Time) error {
setFile := func(path string, info os.FileInfo) error {
var err error
if reg != nil && !reg.MatchString(info.Name()) {
return nil // 跳过不匹配的文件
}
if dateFromName && namereg != nil {
date := namereg.ReplaceAllString(filepath.Base(path), dateFromNameReplace)
if date == "" {
fmt.Printf("从文件名提取日期失败: %s\n", path)
return nil // 跳过无法提取日期的文件
}
var realDate time.Time
if !isTimestamp {
realDate, err = time.ParseInLocation(dateFromNameFormat, date, time.Local)
if err != nil {
fmt.Printf("解析文件名日期失败: %s, 错误: %v\n", path, err)
return nil // 跳过无法解析日期的文件
}
} else {
tmp, err := strconv.ParseInt(date, 10, 64)
if err != nil {
fmt.Printf("解析时间戳失败: %s, 错误: %v\n", path, err)
return nil // 跳过无法解析时间戳的文件
}
realDate = time.Unix(tmp, 0)
}
if onlyModifyCreate || !(onlyModifyAccess || onlyModifyModify) {
mtime = realDate
}
if onlyModifyCreate || !(onlyModifyAccess || onlyModifyModify) {
ctime = realDate
}
if onlyModifyAccess || !(onlyModifyCreate || onlyModifyModify) {
atime = realDate
}
}
if err := SetFileTime(path, ctime, atime, mtime); err != nil {
return fmt.Errorf("设置文件时间失败: %s, 错误: %v", path, err)
}
fmt.Printf("已设置文件时间: %s\n", path)
return nil
}
if !staros.Exists(path) {
return fmt.Errorf("文件不存在: %s", path)
}
if staros.IsFile(path) {
info, err := os.Stat(path)
if err != nil {
return fmt.Errorf("获取文件信息失败: %s, 错误: %v", path, err)
}
return setFile(path, info)
}
return filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
if staros.IsFile(path) {
return setFile(path, info)
}
if staros.IsFolder(path) {
if recursive {
err = SetFileTimes(path, reg, namereg, ctime, atime, mtime)
if err != nil {
return fmt.Errorf("递归设置文件时间失败: %s, 错误: %v", path, err)
}
}
if excludeFolder {
return nil // 如果排除目录本身,则不处理目录的时间
}
return setFile(path, info) // 处理目录本身
}
return nil
})
}

View File

@ -0,0 +1,38 @@
//go:build darwin
package filedate
import (
"fmt"
"os"
"time"
)
func SetFileTime(path string, ctime, atime, mtime time.Time) error {
var err error
originalAtime := atime
originalMtime := mtime
if atime.IsZero() || mtime.IsZero() {
var fi os.FileInfo
fi, err = os.Stat(path)
if err != nil {
return fmt.Errorf("failed to get file info: %w", err)
}
// 获取原始时间
if atime.IsZero() {
originalAtime = fi.ModTime() // macOS 没有访问时间,使用修改时间代替
}
if mtime.IsZero() {
originalMtime = fi.ModTime()
}
}
err = os.Chtimes(path, originalAtime, originalMtime)
if err != nil {
return fmt.Errorf("Chtimes failed: %w", err)
}
return nil
}

View File

@ -0,0 +1,32 @@
//go:build !windows && !darwin
package filedate
import (
"fmt"
"golang.org/x/sys/unix"
"time"
)
func SetFileTime(path string, ctime, atime, mtime time.Time) error {
var ts [2]unix.Timespec
if atime.IsZero() {
ts[0].Nsec = unix.UTIME_OMIT
} else {
ts[0] = unix.NsecToTimespec(atime.UnixNano())
}
if mtime.IsZero() {
ts[1].Nsec = unix.UTIME_OMIT
} else {
ts[1] = unix.NsecToTimespec(mtime.UnixNano())
}
// 使用 AT_FDCWD 表示相对当前工作目录
err := unix.UtimesNanoAt(unix.AT_FDCWD, path, ts[:], unix.AT_SYMLINK_NOFOLLOW)
if err != nil {
return fmt.Errorf("utimensat failed: %w", err)
}
return nil
}

View File

@ -0,0 +1,54 @@
//go:build windows
package filedate
import (
"fmt"
"golang.org/x/sys/windows"
"time"
)
func SetFileTime(path string, ctime, atime, mtime time.Time) error {
path16, err := windows.UTF16PtrFromString(path)
if err != nil {
return err
}
handle, err := windows.CreateFile(
path16,
windows.FILE_WRITE_ATTRIBUTES,
windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE|windows.FILE_SHARE_DELETE,
nil,
windows.OPEN_EXISTING,
windows.FILE_FLAG_BACKUP_SEMANTICS,
0,
)
if err != nil {
return fmt.Errorf("CreateFile failed: %w", err)
}
defer windows.CloseHandle(handle)
var (
ctimePtr *windows.Filetime
atimePtr *windows.Filetime
mtimePtr *windows.Filetime
)
if !ctime.IsZero() {
ctimeFt := windows.NsecToFiletime(ctime.UnixNano())
ctimePtr = &ctimeFt
}
if !atime.IsZero() {
atimeFt := windows.NsecToFiletime(atime.UnixNano())
atimePtr = &atimeFt
}
if !mtime.IsZero() {
mtimeFt := windows.NsecToFiletime(mtime.UnixNano())
mtimePtr = &mtimeFt
}
if err := windows.SetFileTime(handle, ctimePtr, atimePtr, mtimePtr); err != nil {
return fmt.Errorf("SetFileTime failed: %w", err)
}
return nil
}

208
ftp/filedriver.go Normal file
View File

@ -0,0 +1,208 @@
package ftp
import (
"errors"
"fmt"
"goftp.io/server/v2"
"io"
"os"
"path/filepath"
"strings"
)
type Driver struct {
RootPath string
}
// NewDriver implements Driver
func NewDriver(rootPath string) (server.Driver, error) {
var err error
rootPath, err = filepath.Abs(rootPath)
if err != nil {
return nil, err
}
if fi, err := os.Lstat(rootPath); err == nil {
if fi.Mode()&os.ModeSymlink != 0 {
realPath, err := filepath.EvalSymlinks(rootPath)
if err != nil {
return nil, err
}
rootPath = realPath
fmt.Println("Real path:", rootPath)
}
}
return &Driver{rootPath}, nil
}
func (driver *Driver) realPath(path string) string {
paths := strings.Split(path, "/")
realPath, _ := filepath.Abs(filepath.Join(append([]string{driver.RootPath}, paths...)...))
if strings.HasPrefix(realPath, driver.RootPath) {
return realPath
}
return filepath.Join(driver.RootPath)
}
// Stat implements Driver
func (driver *Driver) Stat(ctx *server.Context, path string) (os.FileInfo, error) {
basepath := driver.realPath(path)
rPath, err := filepath.Abs(basepath)
if err != nil {
return nil, err
}
return os.Lstat(rPath)
}
// ListDir implements Driver
func (driver *Driver) ListDir(ctx *server.Context, path string, callback func(os.FileInfo) error) error {
basepath := driver.realPath(path)
return filepath.Walk(basepath, func(f string, info os.FileInfo, err error) error {
if err != nil {
return err
}
rPath, _ := filepath.Rel(basepath, f)
if rPath == info.Name() {
err = callback(info)
if err != nil {
return err
}
if info.IsDir() {
return filepath.SkipDir
}
}
return nil
})
}
// DeleteDir implements Driver
func (driver *Driver) DeleteDir(ctx *server.Context, path string) error {
rPath := driver.realPath(path)
f, err := os.Lstat(rPath)
if err != nil {
return err
}
if f.IsDir() {
return os.RemoveAll(rPath)
}
return errors.New("Not a directory")
}
// DeleteFile implements Driver
func (driver *Driver) DeleteFile(ctx *server.Context, path string) error {
rPath := driver.realPath(path)
f, err := os.Lstat(rPath)
if err != nil {
return err
}
if !f.IsDir() {
return os.Remove(rPath)
}
return errors.New("Not a file")
}
// Rename implements Driver
func (driver *Driver) Rename(ctx *server.Context, fromPath string, toPath string) error {
oldPath := driver.realPath(fromPath)
newPath := driver.realPath(toPath)
return os.Rename(oldPath, newPath)
}
// MakeDir implements Driver
func (driver *Driver) MakeDir(ctx *server.Context, path string) error {
rPath := driver.realPath(path)
return os.MkdirAll(rPath, os.ModePerm)
}
// GetFile implements Driver
func (driver *Driver) GetFile(ctx *server.Context, path string, offset int64) (int64, io.ReadCloser, error) {
rPath := driver.realPath(path)
f, err := os.Open(rPath)
if err != nil {
return 0, nil, err
}
defer func() {
if err != nil && f != nil {
f.Close()
}
}()
info, err := f.Stat()
if err != nil {
return 0, nil, err
}
_, err = f.Seek(offset, io.SeekStart)
if err != nil {
return 0, nil, err
}
return info.Size() - offset, f, nil
}
// PutFile implements Driver
func (driver *Driver) PutFile(ctx *server.Context, destPath string, data io.Reader, offset int64) (int64, error) {
rPath := driver.realPath(destPath)
var isExist bool
f, err := os.Lstat(rPath)
if err == nil {
isExist = true
if f.IsDir() {
return 0, errors.New("A dir has the same name")
}
} else {
if os.IsNotExist(err) {
isExist = false
} else {
return 0, errors.New(fmt.Sprintln("Put File error:", err))
}
}
if offset > -1 && !isExist {
offset = -1
}
if offset == -1 {
if isExist {
err = os.Remove(rPath)
if err != nil {
return 0, err
}
}
f, err := os.Create(rPath)
if err != nil {
return 0, err
}
defer f.Close()
bytes, err := io.Copy(f, data)
if err != nil {
return 0, err
}
return bytes, nil
}
of, err := os.OpenFile(rPath, os.O_APPEND|os.O_RDWR, 0660)
if err != nil {
return 0, err
}
defer of.Close()
info, err := of.Stat()
if err != nil {
return 0, err
}
if offset > info.Size() {
return 0, fmt.Errorf("Offset %d is beyond file size %d", offset, info.Size())
}
_, err = of.Seek(offset, os.SEEK_END)
if err != nil {
return 0, err
}
bytes, err := io.Copy(of, data)
if err != nil {
return 0, err
}
return bytes, nil
}

View File

@ -2,16 +2,34 @@ package ftp
import (
"log"
"net"
"os/user"
"path/filepath"
"strings"
filedriver "github.com/goftp/file-driver"
"github.com/goftp/server"
"github.com/spf13/cobra"
"goftp.io/server/v2"
)
var ports int
var username, pwd string
var path, ip string
var authuser, authpwd string
var path, ip, publicIp string
var username, groupname, passiveports string
var cert, key string
var forceTls bool
type Auth struct {
}
func (a Auth) CheckPasswd(ctx *server.Context, user string, name string) (bool, error) {
if authuser == "" && authpwd == "" {
return true, nil
}
if authuser == user && authpwd == name {
return true, nil
}
return false, nil
}
// ftpCmd represents the ftp command
var Cmd = &cobra.Command{
@ -19,21 +37,56 @@ var Cmd = &cobra.Command{
Short: `FTP文件服务器`,
Long: `FTP文件服务器`,
Run: func(cmd *cobra.Command, args []string) {
path, _ = filepath.Abs(path)
factory := &filedriver.FileDriverFactory{
RootPath: path,
Perm: server.NewSimplePerm("user", "group"),
if username == "" || groupname == "" {
u, err := user.Current()
if err == nil {
username = u.Username
groupname = u.Username
} else {
username = "root"
groupname = "root"
}
}
opts := &server.ServerOpts{
Factory: factory,
Port: ports,
Hostname: ip,
Auth: &server.SimpleAuth{Name: username, Password: pwd},
if publicIp == "" {
if tmp, err := net.Dial("udp", "139.199.163.65:443"); err == nil {
publicIp = strings.Split(tmp.LocalAddr().String(), ":")[0]
tmp.Close()
}
}
path, _ = filepath.Abs(path)
driver, err := NewDriver(path)
if err != nil {
log.Fatal("Error starting server:", err)
}
opts := &server.Options{
Auth: Auth{},
Driver: driver,
Perm: server.NewSimplePerm(username, groupname),
Name: "B612 FTP Server",
Hostname: ip,
PublicIP: publicIp,
PassivePorts: passiveports,
Port: ports,
WelcomeMessage: "B612 FTP Server",
Logger: nil,
RateLimit: 0,
TLS: key != "" && cert != "",
ForceTLS: forceTls,
CertFile: cert,
KeyFile: key,
}
log.Printf("Starting ftp server on %v:%v", opts.Hostname, opts.Port)
log.Printf("Username %v, Password %v", username, pwd)
server := server.NewServer(opts)
err := server.ListenAndServe()
log.Printf("AuthUser %v, Password %v", authuser, authpwd)
log.Printf("Public IP %v", publicIp)
log.Printf("Path %v", path)
log.Printf("Passive Ports %v", passiveports)
log.Printf("TLS %v", opts.TLS)
log.Printf("Username %v, Groupname %v", username, groupname)
server, err := server.NewServer(opts)
if err != nil {
log.Fatal("Error starting server:", err)
}
err = server.ListenAndServe()
if err != nil {
log.Fatal("Error starting server:", err)
}
@ -41,9 +94,16 @@ var Cmd = &cobra.Command{
}
func init() {
Cmd.Flags().StringVarP(&publicIp, "public-ip", "I", "", "公网IP")
Cmd.Flags().StringVarP(&username, "username", "U", "", "FTP用户名")
Cmd.Flags().StringVarP(&groupname, "groupname", "G", "", "FTP组名")
Cmd.Flags().StringVarP(&passiveports, "passiveports", "P", "50000-60000", "被动模式端口范围格式50000-60000")
Cmd.Flags().IntVarP(&ports, "port", "p", 21, "监听端口")
Cmd.Flags().StringVarP(&ip, "ip", "i", "0.0.0.0", "监听地址")
Cmd.Flags().StringVarP(&username, "user", "u", "1", "用户名默认为1")
Cmd.Flags().StringVarP(&pwd, "pwd", "k", "1", "密码默认为1")
Cmd.Flags().StringVarP(&authuser, "auth-user", "u", "", "用户名,默认为任意")
Cmd.Flags().StringVarP(&authpwd, "auth-passwd", "k", "", "密码,默认为任意")
Cmd.Flags().StringVarP(&path, "folder", "f", "./", "本地文件地址")
Cmd.Flags().BoolVarP(&forceTls, "force-tls", "t", false, "强制使用TLS加密传输")
Cmd.Flags().StringVarP(&cert, "cert", "C", "", "TLS证书文件")
Cmd.Flags().StringVarP(&key, "key", "K", "", "TLS密钥文件")
}

36
go.mod
View File

@ -1,32 +1,36 @@
module b612.me/apps/b612
go 1.22.4
go 1.23.0
toolchain go1.23.10
require (
b612.me/astro v0.0.4
b612.me/bcap v0.0.4
b612.me/exif v0.0.0-20250609055701-2bc6bd257a4f
b612.me/notify v0.0.0-20240818092352-85803f75dfa0
b612.me/sdk/whois v0.0.0-20240816133027-129514a15991
b612.me/starcrypto v0.0.5
b612.me/stario v0.0.10
b612.me/starlog v1.3.4
b612.me/starmap v0.0.0-20240818092703-ae61140c5062
b612.me/starnet v0.2.1
b612.me/starnet v0.0.0-20250612085047-7a1767214960
b612.me/staros v1.1.8
b612.me/starssh v0.0.2
b612.me/startext v0.0.0-20220314043758-22c6d5e5b1cd
b612.me/wincmd v0.0.4
github.com/anthonynsimon/bild v0.14.0
github.com/dgraph-io/badger/v3 v3.2103.2
github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2
github.com/emersion/go-smtp v0.20.2
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6
github.com/emersion/go-smtp v0.22.0
github.com/fatih/color v1.16.0
github.com/florianl/go-nfqueue/v2 v2.0.0
github.com/gabriel-vasile/mimetype v1.4.9
github.com/gdamore/tcell v1.4.0
github.com/gdamore/tcell/v2 v2.7.1
github.com/go-acme/lego/v4 v4.16.1
github.com/goftp/file-driver v0.0.0-20180502053751-5d604a0fc0c9
github.com/goftp/server v0.0.0-20200708154336-f64f7c2d8a42
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
github.com/google/gopacket v1.1.19
github.com/h2non/filetype v1.1.3
@ -48,11 +52,12 @@ require (
github.com/tobert/pcstat v0.0.2
github.com/ulikunitz/xz v0.5.12
github.com/vbauerster/mpb/v8 v8.8.3
golang.org/x/crypto v0.32.0
goftp.io/server/v2 v2.0.1
golang.org/x/crypto v0.37.0
golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3
golang.org/x/net v0.32.0
golang.org/x/sys v0.29.0
golang.org/x/term v0.28.0
golang.org/x/net v0.39.0
golang.org/x/sys v0.32.0
golang.org/x/term v0.31.0
gopkg.in/yaml.v3 v3.0.1
software.sslmate.com/src/go-pkcs12 v0.4.0
)
@ -75,15 +80,18 @@ require (
github.com/cpu/goacmedns v0.1.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgraph-io/ristretto v0.1.0 // indirect
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/ebitengine/purego v0.8.1 // indirect
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 // indirect
github.com/gdamore/encoding v1.0.1 // indirect
github.com/go-errors/errors v1.4.2 // indirect
github.com/go-jose/go-jose/v4 v4.0.1 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
@ -94,7 +102,6 @@ require (
github.com/google/uuid v1.3.1 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.5 // indirect
github.com/jlaffaye/ftp v0.1.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/native v1.1.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
@ -120,12 +127,13 @@ require (
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.opencensus.io v0.24.0 // indirect
golang.org/x/image v0.6.0 // indirect
golang.org/x/image v0.18.0 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/sync v0.13.0 // indirect
golang.org/x/text v0.24.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

115
go.sum
View File

@ -2,6 +2,8 @@ b612.me/astro v0.0.4 h1:i60vJ3Dq+9U16ryMqmnjE0uXB28poRBx8boIMbP8VH8=
b612.me/astro v0.0.4/go.mod h1:yC1arIa79Q5ztQgWphj5mX5UvM/gddwTP+jGneojPt4=
b612.me/bcap v0.0.4 h1:iY2Oz+uyG/mue6a/dJiU82ci5Xwkj4xHhre/q0O8G60=
b612.me/bcap v0.0.4/go.mod h1:tpus+4iMpsnxb98Pck70s87Zt4sIWuRZjK23MmmzmoY=
b612.me/exif v0.0.0-20250609055701-2bc6bd257a4f h1:lzmq/A9fvE5xMqEKIsVJQo3RzOUawTd4Ny6pZpWNL5A=
b612.me/exif v0.0.0-20250609055701-2bc6bd257a4f/go.mod h1:ig9W4FPsYf3ZfAfehinro0qNLfV/RNyKO6xF92ku5N0=
b612.me/notify v0.0.0-20240818092352-85803f75dfa0 h1:PaLuNLMM0HBM7qPdk4igtNvqT2CDgdm52sL+KA9I3so=
b612.me/notify v0.0.0-20240818092352-85803f75dfa0/go.mod h1:YyaYF4jj5ygIC/QbCMyhBWnsmmTL8kVs6UlHMGVKiY8=
b612.me/sdk/whois v0.0.0-20240816133027-129514a15991 h1:+eXeVqkoi4s9sNoY9eGt4ieSbr1+Deos8fC8wMfOCNI=
@ -15,8 +17,8 @@ b612.me/starlog v1.3.4 h1:XuVYo6NCij8F4TGSgtEuMhs1WkZ7HZNnYUgQ3nLTt84=
b612.me/starlog v1.3.4/go.mod h1:37GMgkWQMOAjzKs49Hf2i8bLwdXbd9QF4zKhUxFDoSk=
b612.me/starmap v0.0.0-20240818092703-ae61140c5062 h1:ImKEWAxzBYsS/YbqdVOPdUdv6b+i/lSGpipUGueXk7w=
b612.me/starmap v0.0.0-20240818092703-ae61140c5062/go.mod h1:PhtO9wFrwPIHpry2CEdnVNZkrNOgfv77xrE0ZKQDkLM=
b612.me/starnet v0.2.1 h1:17n3wa2QgBYbO1rqDLAhyc2DfvbBc23GSp1v42Pvmiw=
b612.me/starnet v0.2.1/go.mod h1:6q+AXhYeXsIiKV+hZZmqAMn8S48QcdonURJyH66rbzI=
b612.me/starnet v0.0.0-20250612085047-7a1767214960 h1:LRB59HvHW6D4sAKd/P2GDgmnGyx5DM8aSGnJoEifeI0=
b612.me/starnet v0.0.0-20250612085047-7a1767214960/go.mod h1:6q+AXhYeXsIiKV+hZZmqAMn8S48QcdonURJyH66rbzI=
b612.me/staros v1.1.8 h1:5Bpuf9q2nH75S2ekmieJuH3Y8LTqg/voxXCOiMAC3kk=
b612.me/staros v1.1.8/go.mod h1:4KmokjKXFW5h1hbA4aIv5O+2FptVzBubCo7IPirfqm8=
b612.me/starssh v0.0.2 h1:cYlrXjd7ZTesdZG+7XcoLsEEMROaeWMTYonScBLnvyY=
@ -54,6 +56,8 @@ github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpH
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
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/anthonynsimon/bild v0.14.0 h1:IFRkmKdNdqmexXHfEU7rPlAmdUZ8BDZEGtGHDnGWync=
github.com/anthonynsimon/bild v0.14.0/go.mod h1:hcvEAyBjTW69qkKJTfpcDQ83sSZHxwOunsseDfeQhUs=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
@ -84,6 +88,21 @@ github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczC
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
github.com/dsoprea/go-exif/v2 v2.0.0-20200321225314-640175a69fe4/go.mod h1:Lm2lMM2zx8p4a34ZemkaUV95AnMl4ZvLbCUbwOvLC2E=
github.com/dsoprea/go-exif/v3 v3.0.0-20200717053412-08f1b6708903/go.mod h1:0nsO1ce0mh5czxGeLo4+OCZ/C6Eo6ZlMWsz7rH/Gxv8=
github.com/dsoprea/go-exif/v3 v3.0.0-20210625224831-a6301f85c82b/go.mod h1:cg5SNYKHMmzxsr9X6ZeLh/nfBRHHp5PngtEPcujONtk=
github.com/dsoprea/go-exif/v3 v3.0.0-20221003160559-cf5cd88aa559/go.mod h1:rW6DMEv25U9zCtE5ukC7ttBRllXj7g7TAHl7tQrT5No=
github.com/dsoprea/go-exif/v3 v3.0.0-20221003171958-de6cb6e380a8/go.mod h1:akyZEJZ/k5bmbC9gA612ZLQkcED8enS9vuTiuAkENr0=
github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696/go.mod h1:Nm/x2ZUNRW6Fe5C3LxdY1PyZY5wmDv/s5dkPJ/VB3iA=
github.com/dsoprea/go-logging v0.0.0-20200517223158-a10564966e9d/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8=
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd h1:l+vLbuxptsC6VQyQsfD7NnEC8BZuFpz45PgY+pH8YTg=
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8=
github.com/dsoprea/go-utility v0.0.0-20200711062821-fab8125e9bdf/go.mod h1:95+K3z2L0mqsVYd6yveIv1lmtT3tcQQ3dVakPySffW8=
github.com/dsoprea/go-utility/v2 v2.0.0-20200717064901-2fccff4aa15e/go.mod h1:uAzdkPTub5Y9yQwXe8W4m2XuP0tK4a9Q/dantD0+uaU=
github.com/dsoprea/go-utility/v2 v2.0.0-20221003142440-7a1927d49d9d/go.mod h1:LVjRU0RNUuMDqkPTxcALio0LWPFPXxxFCvVGVAwEpFc=
github.com/dsoprea/go-utility/v2 v2.0.0-20221003160719-7bc88537c05e/go.mod h1:VZ7cB0pTjm1ADBWhJUOHESu4ZYy9JN+ZPqjfiW09EPU=
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 h1:DilThiXje0z+3UQ5YjYiSRRzVdtamFpvBQXKwMglWqw=
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349/go.mod h1:4GC5sXji84i/p+irqghpPFZBF8tRN/Q7+700G0/DLe8=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/ebitengine/purego v0.8.1 h1:sdRKd6plj7KYW33EH5As6YKfe8m9zbN9JMrOjNVF/BE=
@ -92,10 +111,10 @@ github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5 h1:m62nsMU279qRD9P
github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM=
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ=
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
github.com/emersion/go-smtp v0.20.2 h1:peX42Qnh5Q0q3vrAnRy43R/JwTnnv75AebxbkTL7Ia4=
github.com/emersion/go-smtp v0.20.2/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 h1:oP4q0fw+fOSWn3DfFi4EXdT+B+gTtzx8GC9xsc26Znk=
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
github.com/emersion/go-smtp v0.22.0 h1:/d3HWxkZZ4riB+0kzfoODh9X+xyCrLEezMnAAa1LEMU=
github.com/emersion/go-smtp v0.22.0/go.mod h1:ZtRRkbTyp2XTHCA+BmyTFTrj8xY4I+b4McvHxCU2gsQ=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@ -105,6 +124,8 @@ github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4Nij
github.com/florianl/go-nfqueue/v2 v2.0.0 h1:NTCxS9b0GSbHkWv1a7oOvZn679fsyDkaSkRvOYpQ9Oo=
github.com/florianl/go-nfqueue/v2 v2.0.0/go.mod h1:M2tBLIj62QpwqjwV0qfcjqGOqP3qiTuXr2uSRBXH9Qk=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw=
github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
@ -114,16 +135,17 @@ github.com/gdamore/tcell/v2 v2.7.1 h1:TiCcmpWHiAU7F0rA2I3S2Y4mmLmO9KHxJ7E1QhYzQb
github.com/gdamore/tcell/v2 v2.7.1/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg=
github.com/go-acme/lego/v4 v4.16.1 h1:JxZ93s4KG0jL27rZ30UsIgxap6VGzKuREsSkkyzeoCQ=
github.com/go-acme/lego/v4 v4.16.1/go.mod h1:AVvwdPned/IWpD/ihHhMsKnveF7HHYAz/CmtXi7OZoE=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U=
github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goftp/file-driver v0.0.0-20180502053751-5d604a0fc0c9 h1:cC0Hbb+18DJ4i6ybqDybvj4wdIDS4vnD0QEci98PgM8=
github.com/goftp/file-driver v0.0.0-20180502053751-5d604a0fc0c9/go.mod h1:GpOj6zuVBG3Inr9qjEnuVTgBlk2lZ1S9DcoFiXWyKss=
github.com/goftp/server v0.0.0-20200708154336-f64f7c2d8a42 h1:JdOp2qR5PF4O75tzHeqrwnDDv8oHDptWyTbyYS4fD8E=
github.com/goftp/server v0.0.0-20200708154336-f64f7c2d8a42/go.mod h1:k/SS6VWkxY7dHPhoMQ8IdRu8L4lQtmGbhyXGg+vCnXE=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
@ -131,6 +153,10 @@ github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOW
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo=
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -175,17 +201,14 @@ github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
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-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M=
github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
@ -194,8 +217,9 @@ github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFck
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jlaffaye/ftp v0.1.0 h1:DLGExl5nBoSFoNshAUHwXAezXwXBvFdx7/qwhucWNSE=
github.com/jlaffaye/ftp v0.1.0/go.mod h1:hhq4G4crv+nW2qXtNYcuzLeOudG92Ps37HEKeg2e3lE=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
github.com/jlaffaye/ftp v0.0.0-20190624084859-c1312a7102bf/go.mod h1:lli8NYPQOFy3O++YmYbqVgOcQ1JPCwdOy+5zSjKJ9qY=
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=
@ -206,10 +230,12 @@ github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2C
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
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/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.12.3 h1:G5AfA94pHPysR56qqrkO2pxEexdDzrpFJ6yt/VqWxVU=
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@ -243,6 +269,8 @@ github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
github.com/minio/minio-go/v6 v6.0.46/go.mod h1:qD0lajrGW49lKZLtXKtCB4X/qkMf0a5tBvN2PaZg7Gg=
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -280,8 +308,11 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shirou/gopsutil/v4 v4.24.10 h1:7VOzPtfw/5YDU+jLEoBwXwxJbQetULywoSV4RYY7HkM=
github.com/shirou/gopsutil/v4 v4.24.10/go.mod h1:s4D/wg+ag4rG0WO7AiTj2BeYCRhym0vM7DHbZRxnIT8=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
@ -296,6 +327,7 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
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=
@ -333,8 +365,12 @@ github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQ
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
goftp.io/server/v2 v2.0.1 h1:H+9UbCX2N206ePDSVNCjBftOKOgil6kQ5RAQNx5hJwE=
goftp.io/server/v2 v2.0.1/go.mod h1:7+H/EIq7tXdfo1Muu5p+l3oQ6rYkDZ8lY7IM5d5kVdQ=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
@ -345,13 +381,13 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3 h1:/RIbNt/Zr7rVhIkQhooTxCxFcdWLGIKnZA4IXNFSrvo=
golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
golang.org/x/image v0.6.0 h1:bR8b5okrPI3g/gyZakLZHeWxAR8Dn5CyxXv1hLH5g/4=
golang.org/x/image v0.6.0/go.mod h1:MXLdDR43H7cDJq5GEGXEVeeNhPgi+YYEQ2pC1byI1x0=
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
@ -370,20 +406,27 @@ golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -398,19 +441,23 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -418,7 +465,9 @@ golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/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.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -429,8 +478,8 @@ golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
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=
@ -441,27 +490,28 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
@ -506,11 +556,14 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
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=

View File

@ -3,6 +3,8 @@ addr=0.0.0.0
port=9999
key=C:\\tech\b612.me.key
cert=C:\\tech\b612.me.cer
tlsallowhttp=false
autogencert=false
enablessl=true
reverse=/::https://www.b612.me
replace=www.b612.me::127.0.0.1:9999

View File

@ -17,30 +17,32 @@ import (
)
type ReverseConfig struct {
Name string
Addr string
ReverseURL map[string]*url.URL
Port int
UsingSSL bool
Key string
Cert string
Host string
SkipSSLVerify bool
InHeader [][2]string
OutHeader [][2]string
Cookie [][3]string //[3]string should contains path::key::value
ReplaceList [][2]string
ReplaceOnce bool
proxy map[string]*rp.ReverseProxy
IPFilterMode int //0=off 1=useremote 2=add 3=filter
FilterXForward bool
FilterRemoteAddr bool
FilterMustKey string
FilterSetKey string
FilterFile string
httpmux http.ServeMux
httpserver http.Server
CIDR []*net.IPNet
Name string
Addr string
ReverseURL map[string]*url.URL
Port int
UsingSSL bool
AllowHTTPWithHttps bool
AutoGenerateCert bool
Key string
Cert string
Host string
SkipSSLVerify bool
InHeader [][2]string
OutHeader [][2]string
Cookie [][3]string //[3]string should contains path::key::value
ReplaceList [][2]string
ReplaceOnce bool
proxy map[string]*rp.ReverseProxy
IPFilterMode int //0=off 1=useremote 2=add 3=filter
FilterXForward bool
FilterRemoteAddr bool
FilterMustKey string
FilterSetKey string
FilterFile string
httpmux http.ServeMux
httpserver http.Server
CIDR []*net.IPNet
basicAuthUser string
basicAuthPwd string
@ -64,23 +66,25 @@ func Parse(path string) (HttpReverseServer, error) {
}
for _, v := range ini.Data {
var ins = ReverseConfig{
Name: v.Name,
Host: v.Get("host"),
Addr: v.Get("addr"),
Port: v.Int("port"),
UsingSSL: v.Bool("enablessl"),
Key: v.Get("key"),
Cert: v.Get("cert"),
ReplaceOnce: v.Bool("replaceonce"),
IPFilterMode: v.Int("ipfiltermode"),
FilterXForward: v.Bool("filterxforward"),
FilterRemoteAddr: v.Bool("filterremoteaddr"),
FilterMustKey: v.Get("filtermustkey"),
FilterSetKey: v.Get("filtersetkey"),
FilterFile: v.Get("filterfile"),
basicAuthUser: v.Get("authuser"),
basicAuthPwd: v.Get("authpasswd"),
warningpage: v.Get("warnpage"),
Name: v.Name,
Host: v.Get("host"),
Addr: v.Get("addr"),
Port: v.Int("port"),
UsingSSL: v.Bool("enablessl"),
AllowHTTPWithHttps: v.Bool("tlsallowhttp"),
AutoGenerateCert: v.Bool("autogencert"),
Key: v.Get("key"),
Cert: v.Get("cert"),
ReplaceOnce: v.Bool("replaceonce"),
IPFilterMode: v.Int("ipfiltermode"),
FilterXForward: v.Bool("filterxforward"),
FilterRemoteAddr: v.Bool("filterremoteaddr"),
FilterMustKey: v.Get("filtermustkey"),
FilterSetKey: v.Get("filtersetkey"),
FilterFile: v.Get("filterfile"),
basicAuthUser: v.Get("authuser"),
basicAuthPwd: v.Get("authpasswd"),
warningpage: v.Get("warnpage"),
}
if ins.IPFilterMode == 3 && ins.FilterFile != "" {
starlog.Infoln("IP Filter Mode 3, Load IP Filter File", ins.FilterFile)

View File

@ -2,10 +2,13 @@ package httpreverse
import (
"b612.me/apps/b612/httpreverse/rp"
"b612.me/apps/b612/utils"
"b612.me/starlog"
"b612.me/starnet"
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"fmt"
"io/ioutil"
@ -58,18 +61,75 @@ func (h *ReverseConfig) Run() error {
Handler: &h.httpmux,
}
starlog.Infoln(h.Name + " Listening on " + h.Addr + ":" + strconv.Itoa(h.Port))
if !h.UsingSSL {
if !h.UsingSSL && !h.AutoGenerateCert {
if err := h.httpserver.ListenAndServe(); err != http.ErrServerClosed {
return err
}
return nil
}
if h.httpserver.ListenAndServeTLS(h.Cert, h.Key); err != http.ErrServerClosed {
if !h.AllowHTTPWithHttps && !h.AutoGenerateCert {
if h.httpserver.ListenAndServeTLS(h.Cert, h.Key); err != http.ErrServerClosed {
return err
}
return nil
}
var lis net.Listener
if !h.AutoGenerateCert {
lis, err = starnet.ListenTLS("tcp", fmt.Sprintf("%s:%d", h.Addr, h.Port), h.Cert, h.Key, h.AllowHTTPWithHttps)
if err != nil {
return err
}
} else {
lis, err = starnet.ListenTLSWithConfig("tcp", fmt.Sprintf("%s:%d", h.Addr, h.Port), &tls.Config{}, autoGenCert, h.AllowHTTPWithHttps)
if err != nil {
return err
}
}
defer lis.Close()
if err := h.httpserver.Serve(lis); err != http.ErrServerClosed {
return err
}
return nil
}
var certCache = make(map[string]tls.Certificate)
var toolCa *x509.Certificate
var toolCaKey any
func autoGenCert(hostname string) *tls.Config {
if cert, ok := certCache[hostname]; ok {
return &tls.Config{Certificates: []tls.Certificate{cert}}
}
if toolCa == nil {
toolCa, toolCaKey = utils.ToolCert()
}
cert, err := utils.GenerateTlsCert(utils.GenerateCertParams{
Country: "CN",
Organization: "B612 HTTP SERVER",
OrganizationUnit: "cert@b612.me",
CommonName: hostname,
Dns: []string{hostname},
KeyUsage: int(x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageCertSign),
ExtendedKeyUsage: []int{
int(x509.ExtKeyUsageServerAuth),
int(x509.ExtKeyUsageClientAuth),
},
IsCA: false,
StartDate: time.Now().Add(-24 * time.Hour),
EndDate: time.Now().AddDate(1, 0, 0),
Type: "RSA",
Bits: 2048,
CA: toolCa,
CAPriv: toolCaKey,
})
if err != nil {
return nil
}
certCache[hostname] = cert
return &tls.Config{Certificates: []tls.Certificate{cert}}
}
func (h *ReverseConfig) Close() error {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()

View File

@ -42,6 +42,8 @@ func init() {
Cmd.Flags().StringToStringVarP(&s.listPwd, "listpwd", "L", map[string]string{}, "列出文件的路径的密码,如/=/password")
Cmd.Flags().BoolVarP(&s.listSameForFile, "list-same", "S", false, "如开启,文件的获取权限将与文件夹保持一致")
Cmd.Flags().StringVarP(&speedlimit, "speedlimit", "s", "", "限速如1M意思是1MB/s")
Cmd.Flags().BoolVarP(&s.allowHttpWithHttps, "allow-http-with-https", "A", false, "同一个端口允许在HTTPS下使用HTTP协议")
Cmd.Flags().BoolVarP(&s.autoGenCert, "autogen-cert", "G", false, "自动生成证书,此时使用--ssl-cert和--ssl-key参数无效")
Cmd.Flags().Bool("daeapplied", false, "")
Cmd.Flags().StringVar(&s.background, "background", "", "背景图片地址")

View File

@ -1,6 +1,9 @@
package httpserver
import "path/filepath"
import (
"github.com/gabriel-vasile/mimetype"
"path/filepath"
)
func (h *HttpServer) FileType(name string) string {
ext := filepath.Ext(name)
@ -224,8 +227,15 @@ func (h *HttpServer) GetExt(fullpath string) string {
}
return ext[1:]
}
func (h *HttpServer) MIME(fullpath string) string {
mime, err := mimetype.DetectFile(fullpath)
if err != nil {
return ""
}
return mime.String()
}
func (h *HttpServer) OldMIME(fullpath string) string {
ext := filepath.Ext(filepath.Base(fullpath))
if len(ext) == 0 || ext == "." {
return ""

View File

@ -1,12 +1,15 @@
package httpserver
import (
"b612.me/apps/b612/utils"
"b612.me/apps/b612/version"
"b612.me/starcrypto"
"b612.me/starlog"
"b612.me/starnet"
"b612.me/staros"
"context"
"crypto/tls"
"crypto/x509"
_ "embed"
"encoding/base64"
"encoding/json"
@ -30,27 +33,29 @@ var ver = version.Version
type HttpServerCfgs func(cfg *HttpServerCfg)
type HttpServerCfg struct {
basicAuthUser string
basicAuthPwd string
envPath string
uploadFolder string
logpath string
indexFile string
cert string
key string
addr string
port string
page404 string
page403 string
page401 string
protectAuthPage []string
disableMIME bool
ctx context.Context
hooks []ServerHook
httpDebug bool
noListPath []string
listPwd map[string]string
listSameForFile bool
basicAuthUser string
basicAuthPwd string
envPath string
uploadFolder string
logpath string
indexFile string
cert string
key string
allowHttpWithHttps bool
autoGenCert bool
addr string
port string
page404 string
page403 string
page401 string
protectAuthPage []string
disableMIME bool
ctx context.Context
hooks []ServerHook
httpDebug bool
noListPath []string
listPwd map[string]string
listSameForFile bool
// speed limit means xx bytes/s
speedlimit uint64
@ -157,25 +162,81 @@ func (h *HttpServer) Run(ctx context.Context) error {
uconn, err := net.Dial("udp", "106.55.44.79:80")
if err == nil {
schema := "http://"
if h.cert != "" {
if h.cert != "" || h.autoGenCert {
schema = "https://"
}
starlog.Infof("Visit: %s%s:%s\n", schema, uconn.LocalAddr().(*net.UDPAddr).IP.String(), h.port)
uconn.Close()
}
starlog.Infoln("Listening on " + h.addr + ":" + h.port)
if h.cert == "" {
if h.cert == "" && !h.autoGenCert {
if err := server.ListenAndServe(); err != http.ErrServerClosed {
return err
}
return nil
}
if err := server.ListenAndServeTLS(h.cert, h.key); err != http.ErrServerClosed {
if !h.allowHttpWithHttps && !h.autoGenCert {
if err := server.ListenAndServeTLS(h.cert, h.key); err != http.ErrServerClosed {
return err
}
return nil
}
var lis net.Listener
if !h.autoGenCert {
lis, err = starnet.ListenTLS("tcp", h.addr+":"+h.port, h.cert, h.key, h.allowHttpWithHttps)
if err != nil {
return err
}
} else {
lis, err = starnet.ListenTLSWithConfig("tcp", h.addr+":"+h.port, &tls.Config{}, autoGenCert, h.allowHttpWithHttps)
if err != nil {
return err
}
}
defer lis.Close()
if err := server.Serve(lis); err != http.ErrServerClosed {
return err
}
return nil
}
var certCache = make(map[string]tls.Certificate)
var toolCa *x509.Certificate
var toolCaKey any
func autoGenCert(hostname string) *tls.Config {
if cert, ok := certCache[hostname]; ok {
return &tls.Config{Certificates: []tls.Certificate{cert}}
}
if toolCa == nil {
toolCa, toolCaKey = utils.ToolCert()
}
cert, err := utils.GenerateTlsCert(utils.GenerateCertParams{
Country: "CN",
Organization: "B612 HTTP SERVER",
OrganizationUnit: "cert@b612.me",
CommonName: hostname,
Dns: []string{hostname},
KeyUsage: int(x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageCertSign),
ExtendedKeyUsage: []int{
int(x509.ExtKeyUsageServerAuth),
int(x509.ExtKeyUsageClientAuth),
},
IsCA: false,
StartDate: time.Now().Add(-24 * time.Hour),
EndDate: time.Now().AddDate(1, 0, 0),
Type: "RSA",
Bits: 2048,
CA: toolCa,
CAPriv: toolCaKey,
})
if err != nil {
return nil
}
certCache[hostname] = cert
return &tls.Config{Certificates: []tls.Certificate{cert}}
}
func (h *HttpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.Listen(w, r)
}

134
image/bild/adjust.go Normal file
View File

@ -0,0 +1,134 @@
package bilds
import (
"image"
"github.com/anthonynsimon/bild/adjust"
"github.com/spf13/cobra"
)
func brightness() *cobra.Command {
var change float64
var cmd = &cobra.Command{
Use: "brightness",
Short: "调整图像的相对亮度",
Args: cobra.ExactArgs(2),
Example: "brightness --change 0.5 input.jpg output.jpg",
Run: func(cmd *cobra.Command, args []string) {
fin := args[0]
fout := args[1]
apply(fin, fout, func(img image.Image) (image.Image, error) {
return adjust.Brightness(img, change), nil
})
}}
cmd.Flags().Float64VarP(&change, "change", "c", 0, "adjust change")
return cmd
}
func contrast() *cobra.Command {
var change float64
var cmd = &cobra.Command{
Use: "contrast",
Short: "调整图像的相对对比度",
Args: cobra.ExactArgs(2),
Example: "contrast --change 0.5 input.jpg output.jpg",
Run: func(cmd *cobra.Command, args []string) {
fin := args[0]
fout := args[1]
apply(fin, fout, func(img image.Image) (image.Image, error) {
return adjust.Contrast(img, change), nil
})
}}
cmd.Flags().Float64VarP(&change, "change", "c", 0, "adjust change")
return cmd
}
func gamma() *cobra.Command {
var change float64
var cmd = &cobra.Command{
Use: "gamma",
Short: "adjust the gamma of an image",
Args: cobra.ExactArgs(2),
Example: "gamma --gamma 1.1 input.jpg output.jpg",
Run: func(cmd *cobra.Command, args []string) {
fin := args[0]
fout := args[1]
apply(fin, fout, func(img image.Image) (image.Image, error) {
return adjust.Gamma(img, change), nil
})
}}
cmd.Flags().Float64VarP(&change, "gamma", "g", 0, "set the gamma of the image")
return cmd
}
func hue() *cobra.Command {
var change int
var cmd = &cobra.Command{
Use: "hue",
Short: "adjust the hue of an image",
Args: cobra.ExactArgs(2),
Example: "hue --change 170 input.jpg output.jpg",
Run: func(cmd *cobra.Command, args []string) {
fin := args[0]
fout := args[1]
apply(fin, fout, func(img image.Image) (image.Image, error) {
return adjust.Hue(img, change), nil
})
}}
cmd.Flags().IntVarP(&change, "change", "c", 0, "adjust change")
return cmd
}
func saturation() *cobra.Command {
var change float64
var cmd = &cobra.Command{
Use: "saturation",
Short: "adjust the saturation of an image",
Args: cobra.ExactArgs(2),
Example: "saturation --change 170 input.jpg output.jpg",
Run: func(cmd *cobra.Command, args []string) {
fin := args[0]
fout := args[1]
apply(fin, fout, func(img image.Image) (image.Image, error) {
return adjust.Saturation(img, change), nil
})
}}
cmd.Flags().Float64VarP(&change, "change", "c", 0, "adjust change")
return cmd
}
func createAdjust() *cobra.Command {
adjustCmd := &cobra.Command{
Use: "adjust",
Short: "adjust basic image features like brightness or contrast",
}
adjustCmd.AddCommand(brightness())
adjustCmd.AddCommand(contrast())
adjustCmd.AddCommand(gamma())
adjustCmd.AddCommand(hue())
adjustCmd.AddCommand(saturation())
return adjustCmd
}

95
image/bild/blend.go Normal file
View File

@ -0,0 +1,95 @@
package bilds
import (
"fmt"
"image"
"github.com/anthonynsimon/bild/blend"
"github.com/spf13/cobra"
)
func buildBlendModeCommand(name string) *cobra.Command {
var strength float64
var cmd = &cobra.Command{
Use: name,
Args: cobra.ExactArgs(3),
Example: fmt.Sprintf("%s --strength 0.5 input1.jpg input2.jpg output.jpg", name),
Run: func(cmd *cobra.Command, args []string) {
fin1 := args[0]
fin2 := args[1]
fout := args[2]
apply2(fin1, fin2, fout, func(img1, img2 image.Image) (image.Image, error) {
switch name {
case "add":
return blend.Opacity(img1, blend.Add(img1, img2), strength), nil
case "colorburn":
return blend.Opacity(img1, blend.ColorBurn(img1, img2), strength), nil
case "colordodge":
return blend.Opacity(img1, blend.ColorDodge(img1, img2), strength), nil
case "darken":
return blend.Opacity(img1, blend.Darken(img1, img2), strength), nil
case "difference":
return blend.Opacity(img1, blend.Difference(img1, img2), strength), nil
case "divide":
return blend.Opacity(img1, blend.Divide(img1, img2), strength), nil
case "exclusion":
return blend.Opacity(img1, blend.Exclusion(img1, img2), strength), nil
case "lighten":
return blend.Opacity(img1, blend.Lighten(img1, img2), strength), nil
case "linearburn":
return blend.Opacity(img1, blend.LinearBurn(img1, img2), strength), nil
case "linearLight":
return blend.Opacity(img1, blend.LinearLight(img1, img2), strength), nil
case "multiply":
return blend.Opacity(img1, blend.Multiply(img1, img2), strength), nil
case "normal":
return blend.Opacity(img1, blend.Normal(img1, img2), strength), nil
case "opacity":
return blend.Opacity(img1, img2, strength), nil
case "overlay":
return blend.Opacity(img1, blend.Overlay(img1, img2), strength), nil
case "screen":
return blend.Opacity(img1, blend.Screen(img1, img2), strength), nil
case "softlight":
return blend.Opacity(img1, blend.SoftLight(img1, img2), strength), nil
case "subtract":
return blend.Opacity(img1, blend.Subtract(img1, img2), strength), nil
}
return blend.Opacity(img1, blend.Add(img1, img2), strength), nil
})
},
}
cmd.Flags().Float64VarP(&strength, "strength", "s", 1.0, "blend mode strength")
return cmd
}
func createBlend() *cobra.Command {
blendCmd := &cobra.Command{
Use: "blend",
Short: "blend two images together",
}
blendCmd.AddCommand(buildBlendModeCommand("add"))
blendCmd.AddCommand(buildBlendModeCommand("colorburn"))
blendCmd.AddCommand(buildBlendModeCommand("colordodge"))
blendCmd.AddCommand(buildBlendModeCommand("darken"))
blendCmd.AddCommand(buildBlendModeCommand("difference"))
blendCmd.AddCommand(buildBlendModeCommand("divide"))
blendCmd.AddCommand(buildBlendModeCommand("exclusion"))
blendCmd.AddCommand(buildBlendModeCommand("lighten"))
blendCmd.AddCommand(buildBlendModeCommand("linearburn"))
blendCmd.AddCommand(buildBlendModeCommand("linearLight"))
blendCmd.AddCommand(buildBlendModeCommand("multiply"))
blendCmd.AddCommand(buildBlendModeCommand("normal"))
blendCmd.AddCommand(buildBlendModeCommand("opacity"))
blendCmd.AddCommand(buildBlendModeCommand("overlay"))
blendCmd.AddCommand(buildBlendModeCommand("screen"))
blendCmd.AddCommand(buildBlendModeCommand("softlight"))
blendCmd.AddCommand(buildBlendModeCommand("subtract"))
return blendCmd
}

64
image/bild/blur.go Normal file
View File

@ -0,0 +1,64 @@
package bilds
import (
"image"
"github.com/anthonynsimon/bild/blur"
"github.com/spf13/cobra"
)
func box() *cobra.Command {
var radius float64
var cmd = &cobra.Command{
Use: "box",
Short: "apply box blur to an input image",
Args: cobra.ExactArgs(2),
Example: "box --radius 0.5 input.jpg output.jpg",
Run: func(cmd *cobra.Command, args []string) {
fin := args[0]
fout := args[1]
apply(fin, fout, func(img image.Image) (image.Image, error) {
return blur.Box(img, radius), nil
})
}}
cmd.Flags().Float64VarP(&radius, "radius", "r", 0, "the blur's radius")
return cmd
}
func gaussian() *cobra.Command {
var radius float64
var cmd = &cobra.Command{
Use: "gaussian",
Short: "apply gaussian blur to an input image",
Args: cobra.ExactArgs(2),
Example: "gaussian --radius 0.5 input.jpg output.jpg",
Run: func(cmd *cobra.Command, args []string) {
fin := args[0]
fout := args[1]
apply(fin, fout, func(img image.Image) (image.Image, error) {
return blur.Gaussian(img, radius), nil
})
}}
cmd.Flags().Float64VarP(&radius, "radius", "r", 0, "the blur's radius")
return cmd
}
func createBlur() *cobra.Command {
var blurCmd = &cobra.Command{
Use: "blur",
Short: "blur an image using the specified method",
}
blurCmd.AddCommand(box())
blurCmd.AddCommand(gaussian())
return blurCmd
}

60
image/bild/channel.go Normal file
View File

@ -0,0 +1,60 @@
package bilds
import (
"fmt"
"image"
"github.com/anthonynsimon/bild/channel"
"github.com/spf13/cobra"
)
func extractChannel() *cobra.Command {
var channels string
var cmd = &cobra.Command{
Use: "extract",
Short: "extracts RGBA channels from an input image",
Args: cobra.ExactArgs(2),
Example: "extract --channels rba input.jpg output.png",
Run: func(cmd *cobra.Command, args []string) {
fin := args[0]
fout := args[1]
// Apply takes care of resolving the destination encoder
apply(fin, fout, func(img image.Image) (image.Image, error) {
onlyChannels := []channel.Channel{}
for _, c := range channels {
switch c {
case 'r':
onlyChannels = append(onlyChannels, channel.Red)
case 'g':
onlyChannels = append(onlyChannels, channel.Green)
case 'b':
onlyChannels = append(onlyChannels, channel.Blue)
case 'a':
onlyChannels = append(onlyChannels, channel.Alpha)
default:
return nil, fmt.Errorf("unknown channel alias '%c'", c)
}
}
result := channel.ExtractMultiple(img, onlyChannels...)
return result, nil
})
}}
cmd.Flags().StringVarP(&channels, "channels", "c", "rgba", "the channels to include in the histogram")
return cmd
}
func createChannel() *cobra.Command {
var cmd = &cobra.Command{
Use: "channel",
Short: "channel operations on images",
}
cmd.AddCommand(extractChannel())
return cmd
}

257
image/bild/effect.go Normal file
View File

@ -0,0 +1,257 @@
package bilds
import (
"image"
"github.com/anthonynsimon/bild/effect"
"github.com/spf13/cobra"
)
func grayscale() *cobra.Command {
var cmd = &cobra.Command{
Use: "grayscale",
Aliases: []string{"gray"},
Short: "applies the grayscale effect",
Args: cobra.ExactArgs(2),
Example: "grayscale input.jpg output.png",
Run: func(cmd *cobra.Command, args []string) {
fin := args[0]
fout := args[1]
// Apply takes care of resolving the destination encoder
apply(fin, fout, func(img image.Image) (image.Image, error) {
return effect.Grayscale(img), nil
})
}}
return cmd
}
func sepia() *cobra.Command {
var cmd = &cobra.Command{
Use: "sepia",
Short: "applies the sepia effect",
Args: cobra.ExactArgs(2),
Example: "sepia input.jpg output.png",
Run: func(cmd *cobra.Command, args []string) {
fin := args[0]
fout := args[1]
// Apply takes care of resolving the destination encoder
apply(fin, fout, func(img image.Image) (image.Image, error) {
return effect.Sepia(img), nil
})
}}
return cmd
}
func sharpen() *cobra.Command {
var cmd = &cobra.Command{
Use: "sharpen",
Aliases: []string{"sharp"},
Short: "applies the sharpen effect",
Args: cobra.ExactArgs(2),
Example: "sharpen input.jpg output.png",
Run: func(cmd *cobra.Command, args []string) {
fin := args[0]
fout := args[1]
// Apply takes care of resolving the destination encoder
apply(fin, fout, func(img image.Image) (image.Image, error) {
return effect.Sharpen(img), nil
})
}}
return cmd
}
func sobel() *cobra.Command {
var cmd = &cobra.Command{
Use: "sobel",
Short: "applies the sobel effect",
Args: cobra.ExactArgs(2),
Example: "sobel input.jpg output.png",
Run: func(cmd *cobra.Command, args []string) {
fin := args[0]
fout := args[1]
// Apply takes care of resolving the destination encoder
apply(fin, fout, func(img image.Image) (image.Image, error) {
return effect.Sobel(img), nil
})
}}
return cmd
}
func invert() *cobra.Command {
var cmd = &cobra.Command{
Use: "invert",
Short: "applies the invert effect",
Args: cobra.ExactArgs(2),
Example: "invert input.jpg output.png",
Run: func(cmd *cobra.Command, args []string) {
fin := args[0]
fout := args[1]
// Apply takes care of resolving the destination encoder
apply(fin, fout, func(img image.Image) (image.Image, error) {
return effect.Invert(img), nil
})
}}
return cmd
}
func median() *cobra.Command {
var radius float64
var cmd = &cobra.Command{
Use: "median",
Short: "applies the median effect",
Args: cobra.ExactArgs(2),
Example: "median --radius 2.5 input.jpg output.png",
Run: func(cmd *cobra.Command, args []string) {
fin := args[0]
fout := args[1]
// Apply takes care of resolving the destination encoder
apply(fin, fout, func(img image.Image) (image.Image, error) {
return effect.Median(img, radius), nil
})
}}
cmd.Flags().Float64VarP(&radius, "radius", "r", 3, "the effect's radius")
return cmd
}
func erode() *cobra.Command {
var radius float64
var cmd = &cobra.Command{
Use: "erode",
Short: "applies the erode effect",
Args: cobra.ExactArgs(2),
Example: "erode --radius 0.5 input.jpg output.png",
Run: func(cmd *cobra.Command, args []string) {
fin := args[0]
fout := args[1]
// Apply takes care of resolving the destination encoder
apply(fin, fout, func(img image.Image) (image.Image, error) {
return effect.Erode(img, radius), nil
})
}}
cmd.Flags().Float64VarP(&radius, "radius", "r", 0.5, "the effect's radius")
return cmd
}
func dilate() *cobra.Command {
var radius float64
var cmd = &cobra.Command{
Use: "dilate",
Short: "applies the dilate effect",
Args: cobra.ExactArgs(2),
Example: "dilate --radius 0.5 input.jpg output.png",
Run: func(cmd *cobra.Command, args []string) {
fin := args[0]
fout := args[1]
// Apply takes care of resolving the destination encoder
apply(fin, fout, func(img image.Image) (image.Image, error) {
return effect.Dilate(img, radius), nil
})
}}
cmd.Flags().Float64VarP(&radius, "radius", "r", 0.5, "the effect's radius")
return cmd
}
func edgedetection() *cobra.Command {
var radius float64
var cmd = &cobra.Command{
Use: "edgedetection",
Short: "applies the edgedetection effect",
Args: cobra.ExactArgs(2),
Example: "edgedetection --radius 0.5 input.jpg output.png",
Run: func(cmd *cobra.Command, args []string) {
fin := args[0]
fout := args[1]
// Apply takes care of resolving the destination encoder
apply(fin, fout, func(img image.Image) (image.Image, error) {
return effect.EdgeDetection(img, radius), nil
})
}}
cmd.Flags().Float64VarP(&radius, "radius", "r", 0.5, "the effect's radius")
return cmd
}
func emboss() *cobra.Command {
var cmd = &cobra.Command{
Use: "emboss",
Short: "applies the emboss effect",
Args: cobra.ExactArgs(2),
Example: "emboss input.jpg output.png",
Run: func(cmd *cobra.Command, args []string) {
fin := args[0]
fout := args[1]
// Apply takes care of resolving the destination encoder
apply(fin, fout, func(img image.Image) (image.Image, error) {
return effect.Emboss(img), nil
})
}}
return cmd
}
func unsharpmask() *cobra.Command {
var radius float64
var amount float64
var cmd = &cobra.Command{
Use: "unsharpmask",
Short: "applies the unsharpmask effect",
Args: cobra.ExactArgs(2),
Example: "unsharpmask input.jpg output.png",
Run: func(cmd *cobra.Command, args []string) {
fin := args[0]
fout := args[1]
// Apply takes care of resolving the destination encoder
apply(fin, fout, func(img image.Image) (image.Image, error) {
return effect.UnsharpMask(img, radius, amount), nil
})
}}
cmd.Flags().Float64VarP(&radius, "radius", "r", 25, "the effect's radius")
cmd.Flags().Float64VarP(&radius, "amount", "a", 1, "the effect's amount")
return cmd
}
func createEffect() *cobra.Command {
var cmd = &cobra.Command{
Use: "effect",
Short: "apply effects on images",
}
cmd.AddCommand(grayscale())
cmd.AddCommand(sepia())
cmd.AddCommand(sharpen())
cmd.AddCommand(sobel())
cmd.AddCommand(invert())
cmd.AddCommand(median())
cmd.AddCommand(erode())
cmd.AddCommand(dilate())
cmd.AddCommand(edgedetection())
cmd.AddCommand(emboss())
cmd.AddCommand(unsharpmask())
return cmd
}

106
image/bild/helpers.go Normal file
View File

@ -0,0 +1,106 @@
package bilds
import (
"errors"
"fmt"
"image"
"os"
"strconv"
"strings"
"github.com/anthonynsimon/bild/imgio"
)
var jpgExtensions = []string{".jpg", ".jpeg"}
var pngExtensions = []string{".png"}
var bmpExtensions = []string{".bmp"}
var (
// ErrWrongSize is thrown when the provided size string does not match the expected form.
errWrongSize = errors.New("size must be of form [width]x[height], i.e. 400x200")
)
type size struct {
Width int
Height int
}
func resolveEncoder(outputfile string, defaultEncoding imgio.Encoder) imgio.Encoder {
lower := strings.ToLower(outputfile)
for _, ext := range jpgExtensions {
if strings.HasSuffix(lower, ext) {
return imgio.JPEGEncoder(100)
}
}
for _, ext := range pngExtensions {
if strings.HasSuffix(lower, ext) {
return imgio.PNGEncoder()
}
}
for _, ext := range bmpExtensions {
if strings.HasSuffix(lower, ext) {
return imgio.BMPEncoder()
}
}
return defaultEncoding
}
func apply(fin, fout string, process func(image.Image) (image.Image, error)) {
in, err := imgio.Open(fin)
exitIfNotNil(err)
result, err := process(in)
exitIfNotNil(err)
encoder := resolveEncoder(fout, imgio.PNGEncoder())
err = imgio.Save(fout, result, encoder)
exitIfNotNil(err)
}
func apply2(fin1, fin2, fout string, process func(image.Image, image.Image) (image.Image, error)) {
in1, err := imgio.Open(fin1)
exitIfNotNil(err)
in2, err := imgio.Open(fin2)
exitIfNotNil(err)
result, err := process(in1, in2)
exitIfNotNil(err)
encoder := resolveEncoder(fout, imgio.PNGEncoder())
err = imgio.Save(fout, result, encoder)
exitIfNotNil(err)
}
func exitIfNotNil(err error) {
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func parseSizeStr(sizestr string) (*size, error) {
parts := strings.Split(sizestr, "x")
if len(parts) != 2 {
return nil, errWrongSize
}
w, err := strconv.Atoi(parts[0])
if err != nil || w < 0 {
return nil, errWrongSize
}
h, err := strconv.Atoi(parts[1])
if err != nil || h < 0 {
return nil, errWrongSize
}
return &size{
Width: w,
Height: h,
}, nil
}

60
image/bild/histogram.go Normal file
View File

@ -0,0 +1,60 @@
package bilds
import (
"fmt"
"image"
"github.com/anthonynsimon/bild/channel"
"github.com/anthonynsimon/bild/histogram"
"github.com/spf13/cobra"
)
func newHisto() *cobra.Command {
var channels string
var cmd = &cobra.Command{
Use: "new",
Short: "creates a RBG histogram from an input image",
Args: cobra.ExactArgs(2),
Example: "new --channels rgb input.jpg output.png",
Run: func(cmd *cobra.Command, args []string) {
fin := args[0]
fout := args[1]
// Apply takes care of resolving the destination encoder
apply(fin, fout, func(img image.Image) (image.Image, error) {
onlyChannels := []channel.Channel{channel.Alpha}
for _, c := range channels {
switch c {
case 'r':
onlyChannels = append(onlyChannels, channel.Red)
case 'g':
onlyChannels = append(onlyChannels, channel.Green)
case 'b':
onlyChannels = append(onlyChannels, channel.Blue)
default:
return nil, fmt.Errorf("unknown channel alias '%c'", c)
}
}
hist := histogram.NewRGBAHistogram(img)
result := channel.ExtractMultiple(hist.Image(), onlyChannels...)
return result, nil
})
}}
cmd.Flags().StringVarP(&channels, "channels", "c", "rgb", "the channels to include in the histogram")
return cmd
}
func createHistogram() *cobra.Command {
var cmd = &cobra.Command{
Use: "histogram",
Short: "histogram operations on images",
}
cmd.AddCommand(newHisto())
return cmd
}

36
image/bild/imgio.go Normal file
View File

@ -0,0 +1,36 @@
package bilds
import (
"image"
"github.com/spf13/cobra"
)
func encode() *cobra.Command {
var cmd = &cobra.Command{
Use: "encode",
Short: "encodes the input image using the desired encoding set by the destination file extension",
Args: cobra.ExactArgs(2),
Example: "encode input.jpg output.png",
Run: func(cmd *cobra.Command, args []string) {
fin := args[0]
fout := args[1]
// Apply takes care of resolving the destination encoder
apply(fin, fout, func(img image.Image) (image.Image, error) {
return img, nil
})
}}
return cmd
}
func createImgio() *cobra.Command {
var cmd = &cobra.Command{
Use: "imgio",
Short: "i/o operations on images",
}
cmd.AddCommand(encode())
return cmd
}

48
image/bild/noise.go Normal file
View File

@ -0,0 +1,48 @@
package bilds
import (
"github.com/anthonynsimon/bild/imgio"
"github.com/anthonynsimon/bild/noise"
"github.com/spf13/cobra"
)
func generateNoise() *cobra.Command {
size := ""
mono := false
var cmd = &cobra.Command{
Use: "new",
Short: "generates an image filled with noise",
Args: cobra.ExactArgs(1),
Example: "new -s 100x100 output.png",
Run: func(cmd *cobra.Command, args []string) {
fout := args[0]
size, err := parseSizeStr(size)
exitIfNotNil(err)
result := noise.Generate(size.Width, size.Height, &noise.Options{
Monochrome: mono,
})
encoder := resolveEncoder(fout, imgio.PNGEncoder())
err = imgio.Save(fout, result, encoder)
exitIfNotNil(err)
}}
cmd.Flags().StringVarP(&size, "size", "s", "512x512", "the width and height of the output image")
cmd.Flags().BoolVarP(&mono, "monochrome", "m", false, "output monochrome noise")
return cmd
}
func createNoise() *cobra.Command {
var cmd = &cobra.Command{
Use: "noise",
Short: "noise generators",
}
cmd.AddCommand(generateNoise())
return cmd
}

32
image/bild/root.go Normal file
View File

@ -0,0 +1,32 @@
package bilds
import (
"github.com/spf13/cobra"
)
// Version of bild's CLI, set by the compiler on release
var Version string
var CmdBild = &cobra.Command{
Use: "bild",
Short: "A collection of parallel image processing algorithms in pure Go",
Version: Version,
}
func init() {
CmdBild.AddCommand(createAdjust())
CmdBild.AddCommand(createBlend())
CmdBild.AddCommand(createBlur())
CmdBild.AddCommand(createImgio())
CmdBild.AddCommand(createNoise())
CmdBild.AddCommand(createSegment())
CmdBild.AddCommand(createHistogram())
CmdBild.AddCommand(createChannel())
CmdBild.AddCommand(createEffect())
}
// Execute starts the cli's root command
func Execute() {
err := CmdBild.Execute()
exitIfNotNil(err)
}

41
image/bild/segment.go Normal file
View File

@ -0,0 +1,41 @@
package bilds
import (
"image"
"github.com/anthonynsimon/bild/segment"
"github.com/spf13/cobra"
)
func threshold() *cobra.Command {
var level uint8
var cmd = &cobra.Command{
Use: "threshold",
Short: "segment an image by a threshold",
Args: cobra.ExactArgs(2),
Example: "threshold --level 200 input.jpg output.jpg",
Run: func(cmd *cobra.Command, args []string) {
fin := args[0]
fout := args[1]
apply(fin, fout, func(img image.Image) (image.Image, error) {
return segment.Threshold(img, level), nil
})
}}
cmd.Flags().Uint8VarP(&level, "level", "l", 128, "the level at which the segmenting threshold will be crossed")
return cmd
}
func createSegment() *cobra.Command {
var blurCmd = &cobra.Command{
Use: "segment",
Short: "segment an image using the specified method",
}
blurCmd.AddCommand(threshold())
return blurCmd
}

231
image/exif.go Normal file
View File

@ -0,0 +1,231 @@
package image
import (
"b612.me/exif"
exifcommon "b612.me/exif/common"
"encoding/hex"
"fmt"
"os"
)
func Exif(path string) error {
f, err := os.Open(path)
if err != nil {
return fmt.Errorf("打开文件失败 %s", path)
}
rawExif, err := exif.SearchAndExtractExifWithReader(f)
f.Close()
if err != nil {
return fmt.Errorf("查询 EXIF 信息失败 %s: %w", path, err)
}
tags, _, err := exif.GetFlatExifDataUniversalSearch(rawExif, nil, true)
if err != nil {
return fmt.Errorf("获取 EXIF 信息失败 %s: %w", path, err)
}
ifd0Map := map[string]string{
// 根 IFD (IFD0) 标签
"IFD.Software": "生成软件名",
"IFD.ImageWidth": "图像宽度",
"IFD.ImageLength": "图像高度",
"IFD.Make": "相机制造商",
"IFD.Model": "相机型号",
"IFD.Orientation": "图像方向",
"IFD.YResolution": "Y轴分辨率",
"IFD.XResolution": "X轴分辨率",
"IFD.ResolutionUnit": "分辨率单位",
"IFD.DateTime": "最后修改时间",
"IFD.ImageDescription": "图像描述",
"IFD.Artist": "图像作者",
"IFD.Copyright": "版权信息",
"IFD.HostComputer": "创建图像的计算机",
"IFD.BitsPerSample": "每个颜色分量的位数",
"IFD.Compression": "压缩方案",
"IFD.PhotometricInterpretation": "像素组成方式",
"IFD.StripOffsets": "图像数据条带偏移量",
"IFD.SamplesPerPixel": "每个像素的分量数",
"IFD.RowsPerStrip": "每个条带的行数",
"IFD.StripByteCounts": "每个条带的字节数",
"IFD.PlanarConfiguration": "数据排列方式",
// Exif 子 IFD 标签
"IFD/Exif.DateTimeOriginal": "原始拍摄时间",
"IFD/Exif.DateTimeDigitized": "数字化时间",
"IFD/Exif.OffsetTimeOriginal": "原始拍摄时区偏移",
"IFD/Exif.ExposureTime": "曝光时间",
"IFD/Exif.FNumber": "光圈F值",
"IFD/Exif.ExposureProgram": "曝光程序",
"IFD/Exif.ISOSpeedRatings": "ISO感光度",
"IFD/Exif.ShutterSpeedValue": "快门速度APEX值",
"IFD/Exif.ApertureValue": "光圈值APEX值",
"IFD/Exif.BrightnessValue": "亮度值APEX值",
"IFD/Exif.ExposureBiasValue": "曝光补偿值EV",
"IFD/Exif.MeteringMode": "测光模式",
"IFD/Exif.Flash": "闪光灯状态",
"IFD/Exif.FocalLength": "焦距(毫米)",
"IFD/Exif.SubjectDistance": "拍摄主体距离(米)",
"IFD/Exif.FlashEnergy": "闪光灯强度",
"IFD/Exif.SpatialFrequencyResponse": "空间频率响应",
"IFD/Exif.FocalPlaneXResolution": "焦平面X分辨率",
"IFD/Exif.FocalPlaneYResolution": "焦平面Y分辨率",
"IFD/Exif.FocalPlaneResolutionUnit": "焦平面分辨率单位",
"IFD/Exif.FileSource": "文件来源",
"IFD/Exif.SceneType": "场景类型",
"IFD/Exif.CFAPattern": "CFA彩色滤镜阵列模式",
"IFD/Exif.CustomRendered": "自定义图像处理",
"IFD/Exif.ExposureMode": "曝光模式",
"IFD/Exif.WhiteBalance": "白平衡",
"IFD/Exif.DigitalZoomRatio": "数码变焦比率",
"IFD/Exif.SceneCaptureType": "场景拍摄类型",
"IFD/Exif.GainControl": "增益控制",
"IFD/Exif.Contrast": "对比度",
"IFD/Exif.Saturation": "饱和度",
"IFD/Exif.Sharpness": "锐度",
"IFD/Exif.DeviceSettingDescription": "设备设置描述",
"IFD/Exif.SubjectDistanceRange": "主体距离范围",
// GPS 子 IFD 标签 (可选添加)
"IFD/GPSInfo.GPSLatitude": "纬度",
"IFD/GPSInfo.GPSLongitude": "经度",
"IFD/GPSInfo.GPSAltitude": "海拔高度",
"IFD/GPSInfo.GPSTimeStamp": "GPS时间戳",
"IFD/GPSInfo.GPSDateStamp": "GPS日期戳",
"IFD/GPSInfo.GPSDOP": "定位精度",
}
for _, tag := range tags {
sample := "%s.%v {%#04x} [%v] %-10v\n"
if name, ok := ifd0Map[tag.IfdPath+"."+tag.TagName]; ok {
sample = "%s.%v {%#04x} [%v] (" + name + ") %-10v\n"
}
switch tag.IfdPath + "." + tag.TagName {
case "IFD.ResolutionUnit":
switch tag.Value.([]uint16)[0] {
case 1:
fmt.Printf("%s.%v {%#04x} [%v] (分辨率单位) 没有单位\n", tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName)
case 2:
fmt.Printf("%s.%v {%#04x} [%v] (分辨率单位) 英寸\n", tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName)
case 3:
fmt.Printf("%s.%v {%#04x} [%v] (分辨率单位) 像素\n", tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName)
default:
fmt.Printf("%s.%v {%#04x} [%v] (分辨率单位) 未知单位:%v\n", tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, tag.Value)
}
case "IFD/GPSInfo.GPSLatitude", "IFD/GPSInfo.GPSLongitude":
switch val := tag.Value.(type) {
case []exifcommon.Rational:
var value string
for i, v := range val {
switch i {
case 0:
value += fmt.Sprintf("%.0f°", float64(v.Numerator)/float64(v.Denominator))
case 1:
value += fmt.Sprintf("%.0f", float64(v.Numerator)/float64(v.Denominator))
case 2:
value += fmt.Sprintf("%.3f″", float64(v.Numerator)/float64(v.Denominator))
}
}
fmt.Printf(sample, tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, value)
default:
fmt.Printf(sample, tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, val)
}
case "IFD/GPSInfo.GPSTimeStamp":
switch val := tag.Value.(type) {
case []exifcommon.Rational:
var value string
for _, v := range val {
value += fmt.Sprintf("%.0f:", float64(v.Numerator)/float64(v.Denominator))
}
fmt.Printf(sample, tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, value[:len(value)-1]) // 去掉最后一个冒号
default:
fmt.Printf(sample, tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, val)
}
default:
switch val := tag.Value.(type) {
case []exifcommon.Rational:
switch tag.IfdPath + "." + tag.TagName {
case "IFD/Exif.ExposureTime":
sample += "秒"
}
fmt.Printf(sample, tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, float64(val[0].Numerator)/float64(val[0].Denominator))
case []uint16:
if len(val) == 1 {
switch tag.IfdPath + "." + tag.TagName {
case "IFD/Exif.ExposureProgram":
valStr := ""
switch val[0] {
case 0:
valStr = "未知"
case 1:
valStr = "手动"
case 2:
valStr = "自动"
case 3:
valStr = "光圈优先"
case 4:
valStr = "快门优先"
case 5:
valStr = "创意(慢速)"
case 6:
valStr = "动作(高速)"
case 7:
valStr = "肖像"
case 8:
valStr = "景观"
case 9:
valStr = "Bulb"
}
fmt.Printf(sample, tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, valStr)
continue
case "IFD/Exif.MeteringMode":
/*
0 = Unknown
1 = Average
2 = Center-weighted average
3 = Spot
4 = Multi-spot
5 = Multi-segment
6 = Partial
255 = Other
*/
valStr := ""
switch val[0] {
case 0:
valStr = "未知"
case 1:
valStr = "平均"
case 2:
valStr = "中央重点平均"
case 3:
valStr = "点测光"
case 4:
valStr = "多点测光"
case 5:
valStr = "多段测光"
case 6:
valStr = "局部测光"
case 255:
valStr = "其他"
}
fmt.Printf(sample, tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, valStr)
continue
}
fmt.Printf(sample, tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, val[0])
} else {
fmt.Printf(sample, tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, val)
}
case []uint32:
if len(val) == 1 {
fmt.Printf(sample, tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, val[0])
} else {
fmt.Printf(sample, tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, val)
}
case []uint8:
fmt.Printf(sample, tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, hex.EncodeToString(val))
case string:
fmt.Printf(sample, tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, val)
default:
fmt.Printf(sample, tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, val)
}
}
}
return nil
}

View File

@ -19,7 +19,7 @@ var fromRgb string
var toRgb string
func init() {
Cmd.AddCommand(imgMirrorCmd, imgRgbCountCmd, imgReplaceCmd, imgReverseCmd)
Cmd.AddCommand(imgMirrorCmd, imgRgbCountCmd, imgReplaceCmd, imgReverseCmd, CmdExifRead)
imgRgbCountCmd.Flags().BoolVarP(&useHex, "hex", "x", false, "使用十六进制表示")
imgRgbCountCmd.Flags().BoolVarP(&useAlpha, "alpha", "a", false, "统计alpha通道")
imgRgbCountCmd.Flags().IntVarP(&useCount, "count", "c", 10, "显示数量")
@ -268,3 +268,23 @@ var imgReverseCmd = &cobra.Command{
fmt.Println("任务完成!")
},
}
var CmdExifRead = &cobra.Command{
Use: "exifread",
Short: "读取图像Exif信息",
Long: "读取图像Exif信息输出为表格格式",
Run: func(this *cobra.Command, args []string) {
if len(args) == 0 {
starlog.Errorln("请指定需要读取Exif信息的图像")
return
}
for _, v := range args {
if err := Exif(v); err != nil {
starlog.Errorln(err, v)
continue
}
fmt.Println(v, "Exif信息读取完成")
}
fmt.Println("任务完成!")
},
}

View File

@ -14,6 +14,7 @@ import (
"b612.me/apps/b612/df"
"b612.me/apps/b612/dfinder"
"b612.me/apps/b612/dns"
"b612.me/apps/b612/filedate"
"b612.me/apps/b612/ftp"
"b612.me/apps/b612/gdu/cmd/gdu"
"b612.me/apps/b612/generate"
@ -23,9 +24,11 @@ import (
"b612.me/apps/b612/httproxy"
"b612.me/apps/b612/httpserver"
"b612.me/apps/b612/image"
bilds "b612.me/apps/b612/image/bild"
"b612.me/apps/b612/keygen"
"b612.me/apps/b612/merge"
"b612.me/apps/b612/mget"
"b612.me/apps/b612/mime"
"b612.me/apps/b612/net"
"b612.me/apps/b612/nmon"
"b612.me/apps/b612/rmt"
@ -59,7 +62,8 @@ func init() {
base64.Cmd, base85.Cmd, base91.Cmd, attach.Cmd, detach.Cmd, df.Cmd, dfinder.Cmd,
ftp.Cmd, generate.Cmd, hash.Cmd, image.Cmd, merge.Cmd, search.Cmd, split.Cmd, vic.Cmd,
calc.Cmd, net.Cmd, rmt.Cmds, rmt.Cmdc, keygen.Cmd, dns.Cmd, whois.Cmd, socks5.Cmd, httproxy.Cmd, smtpserver.Cmd, smtpclient.Cmd,
cert.Cmd, aes.Cmd, tls.Cmd, mget.Cmd, tcpkill.Cmd, tcm.Cmd, astro.CmdCal, astro.Cmd, nmon.Cmd, hcache.Cmd, gdu.Cmd, bed.Cmd)
cert.Cmd, aes.Cmd, tls.Cmd, mget.Cmd, tcpkill.Cmd, tcm.Cmd, astro.CmdCal, astro.Cmd, nmon.Cmd, hcache.Cmd, gdu.Cmd, bed.Cmd, mime.Cmd,
filedate.Cmd, bilds.CmdBild)
}
func main() {

View File

@ -4,6 +4,7 @@ import (
"b612.me/stario"
"b612.me/starlog"
"b612.me/starnet"
"crypto/tls"
"fmt"
"github.com/spf13/cobra"
"os"
@ -41,7 +42,7 @@ func init() {
Cmd.Flags().StringVarP(&proxy, "proxy", "P", "", "代理地址")
Cmd.Flags().StringVarP(&ua, "user-agent", "U", "", "自定义User-Agent")
Cmd.Flags().BoolVarP(&skipVerify, "skip-verify", "k", false, "跳过SSL验证")
Cmd.Flags().StringVarP(&speedcontrol, "speed", "S", "", "限速如1M意思是1MB/s")
Cmd.Flags().StringVarP(&speedcontrol, "speed", "S", "", "限速如1M意思是1MiB/s")
Cmd.Flags().IntVarP(&dialTimeout, "dial-timeout", "d", 5, "连接网络超时时间,单位:秒")
Cmd.Flags().IntVarP(&timeout, "timeout", "T", 0, "下载超时时间,单位:秒")
Cmd.Flags().StringVarP(&user, "user", "u", "", "http basic认证用户")
@ -126,7 +127,9 @@ func Run(cmd *cobra.Command, args []string) {
mg.Setting.SetProxy(proxy)
}
if skipVerify {
mg.Setting.SetSkipTLSVerify(true)
mg.Setting.SetTlsConfig(&tls.Config{
InsecureSkipVerify: true,
})
}
sig := make(chan os.Signal)
signal.Notify(sig, os.Interrupt)

View File

@ -88,7 +88,7 @@ func IOWriter(stopCtx context.Context, ch chan Buffer, state *uint32, di *downlo
}
}
func createFileWithSize(filename string, size int64) (*os.File, error) {
func createFileWithSizeOld(filename string, size int64) (*os.File, error) {
file, err := os.Create(filename)
if err != nil {
return nil, err
@ -109,6 +109,24 @@ func createFileWithSize(filename string, size int64) (*os.File, error) {
return file, nil
}
// 最佳实践
func createFileWithSize(filename string, size int64) (*os.File, error) {
file, err := os.Create(filename)
if err != nil {
return nil, err
}
if size > 0 {
if err := file.Truncate(size); err != nil {
file.Close()
os.Remove(filename)
return nil, err
}
}
return file, nil
}
func CloneHeader(original http.Header) http.Header {
newHeader := make(http.Header)
for key, values := range original {

View File

@ -112,7 +112,7 @@ func (w *Mget) prepareRun(res *starnet.Response, is206 bool) error {
Is206: is206,
}
fmt.Println("Threads:", w.Thread)
if staros.Exists(w.Tareget + ".bgrd") {
if !w.NoWriteRedo && staros.Exists(w.Tareget+".bgrd") {
fmt.Println("Found redo file, try to recover...")
var redo Redo
data, err := os.ReadFile(w.Tareget + ".bgrd")
@ -323,8 +323,8 @@ morejob:
for {
w.Lock()
if len(w.lastUndoInfo) > 0 {
w.threads[idx].Start = int64(w.lastUndoInfo[idx].Min)
w.threads[idx].End = int64(w.lastUndoInfo[idx].Max)
w.threads[idx].Start = int64(w.lastUndoInfo[0].Min)
w.threads[idx].End = int64(w.lastUndoInfo[0].Max)
w.lastUndoInfo = w.lastUndoInfo[1:]
w.Unlock()
} else {
@ -408,7 +408,7 @@ func (w *Mget) WriteServer() error {
}
if w.RedoRPO == 0 {
w.RedoAutoRpo = true
w.RedoRPO = 1024 * 1024 * 1024
w.RedoRPO = 1024 * 1024 * 10
}
for {
select {
@ -435,6 +435,9 @@ func (w *Mget) WriteServer() error {
go w.Redo.Save()
if w.RedoAutoRpo {
w.RedoRPO = int(w.Redo.Speed()) * 2
if w.RedoRPO > 100*1024*1024 {
w.RedoRPO = 1024 * 1024 * 100
}
}
lastUpdateRange = currentRange
}

154
mime/mime.go Normal file
View File

@ -0,0 +1,154 @@
package mime
import (
"b612.me/starlog"
"b612.me/staros"
"encoding/csv"
"github.com/gabriel-vasile/mimetype"
"github.com/spf13/cobra"
"io"
"os"
"path/filepath"
"strings"
)
var recursive, verbose, repair, appendOldName, onlyShowNotMatch, secondExt bool
var outputCsvPath string
var fromSize int
func init() {
Cmd.Flags().BoolVarP(&recursive, "recursive", "r", false, "递归检测文件夹中的所有文件")
Cmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "详细模式,输出更多日志")
Cmd.Flags().BoolVarP(&repair, "repair", "R", false, "修复文件类型,尝试将文件类型修复为正确的类型")
Cmd.Flags().BoolVarP(&appendOldName, "append-mode", "a", false, "在修复文件名时,追加旧文件名后缀")
Cmd.Flags().StringVarP(&outputCsvPath, "output-csv", "o", "", "输出CSV文件路径若不指定则不输出CSV")
Cmd.Flags().IntVarP(&fromSize, "from-size", "s", 0, "需要检测的地址偏移量单位为字节默认从0开始检测负数表示从文件末尾开始计算偏移量")
Cmd.Flags().BoolVarP(&onlyShowNotMatch, "only-show-not-match", "m", false, "只显示文件类型不匹配的文件")
Cmd.Flags().BoolVarP(&secondExt, "second-ext", "S", false, "使用第二个扩展名进行匹配,适用于某些文件类型(如 .tar.gz .7z.001")
}
var Cmd = &cobra.Command{
Use: "mime",
Short: "文件类型检测",
Long: `mime - 文件类型检测`,
Run: func(cmd *cobra.Command, args []string) {
var c *csv.Writer
if len(args) == 0 {
starlog.Errorf("请指定文件路径\n")
os.Exit(1)
}
if outputCsvPath != "" {
file, err := os.Create(outputCsvPath)
if err != nil {
starlog.Errorf("创建CSV文件 %s 失败: %v\n", outputCsvPath, err)
os.Exit(1)
}
defer file.Close()
c = csv.NewWriter(file)
c.Write([]string{"File Path", "Extension", "Detect File Type", "Detect File Extension", "Match"})
defer c.Flush()
}
for _, path := range args {
if staros.IsFolder(path) {
for _, file := range getFiles(path, recursive) {
Detect(c, file, fromSize, onlyShowNotMatch)
}
} else {
Detect(c, path, fromSize, onlyShowNotMatch)
}
}
},
}
func getFiles(folder string, recurring bool) []string {
files := make([]string, 0)
err := filepath.Walk(folder, func(path string, info os.FileInfo, err error) error {
if err != nil {
starlog.Errorf("walk folder %s error: %v", folder, err)
return err
}
if info.IsDir() {
if recurring {
return nil // 继续递归
}
return filepath.SkipDir // 不递归子目录
}
files = append(files, path)
return nil
})
if err != nil {
starlog.Errorf("walk folder %s error: %v", folder, err)
}
return files
}
func Detect(c *csv.Writer, path string, fromSize int, onlyShowNotMatch bool) {
f, err := os.Open(path)
if err != nil {
starlog.Errorf("open file %s error: %v\n", path, err)
os.Exit(1)
}
if fromSize > 0 {
f.Seek(int64(fromSize), io.SeekStart)
} else if fromSize < 0 {
f.Seek(int64(fromSize), io.SeekEnd)
}
tp, err := mimetype.DetectReader(f)
f.Close()
if err != nil {
starlog.Errorf("detect file type error: %v\n", err)
os.Exit(1)
}
ext := strings.ToLower(filepath.Ext(path))
ext2 := strings.ToLower(filepath.Ext(strings.TrimRight(path, ext)))
if verbose {
starlog.Infof("file path: %s\n", path)
starlog.Infof("file type: %s\n", tp.String())
starlog.Infof("file ext: %s\n", tp.Extension())
if ext2 == tp.Extension() && secondExt {
if !onlyShowNotMatch {
starlog.Infof("file type %s match extension %s\n", tp.String(), ext2)
}
} else if ext != tp.Extension() && tp.Extension() != "" {
starlog.Warningf("file type %s not match extension %s\n", tp.String(), ext)
} else if !onlyShowNotMatch {
starlog.Infof("file type %s match extension %s\n", tp.String(), ext)
}
} else {
if ext2 == tp.Extension() && secondExt {
if !onlyShowNotMatch {
starlog.Infof("%s: %s (%s) matched %s\n", path, tp.String(), tp.Extension(), ext2)
}
} else if ext != tp.Extension() && tp.Extension() != "" {
starlog.Warningf("%s: %s (%s) not match %s\n", path, tp.String(), tp.Extension(), ext)
} else if !onlyShowNotMatch {
starlog.Infof("%s: %s (%s) matched %s\n", path, tp.String(), tp.Extension(), ext)
}
}
if c != nil {
match := "false"
if ext == tp.Extension() {
match = "true"
}
err = c.Write([]string{path, ext, tp.String(), tp.Extension(), match})
if err != nil {
starlog.Errorf("write csv file %s error: %v\n", outputCsvPath, err)
}
}
if repair && ext != tp.Extension() && tp.Extension() != "" {
newPath := path[:len(path)-len(ext)] + tp.Extension()
if appendOldName {
newPath = path + tp.Extension()
}
err = os.Rename(path, newPath)
if err != nil {
starlog.Errorf("rename file %s to %s error: %v", path, newPath, err)
os.Exit(1)
}
if verbose {
starlog.Infof("file %s renamed to %s\n", path, newPath)
} else {
starlog.Infof("renamed: %s -> %s\n", path, newPath)
}
}
}

View File

@ -1,11 +1,14 @@
package smtpserver
import (
"b612.me/apps/b612/utils"
"b612.me/starlog"
"b612.me/startext"
"bytes"
"crypto/tls"
"crypto/x509"
"fmt"
"github.com/emersion/go-sasl"
"github.com/spf13/cobra"
"html"
"io"
@ -22,10 +25,11 @@ import (
var addr string
var user, pass string
var allowAnyuser bool
var allowAnyuser, startls bool
var output string
var domain string
var cert, key string
var allowInsecureAuth, autoGenKey bool
var Cmd = &cobra.Command{
Use: "smtps",
@ -33,7 +37,6 @@ var Cmd = &cobra.Command{
Long: "smtp server",
Run: func(cmd *cobra.Command, args []string) {
run()
},
}
@ -46,6 +49,9 @@ func init() {
Cmd.Flags().StringVarP(&domain, "domain", "d", "localhost", "smtp server domain")
Cmd.Flags().StringVarP(&cert, "cert", "c", "", "smtp server cert(TLS)")
Cmd.Flags().StringVarP(&key, "key", "k", "", "smtp server key(TLS)")
Cmd.Flags().BoolVarP(&autoGenKey, "auto-gen-key", "g", false, "auto generate key and cert")
Cmd.Flags().BoolVarP(&startls, "startls", "s", false, "enable starttls")
Cmd.Flags().BoolVarP(&allowInsecureAuth, "allow-insecure-auth", "i", false, "allow insecure auth")
}
type backend struct{}
@ -58,32 +64,50 @@ type session struct {
username string
password string
to string
auth bool
}
func (s *session) AuthPlain(username, password string) error {
s.username = username
s.password = password
starlog.Printf("username:%s,password:%s\n", username, password)
if allowAnyuser {
return nil
} else {
if username != user || password != pass {
return smtp.ErrAuthFailed
func (s *session) AuthMechanisms() []string {
return []string{sasl.Plain}
}
func (s *session) Auth(mech string) (sasl.Server, error) {
return sasl.NewPlainServer(func(identity, username, password string) error {
s.username = username
s.password = password
starlog.Red("username:%s,password:%s\n", username, password)
if allowAnyuser {
s.auth = true
return nil
} else {
if username != user || password != pass {
return smtp.ErrAuthFailed
}
}
s.auth = true
return nil
}), nil
}
func (s *session) Mail(from string, opts *smtp.MailOptions) error {
if !s.auth {
return smtp.ErrAuthRequired
}
return nil
}
func (s *session) Mail(from string, opts *smtp.MailOptions) error {
return nil
}
func (s *session) Rcpt(to string, opts *smtp.RcptOptions) error {
if !s.auth {
return smtp.ErrAuthRequired
}
s.to += to + ";"
return nil
}
func (s *session) Data(r io.Reader) error {
if !s.auth {
return smtp.ErrAuthRequired
}
mailData, err := ioutil.ReadAll(r)
if err != nil {
return err
@ -169,25 +193,61 @@ func (s *session) Logout() error {
func run() {
var err error
s := smtp.NewServer(&backend{})
if cert != "" && key != "" {
var config tls.Config
config.Certificates = make([]tls.Certificate, 1)
config.Certificates[0], err = tls.LoadX509KeyPair(cert, key)
if err != nil {
starlog.Errorln("failed to load cert:", err)
return
}
s.TLSConfig = &config
s.Addr = addr
s.Domain = domain
s.AllowInsecureAuth = true
s.Debug = os.Stdout
starlog.Infoln("Starting TLS-SMTP server at", addr)
starlog.Errorln(s.ListenAndServeTLS())
return
}
s.Addr = addr
s.Domain = domain
s.EnableSMTPUTF8 = true
s.EnableDSN = true
s.EnableBINARYMIME = true
s.EnableRRVS = true
if autoGenKey || (cert != "" && key != "") {
var config tls.Config
config.Certificates = make([]tls.Certificate, 1)
if !autoGenKey {
config.Certificates[0], err = tls.LoadX509KeyPair(cert, key)
if err != nil {
starlog.Errorln("failed to load cert:", err)
return
}
} else {
ca, cakey := utils.ToolCert()
genCert, err := utils.GenerateTlsCert(utils.GenerateCertParams{
Country: "CN",
Organization: "B612 SMTP SERVER",
OrganizationUnit: "CA@B612.ME",
CommonName: s.Domain,
Dns: []string{s.Domain},
KeyUsage: int(x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageCertSign),
ExtendedKeyUsage: []int{
int(x509.ExtKeyUsageServerAuth),
int(x509.ExtKeyUsageClientAuth),
},
IsCA: false,
StartDate: time.Now().Add(-24 * time.Hour),
EndDate: time.Now().AddDate(1, 0, 0),
Type: "RSA",
Bits: 2048,
CA: ca,
CAPriv: cakey,
})
if err != nil {
starlog.Errorln("failed to generate cert:", err)
return
}
config.Certificates[0] = genCert
}
s.TLSConfig = &config
s.AllowInsecureAuth = allowInsecureAuth
s.EnableREQUIRETLS = true
s.Debug = os.Stdout
if !startls {
starlog.Infoln("Starting TLS-SMTP server at", addr)
starlog.Errorln(s.ListenAndServeTLS())
return
}
starlog.Infoln("Starting StartTls-SMTP server at", addr)
starlog.Errorln(s.ListenAndServe())
return
}
s.AllowInsecureAuth = true
s.Debug = os.Stdout

302
utils/cert.go Normal file
View File

@ -0,0 +1,302 @@
package utils
import (
"b612.me/starcrypto"
"crypto"
"crypto/ecdh"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"errors"
"fmt"
"math/big"
"net"
"strings"
"time"
)
type GenerateCertParams struct {
Country string `json:"country"`
Province string `json:"province"`
City string `json:"city"`
Organization string `json:"organization"`
OrganizationUnit string `json:"organization_unit"`
CommonName string `json:"common_name"`
Dns []string `json:"dns"`
KeyUsage int `json:"key_usage"`
ExtendedKeyUsage []int `json:"extended_key_usage"`
IsCA bool `json:"is_ca"`
StartDate time.Time `json:"start_date"`
EndDate time.Time `json:"end_date"`
MaxPathLength int `json:"max_path_length"`
MaxPathLengthZero bool `json:"max_path_length_zero"`
Type string `json:"type"` // e.g., "x509", "ssh"
Bits int `json:"bits"` // e.g., 2048, 4096
CA *x509.Certificate
CAPriv any // private key for CA, if nil, use the generated key
}
func GenerateKeys(types string, bits int) (crypto.PublicKey, any, error) {
var priv any
var pub crypto.PublicKey
var err error
switch strings.ToLower(types) {
case "rsa":
priv, pub, err = starcrypto.GenerateRsaKey(bits)
if err != nil {
return nil, nil, err
}
case "ecdsa":
var cr elliptic.Curve
switch bits {
case 224:
cr = elliptic.P224()
case 256:
cr = elliptic.P256()
case 384:
cr = elliptic.P384()
case 521:
cr = elliptic.P521()
default:
return nil, nil, errors.New("invalid bits,should be 224,256,384,521")
}
priv, pub, err = starcrypto.GenerateEcdsaKey(cr)
if err != nil {
return nil, nil, err
}
case "ed25519":
pub, priv, err = ed25519.GenerateKey(rand.Reader)
if err != nil {
return nil, nil, err
}
case "x25519":
priv, err = ecdh.X25519().GenerateKey(rand.Reader)
if err != nil {
return nil, nil, err
}
pub = priv.(*ecdh.PrivateKey).Public()
case "ecdh":
switch bits {
case 256:
priv, err = ecdh.P256().GenerateKey(rand.Reader)
if err != nil {
return nil, nil, err
}
pub = priv.(*ecdh.PrivateKey).Public()
case 384:
priv, err = ecdh.P384().GenerateKey(rand.Reader)
if err != nil {
return nil, nil, err
}
pub = priv.(*ecdh.PrivateKey).Public()
case 521:
priv, err = ecdh.P521().GenerateKey(rand.Reader)
if err != nil {
return nil, nil, err
}
pub = priv.(*ecdh.PrivateKey).Public()
}
default:
return nil, nil, errors.New("invalid key type,only support rsa,ecdsa")
}
return pub, priv, nil
}
func getSlice(val string) []string {
if val == "" {
return nil
}
return []string{val}
}
func GenerateCert(params GenerateCertParams) ([]byte, []byte, error) {
var needAppendCa bool = true
pub, priv, err := GenerateKeys(params.Type, params.Bits)
if err != nil {
return nil, nil, err
}
var extKeyUsage []x509.ExtKeyUsage = nil
for _, usage := range params.ExtendedKeyUsage {
if extKeyUsage == nil {
extKeyUsage = make([]x509.ExtKeyUsage, 0, len(params.ExtendedKeyUsage))
}
extKeyUsage = append(extKeyUsage, x509.ExtKeyUsage(usage))
}
var trueDNS []string
var trueIp []net.IP
for _, v := range params.Dns {
ip := net.ParseIP(v)
if ip == nil {
trueDNS = append(trueDNS, v)
continue
}
trueIp = append(trueIp, ip)
}
cert := &x509.Certificate{
Version: 3,
SerialNumber: big.NewInt(time.Now().Unix()),
Subject: pkix.Name{
Country: getSlice(params.Country),
Province: getSlice(params.Province),
Locality: getSlice(params.City),
Organization: getSlice(params.Organization),
OrganizationalUnit: getSlice(params.OrganizationUnit),
CommonName: params.CommonName,
},
NotBefore: params.StartDate,
NotAfter: params.EndDate,
BasicConstraintsValid: true,
IsCA: params.IsCA,
MaxPathLenZero: params.MaxPathLengthZero,
ExtKeyUsage: extKeyUsage,
KeyUsage: x509.KeyUsage(params.KeyUsage),
MaxPathLen: params.MaxPathLength,
DNSNames: trueDNS,
IPAddresses: trueIp,
}
if params.CA == nil {
params.CA = cert
needAppendCa = false
}
if params.CAPriv == nil {
params.CAPriv = priv
}
certs, err := x509.CreateCertificate(rand.Reader, cert, params.CA, pub, params.CAPriv)
if err != nil {
return nil, nil, err
}
certBlock := &pem.Block{
Type: "CERTIFICATE",
Bytes: certs,
}
pemData := pem.EncodeToMemory(certBlock)
privData, err := starcrypto.EncodePrivateKey(priv, "")
if err != nil {
return nil, nil, err
}
if needAppendCa {
pemData = append(pemData, []byte("\n")...)
certBlock = &pem.Block{
Type: "CERTIFICATE",
Bytes: params.CA.Raw,
}
pemData = append(pemData, pem.EncodeToMemory(certBlock)...)
}
return pemData, privData, nil
}
func GenerateTlsCert(params GenerateCertParams) (tls.Certificate, error) {
certPem, privPem, err := GenerateCert(params)
if err != nil {
return tls.Certificate{}, err
}
return tls.X509KeyPair(certPem, privPem)
}
func ToolCert() (*x509.Certificate, any) {
certPem := `-----BEGIN CERTIFICATE-----
MIIGFjCCA/6gAwIBAgIEaEqE/DANBgkqhkiG9w0BAQsFADCBnzELMAkGA1UEBhMC
Q04xFjAUBgNVBAcTDUFzdGVyb2lkIEI2MTIxGjALBgNVBBETBEI2MTIwCwYDVQQR
EwRTdGFyMRAwDgYDVQQKEwdCNjEyLk1FMRMwEQYDVQQLEwpDQS5CNjEyLk1FMRsw
GQYDVQQDExJCNjEyIFRvb2xzIFJvb3QgQ0ExGDAWBgNVBAUTD0I2MTJUT09MU1JP
T1RDQTAgFw0wMDAxMDEwODAwMDBaGA8yMDc3MTIzMTIzNTk1OVowaTELMAkGA1UE
BhMCQ04xFjAUBgNVBAcTDUFzdGVyb2lkIEI2MTIxEDAOBgNVBAoTB0I2MTIuTUUx
EzARBgNVBAsTCkNBLkI2MTIuTUUxGzAZBgNVBAMTEkI2MTIgSW50ZXIgVG9vbCBD
QTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANZp4NnlAmIGrXaP3/6R
LEER7XrO6NyrXyKzqjg01aSw/XOvwbiwYb6UKZonhEJyFzGxHWopzUdTESZTD1WQ
3RGtoXzjOF8xzPnB+kwnFy3nrboHrjyYy3MIkgeWzG9FdJlxTj90dwPypwIIKlhV
KxVijilwaCqesg+K6/E9zsq3a2vVx+Od+Ho3CN28SxWgeletNsaHmwHV+OjSkVOK
+Ck43xCl06cMMrx/sBD2FJ+DGQc994Fzy2US69jp2+PkCZEv+VCYRBKYG7C0OOQq
C3utF/lnKfC66Nq4Ql/HFa3DXko+drb2gnbXuuvKDQGd4cGkteqnOxRfU68ahGiJ
0iZ76M1xm52yTDInXMTZRiwMtVT8JxYlLSwnTu3y3jhcaZMrwl6kGmEfOfEKpq86
F5dQ5umT9REK9er1HxxvEg2w7TsgHbXI61BrcfdvTaK+gzSX2XldUbPfMv6W77Z+
BSc9rLEROh3FjPAXXC7bvcCJblRhC5zIKNdY3THUTsQNGc7tma2bQgODhYiZ6Trq
x1rdDluoiEEmnyjUkJ1DuZn9ODQJJZJkyoNbQ5Zy+nMyM+MaG+05zLdOLLpC8GCK
Xn9ldp2wMybGWtm4YOZm2YPWKBj2pMyrGhwkJHDKTWCDdiLz9mur4iZmpsfI1yvV
EZH0/cVNUYrKnCR++Xn811CXAgMBAAGjgYwwgYkwDgYDVR0PAQH/BAQDAgGuMCMG
A1UdJQQcMBoGBFUdJQAGCCsGAQUFBwMBBggrBgEFBQcDAjASBgNVHRMBAf8ECDAG
AQH/AgEAMB0GA1UdDgQWBBTMkml2uqysQSs4QoZH0AoANqIZPzAfBgNVHSMEGDAW
gBSpZ9jSMtZoS9+yELlalGFs53t9ETANBgkqhkiG9w0BAQsFAAOCAgEAoKVTl1cR
45raGhdNhujTrHJ7n8kEi38tJ3Ppo1IeomDDFS6NbHXloUnhhFuTNy+TtySCy94L
4xr13vfyL51Yqf1lgyQKHX0vZskWGp8XlnIiMRsqDR40pp2cDPwFPUM6V7ttLvq4
DwEUnOjTJkq3fmgakS5eBFiH6aHENhO0RbV9KaUEgio2IYGoJMR5MshdAiGYJ2zF
wF5pUJxm9ZwDPv/p+ah/GujKCRFOVXWy/UkYywG+eClwwRy4iJiv9ohTVB0j5su0
jpYvXsDbPRBqcY8lW/jtOLT1AzDVR+asY7ZBOcJc/WZqR1lYAiAldYIMw1bxB+IN
eEjAFEk9SMF9MDTq8GZJFSxhcBa93vOy+g5GkHC1RtNYpi5WFQWXm32vLeIPX+1L
4ZgAtCTwVXxvC84BDa7hTnK7bKF4gwnl1ii23/ouU1UO/ko1i+j3Q29ot0k+DUZ2
BD2F2TAHjkDh3fbgQx8riy2fUnqo22+S7eaACIT84dmS2SqQ/2m/rGuymT3d4ClI
2F9aLDZEQelpjUX82QSizNJFESFPTf37yxsvcrMcCKpr1sApShfixWBQ9bnAJyO7
Lw8AiI/h+jf1UBg/+Qlws4GIFRCC3zEo2Cu33CYTA7UDXDEOu0gzuDkNS9/lWAL3
EdAtgNNfLagVhHl7yF8TGiLUPgszinPBDd0=
-----END CERTIFICATE-----`
privPem := `-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----`
block, _ := pem.Decode([]byte(certPem))
if block == nil || block.Type != "CERTIFICATE" {
return nil, nil
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, nil
}
priv, err := starcrypto.DecodePrivateKey([]byte(privPem), "")
if err != nil {
fmt.Println(err)
return nil, nil
}
return cert, priv
}

142
utils/cert_test.go Normal file
View File

@ -0,0 +1,142 @@
package utils
import (
"b612.me/starcrypto"
"crypto"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/hex"
"encoding/pem"
"errors"
"fmt"
"math/big"
"os"
"testing"
"time"
)
func TestGenerateRootCA(t *testing.T) {
hexStr := "B61220050612B612"
data, _ := hex.DecodeString(hexStr)
num := new(big.Int).SetBytes(data)
var rootCsr = &x509.Certificate{
Version: 3,
SerialNumber: num,
Subject: pkix.Name{
Country: []string{"CN"},
Locality: []string{"Asteroid B612"},
Organization: []string{"B612.ME"},
OrganizationalUnit: []string{"CA.B612.ME"},
PostalCode: []string{"B612", "Star"},
CommonName: "B612 Tools Root CA",
SerialNumber: "B612TOOLSROOTCA",
},
NotBefore: time.Date(2000, 01, 01, 00, 00, 00, 00, time.UTC),
NotAfter: time.Date(2100, 01, 01, 00, 00, 00, 00, time.UTC),
BasicConstraintsValid: true,
IsCA: true,
MaxPathLenZero: false,
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageAny,
x509.ExtKeyUsageServerAuth,
x509.ExtKeyUsageClientAuth,
x509.ExtKeyUsageCodeSigning,
x509.ExtKeyUsageEmailProtection,
x509.ExtKeyUsageIPSECEndSystem,
x509.ExtKeyUsageIPSECTunnel,
x509.ExtKeyUsageIPSECUser,
x509.ExtKeyUsageTimeStamping,
x509.ExtKeyUsageOCSPSigning,
x509.ExtKeyUsageMicrosoftServerGatedCrypto,
x509.ExtKeyUsageNetscapeServerGatedCrypto,
x509.ExtKeyUsageMicrosoftCommercialCodeSigning,
x509.ExtKeyUsageMicrosoftKernelCodeSigning,
},
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement | x509.KeyUsageDigitalSignature,
}
key, _, err := starcrypto.GenerateRsaKey(4096)
if err != nil {
t.Fatal(err)
}
cert, err := MakeCert(key, rootCsr, rootCsr, key.Public())
if err != nil {
t.Fatal(err)
}
priv, _ := starcrypto.EncodePrivateKey(key, "")
fmt.Println(os.WriteFile("../bin/b612toolca.key", priv, 0644))
fmt.Println(os.WriteFile("../bin/b612toolca.crt", cert, 0644))
}
func TestGenerateMiddleCA(t *testing.T) {
var interCsr = &x509.Certificate{
Version: 3,
SerialNumber: big.NewInt(time.Now().Unix()),
Subject: pkix.Name{
Country: []string{"CN"},
Locality: []string{"Asteroid B612"},
Organization: []string{"B612.ME"},
OrganizationalUnit: []string{"CA.B612.ME"},
CommonName: "B612 Inter Tool CA",
},
NotBefore: time.Date(2000, 01, 01, 8, 00, 00, 00, time.UTC),
NotAfter: time.Date(2077, 12, 31, 23, 59, 59, 00, time.UTC),
BasicConstraintsValid: true,
IsCA: true,
MaxPathLen: 0,
MaxPathLenZero: true,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageAny, x509.ExtKeyUsageServerAuth,
x509.ExtKeyUsageClientAuth},
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement | x509.KeyUsageDigitalSignature,
}
rsa, _, err := starcrypto.GenerateRsaKey(4096)
if err != nil {
t.Fatal(err)
}
caKey, caCrt, err := LoadB612CA()
if err != nil {
t.Fatal(err)
}
cert, err := MakeCert(caKey, caCrt, interCsr, rsa.Public())
if err != nil {
t.Fatal(err)
}
priv, _ := starcrypto.EncodePrivateKey(rsa, "")
os.WriteFile("../bin/toolinter.key", priv, 0644)
os.WriteFile("../bin/toolinter.crt", cert, 0644)
}
func MakeCert(caKey any, caCrt *x509.Certificate, csr *x509.Certificate, pub any) ([]byte, error) {
der, err := x509.CreateCertificate(rand.Reader, csr, caCrt, pub, caKey)
if err != nil {
return nil, err
}
cert, err := x509.ParseCertificate(der)
if err != nil {
return nil, err
}
certBlock := &pem.Block{
Type: "CERTIFICATE",
Bytes: cert.Raw,
}
pemData := pem.EncodeToMemory(certBlock)
return pemData, nil
}
func LoadB612CA() (crypto.PrivateKey, *x509.Certificate, error) {
caRootK, _ := os.ReadFile("../bin/b612toolca.key")
caRootC, _ := os.ReadFile("../bin/b612toolca.crt")
caKey, err := starcrypto.DecodePrivateKey(caRootK, "")
if err != nil {
return nil, nil, err
}
block, _ := pem.Decode(caRootC)
if block == nil || block.Type != "CERTIFICATE" {
return nil, nil, errors.New("Failed to decode PEM block containing the certificate")
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, nil, err
}
return caKey, cert, nil
}

View File

@ -1,3 +1,3 @@
package version
var Version = "2.1.0.beta.18"
var Version = "2.1.0.beta.19"