diff --git a/build.bat b/build.bat index b856d99..9a6f763 100644 --- a/build.bat +++ b/build.bat @@ -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 diff --git a/cert/cmd.go b/cert/cmd.go index 84268d3..22b9375 100644 --- a/cert/cmd.go +++ b/cert/cmd.go @@ -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, "证书使用类型,默认数字0,0表示数字签名和密钥加密,1表示证书签名,2表示CRL签名,4表示密钥协商,8表示数据加密") + CmdGen.Flags().IntSliceVarP(&extKeyUsage, "extKeyUsage", "e", nil, "扩展证书使用类型,默认数字0,0表示服务器认证,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, "证书使用类型,默认数字0,0表示数字签名和密钥加密,1表示证书签名,2表示CRL签名,4表示密钥协商,8表示数据加密") + CmdFastGen.Flags().IntSliceVarP(&extKeyUsage, "extKeyUsage", "e", nil, "扩展证书使用类型,默认数字0,0表示服务器认证,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{ diff --git a/filedate/cmd.go b/filedate/cmd.go new file mode 100644 index 0000000..c9323cc --- /dev/null +++ b/filedate/cmd.go @@ -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 + }) +} diff --git a/filedate/filedate_darwin.go b/filedate/filedate_darwin.go new file mode 100644 index 0000000..9973fd4 --- /dev/null +++ b/filedate/filedate_darwin.go @@ -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 +} diff --git a/filedate/filedate_other.go b/filedate/filedate_other.go new file mode 100644 index 0000000..9c31414 --- /dev/null +++ b/filedate/filedate_other.go @@ -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 +} diff --git a/filedate/filedate_windows.go b/filedate/filedate_windows.go new file mode 100644 index 0000000..fb62f4e --- /dev/null +++ b/filedate/filedate_windows.go @@ -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 +} diff --git a/ftp/filedriver.go b/ftp/filedriver.go new file mode 100644 index 0000000..e3b62ff --- /dev/null +++ b/ftp/filedriver.go @@ -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 +} diff --git a/ftp/ftp.go b/ftp/ftp.go index fcd78a5..2c85f43 100644 --- a/ftp/ftp.go +++ b/ftp/ftp.go @@ -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密钥文件") } diff --git a/go.mod b/go.mod index e5e6a5a..f569c08 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index 487e66d..e21a36d 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/httpreverse/cfg.ini b/httpreverse/cfg.ini index 9052712..55a86f3 100644 --- a/httpreverse/cfg.ini +++ b/httpreverse/cfg.ini @@ -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 diff --git a/httpreverse/reverse.go b/httpreverse/reverse.go index 1a79462..e7e0cec 100644 --- a/httpreverse/reverse.go +++ b/httpreverse/reverse.go @@ -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) diff --git a/httpreverse/service.go b/httpreverse/service.go index 6cd395c..d65a3ce 100644 --- a/httpreverse/service.go +++ b/httpreverse/service.go @@ -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() diff --git a/httpserver/cmd.go b/httpserver/cmd.go index 354b748..3a88028 100644 --- a/httpserver/cmd.go +++ b/httpserver/cmd.go @@ -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", "", "背景图片地址") diff --git a/httpserver/mime.go b/httpserver/mime.go index bb18de0..45ede76 100644 --- a/httpserver/mime.go +++ b/httpserver/mime.go @@ -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 "" diff --git a/httpserver/server.go b/httpserver/server.go index 4800816..352d963 100644 --- a/httpserver/server.go +++ b/httpserver/server.go @@ -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) } diff --git a/image/bild/adjust.go b/image/bild/adjust.go new file mode 100644 index 0000000..9a79e64 --- /dev/null +++ b/image/bild/adjust.go @@ -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 +} diff --git a/image/bild/blend.go b/image/bild/blend.go new file mode 100644 index 0000000..8c9de86 --- /dev/null +++ b/image/bild/blend.go @@ -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 +} diff --git a/image/bild/blur.go b/image/bild/blur.go new file mode 100644 index 0000000..2868ca7 --- /dev/null +++ b/image/bild/blur.go @@ -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 +} diff --git a/image/bild/channel.go b/image/bild/channel.go new file mode 100644 index 0000000..db1cbe5 --- /dev/null +++ b/image/bild/channel.go @@ -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 +} diff --git a/image/bild/effect.go b/image/bild/effect.go new file mode 100644 index 0000000..b481bed --- /dev/null +++ b/image/bild/effect.go @@ -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 +} diff --git a/image/bild/helpers.go b/image/bild/helpers.go new file mode 100644 index 0000000..5257fc5 --- /dev/null +++ b/image/bild/helpers.go @@ -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 +} diff --git a/image/bild/histogram.go b/image/bild/histogram.go new file mode 100644 index 0000000..6ca4e9a --- /dev/null +++ b/image/bild/histogram.go @@ -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 +} diff --git a/image/bild/imgio.go b/image/bild/imgio.go new file mode 100644 index 0000000..5c0fa4a --- /dev/null +++ b/image/bild/imgio.go @@ -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 +} diff --git a/image/bild/noise.go b/image/bild/noise.go new file mode 100644 index 0000000..07c40dc --- /dev/null +++ b/image/bild/noise.go @@ -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 +} diff --git a/image/bild/root.go b/image/bild/root.go new file mode 100644 index 0000000..486ca16 --- /dev/null +++ b/image/bild/root.go @@ -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) +} diff --git a/image/bild/segment.go b/image/bild/segment.go new file mode 100644 index 0000000..32e7fa4 --- /dev/null +++ b/image/bild/segment.go @@ -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 +} diff --git a/image/exif.go b/image/exif.go new file mode 100644 index 0000000..b8ed06f --- /dev/null +++ b/image/exif.go @@ -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 +} diff --git a/image/image.go b/image/image.go index cc20399..70bdafa 100644 --- a/image/image.go +++ b/image/image.go @@ -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("任务完成!") + }, +} diff --git a/main.go b/main.go index 1ef956a..1823289 100644 --- a/main.go +++ b/main.go @@ -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() { diff --git a/mget/cmd.go b/mget/cmd.go index c9e1f07..5387c87 100644 --- a/mget/cmd.go +++ b/mget/cmd.go @@ -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) diff --git a/mget/util.go b/mget/util.go index 7507968..29436e9 100644 --- a/mget/util.go +++ b/mget/util.go @@ -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 { diff --git a/mget/wget.go b/mget/wget.go index 3864faf..0c6c9a3 100644 --- a/mget/wget.go +++ b/mget/wget.go @@ -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 } diff --git a/mime/mime.go b/mime/mime.go new file mode 100644 index 0000000..0e8e56a --- /dev/null +++ b/mime/mime.go @@ -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) + } + } +} diff --git a/smtpserver/smtp.go b/smtpserver/smtp.go index 257044f..ed84577 100644 --- a/smtpserver/smtp.go +++ b/smtpserver/smtp.go @@ -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 diff --git a/utils/cert.go b/utils/cert.go new file mode 100644 index 0000000..89aba9a --- /dev/null +++ b/utils/cert.go @@ -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----- +MIIJKQIBAAKCAgEA1mng2eUCYgatdo/f/pEsQRHtes7o3KtfIrOqODTVpLD9c6/B +uLBhvpQpmieEQnIXMbEdainNR1MRJlMPVZDdEa2hfOM4XzHM+cH6TCcXLeetugeu +PJjLcwiSB5bMb0V0mXFOP3R3A/KnAggqWFUrFWKOKXBoKp6yD4rr8T3Oyrdra9XH +4534ejcI3bxLFaB6V602xoebAdX46NKRU4r4KTjfEKXTpwwyvH+wEPYUn4MZBz33 +gXPLZRLr2Onb4+QJkS/5UJhEEpgbsLQ45CoLe60X+Wcp8Lro2rhCX8cVrcNeSj52 +tvaCdte668oNAZ3hwaS16qc7FF9TrxqEaInSJnvozXGbnbJMMidcxNlGLAy1VPwn +FiUtLCdO7fLeOFxpkyvCXqQaYR858QqmrzoXl1Dm6ZP1EQr16vUfHG8SDbDtOyAd +tcjrUGtx929Nor6DNJfZeV1Rs98y/pbvtn4FJz2ssRE6HcWM8BdcLtu9wIluVGEL +nMgo11jdMdROxA0Zzu2ZrZtCA4OFiJnpOurHWt0OW6iIQSafKNSQnUO5mf04NAkl +kmTKg1tDlnL6czIz4xob7TnMt04sukLwYIpef2V2nbAzJsZa2bhg5mbZg9YoGPak +zKsaHCQkcMpNYIN2IvP2a6viJmamx8jXK9URkfT9xU1RisqcJH75efzXUJcCAwEA +AQKCAgAv50PXKrUXlYpXvNk8lM6gUxMNpwCbnKYKqL1VBWjd/LUDjbboPm/4Fj4d +NMr11WudLIb32xMD5mkkeNYqNc1OT86Oim1jx0qWWmJDdVBWbzZ/I4wn+bMqpjWK +AIT4LzpXtdrXjnuwpBvv9kcPqSeRBY3hcD21l/MMSetg4IA9BjG3y/F2xypmk7kl +YRYdZNcwk4BzZoSZKmcMDU9urNO40f30SDg7UBxdxOFfSLtez/ldhtivUWGV2V5b +/hOknKQOjftTqFE9HkLBfeJgB4y9OvTqQcQ7BmdTxmr93wrW4ZlFWSNIyVZomtYm +K+QwAkgX7Wa/YFFwwcN+kZwXhwMuMTvvjpCwstq9ex5U8xuASqz9+wp1Tuj7n6cQ +IirecQYy22TDdfFSZ3wUwlY+y4U9bx/Z7QfymhOlEPTzig/OlUrC1CqJqrLMmGna +108mXkFkbeQypbzXG0cTgrjULX4WrhETqiLi+AjQj7Gr2MSyYZffw4fzjUWECWUZ +EDYX2lza6S0deaTPG0Zbz17V5V39M4LEucHhGQZpP1+MNTI4XxH3/eVKH1XO3uqg +I5dUGJmQDxPJQCfjIuPOaGqued6qF3kZDBwbV7hDs1StONDCILdcG/elpBdKmoxH +SlZ1/hR2xr8BMe8QE2og/IHbJx5SiPCU7f83V3q7YdMLcw9GIQKCAQEA7Bn3CV7g +2QL5ehOnKEVPdWekY11ZUtHjamQVeZRgz1z6XYEJyxeo5ZG+aUp8WEoUWH6IWuzN +rXAhQkT2ReNZu0JofRGVuaZVj9RnrtxQo/+LzYeFfCK2fSfkevFsrpMQyEMXqi8+ +WVUKCFLYbDERmKjGoPVNFUYSUtaomuSzUrie+B2Kmy5EdfUI1nix5t48sPshaqb2 +d1DWAPJ1Xq2OR5vwtndgYnGwaPNpAtaQbLtk2woyGqxIaD1KCaN/91e/nw34NVNu +CbvCwXm4RPyv/VgZySs/4xLzuGXrWH5vyelP7fz5CQpY9GCqlM0F4pch419j9vjo +iQJaYI44aQs2DwKCAQEA6Hv8GAQ+ECkUfNLIjew0xuhFfVSfvU0CBZGTh9FMB+pL +o37/99+kCN5J8lQIDOrnJaI7uMWw0H4lGB5uxYAY0qQSZvLYqyXw1fpPNzGdZl0l +0CYX7sKTFBjCumRYCWH/3d2pkknCu6JdkfPFPcfdKbrdYfVjOXVm8aeFLhl2lOG1 +AuCNmqpOEfrRR0Hdq0TdGwvYW3y+e4/iMuQ0ML69QPyLx2GCq1Al+42z2dKOUzyL +8U2pAlz5mi2olpNbXzgN9fqyvyVjI28WwwEL+j6L23GshnjtZvnJuwk3LPX+Y0a7 +0YbBDX2Vzdnql/aSeHMQy1BMl1lYQIP0Wa6d9fCE+QKCAQEAuJ/VPc6jpQ9eZsfX +fvY0HGrfcZQdtVXLr/ZzlI8i5QSgA15UeiwWNu6xJ0TH14KWRl0r424pp+Z3G4sx +yZTvJi/X5XVKz9HyNnayXVqK9LNwb1f6WggLC/OWB02i3yDBjthoOPyYlOKa5cdi +1bfJOsdAC73GeUxCJ+UUE4ujbpxQM1VmfdLAVj02m//lndNLQloe13eYY9Uig7sV +bOPqzrRylzzichjVCjzNcRq39U7UnzRp2dG/DURgEQl5l8FWZtpVrd1/vrzEnua/ +4bJ3LHUoNNdNLhQz4Y5RavH0GMAJcODRHPCqfu7YdWOdpoLoTRTa5tXdgMYGRlrw +YbQSoQKCAQEA1HWaKIiX/0TLiFdJGQooIS7bcnIHmYPquRQU8yX+ia1AeqXxXqFu +0vvyMBdDVCrIGshsM6vWrnLZi3UkXjF2fembN6HvCFmgAqzB//rDkWzGxbZKYNRI +fTEzpAtXuRtqLWQJN7tYzwjO4jcYpiEkqKIw9vi+OSBld6pUN5DloaGzPnHgdtv0 +hNHmt2wmHALO3YyxqMoTefBAE6ohV/q4Ec+6Hfeq5sxUKdOR7RpTHxZR/a+vKI23 +PYNEcncwJZCgkY8OE0kjlJpM/uDSBVtrjJwRwsJ4kobsKJV/awNT+34E3rJ7csy2 +Pm1Lypx3tsPRMTytAhOQZ0Uv5VWC3eN0YQKCAQAge52QXJe1xYFudWQ0zwWqSpz+ +HUD8MStNFgsPiJ47ufa/wJOHJaIh0qw9iEWsreEjt0jEBwI3NdzJqtElH9Itp8YG +F6K1/AtM2roEuck7KiGu7ouDaA4kSupG60ZK2wn2tmmOqcYYbDtiCcDjhzXQIadQ +CxCyahQ0Pg9JDM2TLeTgnvn5pCjke+0tQfMYJKs4tQ2yO4NC4+6dhXAzObsrgxpd +yw0Ybep9fUUvuhwWJkMWnvSV8FC+pr/uhcYS1zZcFY82vkFZwMJKFPJ2aghgER+/ +HWarSznekvG0leX8t4XefN9WV+Gj9G/isKgF7bYaI3/S3wK5IgQ+AYPx8TSb +-----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 +} diff --git a/utils/cert_test.go b/utils/cert_test.go new file mode 100644 index 0000000..20bf625 --- /dev/null +++ b/utils/cert_test.go @@ -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 +} diff --git a/version/version.go b/version/version.go index d54f645..ae7b39f 100644 --- a/version/version.go +++ b/version/version.go @@ -1,3 +1,3 @@ package version -var Version = "2.1.0.beta.18" +var Version = "2.1.0.beta.19"