Compare commits

...

22 Commits

@ -10,7 +10,7 @@ import (
"b612.me/staros"
)
func EncodeStr(str string, key []byte) string {
func EncodeStr(str string, key []byte) (string, error) {
if len(key) < 16 {
key = starcrypto.Md5(key)
} else {
@ -19,19 +19,25 @@ func EncodeStr(str string, key []byte) string {
key = key[:32]
}
}
ensdata := starcrypto.AesEncryptCFBNoBlock([]byte(str), key)
return starcrypto.Base91EncodeToString(ensdata)
ensdata, err := starcrypto.CustomEncryptAesCFB([]byte(str), key)
if err != nil {
return "", err
}
return starcrypto.Base64Encode(ensdata), nil
}
func DecodeStr(str string, key []byte) string {
func DecodeStr(str string, key []byte) (string, error) {
if len(key) < 16 {
key = starcrypto.Md5(key)
} else {
key = key[:len(key)/16*16]
}
strtmp := starcrypto.Base91DecodeString(str)
str = string(strtmp)
return string(starcrypto.AesDecryptCFBNoBlock([]byte(str), key))
strtmp, err := starcrypto.Base64Decode(str)
if err != nil {
return "", err
}
p, err := starcrypto.CustomDecryptAesCFB(strtmp, key)
return string(p), err
}
func EncodeFile(fpath, out string, key []byte) error {
@ -64,8 +70,13 @@ func EncodeFile(fpath, out string, key []byte) error {
}
fmt.Printf("已完成:%.2f%%\r", float64(sumAll)/float64(stat.Size())*100)
sumAll += int64(n)
encodeBytes := starcrypto.AesEncryptCFBNoBlock(buf[:n], key)
fpdst.Write(encodeBytes)
if n > 0 {
encodeBytes, err := starcrypto.CustomEncryptAesCFBNoBlock(buf[:n], key, key)
if err != nil {
return err
}
fpdst.Write(encodeBytes)
}
if err == io.EOF {
fmt.Print("已完成100% \n")
break
@ -104,8 +115,13 @@ func DecodeFile(fpath, out string, key []byte) error {
}
fmt.Printf("已完成:%.2f%%\r", float64(sumAll)/float64(stat.Size())*100)
sumAll += int64(n)
encodeBytes := starcrypto.AesDecryptCFBNoBlock(buf[:n], key)
fpdst.Write(encodeBytes)
if n > 0 {
encodeBytes, err := starcrypto.CustomDecryptAesCFBNoBlock(buf[:n], key, key)
if err != nil {
return err
}
fpdst.Write(encodeBytes)
}
if err == io.EOF {
fmt.Print("已完成100% \n")
break

@ -14,9 +14,9 @@ import (
)
var Cmd = &cobra.Command{
Use: "aes-cfb",
Short: "使用aes-cfb处理文件或字符串",
Long: "使用aes-cfb处理文件或字符串",
Use: "aescfb",
Short: "使用自定义aes-cfb处理文件或字符串",
Long: "使用自定义aes-cfb处理文件或字符串",
Run: func(this *cobra.Command, args []string) {
var err error
ok, _ := this.Flags().GetBool("file")
@ -70,10 +70,18 @@ var Cmd = &cobra.Command{
}
} else {
if !de {
data := EncodeStr(args[0], byteKey)
data, err := EncodeStr(args[0], byteKey)
if err != nil {
starlog.Criticalln(err)
return
}
fmt.Println(data)
} else {
data := DecodeStr(args[0], byteKey)
data, err := DecodeStr(args[0], byteKey)
if err != nil {
starlog.Criticalln(err)
return
}
fmt.Println(string(data))
}
}

@ -1,6 +1,7 @@
mkdir bin
set GOOS=windows
set GOARCH=amd64
set CGO_ENABLED=0
go build -o .\bin\b612_x86_64.exe -ldflags "-w -s" .
upx -9 .\bin\b612_x86_64.exe
set GOARCH=386
@ -15,7 +16,7 @@ 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

@ -0,0 +1,49 @@
package calc
import (
"b612.me/starlog"
"b612.me/staros"
"errors"
"fmt"
"github.com/spf13/cobra"
"strconv"
"strings"
)
var Cmd = &cobra.Command{
Use: "calc",
Short: "计算器",
Long: "简单的计算器",
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
starlog.Errorln("请至少输入一个算式")
return errors.New("no sentense")
}
var res []string
printRes := func() {
for k, v := range res {
fmt.Println(args[k], "=", v)
}
}
for k, v := range args {
for i := k; i > 0; i-- {
v = strings.ReplaceAll(v, "$"+strconv.Itoa(i), res[i-1])
}
v = strings.ReplaceAll(v, "x", "*")
v = strings.ReplaceAll(v, "X", "*")
v = strings.ReplaceAll(v, "×", "*")
v = strings.ReplaceAll(v, "÷", "*")
v = strings.ReplaceAll(v, "", "(")
v = strings.ReplaceAll(v, "", ")")
c, err := staros.Calc(v)
if err != nil {
printRes()
starlog.Errorf("calc %s Error:%v\n", v, err)
return err
}
res = append(res, strconv.FormatFloat(c, 'f', -1, 64))
}
printRes()
return nil
},
}

@ -0,0 +1,52 @@
package cert
import (
"b612.me/starcrypto"
"crypto"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"errors"
"os"
)
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 LoadCA(caKeyPath, caCertPath, KeyPwd string) (crypto.PrivateKey, *x509.Certificate, error) {
caKeyBytes, err := os.ReadFile(caKeyPath)
if err != nil {
return nil, nil, err
}
caCertBytes, err := os.ReadFile(caCertPath)
if err != nil {
return nil, nil, err
}
caKey, err := starcrypto.DecodePrivateKey(caKeyBytes, KeyPwd)
if err != nil {
return nil, nil, err
}
block, _ := pem.Decode(caCertBytes)
if block == nil || (block.Type != "CERTIFICATE" && block.Type != "CERTIFICATE REQUEST") {
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
}

@ -0,0 +1,844 @@
package cert
import (
"b612.me/starcrypto"
"b612.me/starlog"
"crypto/dsa"
"crypto/ecdh"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"os"
"software.sslmate.com/src/go-pkcs12"
"strings"
)
func ParseCert(data []byte, pwd string) {
{
pems, err := pkcs12.ToPEM(data, pwd)
if err == nil {
for _, v := range pems {
switch v.Type {
case "CERTIFICATE":
cert, err := x509.ParseCertificate(v.Bytes)
if err != nil {
continue
}
starlog.Green("这是一个PKCS12文件\n")
starlog.Green("-----证书信息-----\n\n")
starlog.Green("证书版本:%d\n", cert.Version)
starlog.Green("证书序列号:%d\n", cert.SerialNumber)
starlog.Green("证书签发者:%s\n", cert.Issuer)
starlog.Green("证书开始时间:%s\n", cert.NotBefore)
starlog.Green("证书结束时间:%s\n", cert.NotAfter)
starlog.Green("证书扩展:%+v\n", cert.Extensions)
starlog.Green("证书签名算法:%s\n", cert.SignatureAlgorithm)
starlog.Green("证书签名:%x\n", cert.Signature)
starlog.Green("证书公钥:%+v\n", cert.PublicKey)
starlog.Green("证书公钥算法:%s\n", cert.PublicKeyAlgorithm)
starlog.Green("证书密钥用法:%v\n", keyUsageToStrings(cert.KeyUsage))
starlog.Green("证书扩展密钥用法:%v\n", extKeyUsageToStrings(cert.ExtKeyUsage))
starlog.Green("证书是否CA%t\n", cert.IsCA)
starlog.Green("证书最大路径长度:%d\n", cert.MaxPathLen)
starlog.Green("证书最大路径长度是否为0%t\n", cert.MaxPathLenZero)
starlog.Green("证书是否根证书:%t\n", cert.BasicConstraintsValid)
starlog.Green("证书国家:%+v\n", cert.Subject.Country)
starlog.Green("证书省份:%+v\n", cert.Subject.Province)
starlog.Green("证书城市:%+v\n", cert.Subject.Locality)
starlog.Green("证书组织:%+v\n", cert.Subject.Organization)
starlog.Green("证书组织单位:%+v\n", cert.Subject.OrganizationalUnit)
starlog.Green("证书通用名称:%s\n", cert.Subject.CommonName)
starlog.Green("证书DNS%+v\n", cert.DNSNames)
starlog.Green("证书主题:%s\n", cert.Subject.String())
starlog.Green("-----公钥信息-----\n\n")
pub := cert.PublicKey
switch n := pub.(type) {
case *rsa.PublicKey:
starlog.Green("公钥算法为RSA\n")
starlog.Green("公钥位数:%d\n", n.Size())
starlog.Green("公钥长度:%d\n", n.N.BitLen())
starlog.Green("公钥指数:%d\n", n.E)
case *ecdsa.PublicKey:
starlog.Green("公钥算法为ECDSA\n")
starlog.Green("公钥位数:%d\n", n.Curve.Params().BitSize)
starlog.Green("公钥曲线:%s\n", n.Curve.Params().Name)
starlog.Green("公钥长度:%d\n", n.Params().BitSize)
starlog.Green("公钥公钥X%d\n", n.X)
starlog.Green("公钥公钥Y%d\n", n.Y)
case *dsa.PublicKey:
starlog.Green("公钥算法为DSA\n")
starlog.Green("公钥公钥Y%d\n", n.Y)
case *ecdh.PublicKey:
starlog.Green("公钥算法为ECDH\n")
case *ed25519.PublicKey:
starlog.Green("公钥算法为ED25519\n")
default:
starlog.Green("未知公钥类型\n")
}
case "PRIVATE KEY":
priv, err := x509.ParsePKCS8PrivateKey(v.Bytes)
if err != nil {
priv, err = x509.ParsePKCS1PrivateKey(v.Bytes)
if err != nil {
priv, err = x509.ParseECPrivateKey(v.Bytes)
if err != nil {
starlog.Errorf("解析私钥错误:%s\n", err)
continue
} else {
starlog.Green("这是一个ECDSA私钥\n")
}
} else {
starlog.Green("这是一个PKCS1私钥\n")
}
} else {
starlog.Green("这是一个PKCS8私钥\n")
}
starlog.Green("-----私钥信息-----\n\n")
switch n := priv.(type) {
case *rsa.PrivateKey:
starlog.Green("这是一个RSA私钥\n")
starlog.Green("私钥位数:%d\n", n.Size())
starlog.Green("私钥长度:%d\n", n.N.BitLen())
starlog.Green("私钥指数:%d\n", n.E)
starlog.Green("私钥系数:%d\n", n.D)
starlog.Green("私钥质数p%d\n", n.Primes[0])
starlog.Green("私钥质数q%d\n", n.Primes[1])
starlog.Green("私钥系数dP%d\n", n.Precomputed.Dp)
starlog.Green("私钥系数dQ%d\n", n.Precomputed.Dq)
starlog.Green("私钥系数qInv%d\n", n.Precomputed.Qinv)
case *ecdsa.PrivateKey:
starlog.Green("这是一个ECDSA私钥\n")
starlog.Green("私钥位数:%d\n", n.Curve.Params().BitSize)
starlog.Green("私钥曲线:%s\n", n.Curve.Params().Name)
starlog.Green("私钥长度:%d\n", n.Params().BitSize)
starlog.Green("私钥系数:%d\n", n.D)
starlog.Green("私钥公钥X%d\n", n.PublicKey.X)
starlog.Green("私钥公钥Y%d\n", n.PublicKey.Y)
case *dsa.PrivateKey:
starlog.Green("这是一个DSA私钥\n")
starlog.Green("私钥系数:%d\n", n.X)
starlog.Green("私钥公钥Y%d\n", n.Y)
case *ed25519.PrivateKey:
starlog.Green("这是一个ED25519私钥\n")
case *ecdh.PrivateKey:
starlog.Green("这是一个ECDH私钥\n")
default:
starlog.Green("未知私钥类型\n")
}
}
}
return
}
}
idx := 0
for {
idx++
block, rest := pem.Decode(data)
if block == nil {
if idx == 1 {
starlog.Errorf("未知文件类型\n")
}
return
}
fmt.Println("\n--------------------------------\n")
data = rest
rt := 0
switch block.Type {
case "CERTIFICATE":
rt = 1
starlog.Green("这是一个证书文件\n")
fallthrough
case "CERTIFICATE REQUEST":
if rt == 0 {
starlog.Green("这是一个证书请求文件\n")
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
starlog.Errorf("解析证书错误:%s\n", err)
continue
}
starlog.Green("证书版本:%d\n", cert.Version)
starlog.Green("证书序列号:%d\n", cert.SerialNumber)
starlog.Green("证书签发者:%s\n", cert.Issuer)
starlog.Green("证书开始时间:%s\n", cert.NotBefore)
starlog.Green("证书结束时间:%s\n", cert.NotAfter)
starlog.Green("证书扩展:%+v\n", cert.Extensions)
starlog.Green("证书签名算法:%s\n", cert.SignatureAlgorithm)
starlog.Green("证书签名:%x\n", cert.Signature)
starlog.Green("证书公钥:%+v\n", cert.PublicKey)
starlog.Green("证书公钥算法:%s\n", cert.PublicKeyAlgorithm)
starlog.Green("证书密钥用法:%v\n", keyUsageToStrings(cert.KeyUsage))
starlog.Green("证书扩展密钥用法:%v\n", extKeyUsageToStrings(cert.ExtKeyUsage))
starlog.Green("证书是否CA%t\n", cert.IsCA)
starlog.Green("证书最大路径长度:%d\n", cert.MaxPathLen)
starlog.Green("证书最大路径长度是否为0%t\n", cert.MaxPathLenZero)
starlog.Green("证书是否根证书:%t\n", cert.BasicConstraintsValid)
starlog.Green("证书国家:%+v\n", cert.Subject.Country)
starlog.Green("证书省份:%+v\n", cert.Subject.Province)
starlog.Green("证书城市:%+v\n", cert.Subject.Locality)
starlog.Green("证书组织:%+v\n", cert.Subject.Organization)
starlog.Green("证书组织单位:%+v\n", cert.Subject.OrganizationalUnit)
starlog.Green("证书通用名称:%s\n", cert.Subject.CommonName)
starlog.Green("证书DNS%+v\n", cert.DNSNames)
starlog.Green("证书主题:%s\n", cert.Subject.String())
pub := cert.PublicKey
switch n := pub.(type) {
case *rsa.PublicKey:
starlog.Green("公钥算法为RSA\n")
starlog.Green("公钥位数:%d\n", n.Size())
starlog.Green("公钥长度:%d\n", n.N.BitLen())
starlog.Green("公钥指数:%d\n", n.E)
case *ecdsa.PublicKey:
starlog.Green("公钥算法为ECDSA\n")
starlog.Green("公钥位数:%d\n", n.Curve.Params().BitSize)
starlog.Green("公钥曲线:%s\n", n.Curve.Params().Name)
starlog.Green("公钥长度:%d\n", n.Params().BitSize)
starlog.Green("公钥公钥X%d\n", n.X)
starlog.Green("公钥公钥Y%d\n", n.Y)
case *dsa.PublicKey:
starlog.Green("公钥算法为DSA\n")
starlog.Green("公钥公钥Y%d\n", n.Y)
case *ecdh.PublicKey:
starlog.Green("公钥算法为ECDH\n")
case *ed25519.PublicKey:
starlog.Green("公钥算法为ED25519\n")
default:
starlog.Green("未知公钥类型\n")
}
continue
case "PRIVATE KEY":
starlog.Infof("这是一个私钥文件\n")
priv, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
priv, err = x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
priv, err = x509.ParseECPrivateKey(block.Bytes)
if err != nil {
starlog.Errorf("解析私钥错误:%s\n", err)
continue
} else {
starlog.Green("这是一个ECDSA私钥\n")
}
} else {
starlog.Green("这是一个PKCS1私钥\n")
}
} else {
starlog.Green("这是一个PKCS8私钥\n")
}
switch n := priv.(type) {
case *rsa.PrivateKey:
starlog.Green("这是一个RSA私钥\n")
starlog.Green("私钥位数:%d\n", n.Size())
starlog.Green("私钥长度:%d\n", n.N.BitLen())
starlog.Green("私钥指数:%d\n", n.E)
starlog.Green("私钥系数:%d\n", n.D)
starlog.Green("私钥质数p%d\n", n.Primes[0])
starlog.Green("私钥质数q%d\n", n.Primes[1])
starlog.Green("私钥系数dP%d\n", n.Precomputed.Dp)
starlog.Green("私钥系数dQ%d\n", n.Precomputed.Dq)
starlog.Green("私钥系数qInv%d\n", n.Precomputed.Qinv)
case *ecdsa.PrivateKey:
starlog.Green("这是一个ECDSA私钥\n")
starlog.Green("私钥位数:%d\n", n.Curve.Params().BitSize)
starlog.Green("私钥曲线:%s\n", n.Curve.Params().Name)
starlog.Green("私钥长度:%d\n", n.Params().BitSize)
starlog.Green("私钥系数:%d\n", n.D)
starlog.Green("私钥公钥X%d\n", n.PublicKey.X)
starlog.Green("私钥公钥Y%d\n", n.PublicKey.Y)
case *dsa.PrivateKey:
starlog.Green("这是一个DSA私钥\n")
starlog.Green("私钥系数:%d\n", n.X)
starlog.Green("私钥公钥Y%d\n", n.Y)
case *ed25519.PrivateKey:
starlog.Green("这是一个ED25519私钥\n")
case *ecdh.PrivateKey:
starlog.Green("这是一个ECDH私钥\n")
default:
starlog.Green("未知私钥类型\n")
}
continue
case "PUBLIC KEY":
starlog.Infof("这是一个公钥文件\n")
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
pub, err = x509.ParsePKCS1PublicKey(block.Bytes)
starlog.Green("这是一个PKCS1公钥\n")
} else {
starlog.Green("这是一个PKIX公钥\n")
}
switch n := pub.(type) {
case *rsa.PublicKey:
starlog.Green("这是一个RSA公钥\n")
starlog.Green("公钥位数:%d\n", n.Size())
starlog.Green("公钥长度:%d\n", n.N.BitLen())
starlog.Green("公钥指数:%d\n", n.E)
case *ecdsa.PublicKey:
starlog.Green("这是一个ECDSA公钥\n")
starlog.Green("公钥位数:%d\n", n.Curve.Params().BitSize)
starlog.Green("公钥曲线:%s\n", n.Curve.Params().Name)
starlog.Green("公钥长度:%d\n", n.Params().BitSize)
starlog.Green("公钥公钥X%d\n", n.X)
starlog.Green("公钥公钥Y%d\n", n.Y)
case *ecdh.PublicKey:
starlog.Green("这是一个ECDH公钥\n")
case *ed25519.PublicKey:
starlog.Green("这是一个ED25519公钥\n")
case *dsa.PublicKey:
starlog.Green("这是一个DSA公钥\n")
starlog.Green("公钥公钥Y%d\n", n.Y)
default:
starlog.Green("未知公钥类型\n")
}
continue
case "RSA PRIVATE KEY":
n, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
starlog.Errorf("解析私钥错误:%s\n", err)
continue
}
starlog.Infof("这是一个RSA私钥文件\n")
starlog.Green("私钥位数:%d\n", n.Size())
starlog.Green("私钥长度:%d\n", n.N.BitLen())
starlog.Green("私钥指数:%d\n", n.E)
starlog.Green("私钥系数:%d\n", n.D)
starlog.Green("私钥质数p%d\n", n.Primes[0])
starlog.Green("私钥质数q%d\n", n.Primes[1])
starlog.Green("私钥系数dP%d\n", n.Precomputed.Dp)
starlog.Green("私钥系数dQ%d\n", n.Precomputed.Dq)
starlog.Green("私钥系数qInv%d\n", n.Precomputed.Qinv)
case "RSA PUBLIC KEY":
n, err := x509.ParsePKCS1PublicKey(block.Bytes)
if err != nil {
starlog.Errorf("解析公钥错误:%s\n", err)
continue
}
starlog.Green("这是一个RSA公钥\n")
starlog.Green("公钥位数:%d\n", n.Size())
starlog.Green("公钥长度:%d\n", n.N.BitLen())
starlog.Green("公钥指数:%d\n", n.E)
case "EC PRIVATE KEY":
n, err := x509.ParseECPrivateKey(block.Bytes)
if err != nil {
starlog.Errorf("解析私钥错误:%s\n", err)
continue
}
starlog.Green("这是一个ECDSA私钥\n")
starlog.Green("私钥位数:%d\n", n.Curve.Params().BitSize)
starlog.Green("私钥曲线:%s\n", n.Curve.Params().Name)
starlog.Green("私钥长度:%d\n", n.Params().BitSize)
starlog.Green("私钥系数:%d\n", n.D)
starlog.Green("私钥公钥X%d\n", n.PublicKey.X)
starlog.Green("私钥公钥Y%d\n", n.PublicKey.Y)
case "EC PUBLIC KEY":
p, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
starlog.Errorf("解析公钥错误:%s\n", err)
continue
}
n := p.(*ecdsa.PublicKey)
starlog.Green("这是一个ECDSA公钥\n")
starlog.Green("公钥位数:%d\n", n.Curve.Params().BitSize)
starlog.Green("公钥曲线:%s\n", n.Curve.Params().Name)
starlog.Green("公钥长度:%d\n", n.Params().BitSize)
starlog.Green("公钥公钥X%d\n", n.X)
starlog.Green("公钥公钥Y%d\n", n.Y)
default:
starlog.Infof("未知证书文件类型\n")
}
}
}
func keyUsageToStrings(keyUsage x509.KeyUsage) string {
var usages []string
if keyUsage&x509.KeyUsageDigitalSignature != 0 {
usages = append(usages, "Digital Signature")
}
if keyUsage&x509.KeyUsageContentCommitment != 0 {
usages = append(usages, "Content Commitment")
}
if keyUsage&x509.KeyUsageKeyEncipherment != 0 {
usages = append(usages, "Key Encipherment")
}
if keyUsage&x509.KeyUsageDataEncipherment != 0 {
usages = append(usages, "Data Encipherment")
}
if keyUsage&x509.KeyUsageKeyAgreement != 0 {
usages = append(usages, "Key Agreement")
}
if keyUsage&x509.KeyUsageCertSign != 0 {
usages = append(usages, "Certificate Signing")
}
if keyUsage&x509.KeyUsageCRLSign != 0 {
usages = append(usages, "CRL Signing")
}
if keyUsage&x509.KeyUsageEncipherOnly != 0 {
usages = append(usages, "Encipher Only")
}
if keyUsage&x509.KeyUsageDecipherOnly != 0 {
usages = append(usages, "Decipher Only")
}
return strings.Join(usages, ", ")
}
func extKeyUsageToStrings(extKeyUsages []x509.ExtKeyUsage) string {
var usages []string
for _, extKeyUsage := range extKeyUsages {
switch extKeyUsage {
case x509.ExtKeyUsageAny:
usages = append(usages, "Any")
case x509.ExtKeyUsageServerAuth:
usages = append(usages, "Server Authentication")
case x509.ExtKeyUsageClientAuth:
usages = append(usages, "Client Authentication")
case x509.ExtKeyUsageCodeSigning:
usages = append(usages, "Code Signing")
case x509.ExtKeyUsageEmailProtection:
usages = append(usages, "Email Protection")
case x509.ExtKeyUsageIPSECEndSystem:
usages = append(usages, "IPSEC End System")
case x509.ExtKeyUsageIPSECTunnel:
usages = append(usages, "IPSEC Tunnel")
case x509.ExtKeyUsageIPSECUser:
usages = append(usages, "IPSEC User")
case x509.ExtKeyUsageTimeStamping:
usages = append(usages, "Time Stamping")
case x509.ExtKeyUsageOCSPSigning:
usages = append(usages, "OCSP Signing")
case x509.ExtKeyUsageMicrosoftServerGatedCrypto:
usages = append(usages, "Microsoft Server Gated Crypto")
case x509.ExtKeyUsageNetscapeServerGatedCrypto:
usages = append(usages, "Netscape Server Gated Crypto")
default:
usages = append(usages, fmt.Sprintf("Unknown(%d)", extKeyUsage))
}
}
return strings.Join(usages, ", ")
}
func GetCert(data []byte, pwd string) ([]any, []x509.Certificate, error) {
var common []any
var certs []x509.Certificate
{
pems, err := pkcs12.ToPEM(data, pwd)
if err == nil {
for _, v := range pems {
switch v.Type {
case "CERTIFICATE":
cert, err := x509.ParseCertificate(v.Bytes)
if err != nil {
continue
}
starlog.Green("这是一个PKCS12文件\n")
pub := cert.PublicKey
switch pub.(type) {
case *rsa.PublicKey:
starlog.Green("公钥算法为RSA\n")
case *ecdsa.PublicKey:
starlog.Green("公钥算法为ECDSA\n")
case *dsa.PublicKey:
starlog.Green("公钥算法为DSA\n")
case *ecdh.PublicKey:
starlog.Green("公钥算法为ECDH\n")
case *ed25519.PublicKey:
starlog.Green("公钥算法为ED25519\n")
default:
starlog.Green("未知公钥类型\n")
}
common = append(common, pub)
certs = append(certs, *cert)
case "PRIVATE KEY":
priv, err := x509.ParsePKCS8PrivateKey(v.Bytes)
if err != nil {
priv, err = x509.ParsePKCS1PrivateKey(v.Bytes)
if err != nil {
priv, err = x509.ParseECPrivateKey(v.Bytes)
if err != nil {
starlog.Errorf("解析私钥错误:%s\n", err)
continue
} else {
starlog.Green("这是一个ECDSA私钥\n")
}
} else {
starlog.Green("这是一个PKCS1私钥\n")
}
} else {
starlog.Green("这是一个PKCS8私钥\n")
}
starlog.Green("-----私钥信息-----\n\n")
switch n := priv.(type) {
case *rsa.PrivateKey:
starlog.Green("这是一个RSA私钥\n")
starlog.Green("私钥位数:%d\n", n.Size())
case *ecdsa.PrivateKey:
starlog.Green("这是一个ECDSA私钥\n")
starlog.Green("私钥位数:%d\n", n.Curve.Params().BitSize)
case *dsa.PrivateKey:
starlog.Green("这是一个DSA私钥\n")
case *ed25519.PrivateKey:
starlog.Green("这是一个ED25519私钥\n")
case *ecdh.PrivateKey:
starlog.Green("这是一个ECDH私钥\n")
default:
starlog.Green("未知私钥类型\n")
}
common = append(common, priv)
}
}
return common, certs, nil
}
}
idx := 0
for {
idx++
block, rest := pem.Decode(data)
if block == nil {
if idx == 1 {
starlog.Errorf("未知文件类型\n")
return common, certs, errors.New("未知文件类型")
}
return common, certs, nil
}
data = rest
rt := 0
switch block.Type {
case "CERTIFICATE":
rt = 1
starlog.Green("这是一个证书文件\n")
fallthrough
case "CERTIFICATE REQUEST":
if rt == 0 {
starlog.Green("这是一个证书请求文件\n")
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
starlog.Errorf("解析证书错误:%s\n", err)
continue
}
common = append(common, cert.PublicKey)
certs = append(certs, *cert)
pub := cert.PublicKey
switch pub.(type) {
case *rsa.PublicKey:
starlog.Green("公钥算法为RSA\n")
case *ecdsa.PublicKey:
starlog.Green("公钥算法为ECDSA\n")
case *dsa.PublicKey:
starlog.Green("公钥算法为DSA\n")
case *ecdh.PublicKey:
starlog.Green("公钥算法为ECDH\n")
case *ed25519.PublicKey:
starlog.Green("公钥算法为ED25519\n")
default:
starlog.Green("未知公钥类型\n")
}
continue
case "PRIVATE KEY":
starlog.Infof("这是一个私钥文件\n")
priv, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
priv, err = x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
priv, err = x509.ParseECPrivateKey(block.Bytes)
if err != nil {
starlog.Errorf("解析私钥错误:%s\n", err)
continue
} else {
starlog.Green("这是一个ECDSA私钥\n")
}
} else {
starlog.Green("这是一个PKCS1私钥\n")
}
} else {
starlog.Green("这是一个PKCS8私钥\n")
}
switch n := priv.(type) {
case *rsa.PrivateKey:
starlog.Green("这是一个RSA私钥\n")
starlog.Green("私钥位数:%d\n", n.Size())
case *ecdsa.PrivateKey:
starlog.Green("这是一个ECDSA私钥\n")
starlog.Green("私钥位数:%d\n", n.Curve.Params().BitSize)
case *dsa.PrivateKey:
starlog.Green("这是一个DSA私钥\n")
case *ed25519.PrivateKey:
starlog.Green("这是一个ED25519私钥\n")
case *ecdh.PrivateKey:
starlog.Green("这是一个ECDH私钥\n")
default:
starlog.Green("未知私钥类型\n")
}
common = append(common, priv)
continue
case "PUBLIC KEY":
starlog.Infof("这是一个公钥文件\n")
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
pub, err = x509.ParsePKCS1PublicKey(block.Bytes)
starlog.Green("这是一个PKCS1公钥\n")
} else {
starlog.Green("这是一个PKIX公钥\n")
}
common = append(common, pub)
switch n := pub.(type) {
case *rsa.PublicKey:
starlog.Green("这是一个RSA公钥\n")
starlog.Green("公钥位数:%d\n", n.Size())
case *ecdsa.PublicKey:
starlog.Green("这是一个ECDSA公钥\n")
starlog.Green("公钥位数:%d\n", n.Curve.Params().BitSize)
case *dsa.PublicKey:
starlog.Green("这是一个DSA公钥\n")
case *ecdh.PublicKey:
starlog.Green("这是一个ECDH公钥\n")
case *ed25519.PublicKey:
starlog.Green("这是一个ED25519公钥\n")
default:
starlog.Green("未知公钥类型\n")
}
return common, certs, nil
case "RSA PRIVATE KEY":
n, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
starlog.Errorf("解析私钥错误:%s\n", err)
continue
}
starlog.Infof("这是一个RSA私钥文件\n")
starlog.Green("私钥位数:%d\n", n.Size())
starlog.Green("私钥长度:%d\n", n.N.BitLen())
starlog.Green("私钥指数:%d\n", n.E)
starlog.Green("私钥系数:%d\n", n.D)
starlog.Green("私钥质数p%d\n", n.Primes[0])
starlog.Green("私钥质数q%d\n", n.Primes[1])
starlog.Green("私钥系数dP%d\n", n.Precomputed.Dp)
starlog.Green("私钥系数dQ%d\n", n.Precomputed.Dq)
starlog.Green("私钥系数qInv%d\n", n.Precomputed.Qinv)
common = append(common, n)
case "RSA PUBLIC KEY":
n, err := x509.ParsePKCS1PublicKey(block.Bytes)
if err != nil {
starlog.Errorf("解析公钥错误:%s\n", err)
continue
}
starlog.Green("这是一个RSA公钥\n")
starlog.Green("公钥位数:%d\n", n.Size())
starlog.Green("公钥长度:%d\n", n.N.BitLen())
starlog.Green("公钥指数:%d\n", n.E)
common = append(common, n)
case "EC PRIVATE KEY":
n, err := x509.ParseECPrivateKey(block.Bytes)
if err != nil {
starlog.Errorf("解析私钥错误:%s\n", err)
continue
}
starlog.Green("这是一个ECDSA私钥\n")
starlog.Green("私钥位数:%d\n", n.Curve.Params().BitSize)
starlog.Green("私钥曲线:%s\n", n.Curve.Params().Name)
starlog.Green("私钥长度:%d\n", n.Params().BitSize)
starlog.Green("私钥系数:%d\n", n.D)
starlog.Green("私钥公钥X%d\n", n.PublicKey.X)
starlog.Green("私钥公钥Y%d\n", n.PublicKey.Y)
common = append(common, n)
case "EC PUBLIC KEY":
p, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
starlog.Errorf("解析公钥错误:%s\n", err)
continue
}
n := p.(*ecdsa.PublicKey)
starlog.Green("这是一个ECDSA公钥\n")
starlog.Green("公钥位数:%d\n", n.Curve.Params().BitSize)
starlog.Green("公钥曲线:%s\n", n.Curve.Params().Name)
starlog.Green("公钥长度:%d\n", n.Params().BitSize)
starlog.Green("公钥公钥X%d\n", n.X)
starlog.Green("公钥公钥Y%d\n", n.Y)
common = append(common, n)
default:
starlog.Infof("未知证书文件类型\n")
}
}
}
func Pkcs8(data []byte, pwd string, originName string, outpath string) error {
keys, _, err := GetCert(data, pwd)
if err != nil {
return err
}
for _, v := range keys {
if v == nil {
continue
}
switch n := v.(type) {
case *ecdsa.PrivateKey, *rsa.PrivateKey, *dsa.PrivateKey, *ed25519.PrivateKey, *ecdh.PrivateKey:
data, err = x509.MarshalPKCS8PrivateKey(n)
if err != nil {
return err
}
err = os.WriteFile(outpath+"/"+originName+".pkcs8", pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: data}), 0644)
if err != nil {
return err
} else {
starlog.Green("已将私钥保存到%s\n", outpath+"/"+originName+".pkcs8")
}
case *ecdsa.PublicKey, *rsa.PublicKey, *dsa.PublicKey, *ed25519.PublicKey, *ecdh.PublicKey:
data, err = x509.MarshalPKIXPublicKey(n)
if err != nil {
return err
}
err = os.WriteFile(outpath+"/"+originName+".pub.pkcs8", pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: data}), 0644)
if err != nil {
return err
} else {
starlog.Green("已将公钥保存到%s\n", outpath+"/"+originName+".pub.pkcs8")
}
}
}
return nil
}
func Pkcs1(data []byte, pwd string, originName string, outpath string) error {
keys, _, err := GetCert(data, pwd)
if err != nil {
return err
}
for idx, v := range keys {
if v == nil {
continue
}
switch n := v.(type) {
case *rsa.PrivateKey:
data = x509.MarshalPKCS1PrivateKey(n)
if err != nil {
return err
}
err = os.WriteFile(fmt.Sprintf("%s/%s_%v.pkcs1", outpath, originName, idx), pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: data}), 0644)
if err != nil {
return err
} else {
starlog.Green("已将私钥保存到%s\n", fmt.Sprintf("%s/%s_%v.pkcs1", outpath, originName, idx))
}
case *rsa.PublicKey:
data = x509.MarshalPKCS1PublicKey(n)
if err != nil {
return err
}
err = os.WriteFile(fmt.Sprintf("%s/%s_%v.pub.pkcs1", outpath, originName, idx), pem.EncodeToMemory(&pem.Block{Type: "RSA PUBLIC KEY", Bytes: data}), 0644)
if err != nil {
return err
} else {
starlog.Green("已将公钥保存到%s\n", fmt.Sprintf("%s/%s_%v.pub.pkcs1", outpath, originName, idx))
}
}
}
return nil
}
func Pkcs12(keys []any, certs []x509.Certificate, enPwd string, originName string, outpath string) error {
var priv any
for _, v := range keys {
if v == nil {
continue
}
switch n := v.(type) {
case *ecdsa.PrivateKey, *rsa.PrivateKey, *dsa.PrivateKey, *ed25519.PrivateKey, *ecdh.PrivateKey:
priv = n
break
}
}
if priv == nil {
return errors.New("未找到私钥")
}
if len(certs) == 0 {
return errors.New("未找到证书")
}
mainCert := certs[0]
var certChain []*x509.Certificate
for _, v := range certs[1:] {
certChain = append(certChain, &v)
}
pfxData, err := pkcs12.Encode(rand.Reader, priv, &mainCert, certChain, enPwd)
if err != nil {
return err
}
err = os.WriteFile(outpath+"/"+originName+".pfx", pfxData, 0644)
if err != nil {
starlog.Errorf("保存失败:%s\n", err)
return err
}
starlog.Green("已将PKCS12文件保存到%s\n", outpath+"/"+originName+".pfx")
return nil
}
func Tran(data []byte, pwd string, originName string, outpath string) error {
keys, _, err := GetCert(data, pwd)
if err != nil {
return err
}
for idx, v := range keys {
if v == nil {
continue
}
switch n := v.(type) {
case *ecdsa.PrivateKey, *rsa.PrivateKey:
data, err = starcrypto.EncodePrivateKey(n, "")
if err != nil {
return err
}
err = os.WriteFile(fmt.Sprintf("%s/%s_%v.tran.key", outpath, originName, idx), data, 0644)
if err != nil {
return err
} else {
starlog.Green("已将私钥保存到%s\n", fmt.Sprintf("%s/%s_%v.tran.key", outpath, originName, idx))
}
case *ecdsa.PublicKey, *rsa.PublicKey:
data, err = starcrypto.EncodePublicKey(n)
if err != nil {
return err
}
err = os.WriteFile(fmt.Sprintf("%s/%s_%v.tran.pub", outpath, originName, idx), data, 0644)
if err != nil {
return err
} else {
starlog.Green("已将公钥保存到%s\n", fmt.Sprintf("%s/%s_%v.tran.pub", outpath, originName, idx))
}
case *dsa.PrivateKey, *ed25519.PrivateKey, *ecdh.PrivateKey:
data, err = x509.MarshalPKCS8PrivateKey(n)
if err != nil {
return err
}
err = os.WriteFile(outpath+"/"+originName+".tran.key", pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: data}), 0644)
if err != nil {
return err
} else {
starlog.Green("已将私钥保存到%s\n", outpath+"/"+originName+".tran.key")
}
case *dsa.PublicKey, *ed25519.PublicKey, *ecdh.PublicKey:
data, err = x509.MarshalPKIXPublicKey(n)
if err != nil {
return err
}
err = os.WriteFile(outpath+"/"+originName+".tran.pub", pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: data}), 0644)
if err != nil {
return err
} else {
starlog.Green("已将公钥保存到%s\n", outpath+"/"+originName+".tran.pub")
}
}
}
return nil
}

@ -0,0 +1,323 @@
package cert
import (
"b612.me/starcrypto"
"b612.me/stario"
"b612.me/starlog"
"crypto/x509"
"fmt"
"github.com/spf13/cobra"
"os"
"path/filepath"
"time"
)
var country, province, city, org, orgUnit, name string
var dnsName []string
var start, end time.Time
var startStr, endStr string
var savefolder string
var promptMode bool
var isCa bool
var maxPathLenZero bool
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 Cmd = &cobra.Command{
Use: "cert",
Short: "证书生成与解析",
Long: "证书生成与解析",
}
var CmdCsr = &cobra.Command{
Use: "csr",
Short: "生成证书请求",
Long: "生成证书请求",
Run: func(cmd *cobra.Command, args []string) {
var err error
if promptMode {
if country == "" {
country = stario.MessageBox("请输入国家:", "").MustString()
}
if province == "" {
province = stario.MessageBox("请输入省份:", "").MustString()
}
if city == "" {
city = stario.MessageBox("请输入城市:", "").MustString()
}
if org == "" {
org = stario.MessageBox("请输入组织:", "").MustString()
}
if orgUnit == "" {
orgUnit = stario.MessageBox("请输入组织单位:", "").MustString()
}
if name == "" {
name = stario.MessageBox("请输入通用名称:", "").MustString()
}
if dnsName == nil {
dnsName = stario.MessageBox("请输入dns名称用逗号分割", "").MustSliceString(",")
}
if startStr == "" {
startStr = stario.MessageBox("请输入开始时间:", "").MustString()
}
if endStr == "" {
endStr = stario.MessageBox("请输入结束时间:", "").MustString()
}
}
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)
}
csr := outputCsr(GenerateCsr(country, province, city, org, orgUnit, name, dnsName, start, end, isCa, maxPathLenZero, maxPathLen))
err = os.WriteFile(savefolder+"/"+name+".csr", csr, 0644)
if err != nil {
starlog.Errorln("保存csr文件错误", err)
os.Exit(1)
}
starlog.Infoln("保存csr文件成功", savefolder+"/"+name+".csr")
},
}
var CmdGen = &cobra.Command{
Use: "gen",
Short: "生成证书",
Long: "生成证书",
Run: func(cmd *cobra.Command, args []string) {
if caKey == "" {
starlog.Errorln("CA私钥不能为空")
os.Exit(1)
}
if caCert == "" {
starlog.Errorln("CA证书不能为空")
os.Exit(1)
}
if csr == "" {
starlog.Errorln("证书请求不能为空")
os.Exit(1)
}
if pubKey == "" {
starlog.Errorln("证书公钥不能为空")
os.Exit(1)
}
caKeyRaw, caCertRaw, err := LoadCA(caKey, caCert, caKeyPwd)
if err != nil {
starlog.Errorln("加载CA错误", err)
os.Exit(1)
}
csrRaw, err := LoadCsr(csr)
if err != nil {
starlog.Errorln("加载证书请求错误", err)
os.Exit(1)
}
pubKeyByte, err := os.ReadFile(pubKey)
if err != nil {
starlog.Errorln("加载公钥错误", err)
os.Exit(1)
}
pubKeyRaw, err := starcrypto.DecodePublicKey(pubKeyByte)
if err != nil {
starlog.Errorln("解析公钥错误", err)
os.Exit(1)
}
cert, err := MakeCert(caKeyRaw, caCertRaw, csrRaw, pubKeyRaw)
if err != nil {
starlog.Errorln("生成证书错误", err)
os.Exit(1)
}
err = os.WriteFile(savefolder+"/"+csrRaw.Subject.CommonName+".crt", cert, 0644)
if err != nil {
starlog.Errorln("保存证书错误", err)
os.Exit(1)
}
starlog.Infoln("保存证书成功", savefolder+"/"+csrRaw.Subject.CommonName+".crt")
},
}
var CmdParse = &cobra.Command{
Use: "parse",
Short: "解析证书",
Long: "解析证书",
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 {
starlog.Errorln("请输入证书文件")
os.Exit(1)
}
for _, v := range args {
data, err := os.ReadFile(v)
if err != nil {
starlog.Errorln("读取证书错误", err)
continue
}
ParseCert(data, passwd)
fmt.Println("\n-------" + v + "解析完毕---------\n")
}
},
}
func init() {
Cmd.AddCommand(CmdCsr)
CmdCsr.Flags().BoolVarP(&promptMode, "prompt", "P", false, "是否交互模式")
CmdCsr.Flags().StringVarP(&country, "country", "c", "", "国家")
CmdCsr.Flags().StringVarP(&province, "province", "p", "", "省份")
CmdCsr.Flags().StringVarP(&city, "city", "t", "", "城市")
CmdCsr.Flags().StringVarP(&org, "org", "o", "", "组织")
CmdCsr.Flags().StringVarP(&orgUnit, "orgUnit", "u", "", "组织单位")
CmdCsr.Flags().StringVarP(&name, "name", "n", "", "通用名称")
CmdCsr.Flags().StringSliceVarP(&dnsName, "dnsName", "d", nil, "dns名称")
CmdCsr.Flags().StringVarP(&startStr, "start", "S", time.Now().Format(time.RFC3339), "开始时间,格式:2006-01-02T15:04:05Z07:00")
CmdCsr.Flags().StringVarP(&endStr, "end", "E", time.Now().AddDate(1, 0, 0).Format(time.RFC3339), "结束时间,格式:2006-01-02T15:04:05Z07:00")
CmdCsr.Flags().StringVarP(&savefolder, "savefolder", "s", "./", "保存文件夹")
CmdCsr.Flags().BoolVarP(&isCa, "isCa", "A", false, "是否是CA")
CmdCsr.Flags().BoolVarP(&maxPathLenZero, "maxPathLenZero", "z", false, "允许最大路径长度为0")
CmdCsr.Flags().IntVarP(&maxPathLen, "maxPathLen", "m", 0, "最大路径长度")
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私钥密码")
Cmd.AddCommand(CmdGen)
CmdParse.Flags().StringVarP(&passwd, "passwd", "p", "", "pfx解密密码")
Cmd.AddCommand(CmdParse)
CmdPkcs8.Flags().StringVarP(&passwd, "passwd", "p", "", "pfx解密密码")
CmdPkcs8.Flags().StringVarP(&savefolder, "savefolder", "s", ".", "保存文件夹")
Cmd.AddCommand(CmdPkcs8)
CmdPkcs1.Flags().StringVarP(&passwd, "passwd", "p", "", "pfx解密密码")
CmdPkcs1.Flags().StringVarP(&savefolder, "savefolder", "s", ".", "保存文件夹")
Cmd.AddCommand(CmdPkcs1)
CmdPkcs12.Flags().StringVarP(&passwd, "passwd", "p", "", "pfx解密密码")
CmdPkcs12.Flags().StringVarP(&enPasswd, "pfx-passwd", "P", "", "pfx加密密码")
CmdPkcs12.Flags().StringVarP(&savefolder, "savefolder", "s", ".", "保存文件夹")
Cmd.AddCommand(CmdPkcs12)
CmdBasic.Flags().StringVarP(&passwd, "passwd", "p", "", "pfx解密密码")
CmdBasic.Flags().StringVarP(&savefolder, "savefolder", "s", ".", "保存文件夹")
Cmd.AddCommand(CmdBasic)
}
var CmdPkcs8 = &cobra.Command{
Use: "pkcs8",
Short: "pkcs8转换",
Long: "pkcs8转换",
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 {
starlog.Errorln("请输入证书文件")
os.Exit(1)
}
for _, v := range args {
data, err := os.ReadFile(v)
if err != nil {
starlog.Errorln("读取证书错误", err)
continue
}
err = Pkcs8(data, passwd, filepath.Base(v), savefolder)
if err != nil {
starlog.Errorln("pkcs8转换错误", err)
continue
}
fmt.Println("\n-------" + v + "转换完毕---------\n")
}
},
}
var CmdPkcs1 = &cobra.Command{
Use: "pkcs1",
Short: "pkcs1转换",
Long: "pkcs1转换",
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 {
starlog.Errorln("请输入证书文件")
os.Exit(1)
}
for _, v := range args {
data, err := os.ReadFile(v)
if err != nil {
starlog.Errorln("读取证书错误", err)
continue
}
err = Pkcs1(data, passwd, filepath.Base(v), savefolder)
if err != nil {
starlog.Errorln("pkcs1转换错误", err)
continue
}
fmt.Println("\n-------" + v + "转换完毕---------\n")
}
},
}
var CmdPkcs12 = &cobra.Command{
Use: "pkcs12",
Short: "pkcs12转换",
Long: "pkcs12转换",
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 {
starlog.Errorln("请输入证书文件")
os.Exit(1)
}
var keys []any
var certs []x509.Certificate
for _, v := range args {
data, err := os.ReadFile(v)
if err != nil {
starlog.Errorln("读取证书错误", err)
continue
}
key, cert, err := GetCert(data, passwd)
if err != nil {
starlog.Errorln("证书读取错误", err)
os.Exit(1)
}
keys = append(keys, key...)
certs = append(certs, cert...)
}
err := Pkcs12(keys, certs, enPasswd, filepath.Base(args[0]), savefolder)
if err != nil {
starlog.Errorln("pkcs12转换错误", err)
os.Exit(1)
}
fmt.Println("\n-------pfk转换完毕---------\n")
},
}
var CmdBasic = &cobra.Command{
Use: "basic",
Short: "证书转换为基本类型",
Long: "证书转换为基本类型",
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 {
starlog.Errorln("请输入证书文件")
os.Exit(1)
}
for _, v := range args {
data, err := os.ReadFile(v)
if err != nil {
starlog.Errorln("读取证书错误", err)
continue
}
err = Tran(data, passwd, filepath.Base(v), savefolder)
if err != nil {
starlog.Errorln("证书转换错误", err)
continue
}
fmt.Println("\n-------" + v + "转换完毕---------\n")
}
},
}

@ -0,0 +1,83 @@
package cert
import (
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"errors"
"math/big"
"net"
"os"
"time"
)
func GenerateCsr(country, province, city, org, orgUnit, name string, dnsName []string, start, end time.Time, isCa bool, maxPathLenZero bool, maxPathLen int) *x509.Certificate {
var trueDNS []string
var trueIp []net.IP
for _, v := range dnsName {
ip := net.ParseIP(v)
if ip == nil {
trueDNS = append(trueDNS, v)
continue
}
trueIp = append(trueIp, ip)
}
ku := x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment
eku := x509.ExtKeyUsageServerAuth
if isCa {
ku = x509.KeyUsageCertSign | x509.KeyUsageCRLSign | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement | x509.KeyUsageDigitalSignature
eku = x509.ExtKeyUsageAny
}
return &x509.Certificate{
Version: 3,
SerialNumber: big.NewInt(time.Now().Unix()),
Subject: pkix.Name{
Country: s2s(country),
Province: s2s(province),
Locality: s2s(city),
Organization: s2s((org)),
OrganizationalUnit: s2s(orgUnit),
CommonName: name,
},
DNSNames: trueDNS,
IPAddresses: trueIp,
NotBefore: start,
NotAfter: end,
BasicConstraintsValid: true,
IsCA: isCa,
MaxPathLen: maxPathLen,
MaxPathLenZero: maxPathLenZero,
KeyUsage: ku,
ExtKeyUsage: []x509.ExtKeyUsage{eku},
}
}
func outputCsr(csr *x509.Certificate) []byte {
return pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE REQUEST",
Bytes: csr.Raw,
})
}
func s2s(str string) []string {
if len(str) == 0 {
return nil
}
return []string{str}
}
func LoadCsr(csrPath string) (*x509.Certificate, error) {
csrBytes, err := os.ReadFile(csrPath)
if err != nil {
return nil, err
}
block, _ := pem.Decode(csrBytes)
if block == nil || block.Type != "CERTIFICATE REQUEST" {
return nil, errors.New("Failed to decode PEM block containing the certificate")
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, err
}
return cert, nil
}

@ -19,6 +19,10 @@ var Cmd = &cobra.Command{
Short: "分析nfts磁盘文件占用",
Long: "分析nfts磁盘文件占用",
Run: func(cmd *cobra.Command, args []string) {
if !staros.IsRoot() {
starlog.Criticalln("need administator permission to run this command")
os.Exit(1)
}
if len(args) == 0 {
os.Exit(folderSize("./"))
}

@ -29,6 +29,10 @@ var Cmd = &cobra.Command{
Short: "查找nfts磁盘文件",
Long: "查找nfts磁盘文件",
Run: func(cmd *cobra.Command, args []string) {
if !staros.IsRoot() {
starlog.Criticalln("need administator permission to run this command")
os.Exit(1)
}
os.Exit(fileFinder(dfpath, dfreg, dfonlyname, dfoutpath, dfshow))
},
}

@ -0,0 +1,79 @@
package dns
import (
"b612.me/starlog"
"fmt"
"github.com/spf13/cobra"
"os"
"strings"
)
var dnsServer, serverType, queryType string
func init() {
Cmd.Flags().StringVarP(&dnsServer, "server", "s", "", "dns服务器地址")
Cmd.Flags().StringVarP(&serverType, "type", "t", "udp", "dns服务器类型,支持udp,tcp,dot,doh")
Cmd.Flags().StringVarP(&queryType, "query-type", "q", "A", "dns查询类型支持 A,CNAME,AAAA,MX,NS,SOA,SRV,PTR,ANY,CAA,TLSA,DS,DNSKEY,NSEC,NSEC3,NSEC3PARAM,RRSIG,SPF,SSHFP,TKEY,TSIG,URI")
}
var Cmd = &cobra.Command{
Use: "dns",
Aliases: []string{"dig"},
Short: "dns查询服务",
Long: "DNS查询服务支持UDP,TCP,DoT,DoH",
Run: func(cmd *cobra.Command, args []string) {
itype := 0
switch strings.ToLower(serverType) {
case "udp", "":
itype = 0
case "tcp":
itype = 1
case "dot":
itype = 2
case "doh":
itype = 3
default:
starlog.Errorln("unsupport server type:", serverType)
os.Exit(1)
}
localdns := GetDNSServers()
for _, v := range localdns {
fmt.Println("本地DNS:", v.String())
}
if dnsServer == "" {
switch itype {
case 0, 1:
if len(localdns) != 0 {
dnsServer = localdns[0].String()
} else {
dnsServer = "223.5.5.5:53"
}
case 2:
dnsServer = "dns.b612.me:853"
case 3:
dnsServer = "https://dns.b612.me/dns-query"
}
}
if (itype == 0 || itype == 1) && !strings.Contains(dnsServer, ":") {
dnsServer = dnsServer + ":53"
}
if len(args) == 0 {
starlog.Errorln("请输入查询目标")
return
}
queryType = strings.ToUpper(queryType)
fmt.Println("查询DNS:", dnsServer)
fmt.Println("查询类型:", queryType)
for _, target := range args {
data, err := QueryDns(target, queryType, itype, dnsServer)
if err != nil {
starlog.Errorln(err)
os.Exit(2)
}
fmt.Println("\n")
fmt.Println("Query:", target)
fmt.Println(data.Str)
}
},
}

@ -0,0 +1,175 @@
package dns
import (
"encoding/base64"
"errors"
"fmt"
"github.com/miekg/dns"
"io"
"net"
"net/http"
"time"
)
type Result struct {
Res *dns.Msg
Str string
}
type DnsClient interface {
Exchange(req *dns.Msg, address string) (r *dns.Msg, rtt time.Duration, err error)
}
func QueryDns(domain string, queryType string, serverType int, dnsServer string) (Result, error) {
var c DnsClient
c = new(dns.Client)
m := new(dns.Msg)
if dnsServer == "" {
dnsServer = "223.5.5.5:53"
}
switch serverType {
case 1:
c.(*dns.Client).Net = "tcp"
case 2:
c = &dns.Client{
Net: "tcp-tls",
Dialer: &net.Dialer{
Resolver: net.DefaultResolver,
},
}
case 3:
c = NewDoHClient(WithTimeout(10 * time.Second))
}
switch queryType {
case "A":
m.SetQuestion(dns.Fqdn(domain), dns.TypeA)
case "CNAME":
m.SetQuestion(dns.Fqdn(domain), dns.TypeCNAME)
case "MX":
m.SetQuestion(dns.Fqdn(domain), dns.TypeMX)
case "NS":
m.SetQuestion(dns.Fqdn(domain), dns.TypeNS)
case "TXT":
m.SetQuestion(dns.Fqdn(domain), dns.TypeTXT)
case "SOA":
m.SetQuestion(dns.Fqdn(domain), dns.TypeSOA)
case "SRV":
m.SetQuestion(dns.Fqdn(domain), dns.TypeSRV)
case "AAAA":
m.SetQuestion(dns.Fqdn(domain), dns.TypeAAAA)
case "PTR":
m.SetQuestion(dns.Fqdn(domain), dns.TypePTR)
case "ANY":
m.SetQuestion(dns.Fqdn(domain), dns.TypeANY)
case "CAA":
m.SetQuestion(dns.Fqdn(domain), dns.TypeCAA)
case "TLSA":
m.SetQuestion(dns.Fqdn(domain), dns.TypeTLSA)
case "DS":
m.SetQuestion(dns.Fqdn(domain), dns.TypeDS)
case "DNSKEY":
m.SetQuestion(dns.Fqdn(domain), dns.TypeDNSKEY)
case "NSEC":
m.SetQuestion(dns.Fqdn(domain), dns.TypeNSEC)
case "NSEC3":
m.SetQuestion(dns.Fqdn(domain), dns.TypeNSEC3)
case "NSEC3PARAM":
m.SetQuestion(dns.Fqdn(domain), dns.TypeNSEC3PARAM)
case "RRSIG":
m.SetQuestion(dns.Fqdn(domain), dns.TypeRRSIG)
case "SPF":
m.SetQuestion(dns.Fqdn(domain), dns.TypeSPF)
case "SSHFP":
m.SetQuestion(dns.Fqdn(domain), dns.TypeSSHFP)
case "TKEY":
m.SetQuestion(dns.Fqdn(domain), dns.TypeTKEY)
case "TSIG":
m.SetQuestion(dns.Fqdn(domain), dns.TypeTSIG)
case "URI":
m.SetQuestion(dns.Fqdn(domain), dns.TypeURI)
default:
return Result{}, errors.New("not support query type,only support A,CNAME,MX,NS,SOA,SRV,AAAA,PTR,ANY,CAA,TLSA,DS,DNSKEY,NSEC,NSEC3,NSEC3PARAM,RRSIG,SPF,SSHFP,TKEY,TSIG,URI")
}
r, rtt, err := c.Exchange(m, dnsServer)
if err != nil {
return Result{}, err
}
return Result{
Res: r,
Str: r.String() + "\n" + ";; RTT:\n" + fmt.Sprintf("%v milliseconds", rtt.Milliseconds()),
}, nil
}
const DoHMediaType = "application/dns-message"
type clientOptions struct {
Timeout time.Duration // Timeout for one DNS query
}
type ClientOption func(*clientOptions) error
func WithTimeout(t time.Duration) ClientOption {
return func(o *clientOptions) error {
o.Timeout = t
return nil
}
}
type DoHClient struct {
opt *clientOptions
cli *http.Client
}
func NewDoHClient(opts ...ClientOption) *DoHClient {
o := new(clientOptions)
for _, f := range opts {
f(o)
}
return &DoHClient{
opt: o,
cli: &http.Client{
Timeout: o.Timeout,
},
}
}
func (c *DoHClient) Exchange(req *dns.Msg, address string) (r *dns.Msg, rtt time.Duration, err error) {
var (
buf, b64 []byte
begin = time.Now()
origID = req.Id
)
// Set DNS ID as zero accoreding to RFC8484 (cache friendly)
req.Id = 0
buf, err = req.Pack()
b64 = make([]byte, base64.RawURLEncoding.EncodedLen(len(buf)))
if err != nil {
return
}
base64.RawURLEncoding.Encode(b64, buf)
// No need to use hreq.URL.Query()
hreq, _ := http.NewRequest("GET", address+"?dns="+string(b64), nil)
hreq.Header.Add("Accept", DoHMediaType)
resp, err := c.cli.Do(hreq)
if err != nil {
return
}
defer resp.Body.Close()
content, err := io.ReadAll(resp.Body)
if err != nil {
return
}
if resp.StatusCode != http.StatusOK {
err = errors.New("DoH query failed: " + string(content))
return
}
r = new(dns.Msg)
err = r.Unpack(content)
r.Id = origID
rtt = time.Since(begin)
return
}

@ -0,0 +1,15 @@
package dns
import (
"fmt"
"testing"
)
func TestDefaultDns(t *testing.T) {
p, e := QueryDns("google.com", "A", 3, "https://dns.b612.me/dns-query")
if e != nil {
t.Error(e)
}
fmt.Println(p)
}

@ -0,0 +1,51 @@
//go:build !js && !windows
package dns
import (
"net/netip"
"os"
"strings"
)
func GetDNSServers() (nameservers []netip.AddrPort) {
const filename = "/etc/resolv.conf"
return getLocalNameservers(filename)
}
func getLocalNameservers(filename string) (nameservers []netip.AddrPort) {
const defaultNameserverPort = 53
defaultLocalNameservers := []netip.AddrPort{
netip.AddrPortFrom(netip.AddrFrom4([4]byte{127, 0, 0, 1}), defaultNameserverPort),
netip.AddrPortFrom(netip.AddrFrom16([16]byte{0, 0, 0, 0, 0, 0, 0, 1}), defaultNameserverPort),
}
data, err := os.ReadFile(filename)
if err != nil {
return defaultLocalNameservers
}
lines := strings.Split(string(data), "\n")
for _, line := range lines {
if line == "" {
continue
}
fields := strings.Fields(line)
if len(fields) == 0 || fields[0] != "nameserver" {
continue
}
for _, field := range fields[1:] {
ip, err := netip.ParseAddr(field)
if err != nil {
continue
}
nameservers = append(nameservers,
netip.AddrPortFrom(ip, defaultNameserverPort))
}
}
if len(nameservers) == 0 {
return defaultLocalNameservers
}
return nameservers
}

@ -0,0 +1,275 @@
package dns
import (
"errors"
"fmt"
"net/netip"
"syscall"
"unsafe"
)
func GetDNSServers() (nameservers []netip.AddrPort) {
const defaultDNSPort = 53
defaultLocalNameservers := []netip.AddrPort{
netip.AddrPortFrom(netip.AddrFrom4([4]byte{127, 0, 0, 1}), defaultDNSPort),
netip.AddrPortFrom(netip.AddrFrom16([16]byte{0, 0, 0, 0, 0, 0, 0, 1}), defaultDNSPort),
}
adapterAddresses, err := getAdapterAddresses()
if err != nil {
return defaultLocalNameservers
}
for _, adapterAddress := range adapterAddresses {
const statusUp = 0x01
if adapterAddress.operStatus != statusUp {
continue
}
if adapterAddress.firstGatewayAddress == nil {
// Only search DNS servers for adapters having a gateway
continue
}
dnsServerAddress := adapterAddress.firstDnsServerAddress
for dnsServerAddress != nil {
ip, ok := sockAddressToIP(dnsServerAddress.address.rawSockAddrAny)
if !ok || ipIsSiteLocalAnycast(ip) {
// fec0/10 IPv6 addresses are site local anycast DNS
// addresses Microsoft sets by default if no other
// IPv6 DNS address is set. Site local anycast is
// deprecated since 2004, see
// https://datatracker.ietf.org/doc/html/rfc3879
dnsServerAddress = dnsServerAddress.next
continue
}
nameserver := netip.AddrPortFrom(ip, defaultDNSPort)
nameservers = append(nameservers, nameserver)
dnsServerAddress = dnsServerAddress.next
}
}
if len(nameservers) == 0 {
return defaultLocalNameservers
}
return nameservers
}
var (
errBufferOverflowUnexpected = errors.New("unexpected buffer overflowed because buffer was large enough")
)
func getAdapterAddresses() (
adapterAddresses []*ipAdapterAddresses, err error) {
var buffer []byte
const initialBufferLength uint32 = 15000
sizeVar := initialBufferLength
for {
buffer = make([]byte, sizeVar)
err := runProcGetAdaptersAddresses(
(*ipAdapterAddresses)(unsafe.Pointer(&buffer[0])),
&sizeVar)
if err != nil {
if err.(syscall.Errno) == syscall.ERROR_BUFFER_OVERFLOW {
if sizeVar <= uint32(len(buffer)) {
return nil, fmt.Errorf("%w: buffer size variable %d is "+
"equal or lower to the buffer current length %d",
errBufferOverflowUnexpected, sizeVar, len(buffer))
}
continue
}
return nil, fmt.Errorf("getting adapters addresses: %w", err)
}
noDataFound := sizeVar == 0
if noDataFound {
return nil, nil
}
break
}
adapterAddress := (*ipAdapterAddresses)(unsafe.Pointer(&buffer[0]))
for adapterAddress != nil {
adapterAddresses = append(adapterAddresses, adapterAddress)
adapterAddress = adapterAddress.next
}
return adapterAddresses, nil
}
var (
procGetAdaptersAddresses = syscall.NewLazyDLL("iphlpapi.dll").
NewProc("GetAdaptersAddresses")
)
func runProcGetAdaptersAddresses(adapterAddresses *ipAdapterAddresses,
sizePointer *uint32) (errcode error) {
const family = syscall.AF_UNSPEC
const GAA_FLAG_SKIP_UNICAST = 0x0001
const GAA_FLAG_SKIP_ANYCAST = 0x0002
const GAA_FLAG_SKIP_MULTICAST = 0x0004
const GAA_FLAG_SKIP_FRIENDLY_NAME = 0x0020
const GAA_FLAG_INCLUDE_GATEWAYS = 0x0080
const flags = GAA_FLAG_SKIP_UNICAST | GAA_FLAG_SKIP_ANYCAST |
GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_FRIENDLY_NAME |
GAA_FLAG_INCLUDE_GATEWAYS
const reserved = 0
// See https://learn.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getadaptersaddresses
r1, _, err := syscall.SyscallN(procGetAdaptersAddresses.Addr(),
uintptr(family), uintptr(flags), uintptr(reserved),
uintptr(unsafe.Pointer(adapterAddresses)),
uintptr(unsafe.Pointer(sizePointer)))
switch {
case err != 0:
return err
case r1 != 0:
return syscall.Errno(r1)
default:
return nil
}
}
func sockAddressToIP(rawSockAddress *syscall.RawSockaddrAny) (ip netip.Addr, ok bool) {
if rawSockAddress == nil {
return netip.Addr{}, false
}
sockAddress, err := rawSockAddress.Sockaddr()
if err != nil {
return netip.Addr{}, false
}
switch sockAddress := sockAddress.(type) {
case *syscall.SockaddrInet4:
return netip.AddrFrom4([4]byte{
sockAddress.Addr[0], sockAddress.Addr[1], sockAddress.Addr[2], sockAddress.Addr[3]}),
true
case *syscall.SockaddrInet6:
return netip.AddrFrom16([16]byte{
sockAddress.Addr[0], sockAddress.Addr[1], sockAddress.Addr[2], sockAddress.Addr[3],
sockAddress.Addr[4], sockAddress.Addr[5], sockAddress.Addr[6], sockAddress.Addr[7],
sockAddress.Addr[8], sockAddress.Addr[9], sockAddress.Addr[10], sockAddress.Addr[11],
sockAddress.Addr[12], sockAddress.Addr[13], sockAddress.Addr[14], sockAddress.Addr[15]}),
true
default:
return netip.Addr{}, false
}
}
func ipIsSiteLocalAnycast(ip netip.Addr) bool {
if !ip.Is6() {
return false
}
array := ip.As16()
return array[0] == 0xfe && array[1] == 0xc0
}
// See https://learn.microsoft.com/en-us/windows/win32/api/iptypes/ns-iptypes-ip_adapter_addresses_lh
type ipAdapterAddresses struct {
// The order of fields DOES matter since they are read
// raw from a bytes buffer. However, we are only interested
// in a few select fields, so unneeded fields are either
// named as "_" or removed if they are after the fields
// we are interested in.
_ uint32
_ uint32
next *ipAdapterAddresses
_ *byte
_ *ipAdapterUnicastAddress
_ *ipAdapterAnycastAddress
_ *ipAdapterMulticastAddress
firstDnsServerAddress *ipAdapterDnsServerAdapter
_ *uint16
_ *uint16
_ *uint16
_ [syscall.MAX_ADAPTER_ADDRESS_LENGTH]byte
_ uint32
_ uint32
_ uint32
_ uint32
operStatus uint32
_ uint32
_ [16]uint32
_ *ipAdapterPrefix
_ uint64
_ uint64
_ *ipAdapterWinsServerAddress
firstGatewayAddress *ipAdapterGatewayAddress
// Additional fields not needed here
}
type ipAdapterUnicastAddress struct {
// The order of fields DOES matter since they are read raw
// from a bytes buffer. However, we are not interested in
// the value of any field, so they are all named as "_".
_ uint32
_ uint32
_ *ipAdapterUnicastAddress
_ ipAdapterSocketAddress
_ int32
_ int32
_ int32
_ uint32
_ uint32
_ uint32
_ uint8
}
type ipAdapterAnycastAddress struct {
// The order of fields DOES matter since they are read raw
// from a bytes buffer. However, we are not interested in
// the value of any field, so they are all named as "_".
_ uint32
_ uint32
_ *ipAdapterAnycastAddress
_ ipAdapterSocketAddress
}
type ipAdapterMulticastAddress struct {
// The order of fields DOES matter since they are read raw
// from a bytes buffer. However, we are only interested in
// a few select fields, so unneeded fields are named as "_".
_ uint32
_ uint32
_ *ipAdapterMulticastAddress
_ ipAdapterSocketAddress
}
type ipAdapterDnsServerAdapter struct {
// The order of fields DOES matter since they are read raw
// from a bytes buffer. However, we are only interested in
// a few select fields, so unneeded fields are named as "_".
_ uint32
_ uint32
next *ipAdapterDnsServerAdapter
address ipAdapterSocketAddress
}
type ipAdapterPrefix struct {
_ uint32
_ uint32
_ *ipAdapterPrefix
_ ipAdapterSocketAddress
_ uint32
}
type ipAdapterWinsServerAddress struct {
_ uint32
_ uint32
_ *ipAdapterWinsServerAddress
_ ipAdapterSocketAddress
}
type ipAdapterGatewayAddress struct {
_ uint32
_ uint32
_ *ipAdapterGatewayAddress
_ ipAdapterSocketAddress
}
type ipAdapterSocketAddress struct {
rawSockAddrAny *syscall.RawSockaddrAny
}

@ -30,7 +30,6 @@ var Cmd = &cobra.Command{
Hostname: ip,
Auth: &server.SimpleAuth{Name: username, Password: pwd},
}
log.Printf("Starting ftp server on %v:%v", opts.Hostname, opts.Port)
log.Printf("Username %v, Password %v", username, pwd)
server := server.NewServer(opts)

@ -3,34 +3,45 @@ module b612.me/apps/b612
go 1.19
require (
b612.me/notify v1.2.4
b612.me/starcrypto v0.0.2
b612.me/stario v0.0.8
b612.me/starlog v1.3.2
b612.me/staros v1.1.6
b612.me/notify v1.2.5
b612.me/starcrypto v0.0.5
b612.me/stario v0.0.9
b612.me/starlog v1.3.3
b612.me/starnet v0.1.8
b612.me/staros v1.1.7
b612.me/starssh v0.0.2
b612.me/startext v0.0.0-20220314043758-22c6d5e5b1cd
b612.me/wincmd v0.0.2
b612.me/wincmd v0.0.3
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/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/inconshreveable/mousetrap v1.1.0
github.com/likexian/whois v1.15.1
github.com/miekg/dns v1.1.58
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
github.com/spf13/cobra v1.6.1
github.com/spf13/cobra v1.8.0
github.com/things-go/go-socks5 v0.0.5
golang.org/x/crypto v0.21.0
software.sslmate.com/src/go-pkcs12 v0.4.0
)
require (
b612.me/starmap v1.2.3 // indirect
b612.me/starnet v0.1.7 // indirect
b612.me/win32api v0.0.1 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
b612.me/starmap v1.2.4 // indirect
b612.me/win32api v0.0.2 // indirect
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 // indirect
github.com/jlaffaye/ftp v0.1.0 // indirect
github.com/kr/fs v0.1.0 // indirect
github.com/pkg/sftp v1.13.4 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/testify v1.8.0 // indirect
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064 // indirect
golang.org/x/image v0.6.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/term v0.5.0 // indirect
golang.org/x/text v0.8.0 // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/term v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.17.0 // indirect
)

101
go.sum

@ -1,30 +1,37 @@
b612.me/notify v1.2.4 h1:cjP80V9FeM+ib1DztZdykusakcbjNI4dAB1pXE8U6bo=
b612.me/notify v1.2.4/go.mod h1:SlCrG1kPRVhYUrIkwY/j0zAwCU4VeTHubcZoQXW8Anw=
b612.me/starcrypto v0.0.2 h1:aRf1HcqK8GqHYxLAhWfFC4W/EqQLEFNEmxsBu3wG30o=
b612.me/starcrypto v0.0.2/go.mod h1:hz0xRnfWNpYOlVrIPoGrQOWPibq4YiUZ7qN5tsQbzPo=
b612.me/stario v0.0.7/go.mod h1:or4ssWcxQSjMeu+hRKEgtp0X517b3zdlEOAms8Qscvw=
b612.me/stario v0.0.8 h1:kaA4pszAKLZJm2D9JmiuYSpgjTeE3VaO74vm+H0vBGM=
b612.me/stario v0.0.8/go.mod h1:or4ssWcxQSjMeu+hRKEgtp0X517b3zdlEOAms8Qscvw=
b612.me/starlog v1.3.2 h1:bFUJyZEpcOcBwPlzlhPBwlYxq7aDcR8pJNoaDk+SUNE=
b612.me/starlog v1.3.2/go.mod h1:bxSvBSzlJoLfrZJ5b9CJFuQaXjFi8PYUbGWitNO1FYA=
b612.me/starmap v1.2.3 h1:+ao++KgbSGMA4UzcHm/EXJoukbUudk8t5ac7rjwV9KA=
b612.me/starmap v1.2.3/go.mod h1:K+exTSWg8i/taoUyGR6DPW6Ja0k6aIdpcniqByOf4O0=
b612.me/starnet v0.1.7 h1:k3CUfYNRolC/xw5Ekus2NVWHlqeykSyAH8USGTPKA5o=
b612.me/starnet v0.1.7/go.mod h1:DNC4i/ezgVLlmxnquf8AeljsL4mQ5vAyxh8vGPQqsys=
b612.me/staros v1.1.6 h1:m3QaEmPyvPcJVomjWs8cDeauDYFNKv7cLHTiOHClKqM=
b612.me/staros v1.1.6/go.mod h1:O657LC3qag4VSsHNmt5RM8gKJvzoEGq8IF8WegcRgq0=
b612.me/notify v1.2.5 h1:fASpzi8YAo78g6jKnefzfbsQz0nGNYFbClB2Bylj+MA=
b612.me/notify v1.2.5/go.mod h1:GTnAdC6v9krGxtC8Gkn8TcyUsYnHSiHjRAXsONPiLpI=
b612.me/starcrypto v0.0.3/go.mod h1:pF5A16p8r/h1G0x7ZNmmAF6K1sdIMpbCUxn2WGC8gZ0=
b612.me/starcrypto v0.0.5 h1:Aa4pRDO2lBH2Aw+vz8NuUtRb73J8z5aOa9SImBY5sq4=
b612.me/starcrypto v0.0.5/go.mod h1:pF5A16p8r/h1G0x7ZNmmAF6K1sdIMpbCUxn2WGC8gZ0=
b612.me/stario v0.0.9 h1:bFDlejUJMwZ12a09snZJspQsOlkqpDAl9qKPEYOGWCk=
b612.me/stario v0.0.9/go.mod h1:x4D/x8zA5SC0pj/uJAi4FyG5p4j5UZoMEZfvuRR6VNw=
b612.me/starlog v1.3.3 h1:xYCHouOTpo6dsFg2A92TqTznxvRPPS/ovMWs7CJZ9WI=
b612.me/starlog v1.3.3/go.mod h1:h928hRahvWqcXXxy0uKWZ+oFe3K7kFQDHKiBemedLyE=
b612.me/starmap v1.2.4 h1:gfAyBtzW3KKCIyI14I2pEqGsR/u2E+3tkH0xRqtWb4E=
b612.me/starmap v1.2.4/go.mod h1:EhOUzkItc5IcyBmr1C7/vmZBbW3GgCWs63hGn7WhuMc=
b612.me/starnet v0.1.8 h1:sTNytUFP38i2BFR9nha3lTSLYb7El3tvKpZplYCrhZk=
b612.me/starnet v0.1.8/go.mod h1:k862Kf8DiVWTqdX6PHTFb6NoT+3G3Y74n8NCyNhuP0Y=
b612.me/staros v1.1.7 h1:GkQp5sBPRqo3pOh6nKyKffJydyYrjlfzpsPxNeVJ26g=
b612.me/staros v1.1.7/go.mod h1:Yi/WfvIqRAPQEf/eiaaIwrL5LNcUbqzMIuFIyJJOU40=
b612.me/starssh v0.0.2 h1:cYlrXjd7ZTesdZG+7XcoLsEEMROaeWMTYonScBLnvyY=
b612.me/starssh v0.0.2/go.mod h1:1gvG/GT5Y5EvOx9ZKnLFUa+wOX20HaqS1IuTnU7BOlk=
b612.me/startext v0.0.0-20220314043758-22c6d5e5b1cd h1:EsmnczYZhOV8JTxD/m0N0qBjfZN8JuLNrTJ6z3S8YqA=
b612.me/startext v0.0.0-20220314043758-22c6d5e5b1cd/go.mod h1:yKdeLQHZ3scqyjw1ZODCoL+hLmkOp2eu5riP4agraz8=
b612.me/win32api v0.0.1 h1:vLFB1xhO6pd9+zB2EyaapKB459Urv3v+C1YwgwOFEWo=
b612.me/win32api v0.0.1/go.mod h1:MHu0JBQjzxQ2yxpZPUBbn5un45o67eF5iWKa4Q9e0yE=
b612.me/wincmd v0.0.2 h1:Ub1WtelVT6a3vD4B6zDYo3UPO/t9ymnI3x1dQPJcrGw=
b612.me/wincmd v0.0.2/go.mod h1:bwpyCKfSDY8scSMo3Lrd0Qnqvpz7/CILL7oodfG0wgo=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
b612.me/win32api v0.0.2 h1:5PwvPR5fYs3a/v+LjYdtRif+5Q04zRGLTVxmCYNjCpA=
b612.me/win32api v0.0.2/go.mod h1:sj66sFJDKElEjOR+0YhdSW6b4kq4jsXu4T5/Hnpyot0=
b612.me/wincmd v0.0.3 h1:GYrkYnNun39yfNcA2+u0h4VW/BYbTrJK39QW4W1LCYA=
b612.me/wincmd v0.0.3/go.mod h1:nWdNREHO6F+2PngEUcyYN3Eo7DzYEVa/fO6czd9d/fo=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5 h1:m62nsMU279qRD9PQSWD1l66kmkXzuYcnVJqL4XLeV2M=
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/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=
@ -33,76 +40,98 @@ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF0
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
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/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/likexian/gokit v0.25.13 h1:p2Uw3+6fGG53CwdU2Dz0T6bOycdb2+bAFAa3ymwWVkM=
github.com/likexian/whois v1.15.1 h1:6vTMI8n9s1eJdmcO4R9h1x99aQWIZZX1CD3am68gApU=
github.com/likexian/whois v1.15.1/go.mod h1:/nxmQ6YXvLz+qTxC/QFtEJNAt0zLuRxJrKiWpBJX8X0=
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/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/pkg/sftp v1.13.4 h1:Lb0RYJCmgUcBgZosfoi9Y9sbl6+LJgOIgk/2Y4YjMFg=
github.com/pkg/sftp v1.13.4/go.mod h1:LzqnAvaD5TWeNBsZpfKxSYn1MbjWwOsCIAFFJbpIsK8=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/things-go/go-socks5 v0.0.5 h1:qvKaGcBkfDrUL33SchHN93srAmYGzb4CxSM2DPYufe8=
github.com/things-go/go-socks5 v0.0.5/go.mod h1:mtzInf8v5xmsBpHZVbIw2YQYhc4K0jRwzfsH64Uh0IQ=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220313003712-b769efc7c000/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064 h1:S25/rfnfsMVgORT4/J61MJ7rdyseOZOyvLIrZEZ7s6s=
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
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/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
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.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.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/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=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
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 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=
software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=

@ -1,15 +1,26 @@
package hash
import (
"b612.me/starcrypto"
"crypto/hmac"
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"encoding/hex"
"fmt"
"golang.org/x/crypto/md4"
"golang.org/x/crypto/ripemd160"
"hash"
"hash/crc32"
"io"
"os"
"b612.me/starlog"
"github.com/spf13/cobra"
)
var hmacKey string
var Cmd = &cobra.Command{
Use: "hash",
Short: "多种方法哈希值校验",
@ -18,7 +29,7 @@ var Cmd = &cobra.Command{
var cumethod, method []string
var result = make(map[string]string)
var err error
cumethod = []string{"md5", "crc32", "sha512", "sha384", "sha256", "sha224", "sha1"}
cumethod = []string{"md5", "crc32", "sha512", "sha384", "sha256", "sha224", "sha1", "md4", "ripemd160", "hmacmd5", "hmacmd4", "hmacsha1", "hmacsha224", "hmacsha256", "hmacsha384", "hmacsha512"}
if ok, _ := this.Flags().GetBool("all"); ok {
method = cumethod
} else {
@ -37,7 +48,7 @@ var Cmd = &cobra.Command{
}
}
if ok, _ := this.Flags().GetBool("file"); ok {
result, err = starcrypto.FileSumAll(args[0], method, func(pect float64) {
result, err = FileSumAll(args[0], hmacKey, method, func(pect float64) {
if pect != 100.0 {
fmt.Printf("校验已完成:%f%%\r", pect)
} else {
@ -46,7 +57,7 @@ var Cmd = &cobra.Command{
})
} else {
var bres map[string][]byte
bres, err = starcrypto.SumAll([]byte(args[0]), method)
bres, err = SumAll([]byte(args[0]), hmacKey, method)
if err == nil {
for k, v := range bres {
result[k] = hex.EncodeToString(v)
@ -63,6 +74,7 @@ var Cmd = &cobra.Command{
}
func init() {
Cmd.Flags().StringVarP(&hmacKey, "hmac-key", "k", "b612.me", "HMAC密钥")
Cmd.Flags().BoolP("all", "a", false, "使用所有的校验方法")
Cmd.Flags().BoolP("file", "f", false, "对指定文件进行校验")
Cmd.Flags().BoolP("md5", "m", false, "进行MD5校验默认")
@ -72,4 +84,185 @@ func init() {
Cmd.Flags().Bool("sha256", false, "进行SHA256校验")
Cmd.Flags().Bool("sha224", false, "进行SHA224校验")
Cmd.Flags().Bool("sha1", false, "进行SHA1校验")
Cmd.Flags().Bool("md4", false, "进行MD4校验")
Cmd.Flags().Bool("ripemd160", false, "进行RIPEMD160校验")
Cmd.Flags().Bool("hmacmd5", false, "进行HMACMD5校验")
Cmd.Flags().Bool("hmacmd4", false, "进行HMACMD4校验")
Cmd.Flags().Bool("hmacsha1", false, "进行HMACSHA1校验")
Cmd.Flags().Bool("hmacsha224", false, "进行HMACSHA224校验")
Cmd.Flags().Bool("hmacsha256", false, "进行HMACSHA256校验")
Cmd.Flags().Bool("hmacsha384", false, "进行HMACSHA384校验")
Cmd.Flags().Bool("hmacsha512", false, "进行HMACSHA512校验")
}
func FileSumAll(filepath string, key string, method []string, shell func(float64)) (map[string]string, error) {
result := make(map[string]string)
methods := make(map[string]hash.Hash)
var iscrc bool
if len(method) == 0 {
method = []string{"sha512", "sha256", "sha384", "sha224", "sha1", "crc32", "md5", "md4", "ripemd160", "hmacmd5", "hmacmd4", "hmacsha1", "hmacsha224", "hmacsha256", "hmacsha384", "hmacsha512"}
}
fp, err := os.Open(filepath)
defer fp.Close()
if err != nil {
return result, err
}
stat, _ := os.Stat(filepath)
filebig := float64(stat.Size())
sum512 := sha512.New()
sum384 := sha512.New384()
sum256 := sha256.New()
sum224 := sha256.New224()
sum1 := sha1.New()
crcsum := crc32.NewIEEE()
md5sum := md5.New()
md4sum := md4.New()
ripemd160sum := ripemd160.New()
hmacmd5sum := hmac.New(md5.New, []byte(key))
hmacmd4sum := hmac.New(md4.New, []byte(key))
hmacsha1sum := hmac.New(sha1.New, []byte(key))
hmacsha224sum := hmac.New(sha256.New224, []byte(key))
hmacsha256sum := hmac.New(sha256.New, []byte(key))
hmacsha384sum := hmac.New(sha512.New384, []byte(key))
hmacsha512sum := hmac.New(sha512.New, []byte(key))
for _, v := range method {
switch v {
case "md5":
methods["md5"] = md5sum
case "crc32":
iscrc = true
case "sha1":
methods["sha1"] = sum1
case "sha224":
methods["sha224"] = sum224
case "sha256":
methods["sha256"] = sum256
case "sha384":
methods["sha384"] = sum384
case "sha512":
methods["sha512"] = sum512
case "md4":
methods["md4"] = md4sum
case "ripemd160":
methods["ripemd160"] = ripemd160sum
case "hmacmd5":
methods["hmacmd5"] = hmacmd5sum
case "hmacmd4":
methods["hmacmd4"] = hmacmd4sum
case "hmacsha1":
methods["hmacsha1"] = hmacsha1sum
case "hmacsha224":
methods["hmacsha224"] = hmacsha224sum
case "hmacsha256":
methods["hmacsha256"] = hmacsha256sum
case "hmacsha384":
methods["hmacsha384"] = hmacsha384sum
case "hmacsha512":
methods["hmacsha512"] = hmacsha512sum
}
}
writer := 0
for {
buf := make([]byte, 1048574)
n, err := fp.Read(buf)
if err != nil {
if err == io.EOF {
break
}
return result, err
}
writer += n
pect := (float64(writer) / filebig) * 100
go shell(pect)
for _, v := range methods {
v.Write(buf[0:n])
}
if iscrc {
crcsum.Write(buf[0:n])
}
}
for k, v := range methods {
result[k] = hex.EncodeToString(v.Sum(nil))
}
if iscrc {
result["crc32"] = hex.EncodeToString(crcsum.Sum(nil))
}
return result, nil
}
func SumAll(data []byte, key string, method []string) (map[string][]byte, error) {
result := make(map[string][]byte)
methods := make(map[string]hash.Hash)
var iscrc bool
if len(method) == 0 {
method = []string{"sha512", "sha256", "sha384", "sha224", "sha1", "crc32", "md5", "md4", "ripemd160", "hmacmd5", "hmacmd4", "hmacsha1", "hmacsha224", "hmacsha256", "hmacsha384", "hmacsha512"}
}
sum512 := sha512.New()
sum384 := sha512.New384()
sum256 := sha256.New()
sum224 := sha256.New224()
sum1 := sha1.New()
crcsum := crc32.NewIEEE()
md5sum := md5.New()
md4sum := md4.New()
ripemd160sum := ripemd160.New()
hmacmd5sum := hmac.New(md5.New, []byte(key))
hmacmd4sum := hmac.New(md4.New, []byte(key))
hmacsha1sum := hmac.New(sha1.New, []byte(key))
hmacsha224sum := hmac.New(sha256.New224, []byte(key))
hmacsha256sum := hmac.New(sha256.New, []byte(key))
hmacsha384sum := hmac.New(sha512.New384, []byte(key))
hmacsha512sum := hmac.New(sha512.New, []byte(key))
for _, v := range method {
switch v {
case "md5":
methods["md5"] = md5sum
case "crc32":
iscrc = true
case "sha1":
methods["sha1"] = sum1
case "sha224":
methods["sha224"] = sum224
case "sha256":
methods["sha256"] = sum256
case "sha384":
methods["sha384"] = sum384
case "sha512":
methods["sha512"] = sum512
case "md4":
methods["md4"] = md4sum
case "ripemd160":
methods["ripemd160"] = ripemd160sum
case "hmacmd5":
methods["hmacmd5"] = hmacmd5sum
case "hmacmd4":
methods["hmacmd4"] = hmacmd4sum
case "hmacsha1":
methods["hmacsha1"] = hmacsha1sum
case "hmacsha224":
methods["hmacsha224"] = hmacsha224sum
case "hmacsha256":
methods["hmacsha256"] = hmacsha256sum
case "hmacsha384":
methods["hmacsha384"] = hmacsha384sum
case "hmacsha512":
methods["hmacsha512"] = hmacsha512sum
}
}
for _, v := range methods {
v.Write(data)
}
if iscrc {
crcsum.Write(data)
}
for k, v := range methods {
result[k] = v.Sum(nil)
}
if iscrc {
result["crc32"] = crcsum.Sum(nil)
}
return result, nil
}

@ -11,7 +11,7 @@ import (
var remote, config string
var addr, key, cert, log string
var port int
var enablessl bool
var enablessl, skipsslverify bool
var host string
func init() {
@ -23,6 +23,7 @@ func init() {
Cmd.Flags().StringVarP(&cert, "cert", "c", "", "ssl 证书地址")
Cmd.Flags().StringVarP(&log, "log", "l", "", "log日志地址")
Cmd.Flags().BoolVarP(&enablessl, "enable-ssl", "s", false, "启用ssl")
Cmd.Flags().BoolVarP(&skipsslverify, "skil-ssl-verify", "S", false, "跳过证书验证")
Cmd.Flags().IntVarP(&port, "port", "p", 8080, "监听端口")
}
@ -69,11 +70,12 @@ var Cmd = &cobra.Command{
ReverseURL: map[string]*url.URL{
"/": u,
},
Port: port,
UsingSSL: enablessl,
Key: key,
Cert: cert,
XForwardMode: 1,
Port: port,
UsingSSL: enablessl,
SkipSSLVerify: skipsslverify,
Key: key,
Cert: cert,
XForwardMode: 1,
}
go func() {
sig := make(chan os.Signal)

@ -12,23 +12,24 @@ import (
)
type ReverseConfig struct {
Name string
Addr string
ReverseURL map[string]*url.URL
Port int
UsingSSL bool
Key string
Cert string
Host string
InHeader [][2]string
OutHeader [][2]string
Cookie [][3]string //[3]string should contains path::key::value
ReplaceList [][2]string
ReplaceOnce bool
proxy map[string]*httputil.ReverseProxy
XForwardMode int //0=off 1=useremote 2=add
httpmux http.ServeMux
httpserver http.Server
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]*httputil.ReverseProxy
XForwardMode int //0=off 1=useremote 2=add
httpmux http.ServeMux
httpserver http.Server
basicAuthUser string
basicAuthPwd string

@ -4,9 +4,11 @@ import (
"b612.me/starlog"
"bytes"
"context"
"crypto/tls"
"encoding/base64"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/http/httputil"
"net/url"
@ -15,7 +17,7 @@ import (
"time"
)
var version = "2.0.1"
var version = "2.1.0"
func (h *ReverseConfig) Run() error {
err := h.init()
@ -24,7 +26,7 @@ func (h *ReverseConfig) Run() error {
}
for key, proxy := range h.proxy {
h.httpmux.HandleFunc(key, func(writer http.ResponseWriter, request *http.Request) {
starlog.Infof("<%s> Req Path:%s Addr:%s UA:%s\n", h.Name, request.URL.Path, request.RemoteAddr, request.Header.Get("User-Agent"))
starlog.Infof("<%s> Req Path:%s ListenAddr:%s UA:%s\n", h.Name, request.URL.Path, request.RemoteAddr, request.Header.Get("User-Agent"))
if !h.BasicAuth(writer, request) {
h.SetResponseHeader(writer)
@ -75,10 +77,61 @@ func (h *ReverseConfig) Close() error {
return h.httpserver.Shutdown(ctx)
}
func (h *ReverseConfig) dialTLS(ctx context.Context, network, addr string) (net.Conn, error) {
conn, err := net.DialTimeout(network, addr, time.Second*20)
if err != nil {
return nil, err
}
host, _, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
if h.Host != "" {
host = h.Host
}
cfg := &tls.Config{ServerName: host}
tlsConn := tls.Client(conn, cfg)
if err := tlsConn.Handshake(); err != nil {
conn.Close()
return nil, err
}
cs := tlsConn.ConnectionState()
cert := cs.PeerCertificates[0]
// Verify here
if !h.SkipSSLVerify {
err = cert.VerifyHostname(host)
if err != nil {
return nil, err
}
}
return tlsConn, nil
}
func (h *ReverseConfig) init() error {
h.proxy = make(map[string]*httputil.ReverseProxy)
for key, val := range h.ReverseURL {
h.proxy[key] = httputil.NewSingleHostReverseProxy(val)
h.proxy[key] = &httputil.ReverseProxy{
Transport: &http.Transport{DialTLSContext: h.dialTLS},
Director: func(req *http.Request) {
targetQuery := val.RawQuery
req.URL.Scheme = val.Scheme
if h.Host == "" {
req.Host = val.Host
} else {
req.Host = h.Host
}
req.URL.Host = val.Host
req.URL.Path, req.URL.RawPath = joinURLPath(val, req.URL)
if targetQuery == "" || req.URL.RawQuery == "" {
req.URL.RawQuery = targetQuery + req.URL.RawQuery
} else {
req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
}
},
}
h.proxy[key].ModifyResponse = h.ModifyResponse()
originalDirector := h.proxy[key].Director
h.proxy[key].Director = func(req *http.Request) {

@ -0,0 +1,43 @@
package httproxy
import (
"b612.me/starlog"
"github.com/elazarl/goproxy"
"github.com/elazarl/goproxy/ext/auth"
"github.com/spf13/cobra"
"net/http"
)
var username, password string
var listen string
func init() {
Cmd.Flags().StringVarP(&username, "username", "u", "", "用户名")
Cmd.Flags().StringVarP(&password, "password", "p", "", "密码")
Cmd.Flags().StringVarP(&listen, "listen", "l", ":8000", "监听地址")
}
var Cmd = &cobra.Command{
Use: "httproxy",
Short: "http代理",
Long: "http代理",
Run: func(cmd *cobra.Command, args []string) {
run()
},
}
func run() {
// Create a http server
p := goproxy.NewProxyHttpServer()
p.Verbose = true
starlog.Infof("start http proxy server on %s username %s password %s \n", listen, username, password)
if username != "" && password != "" {
auth.ProxyBasic(p, "B612 Http Proxy Need Password", func(user, pwd string) bool {
return user == username && pwd == password
})
}
err := http.ListenAndServe(listen, p)
if err != nil {
starlog.Errorln("http proxy server error:", err)
}
}

File diff suppressed because one or more lines are too long

@ -4,6 +4,7 @@ import (
"b612.me/starlog"
"b612.me/staros"
"context"
"encoding/json"
"github.com/spf13/cobra"
"os"
"os/signal"
@ -13,8 +14,10 @@ import (
var s HttpServer
var daemon bool
var hooks string
func init() {
Cmd.Flags().StringVarP(&hooks, "hook", "H", "", "fileget hook for modify")
Cmd.Flags().StringVarP(&s.port, "port", "p", "80", "监听端口")
Cmd.Flags().StringVarP(&s.addr, "ip", "i", "0.0.0.0", "监听ip")
Cmd.Flags().StringVarP(&s.envPath, "folder", "f", "./", "本地文件地址")
@ -36,6 +39,24 @@ var Cmd = &cobra.Command{
Short: "HTTP文件服务器(HTTP File Browser Server)",
Long: `HTTP文件服务器(HTTP File Browser Server)`,
Run: func(cmd *cobra.Command, args []string) {
if hooks != "" {
if !staros.Exists(hooks) {
starlog.Criticalln("hook file not exists")
os.Exit(2)
}
data, err := os.ReadFile(hooks)
if err != nil {
starlog.Criticalln("read hook file error", err)
os.Exit(3)
}
var hk []ServerHook
err = json.Unmarshal(data, &hk)
if err != nil {
starlog.Errorln("Unmarshal hook Json Failed", err)
os.Exit(4)
}
s.hooks = hk
}
apply, _ := cmd.Flags().GetBool("daeapplied")
if daemon && !apply {
nArgs := append(os.Args[1:], "--daeapplied")

File diff suppressed because one or more lines are too long

@ -5,7 +5,7 @@ import "path/filepath"
func (h *HttpServer) FileType(name string) string {
ext := filepath.Ext(name)
if len(ext) == 0 || ext == "." {
return "文件"
return "其他文件"
}
ext = ext[1:]
mimeMap := map[string]string{
@ -217,6 +217,14 @@ func (h *HttpServer) FileType(name string) string {
return mime
}
func (h *HttpServer) GetExt(fullpath string) string {
ext := filepath.Ext(filepath.Base(fullpath))
if len(ext) == 0 || ext == "." {
return ""
}
return ext[1:]
}
func (h *HttpServer) MIME(fullpath string) string {
ext := filepath.Ext(filepath.Base(fullpath))
if len(ext) == 0 || ext == "." {

@ -3,8 +3,10 @@ package httpserver
import (
"b612.me/starcrypto"
"b612.me/starlog"
"b612.me/starnet"
"b612.me/staros"
"context"
_ "embed"
"encoding/base64"
"errors"
"fmt"
@ -20,7 +22,7 @@ import (
"time"
)
var version = "2.0.1"
var version = "2.1.0"
type HttpServerCfgs func(cfg *HttpServerCfg)
@ -38,11 +40,29 @@ type HttpServerCfg struct {
protectAuthPage []string
disableMIME bool
ctx context.Context
hooks []ServerHook
}
type ServerHook struct {
MatchType []string `json:"match_type"`
Url string `json:"url"`
Timeout int `json:"timeout"`
MaxHookLength int `json:"max_hook_length"`
}
type HttpServer struct {
HttpServerCfg
}
//go:embed bootstrap.css
var bootStrap []byte
//go:embed jquery.js
var jquery []byte
//go:embed upload.html
var uploadPage []byte
var htmlTitle string = `<!DOCTYPE html>
<html lang="zh_CN">
<head>
@ -82,11 +102,31 @@ var htmlTitle string = `<!DOCTYPE html>
padding: 12px;
text-align: left;
border-bottom: 1px solid #ddd;
position: relative;
}
th[data-sort]:before {
content: "▼";
display: inline-block;
height: 20px;
width: 20px;
margin-right: 10px;
vertical-align: middle;
position: absolute;
right: 0;
top: 50%%;
transform: translateY(-50%%);
opacity: 0.3;
transition: all 0.2s ease-in-out;
}
th:hover {
cursor: pointer;
background-color: #ddd;
th[data-sort].asc:before {
content: "▲";
opacity: 1;
}
th:hover:before {
opacity: 1;
}
.filename {
@ -112,49 +152,69 @@ var htmlTitle string = `<!DOCTYPE html>
<table>
<thead>
<tr>
<th onclick="sortTable(0)">Name</th>
<th onclick="sortTable(1)">Modified</th>
<th onclick="sortTable(2)">Size</th>
<th onclick="sortTable(3)">Type</th>
<th data-sort="name" class="asc">Name</th>
<th data-sort="modified">Modified</th>
<th data-sort="size">Size</th>
<th data-sort="type">Type</th>
</tr>
</thead>
<tbody>`
var htmlTail = ` </tbody>
</table>
<hr />
<pre>
<h2 style="text-align: center;">B612.Me © Apache 2.0 License</h2>
</pre>
</div>
<script>
function sortTable(n) {
const table = document.querySelector('table');
const rows = table.rows;
let switching = true;
let shouldSwitch = false;
let direction = 'asc';
let switchcount = 0;
function sortTable(th, n) {
const table = document.querySelector('table');
const rows = table.rows;
let switching = true;
let shouldSwitch = false;
let direction = 'asc';
let switchcount = 0;
while (switching) {
switching = false;
let i;
for (i = 1; i < rows.length - 1; i++) {
shouldSwitch = false;
const x = rows[i].getElementsByTagName("td")[n];
const y = rows[i + 1].getElementsByTagName("td")[n];
let xValue, yValue;
if (n === 2) { // size sorting
if (x.innerText==="-") {
xValue=-1;
}else{
xValue = parseInt(x.innerText.split(' ')[0]);
}
if (y.innerText==="-") {
yValue=-1;
}else{
yValue = parseInt(y.innerText.split(' ')[0]);
}
} else {
xValue = x.innerText.toLowerCase();
yValue = y.innerText.toLowerCase();
}
if (direction === 'asc') {
if (x.innerHTML.toLowerCase() > y.innerHTML.toLowerCase()) {
if (xValue > yValue) {
shouldSwitch = true;
break;
}
} else if (direction === 'desc') {
if (x.innerHTML.toLowerCase() < y.innerHTML.toLowerCase()) {
if (xValue < yValue) {
shouldSwitch = true;
break;
}
}
}
if (shouldSwitch) {
rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
switching = true;
@ -166,12 +226,68 @@ var htmlTail = ` </tbody>
}
}
}
// update sort class
const ths = table.getElementsByTagName('th');
for (let i = 0; i < ths.length; i++) {
const currentTh = ths[i];
if (currentTh !== th) {
currentTh.classList.remove('asc');
} else {
currentTh.classList.toggle('asc');
}
}
// hide arrow on non-sorting columns
const sortableThs = table.querySelectorAll('thead th[data-sort]');
for (let i = 0; i < sortableThs.length; i++) {
const sortableTh = sortableThs[i];
if (sortableTh !== th) {
sortableTh.classList.remove('asc');
}
}
}
// add sorting event listener to thead
const ths = document.querySelectorAll('table th[data-sort]');
for (let i = 0; i < ths.length; i++) {
const th = ths[i];
th.addEventListener('click', () => {
const sortType = th.getAttribute('data-sort');
let columnIndex;
switch (sortType) {
case 'name':
columnIndex = 0;
break;
case 'modified':
columnIndex = 1;
break;
case 'size':
columnIndex = 2;
break;
case 'type':
columnIndex = 3;
break;
}
sortTable(th, columnIndex);
});
}
</script>
</body>
</html>
`
func WithHooks(hooks []ServerHook) HttpServerCfgs {
return func(cfg *HttpServerCfg) {
for k, v := range hooks {
if v.MaxHookLength == 0 {
hooks[k].MaxHookLength = 1024 * 1024
}
}
cfg.hooks = hooks
}
}
func WithTLSCert(cert, key string) HttpServerCfgs {
return func(cfg *HttpServerCfg) {
cfg.key = key
@ -293,13 +409,19 @@ func (h *HttpServer) BasicAuth(log *starlog.StarLogger, w http.ResponseWriter, r
func (h *HttpServer) SetUpload(w http.ResponseWriter, r *http.Request, path string) bool {
if h.uploadFolder != "" {
if r.URL.Query().Get("bootstrap") == "true" {
w.Header().Set("Content-Type", "text/css")
w.Write(bootStrap)
return true
}
if r.URL.Query().Get("jquery") == "true" {
w.Header().Set("Content-Type", "application/javascript")
w.Write(jquery)
return true
}
if len(r.URL.Query()["upload"]) != 0 {
w.Write([]byte(`<html><body><form id= "uploadForm" action= "/recv?upload=true" method= "post" enctype ="multipart/form-data">
<h1 >B612 File Upload Page </h1>
<p > <input type ="file" name="victorique" /></p>
<input type ="submit" value="上传"/>
</form>
<h2>Copyright@b612.me </h2></body></html>`))
w.Header().Set("Content-Type", "text/html")
w.Write(uploadPage)
return true
}
}
@ -318,10 +440,10 @@ func (h *HttpServer) Listen(w http.ResponseWriter, r *http.Request) {
return
}
fullpath := filepath.Join(h.envPath, path)
if path == "/" && h.indexFile != "" {
if staros.Exists(filepath.Join(h.envPath, h.indexFile)) {
fullpath = filepath.Join(h.envPath, h.indexFile)
path = "/" + h.indexFile
if h.indexFile != "" && staros.IsFolder(fullpath) {
if staros.Exists(filepath.Join(fullpath, h.indexFile)) {
fullpath = filepath.Join(fullpath, h.indexFile)
path = filepath.Join(path, h.indexFile)
}
}
log.Noticef("Start Method:%s Path:%s IP:%s\n", r.Method, path, r.RemoteAddr)
@ -404,6 +526,9 @@ func (h *HttpServer) BuildHeader(w http.ResponseWriter, r *http.Request, fullpat
w.Header().Set("ETag", starcrypto.Md5Str([]byte(finfo.ModTime().String())))
w.Header().Set("Last-Modified", strings.ReplaceAll(finfo.ModTime().UTC().Format("Mon, 2 Jan 2006 15:04:05 MST"), "UTC", "GMT"))
if r.Method != "OPTIONS" {
if _, ok := h.willHook(fullpath); ok {
return nil
}
w.Header().Set("Content-Length", strconv.FormatInt(finfo.Size(), 10))
start, end := h.CalcRange(r)
if start != -1 {
@ -420,6 +545,24 @@ func (h *HttpServer) BuildHeader(w http.ResponseWriter, r *http.Request, fullpat
return nil
}
func (h *HttpServer) willHook(fullpath string) (ServerHook, bool) {
finfo, err := os.Stat(fullpath)
if err != nil {
return ServerHook{}, false
}
if finfo.Size() < 1024*1024*10 && len(h.hooks) > 0 {
ext := h.GetExt(fullpath)
for _, hk := range h.hooks {
for _, e := range hk.MatchType {
if e == ext {
return hk, true
}
}
}
}
return ServerHook{}, false
}
func (h *HttpServer) ResponseGet(log *starlog.StarLogger, w http.ResponseWriter, r *http.Request, fullpath string) error {
if staros.IsFolder(fullpath) {
return h.getFolder(log, w, r, fullpath)
@ -444,14 +587,14 @@ func (h *HttpServer) getFolder(log *starlog.StarLogger, w http.ResponseWriter, r
if h.uploadFolder != "" {
upload = `<a href=/b612?upload=true>Upload Web Page Is Openned!</a>`
}
w.Write([]byte(fmt.Sprintf(htmlTitle, r.URL.Path, version, r.URL.Path, upload)))
w.Write([]byte(fmt.Sprintf(htmlTitle, r.URL.Path, version, "Index of "+r.URL.Path, upload)))
if r.URL.Path != "/" {
p := r.URL.Path
if p[len(p)-1:] != "/" {
p += "/"
}
w.Write([]byte(fmt.Sprintf(`<tr><td><a class="filename" href="%s">%s</a></td><td>%s</td><td>%s</td><td class="filetype">%s</td></tr>`,
p+"..", "..", "-", "-", "上层文件夹")))
p+"..", "../", "-", "-", "上层文件夹")))
}
if r.URL.Path == "/" {
r.URL.Path = ""
@ -466,7 +609,7 @@ func (h *HttpServer) getFolder(log *starlog.StarLogger, w http.ResponseWriter, r
r.URL.Path+"/"+v.Name(), v.Name(), v.ModTime().Format("2006-01-02 15:04:05"), fmt.Sprintf("%d (%s)", v.Size(), h.trimSize(v.Size())), h.FileType(v.Name()))))
} else {
w.Write([]byte(fmt.Sprintf(`<tr><td><a class="filename" href="%s">%s</a></td><td>%s</td><td>%s</td><td class="filetype">%s</td></tr>`,
r.URL.Path+"/"+v.Name(), v.Name(), v.ModTime().Format("2006-01-02 15:04:05"), "-", "文件夹")))
r.URL.Path+"/"+v.Name(), v.Name()+"/", v.ModTime().Format("2006-01-02 15:04:05"), "-", "文件夹")))
}
}
}
@ -477,6 +620,7 @@ func (h *HttpServer) getFolder(log *starlog.StarLogger, w http.ResponseWriter, r
func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r *http.Request, fullpath string) error {
if !staros.Exists(fullpath) {
w.WriteHeader(404)
w.Write([]byte(`<html><title>B612 Http Server</title><body><h1 "style="text-align: center;">404 NOT FOUND</h1><hr ></body></html>`))
return errors.New("File Not Found! 404 ERROR")
}
//starlog.Debugln(r.Header)
@ -485,7 +629,7 @@ func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r *
if err != nil {
log.Errorf("Failed to open file %s,reason:%v\n", r.URL.Path, err)
w.WriteHeader(502)
w.Write([]byte("<h1>502 SERVER ERROR</h1>"))
w.Write([]byte(`<html><title>B612 Http Server</title><body><h1 "style="text-align: center;">502 SERVER ERROR</h1><hr ></body></html>`))
return err
}
defer fp.Close()
@ -495,11 +639,11 @@ func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r *
var tani string
tani = fmt.Sprintf("%v Byte", transferData)
if f64 := float64(transferData) / 1024; f64 > 1 {
tani = fmt.Sprintf("%v KB", f64)
tani = fmt.Sprintf("%v KiB", f64)
if f64 = float64(f64) / 1024; f64 > 1 {
tani = fmt.Sprintf("%v MB", f64)
tani = fmt.Sprintf("%v MiB", f64)
if f64 = float64(f64) / 1024; f64 > 1 {
tani = fmt.Sprintf("%v GB", f64)
tani = fmt.Sprintf("%v GiB", f64)
}
}
}
@ -508,25 +652,62 @@ func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r *
}
}()
if startRange == -1 {
w.WriteHeader(200)
for {
buf := make([]byte, 1048576)
n, err := fp.Read(buf)
if n != 0 {
ns, err := w.Write(buf[0:n])
transferData += ns
hook, needCurl := h.willHook(fullpath)
if !needCurl {
w.WriteHeader(200)
for {
buf := make([]byte, 1048576)
n, err := fp.Read(buf)
if n != 0 {
ns, err := w.Write(buf[0:n])
transferData += ns
if err != nil {
log.Errorf("Transfer File %s to Remote Failed:%v\n", fullpath, err)
return err
}
}
if err != nil {
log.Errorf("Transfer File %s to Remote Failed:%v\n", fullpath, err)
if err == io.EOF {
break
}
log.Errorln("Read File %s Failed:%v\n", fullpath, err)
return err
}
}
return nil
}
data, err := os.ReadFile(fullpath)
if err != nil {
w.WriteHeader(502)
w.Write([]byte(`<html><title>B612 Http Server</title><body><h1 "style="text-align: center;">502 SERVER ERROR</h1><hr ></body></html>`))
log.Errorf("Read File %s Failed:%v\n", fullpath, err)
return err
}
b64 := base64.StdEncoding.EncodeToString(data)
req, err := starnet.Curl(starnet.NewRequests(hook.Url, starnet.BuildPostForm(map[string]string{
"data": b64,
"ip": r.RemoteAddr,
}),
"POST",
starnet.WithTimeout(time.Duration(hook.Timeout)*time.Millisecond)))
if err != nil || len(req.RecvData) == 0 {
w.Header().Set("Content-Length", strconv.Itoa(len(data)))
w.WriteHeader(200)
ns, err := w.Write(data)
transferData += ns
if err != nil {
if err == io.EOF {
break
}
log.Errorln("Read File %s Failed:%v\n", fullpath, err)
log.Errorf("Transfer File %s to Remote Failed:%v\n", fullpath, err)
return err
}
return nil
}
w.WriteHeader(200)
w.Header().Set("Content-Length", strconv.Itoa(len(req.RecvData)))
ns, err := w.Write(req.RecvData)
transferData += ns
if err != nil {
log.Errorf("Transfer File %s to Remote Failed:%v\n", fullpath, err)
return err
}
return nil
}
@ -575,11 +756,11 @@ func (h *HttpServer) trimSize(size int64) string {
var tani string
tani = fmt.Sprintf("%v Byte", size)
if f64 := float64(size) / 1024; f64 > 1 {
tani = fmt.Sprintf("%.3f KB", math.Trunc(f64*1e3+0.5)*1e-3)
tani = fmt.Sprintf("%.3f KiB", math.Trunc(f64*1e3+0.5)*1e-3)
if f64 = float64(f64) / 1024; f64 > 1 {
tani = fmt.Sprintf("%.3f MB", math.Trunc(f64*1e3+0.5)*1e-3)
tani = fmt.Sprintf("%.3f MiB", math.Trunc(f64*1e3+0.5)*1e-3)
if f64 = float64(f64) / 1024; f64 > 1 {
tani = fmt.Sprintf("%.3f GB", math.Trunc(f64*1e3+0.5)*1e-3)
tani = fmt.Sprintf("%.3f GiB", math.Trunc(f64*1e3+0.5)*1e-3)
}
}
}

@ -0,0 +1,185 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>B612 File Upload Page</title>
<link rel="stylesheet" href="/css?bootstrap=true">
<script src="/js?jquery=true"></script>
<style>
@media (max-width: 600px) {
.progress-bar, p {
width: 100%;
}
}
.file-upload {
border: 1px solid #ddd;
padding: 10px;
margin-bottom: 10px;
border-radius: 5px;
background-color: #f9f9f9;
}
</style>
</head>
<body>
<div class="container">
<h1 class="text-center">B612 File Upload Page</h1>
<form id="uploadForm" action="/recv?upload=true" method="post" enctype="multipart/form-data">
<div class="form-group">
<label for="victorique">上传文件:</label>
<input type="file" class="form-control-file" id="victorique" name="victorique" multiple>
</div>
<button type="submit" class="btn btn-primary">上传</button>
<button type="button" id="clearButton" class="btn btn-secondary">清除</button>
<button type="button" id="cancelAllButton" class="btn btn-danger">取消所有上传</button>
</form>
<div id="progressContainer"></div>
</div>
<script>
var completedUploads = 0;
var anyUploadFailed = false;
var xhrs = []; // Array to hold all the XHR objects
$("#victorique").on("change", function(e){
document.getElementById('progressContainer').innerHTML = '';
var files = document.getElementById('victorique').files;
for (var i = 0; i < files.length; i++) {
createProgressBar(files[i]);
}
});
$("#clearButton").on("click", function(e){
document.getElementById('victorique').value = '';
document.getElementById('progressContainer').innerHTML = '';
});
$("#cancelAllButton").on("click", function(e){
for (var i = 0; i < xhrs.length; i++) {
xhrs[i].abort();
}
});
$("#uploadForm").on("submit", function(e){
e.preventDefault();
completedUploads = 0; // Reset the counter
anyUploadFailed = false; // Reset the flag
var files = document.getElementById('victorique').files;
var fileUploads = document.querySelectorAll('.file-upload');
for (var i = 0; i < files.length; i++) {
uploadFile(files[i], fileUploads[i]);
}
});
function createProgressBar(file) {
var progressContainer = document.getElementById('progressContainer');
var fileUpload = document.createElement('div');
fileUpload.className = 'file-upload';
progressContainer.appendChild(fileUpload);
var fileNameLabel = document.createElement('p');
fileNameLabel.innerHTML = file.name;
fileUpload.appendChild(fileNameLabel);
var progressBar = document.createElement('div');
progressBar.className = 'progress-bar';
progressBar.setAttribute('role', 'progressbar');
progressBar.style.width = '0%';
progressBar.setAttribute('aria-valuenow', '0');
progressBar.setAttribute('aria-valuemin', '0');
progressBar.setAttribute('aria-valuemax', '100');
fileUpload.appendChild(progressBar);
var speedLabel = document.createElement('p');
var sizeLabel = document.createElement('p');
var cancelButton = document.createElement('button');
cancelButton.innerHTML = '取消上传';
cancelButton.className = 'btn btn-danger';
fileUpload.appendChild(speedLabel);
fileUpload.appendChild(sizeLabel);
fileUpload.appendChild(cancelButton);
// Save these elements as custom properties
fileUpload.progressBar = progressBar;
fileUpload.speedLabel = speedLabel;
fileUpload.sizeLabel = sizeLabel;
fileUpload.cancelButton = cancelButton;
return fileUpload;
}
function uploadFile(file, fileUpload) {
var formData = new FormData();
formData.append('victorique', file);
var start = Date.now();
var lastLoaded = 0;
var progressBar = fileUpload.progressBar;
var speedLabel = fileUpload.speedLabel;
var sizeLabel = fileUpload.sizeLabel;
var cancelButton = fileUpload.cancelButton;
var xhr = $.ajax({
xhr: function(){
var xhr = new window.XMLHttpRequest();
xhr.upload.addEventListener("progress", function(evt){
if(evt.lengthComputable){
var percentComplete = ((evt.loaded / evt.total) * 100).toFixed(2);
var timeElapsed = (Date.now() - start) / 1000; // Time elapsed in seconds
var bytesLoaded = evt.loaded - lastLoaded;
lastLoaded = evt.loaded;
var speed = bytesLoaded / timeElapsed; // Speed in bytes/second
var formattedSpeed = formatSize(speed);
progressBar.style.width = percentComplete + '%';
progressBar.innerHTML = percentComplete + '%';
speedLabel.innerHTML = "上传速度: " + formattedSpeed + "/秒";
sizeLabel.innerHTML = "已上传: " + formatSize(evt.loaded) + " / " + formatSize(evt.total);
start = Date.now();
}
}, false);
return xhr;
},
url: '/recv?upload=true',
type: 'POST',
data: formData,
cache: false,
contentType: false,
processData: false,
success: function(response){
completedUploads++;
if (completedUploads === document.getElementById('victorique').files.length) {
if (anyUploadFailed) {
alert('一些文件上传失败!');
} else {
alert('所有文件上传成功!');
}
}
},
error: function(response){
anyUploadFailed = true;
completedUploads++;
if (completedUploads === document.getElementById('victorique').files.length) {
alert('一些文件上传失败!');
}
}
});
xhrs.push(xhr); // Add the XHR object to the array
// Add click event to the cancel button
cancelButton.onclick = function() {
xhr.abort();
};
}
function formatSize(size) {
var i = 0;
var units = ['字节', 'KB', 'MB', 'GB'];
while (size >= 1024) {
size /= 1024;
i++;
}
return size.toFixed(2) + ' ' + units[i];
}
</script>
<footer class="footer mt-auto py-3">
<div class="container text-center">
<span class="text-muted">Copyright@b612.me</span>
</div>
</footer>
</body>
</html>

@ -45,6 +45,11 @@ func MergePhoto(big, small image.Image, bigsize, smallsize uint, x, y int) image
return nimg
}
func CompressPhoto(pho image.Image) image.Image {
//b := pho.Bounds()
return resize.Resize(0, 0, pho, resize.Bilinear)
}
func SavePhoto(path string, img image.Image) error {
imgf, err := os.Create(path)
if err != nil {

@ -0,0 +1,15 @@
package image
import "testing"
func TestCompressPhoto(t *testing.T) {
p, err := OpenImage("../bin/original.jpg")
if err != nil {
t.Error(err)
}
p = CompressPhoto(p)
err = SavePhoto("../bin/compressed.jpg", p)
if err != nil {
t.Error(err)
}
}

@ -0,0 +1,166 @@
package keygen
import (
"b612.me/starcrypto"
"b612.me/starlog"
"b612.me/staros"
"crypto/ecdsa"
"crypto/rsa"
"github.com/spf13/cobra"
"os"
"time"
)
var k KeyGen
var startdate string
var duration int
var secret string
var path string
var key string
var outpath string
var sshPub bool
func init() {
Cmd.Flags().StringVarP(&k.Type, "type", "t", "rsa", "Key Type: rsa, ecdsa, ed25519")
Cmd.Flags().StringVarP(&k.Encrypt, "encrypt", "e", "", "Encrypt Key with Password (not recommended)")
Cmd.Flags().IntVarP(&k.Bits, "bits", "b", 2048, "Key Bits Rsa: 1024, 2048, 4096 Ecdsa: 224, 256, 384, 521")
Cmd.Flags().StringVarP(&k.Prefix, "prefix", "p", "mykey", "Output File Prefix")
Cmd.Flags().StringVarP(&k.Outfolder, "outfolder", "o", ".", "Output Folder")
Cmd.Flags().BoolVarP(&k.Force, "force", "f", false, "Force Overwrite")
Cmd.Flags().StringVarP(&k.Country, "country", "c", "CN", "Country")
Cmd.Flags().StringVarP(&k.Locality, "locality", "l", "Beijing", "Locality")
Cmd.Flags().StringVarP(&k.Organization, "organization", "O", "B612", "Organization")
Cmd.Flags().StringVarP(&k.OrganizationalUnit, "organizationalunit", "U", "B612", "OrganizationalUnit")
Cmd.Flags().StringVarP(&k.CommonName, "commonname", "C", "Little Prince", "CommonName")
Cmd.Flags().StringVarP(&startdate, "startdate", "s", "", "Cert Start Date")
Cmd.Flags().IntVarP(&duration, "duration", "d", 3650, "Cert Duration")
CmdEn.Flags().StringVarP(&secret, "secret", "s", "", "new Private Key Password,if empty,disable password")
CmdEn.Flags().StringVarP(&path, "path", "p", "", "private key file path")
CmdEn.Flags().StringVarP(&key, "key", "k", "", "private key old password,if empty,disable password")
CmdEn.Flags().StringVarP(&outpath, "outpath", "o", "./newkey", "new key file output path")
Cmd.AddCommand(CmdEn)
CmdPub.Flags().StringVarP(&path, "path", "p", "", "private key file path")
CmdPub.Flags().StringVarP(&outpath, "outpath", "o", "./public.key", "public key file output path")
CmdPub.Flags().BoolVarP(&sshPub, "ssh", "s", false, "output ssh public key")
Cmd.AddCommand(CmdPub)
}
var Cmd = &cobra.Command{
Use: "keygen",
Short: "rsa与ecdsa密钥生成工具",
Long: "rsa与ecdsa密钥生成工具支持加密私钥生成证书",
Run: func(cmd *cobra.Command, args []string) {
var err error
if startdate != "" {
k.StartDate, err = time.Parse("2006-01-02", startdate)
if err != nil {
starlog.Errorln(err)
os.Exit(1)
}
} else {
k.StartDate = time.Now()
}
if duration < 0 {
starlog.Errorln("duration should be positive")
os.Exit(1)
}
k.EndDate = k.StartDate.AddDate(0, 0, duration)
err = k.Gen()
if err != nil {
starlog.Errorln(err)
os.Exit(1)
}
starlog.Infoln("Key Generated,Ouput to", k.Outfolder)
},
}
var CmdEn = &cobra.Command{
Use: "pwd",
Short: "encrypt/change private key",
Run: func(cmd *cobra.Command, args []string) {
if !staros.Exists(path) {
starlog.Errorln("file not exists")
os.Exit(1)
}
data, err := os.ReadFile(path)
if err != nil {
starlog.Errorln("read file error:", err)
os.Exit(1)
}
priv, err := starcrypto.DecodePrivateKey(data, key)
if err != nil {
starlog.Errorln("decode private key error:", err)
os.Exit(1)
}
data, err = starcrypto.EncodePrivateKey(priv, secret)
if err != nil {
starlog.Errorln("encode private key error:", err)
os.Exit(1)
}
err = os.WriteFile(outpath, data, 0644)
if err != nil {
starlog.Errorln("write new file error:", err)
os.Exit(1)
}
starlog.Infoln("new key saved to", outpath)
},
}
var CmdPub = &cobra.Command{
Use: "pub",
Short: "通过私钥生成公钥",
Run: func(cmd *cobra.Command, args []string) {
var pub any
if !staros.Exists(path) {
starlog.Errorln("file not exists")
os.Exit(1)
}
data, err := os.ReadFile(path)
if err != nil {
starlog.Errorln("read file error:", err)
os.Exit(1)
}
priv, err := starcrypto.DecodePrivateKey(data, key)
if err != nil {
starlog.Errorln("decode private key error:", err)
os.Exit(1)
}
switch n := priv.(type) {
case *rsa.PrivateKey:
starlog.Infoln("found rsa private key")
pub = n.Public()
case *ecdsa.PrivateKey:
starlog.Infoln("found ecdsa private key")
pub = n.Public()
default:
starlog.Errorln("unknown private key type")
os.Exit(1)
}
if sshPub {
data, err = starcrypto.EncodeSSHPublicKey(pub)
if err != nil {
starlog.Errorln("encode ssh public key error:", err)
os.Exit(1)
}
} else {
data, err = starcrypto.EncodePublicKey(pub)
if err != nil {
starlog.Errorln("encode public key error:", err)
os.Exit(1)
}
}
starlog.Infoln("public key:", string(data))
err = os.WriteFile(outpath, data, 0644)
if err != nil {
starlog.Errorln("write public key error:", err)
os.Exit(1)
}
starlog.Infoln("public key saved to", outpath)
},
}

@ -0,0 +1,204 @@
package keygen
import (
"b612.me/starcrypto"
"b612.me/staros"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/hex"
"encoding/pem"
"errors"
"fmt"
"io"
"math/big"
"os"
"path/filepath"
"strings"
"time"
)
type KeyGen struct {
Type string
Encrypt string
Bits int
Outfolder string
Prefix string
Force bool
//
Country string
Locality string
Organization string
OrganizationalUnit string
CommonName string
StartDate time.Time
EndDate time.Time
}
func (k *KeyGen) Gen() error {
if !k.Force && staros.Exists(filepath.Join(k.Outfolder, k.Prefix+".crt")) {
return errors.New("crt file exists")
}
if !k.Force && staros.Exists(filepath.Join(k.Outfolder, k.Prefix)) {
return errors.New("key file exists")
}
if !k.Force && staros.Exists(filepath.Join(k.Outfolder, k.Prefix+".pub")) {
return errors.New("ssh pub file exists")
}
if !k.Force && staros.Exists(filepath.Join(k.Outfolder, k.Prefix+".key.pub")) {
return errors.New("pub file exists")
}
var sshPubByte, keyPubByte, keyPrivByte, Crt []byte
var priv, pub any
var err error
switch strings.ToLower(k.Type) {
case "rsa":
priv, pub, err = starcrypto.GenerateRsaKey(k.Bits)
if err != nil {
return err
}
case "ecdsa", "ecdh":
var cr elliptic.Curve
switch k.Bits {
case 224:
cr = elliptic.P224()
case 256:
cr = elliptic.P256()
case 384:
cr = elliptic.P384()
case 521:
cr = elliptic.P521()
default:
return errors.New("invalid bits,should be 224,256,384,521")
}
priv, pub, err = starcrypto.GenerateEcdsaKey(cr)
if err != nil {
return err
}
case "ed25519":
pub, priv, err = ed25519.GenerateKey(rand.Reader)
if err != nil {
return err
}
default:
return errors.New("invalid key type,only support rsa,ecdsa")
}
sshPubByte, err = starcrypto.EncodeSSHPublicKey(pub)
if err != nil {
return err
}
keyPubByte, err = starcrypto.EncodePublicKey(pub)
if err != nil {
return err
}
keyPrivByte, err = starcrypto.EncodePrivateKey(priv, k.Encrypt)
if err != nil {
return err
}
_, Crt, err = k.GenerateCert(priv)
if err != nil {
return err
}
err = os.WriteFile(filepath.Join(k.Outfolder, k.Prefix+".crt"), Crt, 0644)
if err != nil {
return err
}
err = os.WriteFile(filepath.Join(k.Outfolder, k.Prefix), keyPrivByte, 0644)
if err != nil {
return err
}
err = os.WriteFile(filepath.Join(k.Outfolder, k.Prefix+".pub"), sshPubByte, 0644)
if err != nil {
return err
}
err = os.WriteFile(filepath.Join(k.Outfolder, k.Prefix+".key.pub"), keyPubByte, 0644)
if err != nil {
return err
}
return nil
}
func (k *KeyGen) GenerateCert(priv crypto.PrivateKey) ([]byte, []byte, error) {
//csr,pub
tmpByte := make([]byte, 64)
io.ReadFull(rand.Reader, tmpByte)
hexStr := starcrypto.String(tmpByte)
data, _ := hex.DecodeString(hexStr)
num := new(big.Int).SetBytes(data)
var country, locality, organization, organizationalUnit []string
if k.Country != "" {
country = []string{k.Country}
}
if k.Locality != "" {
locality = []string{k.Locality}
}
if k.Organization != "" {
organization = []string{k.Organization}
}
if k.OrganizationalUnit != "" {
organizationalUnit = []string{k.OrganizationalUnit}
}
var rootCsr = &x509.Certificate{
Version: 3,
SerialNumber: num,
Subject: pkix.Name{
Country: country,
Locality: locality,
Organization: organization,
OrganizationalUnit: organizationalUnit,
CommonName: k.CommonName,
},
NotBefore: k.StartDate,
NotAfter: k.EndDate,
BasicConstraintsValid: true,
IsCA: false,
MaxPathLenZero: false,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement | x509.KeyUsageDigitalSignature,
}
var cert []byte
var err error
switch priv.(type) {
case *rsa.PrivateKey:
cert, err = MakeCert(priv.(*rsa.PrivateKey), rootCsr, rootCsr, priv.(*rsa.PrivateKey).Public())
if err != nil {
return nil, nil, err
}
case *ecdsa.PrivateKey:
cert, err = MakeCert(priv.(*ecdsa.PrivateKey), rootCsr, rootCsr, priv.(*ecdsa.PrivateKey).Public())
case ed25519.PrivateKey:
cert, err = MakeCert(priv.(ed25519.PrivateKey), rootCsr, rootCsr, priv.(ed25519.PrivateKey).Public())
default:
return nil, nil, errors.New("invalid private key type:" + fmt.Sprintf("%T", priv))
}
csrPem := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE REQUEST",
Bytes: rootCsr.Raw,
})
return csrPem, cert, nil
}
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
}

@ -0,0 +1,25 @@
package keygen
import (
"testing"
)
func TestKeyGen_Gen(t *testing.T) {
var k = KeyGen{
Type: "rsa",
Encrypt: "",
Bits: 2048,
Outfolder: ".",
Prefix: "mykey",
Force: true,
Country: "CN",
Locality: "Beijing",
Organization: "B612",
OrganizationalUnit: "B612",
CommonName: "Little Prince",
}
err := k.Gen()
if err != nil {
t.Error(err)
}
}

@ -1,39 +1,62 @@
package main
import (
"b612.me/apps/b612/aes"
"b612.me/apps/b612/attach"
"b612.me/apps/b612/base64"
"b612.me/apps/b612/base85"
"b612.me/apps/b612/base91"
"b612.me/apps/b612/calc"
"b612.me/apps/b612/cert"
"b612.me/apps/b612/detach"
"b612.me/apps/b612/df"
"b612.me/apps/b612/dfinder"
"b612.me/apps/b612/dns"
"b612.me/apps/b612/ftp"
"b612.me/apps/b612/generate"
"b612.me/apps/b612/hash"
"b612.me/apps/b612/httpreverse"
"b612.me/apps/b612/httproxy"
"b612.me/apps/b612/httpserver"
"b612.me/apps/b612/image"
"b612.me/apps/b612/keygen"
"b612.me/apps/b612/merge"
"b612.me/apps/b612/net"
"b612.me/apps/b612/rmt"
"b612.me/apps/b612/search"
"b612.me/apps/b612/smtpclient"
"b612.me/apps/b612/smtpserver"
"b612.me/apps/b612/socks5"
"b612.me/apps/b612/split"
"b612.me/apps/b612/tcping"
"b612.me/apps/b612/tls"
"b612.me/apps/b612/uac"
"b612.me/apps/b612/vic"
"b612.me/apps/b612/whois"
"b612.me/stario"
"b612.me/starlog"
"github.com/inconshreveable/mousetrap"
"github.com/spf13/cobra"
)
var cmdRoot = &cobra.Command{
Use: "b612",
Version: "2.0.1",
Version: "2.1.0.beta.6",
}
func init() {
cobra.MousetrapHelpText = ""
cmdRoot.AddCommand(tcping.Cmd, uac.Cmd, httpserver.Cmd, httpreverse.Cmd,
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)
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)
}
func main() {
starlog.SetLevelColor(starlog.LvError, []starlog.Attr{starlog.FgHiMagenta})
cmdRoot.Execute()
if mousetrap.StartedByExplorer() {
stario.StopUntil("Press Any Key to Continue...", "", true)
}
}

@ -1 +1,97 @@
package net
import (
"b612.me/apps/b612/netforward"
"b612.me/starlog"
"fmt"
"github.com/spf13/cobra"
"time"
)
var Cmd = &cobra.Command{
Use: "net",
Short: "网络工具包括nat穿透端口转发等",
}
func init() {
Cmd.AddCommand(netforward.CmdNetforward)
}
var natc NatClient
var nats NatServer
var dns, ipinfoaddr string
var timeout int
var maxHop int
var disableIpInfo bool
var bindAddr string
var hideIncorrect bool
func init() {
CmdNatClient.Flags().StringVarP(&natc.ServiceTarget, "target", "t", "", "forward server target address")
CmdNatClient.Flags().StringVarP(&natc.CmdTarget, "server", "s", "", "nat server command address")
CmdNatClient.Flags().StringVarP(&natc.Passwd, "passwd", "p", "", "password")
CmdNatClient.Flags().BoolVarP(&natc.enableTCP, "enable-tcp", "T", true, "enable tcp forward")
CmdNatClient.Flags().BoolVarP(&natc.enableUDP, "enable-udp", "U", true, "enable udp forward")
CmdNatClient.Flags().IntVarP(&natc.DialTimeout, "dial-timeout", "d", 10000, "dial timeout milliseconds")
CmdNatClient.Flags().IntVarP(&natc.UdpTimeout, "udp-timeout", "D", 60000, "udp connection timeout milliseconds")
Cmd.AddCommand(CmdNatClient)
CmdNatServer.Flags().StringVarP(&nats.ListenAddr, "listen", "l", "", "listen address")
CmdNatServer.Flags().StringVarP(&nats.Passwd, "passwd", "p", "", "password")
CmdNatServer.Flags().Int64VarP(&nats.UDPTimeout, "udp-timeout", "D", 60000, "udp connection timeout milliseconds")
CmdNatServer.Flags().Int64VarP(&nats.NetTimeout, "dial-timeout", "d", 10000, "dial timeout milliseconds")
CmdNatServer.Flags().BoolVarP(&nats.enableTCP, "enable-tcp", "T", true, "enable tcp forward")
CmdNatServer.Flags().BoolVarP(&nats.enableUDP, "enable-udp", "U", true, "enable udp forward")
Cmd.AddCommand(CmdNatServer)
CmdNetTrace.Flags().StringVarP(&dns, "dns", "d", "", "自定义dns服务器")
CmdNetTrace.Flags().StringVarP(&ipinfoaddr, "ipinfo", "i", "https://ip.b612.me/{ip}/detail", "自定义ip信息查询地址")
CmdNetTrace.Flags().IntVarP(&timeout, "timeout", "t", 800, "超时时间,单位毫秒")
CmdNetTrace.Flags().IntVarP(&maxHop, "max-hop", "m", 32, "最大跳数")
CmdNetTrace.Flags().BoolVarP(&disableIpInfo, "disable-ipinfo", "D", false, "禁用ip信息查询")
CmdNetTrace.Flags().StringVarP(&bindAddr, "bind", "b", "0.0.0.0", "绑定地址")
CmdNetTrace.Flags().BoolVarP(&hideIncorrect, "hide-incorrect", "H", false, "隐藏错误节点")
Cmd.AddCommand(CmdNetTrace, cmdSSHJar)
}
var CmdNatClient = &cobra.Command{
Use: "natc",
Short: "nat穿透客户端",
Run: func(cmd *cobra.Command, args []string) {
if natc.ServiceTarget == "" || natc.CmdTarget == "" {
cmd.Help()
return
}
natc.Run()
},
}
var CmdNatServer = &cobra.Command{
Use: "nats",
Short: "nat穿透服务端",
Run: func(cmd *cobra.Command, args []string) {
nats.Run()
},
}
var CmdNetTrace = &cobra.Command{
Use: "trace",
Short: "网络路径追踪",
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 {
cmd.Help()
return
}
if disableIpInfo {
ipinfoaddr = ""
}
for _, target := range args {
starlog.Infoln("Traceroute to ", target)
Traceroute(target, bindAddr, dns, maxHop, time.Millisecond*time.Duration(timeout), ipinfoaddr, hideIncorrect)
fmt.Println("-----------------------------")
}
},
}

@ -1,213 +0,0 @@
package net
import (
"b612.me/starlog"
"context"
"errors"
"fmt"
"io"
"net"
"sync"
"sync/atomic"
"time"
)
type NetForward struct {
LocalAddr string
LocalPort int
RemoteURI string
EnableTCP bool
EnableUDP bool
DialTimeout time.Duration
UDPTimeout time.Duration
stopCtx context.Context
stopFn context.CancelFunc
running int32
}
func (n *NetForward) Close() {
n.stopFn()
}
func (n *NetForward) Run() error {
if !atomic.CompareAndSwapInt32(&n.running, 0, 1) {
return errors.New("already running")
}
n.stopCtx, n.stopFn = context.WithCancel(context.Background())
if n.DialTimeout == 0 {
n.DialTimeout = time.Second * 10
}
var wg sync.WaitGroup
if n.EnableTCP {
wg.Add(1)
go func() {
defer wg.Done()
n.runTCP()
}()
}
if n.EnableUDP {
wg.Add(1)
go func() {
defer wg.Done()
n.runUDP()
}()
}
wg.Wait()
return nil
}
func (n *NetForward) runTCP() error {
listen, err := net.Listen("tcp", fmt.Sprintf("%s:%d", n.LocalAddr, n.LocalPort))
if err != nil {
starlog.Errorln("Listening On Tcp Failed:", err)
return err
}
go func() {
<-n.stopCtx.Done()
listen.Close()
}()
starlog.Infof("Listening TCP on %v\n", fmt.Sprintf("%s:%d", n.LocalAddr, n.LocalPort))
for {
conn, err := listen.Accept()
if err != nil {
continue
}
log := starlog.Std.NewFlag()
log.Infof("Accept New TCP Conn from %v\n", conn.RemoteAddr().String())
go func(conn net.Conn) {
rmt, err := net.DialTimeout("tcp", n.RemoteURI, n.DialTimeout)
if err != nil {
log.Errorf("Dial Remote %s Failed:%v\n", n.RemoteURI, err)
conn.Close()
return
}
log.Infof("Connect %s <==> %s\n", conn.RemoteAddr().String(), n.RemoteURI)
Copy(rmt, conn)
log.Noticef("Connection Closed %s <==> %s", conn.RemoteAddr().String(), n.RemoteURI)
}(conn)
}
}
type UDPConn struct {
net.Conn
listen *net.UDPConn
remoteAddr *net.UDPAddr
lastbeat int64
}
func (u UDPConn) Write(p []byte) (n int, err error) {
u.lastbeat = time.Now().Unix()
return u.Conn.Write(p)
}
func (u UDPConn) Read(p []byte) (n int, err error) {
u.lastbeat = time.Now().Unix()
return u.Conn.Read(p)
}
func (u UDPConn) Work() {
buf := make([]byte, 8192)
for {
count, err := u.Read(buf)
if err != nil {
u.Close()
u.lastbeat = 0
return
}
_, err = u.listen.Write(buf[0:count])
if err != nil {
u.lastbeat = 0
return
}
}
}
func (n *NetForward) runUDP() error {
var mu sync.RWMutex
udpAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%v", n.LocalAddr, n.LocalPort))
if err != nil {
return err
}
listen, err := net.ListenUDP("udp", udpAddr)
if err != nil {
return err
}
starlog.Infof("Listening UDP on %v\n", fmt.Sprintf("%s:%d", n.LocalAddr, n.LocalPort))
go func() {
<-n.stopCtx.Done()
listen.Close()
}()
udpMap := make(map[string]UDPConn)
go func() {
for {
select {
case <-n.stopCtx.Done():
return
case <-time.After(time.Second * 60):
mu.Lock()
for k, v := range udpMap {
if time.Now().Unix() > int64(n.UDPTimeout.Seconds())+v.lastbeat {
delete(udpMap, k)
starlog.Noticef("Connection Closed %s <==> %s", v.remoteAddr.String(), n.RemoteURI)
}
}
mu.Unlock()
}
}
}()
buf := make([]byte, 8192)
for {
count, rmt, err := listen.ReadFromUDP(buf)
if err != nil || rmt.String() == n.RemoteURI {
continue
}
go func(data []byte, rmt *net.UDPAddr) {
log := starlog.Std.NewFlag()
mu.Lock()
addr, ok := udpMap[rmt.String()]
if !ok {
log.Infof("Accept New UDP Conn from %v\n", rmt.String())
conn, err := net.Dial("udp", n.RemoteURI)
if err != nil {
log.Errorf("Dial Remote %s Failed:%v\n", n.RemoteURI, err)
mu.Unlock()
return
}
addr = UDPConn{
Conn: conn,
remoteAddr: rmt,
listen: listen,
lastbeat: time.Now().Unix(),
}
udpMap[rmt.String()] = addr
go addr.Work()
log.Infof("Connect %s <==> %s\n", rmt.String(), n.RemoteURI)
}
mu.Unlock()
_, err := addr.Write(data)
if err != nil {
mu.Lock()
addr.Close()
delete(udpMap, addr.remoteAddr.String())
mu.Unlock()
log.Noticef("Connection Closed %s <==> %s", rmt.String(), n.RemoteURI)
}
}(buf[0:count], rmt)
}
}
func Copy(dst, src net.Conn) {
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
io.Copy(dst, src)
}()
go func() {
defer wg.Done()
io.Copy(src, dst)
}()
wg.Wait()
dst.Close()
src.Close()
}

@ -1,16 +0,0 @@
package net
import "testing"
func TestForward(t *testing.T) {
var f = NetForward{
LocalAddr: "127.0.0.1",
LocalPort: 22232,
RemoteURI: "127.0.0.1:1127",
EnableTCP: true,
EnableUDP: true,
DialTimeout: 0,
UDPTimeout: 0,
}
f.Run()
}

@ -0,0 +1,29 @@
package net
import (
"testing"
"time"
)
func TestNat(t *testing.T) {
var s = NatServer{
ListenAddr: "0.0.0.0:10020",
enableTCP: true,
enableUDP: true,
}
var c = NatClient{
ServiceTarget: "dns.b612.me:521",
CmdTarget: "127.0.0.1:10020",
enableTCP: true,
enableUDP: true,
}
go s.Run()
go c.Run()
for {
time.Sleep(time.Second * 20)
}
}
func TestTrace(t *testing.T) {
//Traceroute("b612.me", "", 32, time.Millisecond*800, "https://ip.b612.me/{ip}/detail")
}

@ -1,27 +1,343 @@
package net
import (
"b612.me/starlog"
"bytes"
"context"
"crypto/sha256"
"io"
"net"
"sync"
"time"
)
type SimpleNatClient struct {
type NatClient struct {
mu sync.RWMutex
cmdTCPConn net.Conn
cmdUDPConn *net.UDPAddr
cmdUDPConn *net.UDPConn
ServiceTarget string
CmdTarget string
tcpAlived bool
DialTimeout int
UdpTimeout int
enableTCP bool
enableUDP bool
Passwd string
udpAlived bool
stopCtx context.Context
stopFn context.CancelFunc
}
func (s *SimpleNatClient) tcpCmdConn() net.Conn {
func (s *NatClient) tcpCmdConn() net.Conn {
s.mu.RLock()
defer s.mu.RUnlock()
return s.cmdTCPConn
}
func (s *SimpleNatClient) tcpCmdConnAlived() bool {
func (s *NatClient) udpCmdConn() *net.UDPConn {
s.mu.RLock()
defer s.mu.RUnlock()
return s.cmdUDPConn
}
func (s *NatClient) tcpCmdConnAlived() bool {
s.mu.RLock()
defer s.mu.RUnlock()
return s.tcpAlived
}
func (s *NatClient) setTcpCmdConnAlived(v bool) {
s.mu.Lock()
defer s.mu.Unlock()
s.tcpAlived = v
}
func (s *NatClient) udpCmdConnAlived() bool {
s.mu.RLock()
defer s.mu.RUnlock()
return s.udpAlived
}
func (s *NatClient) setUdpCmdConnAlived(v bool) {
s.mu.Lock()
defer s.mu.Unlock()
s.udpAlived = v
}
func (s *NatClient) Run() error {
s.stopCtx, s.stopFn = context.WithCancel(context.Background())
if s.DialTimeout == 0 {
s.DialTimeout = 10000
}
if s.Passwd != "" {
MSG_CMD_HELLO = sha256.New().Sum(append(MSG_CMD_HELLO, []byte(s.Passwd)...))[:16]
}
var wg sync.WaitGroup
if s.enableUDP {
wg.Add(1)
go func() {
defer wg.Done()
s.runUdp()
}()
}
if s.enableTCP {
wg.Add(1)
go func() {
defer wg.Done()
s.runTcp()
}()
}
wg.Wait()
return nil
}
func (s *NatClient) runTcp() error {
var err error
starlog.Noticeln("nat client tcp module start run")
for {
select {
case <-s.stopCtx.Done():
if s.cmdTCPConn != nil {
s.setTcpCmdConnAlived(false)
s.cmdTCPConn.Close()
return nil
}
case <-time.After(time.Millisecond * 1500):
}
if s.cmdTCPConn != nil && s.tcpCmdConnAlived() {
continue
}
s.cmdTCPConn, err = net.DialTimeout("tcp", s.CmdTarget, time.Millisecond*time.Duration(s.DialTimeout))
if err != nil {
starlog.Errorf("dail remote tcp cmd server %v fail:%v;will retry\n", s.CmdTarget, err)
time.Sleep(time.Second * 2)
s.cmdTCPConn = nil
continue
}
starlog.Infoln("dail remote tcp cmd server ok,remote:", s.CmdTarget)
s.tcpCmdConn().Write(MSG_CMD_HELLO)
s.setTcpCmdConnAlived(true)
go s.handleTcpCmdConn(s.tcpCmdConn())
}
}
func (s *NatClient) runUdp() error {
starlog.Noticeln("nat client udp module start run")
if s.UdpTimeout == 0 {
s.UdpTimeout = 600000
}
for {
select {
case <-s.stopCtx.Done():
if s.cmdTCPConn != nil {
s.setUdpCmdConnAlived(false)
s.cmdUDPConn.Close()
return nil
}
case <-time.After(time.Millisecond * 3000):
}
if s.cmdUDPConn != nil && s.udpCmdConnAlived() {
continue
}
rmt, err := net.ResolveUDPAddr("udp", s.CmdTarget)
if err != nil {
starlog.Errorf("dail remote udp cmd server %v fail:%v;will retry\n", s.CmdTarget, err)
time.Sleep(time.Second * 2)
continue
}
s.cmdUDPConn, err = net.DialUDP("udp", nil, rmt)
if err != nil {
starlog.Errorf("dail remote udp cmd server %v fail:%v;will retry\n", s.CmdTarget, err)
time.Sleep(time.Second * 2)
s.cmdTCPConn = nil
continue
}
starlog.Infoln("dail remote udp cmd server ok,remote:", s.CmdTarget)
s.udpCmdConn().Write(MSG_CMD_HELLO)
s.setUdpCmdConnAlived(true)
go s.handleUdpCmdConn(s.udpCmdConn())
}
}
func (s *NatClient) handleUdpCmdConn(conn *net.UDPConn) {
for {
header := make([]byte, 16)
_, err := io.ReadFull(conn, header)
if err != nil {
starlog.Infoln("udp cmd server read fail:", err)
conn.Close()
s.setUdpCmdConnAlived(false)
return
}
if bytes.Equal(header, MSG_CMD_HELLO_REPLY) {
continue
}
if bytes.Equal(header, MSG_NEW_CONN_HELLO) {
go s.newRemoteUdpConn()
}
if bytes.Equal(header, MSG_HEARTBEAT) {
_, err = conn.Write(MSG_HEARTBEAT)
if err != nil {
conn.Close()
s.setUdpCmdConnAlived(false)
return
}
}
}
}
func (s *NatClient) handleTcpCmdConn(conn net.Conn) {
for {
header := make([]byte, 16)
_, err := io.ReadFull(conn, header)
if err != nil {
starlog.Infoln("tcp cmd server read fail:", err)
conn.Close()
s.setTcpCmdConnAlived(false)
return
}
if bytes.Equal(header, MSG_CMD_HELLO_REPLY) {
continue
}
if bytes.Equal(header, MSG_NEW_CONN_HELLO) {
go s.newRemoteTcpConn()
}
if bytes.Equal(header, MSG_HEARTBEAT) {
_, err = conn.Write(MSG_HEARTBEAT)
if err != nil {
conn.Close()
s.setTcpCmdConnAlived(false)
return
}
}
}
}
func (s *NatClient) newRemoteTcpConn() {
log := starlog.Std.NewFlag()
starlog.Infoln("recv request,create new tcp conn")
nconn, err := net.DialTimeout("tcp", s.CmdTarget, time.Millisecond*time.Duration(s.DialTimeout))
if err != nil {
log.Errorf("dail server tcp conn %v fail:%v\n", s.CmdTarget, err)
return
}
_, err = nconn.Write(MSG_NEW_CONN_HELLO)
if err != nil {
nconn.Close()
log.Errorf("write new tcp client hello to server %v fail:%v\n", s.CmdTarget, err)
return
}
cconn, err := net.DialTimeout("tcp", s.ServiceTarget, time.Millisecond*time.Duration(s.DialTimeout))
if err != nil {
log.Errorf("dail remote tcp conn %v fail:%v\n", s.CmdTarget, err)
nconn.Close()
return
}
go func() {
for {
data := make([]byte, 8192)
nconn.SetReadDeadline(time.Now().Add(time.Millisecond * time.Duration(s.UdpTimeout)))
n, err := nconn.Read(data)
if err != nil {
starlog.Infoln("read from tcp server fail:", nconn.RemoteAddr(), err)
nconn.Close()
cconn.Close()
return
}
_, err = cconn.Write(data[:n])
//starlog.Debugln("write to udp client:", p, err, cconn.LocalAddr(), cconn.RemoteAddr())
if err != nil {
starlog.Infoln("write to tcp client fail:", cconn.RemoteAddr(), err)
nconn.Close()
cconn.Close()
return
}
}
}()
go func() {
for {
data := make([]byte, 8192)
cconn.SetReadDeadline(time.Now().Add(time.Millisecond * time.Duration(s.UdpTimeout)))
n, err := cconn.Read(data)
if err != nil {
starlog.Infoln("read from tcp server fail:", cconn.RemoteAddr(), err)
nconn.Close()
cconn.Close()
return
}
_, err = nconn.Write(data[:n])
if err != nil {
starlog.Infoln("write to tcp client fail:", nconn.RemoteAddr(), err)
nconn.Close()
cconn.Close()
return
}
}
}()
}
func (s *NatClient) newRemoteUdpConn() {
log := starlog.Std.NewFlag()
starlog.Infoln("recv request,create new udp conn")
rmt, err := net.ResolveUDPAddr("udp", s.CmdTarget)
if err != nil {
log.Errorf("dail server udp conn %v fail:%v\n", s.CmdTarget, err)
return
}
nconn, err := net.DialUDP("udp", nil, rmt)
if err != nil {
log.Errorf("dail server udp conn %v fail:%v\n", s.CmdTarget, err)
return
}
log.Infof("dail server udp conn %v ok\n", s.CmdTarget)
_, err = nconn.Write(MSG_NEW_CONN_HELLO)
if err != nil {
nconn.Close()
log.Errorf("write new udp client hello to server %v fail:%v\n", s.CmdTarget, err)
return
}
rmt, err = net.ResolveUDPAddr("udp", s.ServiceTarget)
if err != nil {
log.Errorf("dail server udp conn %v fail:%v\n", s.ServiceTarget, err)
return
}
cconn, err := net.DialUDP("udp", nil, rmt)
if err != nil {
log.Errorf("dail remote udp conn %v fail:%v\n", s.ServiceTarget, err)
return
}
log.Infof("dail remote udp conn %v ok\n", s.ServiceTarget)
go func() {
for {
data := make([]byte, 8192)
nconn.SetReadDeadline(time.Now().Add(time.Millisecond * time.Duration(s.UdpTimeout)))
n, err := nconn.Read(data)
if err != nil {
starlog.Infoln("read from udp server fail:", err)
return
}
_, err = cconn.Write(data[:n])
//starlog.Debugln("write to udp client:", p, err, cconn.LocalAddr(), cconn.RemoteAddr())
if err != nil {
starlog.Infoln("write to udp client fail:", err)
return
}
}
}()
go func() {
for {
data := make([]byte, 8192)
cconn.SetReadDeadline(time.Now().Add(time.Millisecond * time.Duration(s.UdpTimeout)))
n, err := cconn.Read(data)
if err != nil {
starlog.Infoln("read from udp server fail:", err)
return
}
_, err = nconn.Write(data[:n])
if err != nil {
starlog.Infoln("write to udp client fail:", err)
return
}
}
}()
}

@ -3,136 +3,379 @@ package net
import (
"b612.me/starlog"
"bytes"
"errors"
"context"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"net"
"strings"
"sync"
"sync/atomic"
"time"
)
var MSG_CMD_HELLO = []byte{11, 27, 19, 96, 182, 18, 25, 150, 17, 39}
var MSG_NEW_CONN = []byte{0, 0, 0, 0, 255, 255, 255, 255, 11, 27}
var MSG_NEW_CONN_REQ = []byte{0, 0, 0, 0, 255, 255, 255, 255, 19, 96}
var MSG_CLOSE = []byte{255, 255, 0, 0, 255, 0, 0, 255, 255, 27}
var MSG_HEARTBEAT = []byte{6, 66, 66, 6, 6, 66, 6, 66, 11, 27}
// MSG_CMD_HELLO 控制链路主动链接参头 16byte
var MSG_CMD_HELLO, _ = hex.DecodeString("B6121127AF7ECDA11965122519670220")
var MSG_CMD_HELLO_REPLY, _ = hex.DecodeString("B6121127AF7ECDA22002200820112014")
type SimpleNatServer struct {
mu sync.RWMutex
// MSG_NEW_CONN_HELLO 交链路主动连接头 16byte
var MSG_NEW_CONN_HELLO, _ = hex.DecodeString("B6121127AF7ECDFF201820202022B612")
// MSG_HEARTBEAT 心跳报文 16byte
var MSG_HEARTBEAT, _ = hex.DecodeString("B612112704011008B612112704011008")
type NatServer struct {
sync.RWMutex
cmdTCPConn net.Conn
cmdUDPConn *net.UDPAddr
listenTcp net.Listener
listenUDP *net.UDPConn
Addr string
Port int
udpConnMap sync.Map
udpPairMap sync.Map
udpCmdAddr *net.UDPAddr
ListenAddr string
lastTCPHeart int64
lastUDPHeart int64
Passwd string
DialTimeout int64
NetTimeout int64
UDPTimeout int64
running int32
tcpConnPool chan net.Conn
tcpAlived bool
tcpConnPool chan net.Conn
udpConnPool chan addionData
stopCtx context.Context
stopFn context.CancelFunc
enableTCP bool
enableUDP bool
}
func (s *SimpleNatServer) getConnfromTCPPool() (net.Conn, error) {
select {
case conn := <-s.tcpConnPool:
return conn, nil
case <-time.After(time.Second * 10):
return nil, errors.New("no connection got")
func (n *NatServer) Run() error {
if n.running != 0 {
return fmt.Errorf("Server Already Run")
}
n.stopCtx, n.stopFn = context.WithCancel(context.Background())
if n.NetTimeout == 0 {
n.NetTimeout = 10000
}
if n.Passwd != "" {
MSG_CMD_HELLO = sha256.New().Sum(append(MSG_CMD_HELLO, []byte(n.Passwd)...))[:16]
}
var wg sync.WaitGroup
if n.enableUDP {
wg.Add(1)
go func() {
defer wg.Done()
n.runUdpListen()
}()
}
if n.enableTCP {
wg.Add(1)
go func() {
defer wg.Done()
n.runTcpListen()
}()
}
wg.Wait()
return nil
}
func (s *SimpleNatServer) tcpCmdConn() net.Conn {
s.mu.RLock()
defer s.mu.RUnlock()
return s.cmdTCPConn
}
func (s *SimpleNatServer) tcpCmdConnAlived() bool {
s.mu.RLock()
defer s.mu.RUnlock()
return s.tcpAlived
}
func (s *SimpleNatServer) listenTCP() error {
func (n *NatServer) runTcpListen() error {
var err error
s.tcpConnPool = make(chan net.Conn, 10)
s.listenTcp, err = net.Listen("tcp", fmt.Sprintf("%s:d", s.Addr, s.Port))
n.tcpConnPool = make(chan net.Conn, 128)
atomic.AddInt32(&n.running, 1)
defer atomic.AddInt32(&n.running, -1)
starlog.Infoln("nat server tcp listener start run")
n.listenTcp, err = net.Listen("tcp", n.ListenAddr)
if err != nil {
starlog.Errorln("failed to listen tcp", err)
starlog.Errorln("nat server tcp listener start failed:", err)
return err
}
msgChan := make(chan []byte, 16)
for {
conn, err := s.listenTcp.Accept()
conn, err := n.listenTcp.Accept()
if err != nil {
continue
}
if s.tcpCmdConnAlived() {
go s.tcpClientServe(conn.(*net.TCPConn))
var ok bool
if n.cmdTCPConn == nil {
if conn, ok = n.checkIsTcpControlConn(conn); ok {
n.cmdTCPConn = conn
conn.Write(MSG_CMD_HELLO_REPLY)
go n.handleTcpControlConn(conn, msgChan)
continue
}
}
if conn, ok = n.checkIsTcpNewConn(conn); ok {
starlog.Noticef("new tcp cmd conn is client conn %v\n", conn.RemoteAddr().String())
n.tcpConnPool <- conn
continue
}
go s.waitingForTCPCmd(conn.(*net.TCPConn))
starlog.Noticef("new tcp cmd conn is not client conn %v\n", conn.RemoteAddr().String())
go func() {
msgChan <- MSG_NEW_CONN_HELLO
}()
go n.pairNewClientConn(conn)
}
return nil
}
func (s *SimpleNatServer) tcpClientServe(conn *net.TCPConn) {
if !s.tcpCmdConnAlived() {
conn.Close()
return
func (n *NatServer) runUdpListen() error {
var err error
atomic.AddInt32(&n.running, 1)
defer atomic.AddInt32(&n.running, -1)
starlog.Infoln("nat server udp listener start run")
if n.UDPTimeout == 0 {
n.UDPTimeout = 120
}
n.udpConnPool = make(chan addionData, 128)
udpListenAddr, err := net.ResolveUDPAddr("udp", n.ListenAddr)
if err != nil {
starlog.Errorln("nat server udp listener start failed:", err)
return err
}
n.listenUDP, err = net.ListenUDP("udp", udpListenAddr)
if err != nil {
starlog.Errorln("nat server tcp listener start failed:", err)
return err
}
go func() {
for {
select {
case <-n.stopCtx.Done():
if n.listenUDP != nil {
n.listenUDP.Close()
}
case <-time.After(time.Second * 30):
if time.Now().Unix()-n.lastUDPHeart > n.UDPTimeout {
if n.udpCmdAddr != nil {
n.udpCmdAddr = nil
}
}
if n.udpCmdAddr != nil {
n.listenUDP.WriteToUDP(MSG_HEARTBEAT, n.udpCmdAddr)
}
n.udpConnMap.Range(func(key, value interface{}) bool {
if time.Now().Unix()-value.(addionData).lastHeartbeat > n.UDPTimeout {
if taregt, ok := n.udpPairMap.Load(key); ok {
n.udpConnMap.Delete(taregt)
n.udpPairMap.Delete(taregt)
}
n.udpConnMap.Delete(key)
n.udpPairMap.Delete(key)
}
return true
})
}
}
}()
for {
data := make([]byte, 8192)
c, udpAddr, err := n.listenUDP.ReadFromUDP(data)
if err != nil {
continue
}
n.handleUdpData(udpAddr, data[:c])
}
}
if strings.Split(conn.RemoteAddr().String(), ":")[0] == strings.Split(s.tcpCmdConn().RemoteAddr().String(), ":")[0] {
conn.SetReadDeadline(time.Now().Add(5 * time.Second))
cmdBuf := make([]byte, 10)
if _, err := io.ReadFull(conn, cmdBuf); err == nil {
conn.SetReadDeadline(time.Time{})
if bytes.Equal(cmdBuf, MSG_NEW_CONN) {
starlog.Noticef("Nat Server Recv New Client Conn From %v\n", conn.RemoteAddr().String())
s.tcpConnPool <- conn
type addionData struct {
lastHeartbeat int64
Addr *net.UDPAddr
MsgFrom []byte
}
func (n *NatServer) handleUdpData(addr *net.UDPAddr, data []byte) {
starlog.Infoln("handle udp data from:", addr.String())
if addr.String() == n.udpCmdAddr.String() && len(data) >= 16 {
if bytes.Equal(data[:16], MSG_HEARTBEAT) {
starlog.Infoln("recv udp cmd heartbeat")
n.lastUDPHeart = time.Now().Unix()
}
return
}
if n.udpCmdAddr == nil {
if len(data) >= 16 && bytes.Equal(data[:16], MSG_CMD_HELLO) {
starlog.Infof("recv udp cmd hello from %v\n", addr.String())
n.udpCmdAddr = addr
n.lastUDPHeart = time.Now().Unix()
n.listenUDP.WriteToUDP(MSG_CMD_HELLO_REPLY, addr)
return
}
}
if _, ok := n.udpConnMap.Load(addr.IP.String()); ok {
if target, ok := n.udpPairMap.Load(addr.IP.String()); ok {
starlog.Infof("found udp pair data %v <=====> %v\n", addr.String(), target.(*net.UDPAddr).String())
rmt := target.(*net.UDPAddr)
if _, ok := n.udpConnMap.Load(rmt.IP.String()); !ok {
n.udpConnMap.Delete(addr.IP.String())
n.udpPairMap.Delete(addr.IP.String())
n.udpPairMap.Delete(rmt.IP.String())
starlog.Errorf("udp pair data %v <=====> %v fail,remote not found\n", addr.String(), rmt.String())
return
}
tmp, _ := n.udpConnMap.Load(addr.IP.String())
current := tmp.(addionData)
current.lastHeartbeat = time.Now().Unix()
n.udpConnMap.Store(addr.IP.String(), current)
return
}
conn.SetReadDeadline(time.Time{})
}
starlog.Noticef("Nat Server Recv New Side Conn From %v\n", conn.RemoteAddr().String())
_, err := s.tcpCmdConn().Write(MSG_NEW_CONN_REQ)
if err != nil {
s.mu.Lock()
s.cmdTCPConn.Close()
s.tcpAlived = false
s.mu.Unlock()
starlog.Errorf("Failed to Write CMD To Client:%v\n", err)
return
if len(data) >= 16 {
if bytes.Equal(data[:16], MSG_NEW_CONN_HELLO) {
starlog.Infof("recv new udp conn hello from %v\n", addr.String())
if len(data) < 16 {
data = data[16:]
} else {
data = []byte{}
}
n.udpConnMap.Store(addr.IP.String(), addionData{
lastHeartbeat: time.Now().Unix(),
Addr: addr,
})
n.udpConnPool <- addionData{
lastHeartbeat: time.Now().Unix(),
Addr: addr,
MsgFrom: data,
}
return
}
}
reverse, err := s.getConnfromTCPPool()
if err != nil {
starlog.Errorf("Nat Server Conn to %v Closed %v\n", conn.RemoteAddr(), err)
conn.Close()
starlog.Infof("wait pair udp conn %v\n", addr.String())
if n.udpCmdAddr == nil {
starlog.Infof("wait pair udp conn %v fail,cmd addr is nil\n", addr.String())
return
} else {
n.listenUDP.WriteToUDP(MSG_NEW_CONN_HELLO, n.udpCmdAddr)
}
starlog.Infof("Nat Server Conn %v<==>%v Connected\n", conn.RemoteAddr(), reverse.RemoteAddr())
Copy(reverse, conn)
starlog.Warningf("Nat Server Conn %v<==>%v Closed\n", conn.RemoteAddr(), reverse.RemoteAddr())
go func() {
pairAddr := <-n.udpConnPool
n.udpConnMap.Store(addr.String(), addionData{
lastHeartbeat: time.Now().Unix(),
Addr: addr,
})
n.udpPairMap.Store(addr.IP.String(), pairAddr.Addr)
n.udpPairMap.Store(pairAddr.Addr.String(), addr.IP)
starlog.Infof("pair udp conn %v <=====> %v\n", addr.String(), pairAddr.Addr.String())
if len(pairAddr.MsgFrom) > 0 {
n.listenUDP.WriteToUDP(pairAddr.MsgFrom, addr)
}
n.listenUDP.WriteToUDP(data, pairAddr.Addr)
}()
}
func (s *SimpleNatServer) waitingForTCPCmd(conn *net.TCPConn) {
conn.SetReadDeadline(time.Now().Add(time.Duration(s.DialTimeout) * time.Second))
cmdBuf := make([]byte, 10)
if _, err := io.ReadFull(conn, cmdBuf); err != nil {
func (n *NatServer) pairNewClientConn(conn net.Conn) {
log := starlog.Std.NewFlag()
log.Noticef("start pair tcp cmd conn %v\n", conn.RemoteAddr().String())
select {
case <-time.After(time.Millisecond * time.Duration(n.NetTimeout)):
log.Errorln("pair new conn fail,wait timeout,conn is:", conn)
conn.Close()
return
case nconn := <-n.tcpConnPool:
log.Infof("pair %v <======> %v ok\n", conn.RemoteAddr().String(), nconn.RemoteAddr().String())
go func() {
defer nconn.Close()
defer conn.Close()
io.Copy(nconn, conn)
}()
go func() {
defer nconn.Close()
defer conn.Close()
io.Copy(conn, nconn)
}()
return
}
if bytes.Equal(cmdBuf, MSG_CMD_HELLO) {
s.mu.Lock()
s.cmdTCPConn = conn
s.tcpAlived = true
conn.SetKeepAlive(true)
conn.SetKeepAlivePeriod(time.Second * 20)
s.mu.Unlock()
}
func (n *NatServer) handleTcpControlConn(conn net.Conn, msg chan []byte) {
go func() {
for {
select {
case data := <-msg:
_, err := conn.Write(data)
if err != nil {
conn.Close()
n.cmdTCPConn = nil
return
}
case <-time.After(time.Minute):
_, err := conn.Write(MSG_HEARTBEAT)
if err != nil {
conn.Close()
n.cmdTCPConn = nil
return
}
}
}
}()
for {
header := make([]byte, 16)
_, err := io.ReadFull(conn, header)
if err != nil {
conn.Close()
n.cmdTCPConn = nil
return
}
if bytes.Equal(header, MSG_HEARTBEAT) {
n.lastTCPHeart = time.Now().Unix()
}
continue
}
}
func (n *NatServer) checkIsTcpControlConn(conn net.Conn) (net.Conn, bool) {
log := starlog.Std.NewFlag()
log.Noticef("start check tcp cmd conn %v\n", conn.RemoteAddr().String())
header := make([]byte, 16)
conn.SetReadDeadline(time.Now().Add(time.Millisecond * 1200))
count, err := io.ReadFull(conn, header)
conn.SetReadDeadline(time.Time{})
if err == nil {
if bytes.Equal(header, MSG_CMD_HELLO) {
log.Infof("check tcp cmd conn success:%v\n", conn.RemoteAddr().String())
return conn, true
}
}
log.Infof("check tcp cmd conn fail:%v %v\n", conn.RemoteAddr().String(), err)
return NewCensorConn(header[:count], conn), false
}
func (n *NatServer) checkIsTcpNewConn(conn net.Conn) (net.Conn, bool) {
if n.cmdTCPConn == nil {
return conn, false
}
remoteIp := strings.Split(n.cmdTCPConn.RemoteAddr().String(), ":")[0]
newConnIp := strings.Split(conn.RemoteAddr().String(), ":")[0]
if remoteIp != newConnIp {
return conn, false
}
header := make([]byte, 16)
conn.SetReadDeadline(time.Now().Add(time.Millisecond * 1200))
read, err := io.ReadFull(conn, header)
conn.SetReadDeadline(time.Time{})
if err == nil {
if bytes.Equal(header, MSG_NEW_CONN_HELLO) {
return conn, true
}
}
return NewCensorConn(header[:read], conn), false
}
type censorConn struct {
reader io.Reader
conn net.Conn
}
func NewCensorConn(header []byte, conn net.Conn) censorConn {
return censorConn{
reader: io.MultiReader(bytes.NewReader(header), conn),
conn: conn,
}
}
func (c censorConn) Read(p []byte) (int, error) { return c.reader.Read(p) }
func (c censorConn) Write(p []byte) (int, error) { return c.conn.Write(p) }
func (c censorConn) Close() error { return c.conn.Close() }
func (c censorConn) LocalAddr() net.Addr { return c.conn.LocalAddr() }
func (c censorConn) RemoteAddr() net.Addr { return c.conn.RemoteAddr() }
func (c censorConn) SetDeadline(t time.Time) error { return c.conn.SetDeadline(t) }
func (c censorConn) SetReadDeadline(t time.Time) error { return c.conn.SetReadDeadline(t) }
func (c censorConn) SetWriteDeadline(t time.Time) error { return c.conn.SetWriteDeadline(t) }

@ -0,0 +1,35 @@
//go:build darwin
package net
import (
"net"
"syscall"
)
func SetTcpInfo(conn *net.TCPConn, usingKeepAlive bool, keepAliveIdel, keepAlivePeriod, keepAliveCount, userTimeout int) error {
rawConn, err := conn.SyscallConn()
if err != nil {
return err
}
if usingKeepAlive {
err = rawConn.Control(func(fd uintptr) {
err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, syscall.TCP_KEEPALIVE, keepAliveIdel)
if err != nil {
return
}
err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, 0x101, keepAlivePeriod)
if err != nil {
return
}
})
} else {
err = conn.SetKeepAlive(false)
}
if userTimeout > 0 {
err = rawConn.Control(func(fd uintptr) {
err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, 0x12, userTimeout)
})
}
return err
}

@ -0,0 +1,39 @@
//go:build !(windows && darwin)
package net
import (
"net"
"syscall"
)
func SetTcpInfo(conn *net.TCPConn, usingKeepAlive bool, keepAliveIdel, keepAlivePeriod, keepAliveCount, userTimeout int) error {
rawConn, err := conn.SyscallConn()
if err != nil {
return err
}
if usingKeepAlive {
err = rawConn.Control(func(fd uintptr) {
err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, syscall.TCP_KEEPIDLE, keepAliveIdel)
if err != nil {
return
}
err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, syscall.TCP_KEEPINTVL, keepAlivePeriod)
if err != nil {
return
}
err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, syscall.TCP_KEEPCNT, keepAliveCount)
if err != nil {
return
}
})
} else {
err = conn.SetKeepAlive(false)
}
if userTimeout > 0 {
err = rawConn.Control(func(fd uintptr) {
err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, 0x12, userTimeout)
})
}
return err
}

@ -0,0 +1,33 @@
//go:build windows
package net
import (
"net"
"os"
"runtime"
"syscall"
"unsafe"
)
func SetTcpInfo(conn *net.TCPConn, usingKeepAlive bool, keepAliveIdel, keepAlivePeriod, keepAliveCount, userTimeout int) error {
if usingKeepAlive {
rawConn, err := conn.SyscallConn()
if err != nil {
return err
}
err = rawConn.Control(func(fd uintptr) {
ka := syscall.TCPKeepalive{
OnOff: 1,
Time: uint32(keepAliveIdel),
Interval: uint32(keepAlivePeriod),
}
ret := uint32(0)
size := uint32(unsafe.Sizeof(ka))
err = syscall.WSAIoctl(syscall.Handle(fd), syscall.SIO_KEEPALIVE_VALS, (*byte)(unsafe.Pointer(&ka)), size, nil, 0, &ret, nil, 0)
runtime.KeepAlive(fd)
})
return os.NewSyscallError("wsaioctl", err)
}
return conn.SetKeepAlive(false)
}

@ -0,0 +1,145 @@
package net
import (
"b612.me/starcrypto"
"b612.me/starlog"
"b612.me/starnet"
"crypto/elliptic"
"encoding/json"
"fmt"
"github.com/spf13/cobra"
"golang.org/x/crypto/ssh"
"net"
"os"
"os/signal"
"strings"
)
var (
listenAddr string
keyFile string
KeyPasswd string
outpath string
curlUrl string
curlArg []string
)
func init() {
cmdSSHJar.Flags().StringVarP(&listenAddr, "listen", "l", "0.0.0.0:22", "监听地址")
cmdSSHJar.Flags().StringVarP(&keyFile, "key", "k", "", "私钥文件")
cmdSSHJar.Flags().StringVarP(&KeyPasswd, "passwd", "p", "", "私钥密码")
cmdSSHJar.Flags().StringVarP(&outpath, "output", "o", "", "输出文件")
}
var cmdSSHJar = &cobra.Command{
Use: "sshjar",
Short: "SSH蜜罐",
Long: "SSH蜜罐",
Run: func(cmd *cobra.Command, args []string) {
runSSHHoneyJar(listenAddr, keyFile, KeyPasswd, outpath)
},
}
func runSSHHoneyJar(listenAddr, keyFile, KeyPasswd, outpath string) {
var f *os.File
var err error
if outpath != "" {
f, err = os.OpenFile(outpath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
starlog.Errorf("Failed to open file %s (%s)", outpath, err)
return
}
}
defer f.Close()
config := &ssh.ServerConfig{
// 密码验证回调函数
PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
starlog.Infof("Login attempt from %s with %s %s\n", c.RemoteAddr(), c.User(), string(pass))
data := []string{c.RemoteAddr().String(), c.User(), string(pass)}
bts, _ := json.Marshal(data)
if f != nil {
f.Write(bts)
f.Write([]byte("\n"))
}
if curlUrl != "" {
go func() {
data := map[string]string{
"ip": c.RemoteAddr().String(),
"user": c.User(),
"passwd": string(pass),
}
if curlArg != nil && len(curlArg) > 0 {
for _, v := range curlArg {
args := strings.SplitN(v, ":", 2)
if len(args) == 2 {
data[args[0]] = args[1]
}
}
starnet.Curl(starnet.NewRequests(curlUrl, []byte(starnet.BuildQuery(data)), "POST"))
}
}()
}
return nil, fmt.Errorf("password rejected for %q", c.User())
},
PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
return nil, fmt.Errorf("public key rejected for %q", conn.User())
},
}
if keyFile == "" {
secKey, _, err := starcrypto.GenerateEcdsaKey(elliptic.P256())
if err != nil {
starlog.Errorf("Failed to generate ECDSA key (%s)", err)
return
}
key, err := ssh.NewSignerFromKey(secKey)
if err != nil {
starlog.Errorf("Failed to generate signer from key (%s)", err)
return
}
config.AddHostKey(key)
} else {
keyByte, err := os.ReadFile(keyFile)
if err != nil {
starlog.Errorf("Failed to read private key from %s (%s)", keyFile, err)
return
}
var key ssh.Signer
if KeyPasswd != "" {
key, err = ssh.ParsePrivateKeyWithPassphrase(keyByte, []byte(KeyPasswd))
} else {
key, err = ssh.ParsePrivateKey(keyByte)
}
if err != nil {
starlog.Errorf("Failed to load private key from %s (%s)", keyFile, err)
return
}
config.AddHostKey(key)
}
listener, err := net.Listen("tcp", listenAddr)
if err != nil {
starlog.Errorf("Failed to listen on %s (%s)", listenAddr, err)
return
}
starlog.Noticeln("SSH HoneyJar is listening on", listenAddr)
sig := make(chan os.Signal, 1)
signal.Notify(sig, os.Interrupt, os.Kill)
for {
select {
case <-sig:
starlog.Noticef("SSH HoneyJar is shutting down")
listener.Close()
return
default:
}
conn, err := listener.Accept()
if err != nil {
continue
}
starlog.Infof("New connection from %s\n", conn.RemoteAddr())
go func(conn net.Conn) {
ssh.NewServerConn(conn, config)
conn.Close()
}(conn)
}
}

@ -0,0 +1,7 @@
package net
import "testing"
func TestSSHJar(t *testing.T) {
//runSSHHoneyJar("0.0.0.0:22")
}

@ -0,0 +1,246 @@
package net
import (
"b612.me/stario"
"b612.me/starlog"
"context"
"encoding/hex"
"fmt"
"net"
"os"
"path/filepath"
"runtime"
"strings"
"time"
)
type TcpClient struct {
LocalAddr string
RemoteAddr string
UsingKeepAlive bool
KeepAlivePeriod int
KeepAliveIdel int
KeepAliveCount int
Interactive bool
UserTimeout int
ShowRecv bool
ShowAsHex bool
SaveToFolder string
Rmt *TcpConn
LogPath string
stopCtx context.Context
stopFn context.CancelFunc
}
func (s *TcpClient) Close() error {
return s.Rmt.Close()
}
func (s *TcpClient) handleInteractive() {
var currentCmd string
notifyMap := make(map[string]chan struct{})
if !s.Interactive {
return
}
starlog.Infoln("Interactive mode enabled")
for {
select {
case <-s.stopCtx.Done():
starlog.Infoln("Interactive mode stopped due to context done")
return
default:
}
cmd := stario.MessageBox("", "").MustString()
if cmd == "" {
continue
}
cmdf := strings.Fields(cmd)
switch cmdf[0] {
case "hex":
currentCmd = "hex"
starlog.Infoln("Switch to hex mode,send hex to remote client")
case "text":
currentCmd = "text"
starlog.Infoln("Switch to text mode,send text to remote client")
case "close":
if s.Rmt.TCPConn == nil {
starlog.Errorln("No client selected")
continue
}
s.Rmt.TCPConn.Close()
starlog.Infof("Client %s closed\n", s.Rmt.RemoteAddr().String())
s.Rmt = nil
currentCmd = ""
case "startauto":
if s.Rmt == nil {
starlog.Errorln("No client selected")
continue
}
notifyMap[s.Rmt.RemoteAddr().String()] = make(chan struct{})
go func(conn *TcpConn) {
for {
select {
case <-notifyMap[conn.RemoteAddr().String()]:
starlog.Infoln("Auto send stopped")
return
default:
}
_, err := conn.Write([]byte(strings.Repeat("B612", 256)))
if err != nil {
starlog.Errorln("Write error:", err)
return
}
}
}(s.Rmt)
starlog.Infoln("Auto send started")
case "closeauto":
if s.Rmt == nil {
starlog.Errorln("No client selected")
continue
}
close(notifyMap[s.Rmt.RemoteAddr().String()])
case "send":
if s.Rmt == nil {
starlog.Errorln("No client selected")
continue
}
if currentCmd == "hex" {
data, err := hex.DecodeString(strings.TrimSpace(strings.TrimPrefix(cmd, "send")))
if err != nil {
starlog.Errorln("Hex decode error:", err)
continue
}
_, err = s.Rmt.Write(data)
if err != nil {
starlog.Errorln("Write error:", err)
} else {
if s.Rmt.f != nil {
s.Rmt.f.Write([]byte(time.Now().String() + " send\n"))
s.Rmt.f.Write(data)
s.Rmt.f.Write([]byte("\n"))
}
}
} else {
_, err := s.Rmt.Write([]byte(strings.TrimSpace(strings.TrimPrefix(cmd, "send"))))
if err != nil {
starlog.Errorln("Write error:", err)
} else {
if s.Rmt.f != nil {
s.Rmt.f.Write([]byte(time.Now().String() + " send\n"))
s.Rmt.f.Write([]byte(cmdf[1]))
s.Rmt.f.Write([]byte("\n"))
}
}
}
starlog.Infof("Send to %s success\n", s.Rmt.RemoteAddr().String())
}
}
}
func (s *TcpClient) Run() error {
var err error
s.stopCtx, s.stopFn = context.WithCancel(context.Background())
if s.LogPath != "" {
err := starlog.SetLogFile(s.LogPath, starlog.Std, true)
if err != nil {
starlog.Errorln("SetLogFile error:", err)
return fmt.Errorf("SetLogFile error: %w", err)
}
}
var localAddr *net.TCPAddr
if s.LocalAddr != "" {
localAddr, err = net.ResolveTCPAddr("tcp", s.LocalAddr)
if err != nil {
starlog.Errorln("ResolveTCPAddr error:", err)
return fmt.Errorf("ResolveTCPAddr error: %w", err)
}
}
remoteAddr, err := net.ResolveTCPAddr("tcp", s.RemoteAddr)
if err != nil {
starlog.Errorln("ResolveTCPAddr error:", err)
return fmt.Errorf("ResolveTCPAddr error: %w", err)
}
conn, err := net.DialTCP("tcp", localAddr, remoteAddr)
if err != nil {
starlog.Errorln("Dial TCP error:", err)
return fmt.Errorf("Dial TCP error: %w", err)
}
starlog.Infof("Connected to %s LocalAddr: %s\n", conn.RemoteAddr().String(), conn.LocalAddr().String())
if s.Interactive {
go s.handleInteractive()
}
s.Rmt = s.getTcpConn(conn)
s.handleConn(s.Rmt)
return nil
}
func (s *TcpClient) getTcpConn(conn *net.TCPConn) *TcpConn {
var err error
var f *os.File
if s.SaveToFolder != "" {
f, err = os.Create(filepath.Join(s.SaveToFolder, strings.ReplaceAll(conn.RemoteAddr().String(), ":", "_")))
if err != nil {
starlog.Errorf("Create file error for %s: %v\n", conn.RemoteAddr().String(), err)
}
}
return &TcpConn{
TCPConn: conn,
f: f,
}
}
func (s *TcpClient) handleConn(conn *TcpConn) {
var err error
log := starlog.Std.NewFlag()
err = SetTcpInfo(conn.TCPConn, s.UsingKeepAlive, s.KeepAliveIdel, s.KeepAlivePeriod, s.KeepAliveCount, s.UserTimeout)
if err != nil {
log.Errorf("SetTcpInfo error for %s: %v\n", conn.RemoteAddr().String(), err)
conn.Close()
return
}
log.Infof("SetKeepAlive success for %s\n", conn.RemoteAddr().String())
log.Infof("KeepAlivePeriod: %d, KeepAliveIdel: %d, KeepAliveCount: %d, UserTimeout: %d\n", s.KeepAlivePeriod, s.KeepAliveIdel, s.KeepAliveCount, s.UserTimeout)
if runtime.GOOS != "linux" {
log.Warningln("keepAliveCount and userTimeout only work on linux")
}
for {
select {
case <-s.stopCtx.Done():
log.Infof("Connection from %s closed due to context done\n", conn.RemoteAddr().String())
conn.Close()
return
default:
}
buf := make([]byte, 8192)
n, err := conn.Read(buf)
if err != nil {
log.Errorf("Read error for %s: %v\n", conn.RemoteAddr().String(), err)
conn.Close()
return
}
if n > 0 {
if s.ShowRecv {
if s.ShowAsHex {
log.Printf("Recv from %s: %x\n", conn.RemoteAddr().String(), buf[:n])
} else {
log.Printf("Recv from %s: %s\n", conn.RemoteAddr().String(), string(buf[:n]))
}
}
if conn.f != nil {
conn.f.Write([]byte(time.Now().String() + " recv\n"))
conn.f.Write(buf[:n])
conn.f.Write([]byte("\n"))
}
}
}
}
func (s *TcpClient) Stop() {
s.stopFn()
if s.Rmt != nil {
s.Rmt.Close()
}
}

@ -0,0 +1,80 @@
package net
import (
"b612.me/starlog"
"github.com/spf13/cobra"
"os"
"os/signal"
"time"
)
var (
tcps TcpServer
tcpc TcpClient
)
func init() {
CmdTcps.Flags().StringVarP(&tcps.LocalAddr, "local", "l", "0.0.0.0:29127", "本地地址")
CmdTcps.Flags().BoolVarP(&tcps.UsingKeepAlive, "keepalive", "k", true, "启用KeepAlive")
CmdTcps.Flags().IntVarP(&tcps.KeepAlivePeriod, "keepalive-period", "p", 10, "KeepAlive重试周期")
CmdTcps.Flags().IntVarP(&tcps.KeepAliveIdel, "keepalive-idel", "i", 15, "KeepAlive空闲时间")
CmdTcps.Flags().IntVarP(&tcps.KeepAliveCount, "keepalive-count", "c", 3, "KeepAlive次数")
CmdTcps.Flags().BoolVarP(&tcps.Interactive, "interactive", "I", false, "交互模式")
CmdTcps.Flags().IntVarP(&tcps.UserTimeout, "user-timeout", "u", 0, "用户超时时间(毫秒)")
CmdTcps.Flags().BoolVarP(&tcps.ShowRecv, "show-recv", "r", true, "显示接收数据")
CmdTcps.Flags().BoolVarP(&tcps.ShowAsHex, "show-hex", "H", false, "显示十六进制")
CmdTcps.Flags().StringVarP(&tcps.SaveToFolder, "save", "s", "", "保存到文件夹")
CmdTcps.Flags().StringVarP(&tcps.LogPath, "log", "L", "", "日志文件路径")
Cmd.AddCommand(CmdTcps)
CmdTcpc.Flags().StringVarP(&tcpc.LocalAddr, "local", "l", "", "本地地址")
CmdTcpc.Flags().BoolVarP(&tcpc.UsingKeepAlive, "keepalive", "k", true, "启用KeepAlive")
CmdTcpc.Flags().IntVarP(&tcpc.KeepAlivePeriod, "keepalive-period", "p", 1, "KeepAlive重试周期")
CmdTcpc.Flags().IntVarP(&tcpc.KeepAliveIdel, "keepalive-idel", "i", 15, "KeepAlive空闲时间")
CmdTcpc.Flags().IntVarP(&tcpc.KeepAliveCount, "keepalive-count", "c", 3, "KeepAlive次数")
CmdTcpc.Flags().BoolVarP(&tcpc.Interactive, "interactive", "I", false, "交互模式")
CmdTcpc.Flags().IntVarP(&tcpc.UserTimeout, "user-timeout", "u", 0, "用户超时时间(毫秒)")
CmdTcpc.Flags().BoolVarP(&tcpc.ShowRecv, "show-recv", "r", true, "显示接收数据")
CmdTcpc.Flags().BoolVarP(&tcpc.ShowAsHex, "show-hex", "H", false, "显示十六进制")
CmdTcpc.Flags().StringVarP(&tcpc.SaveToFolder, "save", "s", "", "保存到文件夹")
CmdTcpc.Flags().StringVarP(&tcpc.LogPath, "log", "L", "", "日志文件路径")
Cmd.AddCommand(CmdTcpc)
}
var CmdTcps = &cobra.Command{
Use: "tcps",
Short: "TCP服务端",
Run: func(cmd *cobra.Command, args []string) {
go func() {
s := make(chan os.Signal, 1)
signal.Notify(s, os.Interrupt, os.Kill)
<-s
tcps.Stop()
time.Sleep(5 * time.Second)
os.Exit(0)
}()
tcps.Run()
},
}
var CmdTcpc = &cobra.Command{
Use: "tcpc",
Short: "TCP客户端",
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 {
starlog.Errorln("请指定目标地址")
return
}
tcpc.RemoteAddr = args[0]
go func() {
s := make(chan os.Signal, 1)
signal.Notify(s, os.Interrupt, os.Kill)
<-s
tcpc.Stop()
time.Sleep(5 * time.Second)
os.Exit(0)
}()
tcpc.Run()
},
}

@ -0,0 +1,285 @@
package net
import (
"b612.me/stario"
"b612.me/starlog"
"context"
"encoding/hex"
"fmt"
"net"
"os"
"path/filepath"
"runtime"
"strings"
"sync"
"time"
)
type TcpConn struct {
*net.TCPConn
f *os.File
}
type TcpServer struct {
LocalAddr string
UsingKeepAlive bool
KeepAlivePeriod int
KeepAliveIdel int
KeepAliveCount int
sync.Mutex
Clients map[string]*TcpConn
Interactive bool
UserTimeout int
ShowRecv bool
ShowAsHex bool
SaveToFolder string
Listen *net.TCPListener
LogPath string
stopCtx context.Context
stopFn context.CancelFunc
}
func (s *TcpServer) Close() error {
return s.Listen.Close()
}
func (s *TcpServer) handleInteractive() {
var conn *TcpConn
var currentCmd string
notifyMap := make(map[string]chan struct{})
if !s.Interactive {
return
}
starlog.Infoln("Interactive mode enabled")
for {
select {
case <-s.stopCtx.Done():
starlog.Infoln("Interactive mode stopped due to context done")
return
default:
}
cmd := stario.MessageBox("", "").MustString()
if cmd == "" {
continue
}
cmdf := strings.Fields(cmd)
switch cmdf[0] {
case "list":
s.Lock()
for k, v := range s.Clients {
starlog.Green("Client %s: %s\n", k, v.RemoteAddr().String())
}
s.Unlock()
case "use":
if len(cmdf) < 2 {
starlog.Errorln("use command need a client address")
continue
}
conn = s.Clients[cmdf[1]]
if conn == nil {
starlog.Errorln("Client not found")
continue
}
starlog.Infof("Using client %s\n", conn.RemoteAddr().String())
case "hex":
currentCmd = "hex"
starlog.Infoln("Switch to hex mode,send hex to remote client")
case "text":
currentCmd = "text"
starlog.Infoln("Switch to text mode,send text to remote client")
case "close":
if conn.TCPConn == nil {
starlog.Errorln("No client selected")
continue
}
conn.TCPConn.Close()
starlog.Infof("Client %s closed\n", conn.RemoteAddr().String())
conn = nil
currentCmd = ""
case "startauto":
if conn == nil {
starlog.Errorln("No client selected")
continue
}
notifyMap[conn.RemoteAddr().String()] = make(chan struct{})
go func(conn *TcpConn) {
for {
select {
case <-notifyMap[conn.RemoteAddr().String()]:
starlog.Infoln("Auto send stopped")
return
default:
}
_, err := conn.Write([]byte(strings.Repeat("B612", 256)))
if err != nil {
starlog.Errorln("Write error:", err)
return
}
}
}(conn)
starlog.Infoln("Auto send started")
case "closeauto":
if conn == nil {
starlog.Errorln("No client selected")
continue
}
close(notifyMap[conn.RemoteAddr().String()])
case "send":
if conn == nil {
starlog.Errorln("No client selected")
continue
}
if currentCmd == "hex" {
data, err := hex.DecodeString(strings.TrimSpace(strings.TrimPrefix(cmd, "send")))
if err != nil {
starlog.Errorln("Hex decode error:", err)
continue
}
_, err = conn.Write(data)
if err != nil {
starlog.Errorln("Write error:", err)
} else {
if conn.f != nil {
conn.f.Write([]byte(time.Now().String() + " send\n"))
conn.f.Write(data)
conn.f.Write([]byte("\n"))
}
}
} else {
_, err := conn.Write([]byte(strings.TrimSpace(strings.TrimPrefix(cmd, "send"))))
if err != nil {
starlog.Errorln("Write error:", err)
} else {
if conn.f != nil {
conn.f.Write([]byte(time.Now().String() + " send\n"))
conn.f.Write([]byte(cmdf[1]))
conn.f.Write([]byte("\n"))
}
}
}
starlog.Infof("Send to %s success\n", conn.RemoteAddr().String())
}
}
}
func (s *TcpServer) Run() error {
s.stopCtx, s.stopFn = context.WithCancel(context.Background())
if s.LogPath != "" {
err := starlog.SetLogFile(s.LogPath, starlog.Std, true)
if err != nil {
starlog.Errorln("SetLogFile error:", err)
return fmt.Errorf("SetLogFile error: %w", err)
}
}
s.Clients = make(map[string]*TcpConn)
tcpAddr, err := net.ResolveTCPAddr("tcp", s.LocalAddr)
if err != nil {
starlog.Errorln("ResolveTCPAddr error:", err)
return fmt.Errorf("ResolveTCPAddr error: %w", err)
}
s.Listen, err = net.ListenTCP("tcp", tcpAddr)
if err != nil {
starlog.Errorln("ListenTCP error:", err)
return fmt.Errorf("ListenTCP error: %w", err)
}
starlog.Infof("TcpServer listen on %s\n", s.LocalAddr)
if s.Interactive {
go s.handleInteractive()
}
for {
select {
case <-s.stopCtx.Done():
starlog.Infoln("TcpServer stopped due to context done")
return s.Listen.Close()
default:
}
conn, err := s.Listen.AcceptTCP()
if err != nil {
starlog.Errorln("AcceptTCP error:", err)
continue
}
starlog.Infof("Accept new connection from %s", conn.RemoteAddr().String())
s.Lock()
s.Clients[conn.RemoteAddr().String()] = s.getTcpConn(conn)
s.Unlock()
go s.handleConn(s.Clients[conn.RemoteAddr().String()])
}
}
func (s *TcpServer) getTcpConn(conn *net.TCPConn) *TcpConn {
var err error
var f *os.File
if s.SaveToFolder != "" {
f, err = os.Create(filepath.Join(s.SaveToFolder, strings.ReplaceAll(conn.RemoteAddr().String(), ":", "_")))
if err != nil {
starlog.Errorf("Create file error for %s: %v\n", conn.RemoteAddr().String(), err)
}
}
return &TcpConn{
TCPConn: conn,
f: f,
}
}
func (s *TcpServer) handleConn(conn *TcpConn) {
var err error
log := starlog.Std.NewFlag()
err = SetTcpInfo(conn.TCPConn, s.UsingKeepAlive, s.KeepAliveIdel, s.KeepAlivePeriod, s.KeepAliveCount, s.UserTimeout)
if err != nil {
log.Errorf("SetTcpInfo error for %s: %v\n", conn.RemoteAddr().String(), err)
s.Lock()
delete(s.Clients, conn.RemoteAddr().String())
s.Unlock()
conn.Close()
return
}
log.Infof("SetKeepAlive success for %s\n", conn.RemoteAddr().String())
log.Infof("KeepAlivePeriod: %d, KeepAliveIdel: %d, KeepAliveCount: %d, UserTimeout: %d\n", s.KeepAlivePeriod, s.KeepAliveIdel, s.KeepAliveCount, s.UserTimeout)
if runtime.GOOS != "linux" {
log.Warningln("keepAliveCount and userTimeout only work on linux")
}
for {
select {
case <-s.stopCtx.Done():
log.Infof("Connection from %s closed due to context done\n", conn.RemoteAddr().String())
s.Lock()
delete(s.Clients, conn.RemoteAddr().String())
s.Unlock()
conn.Close()
return
default:
}
buf := make([]byte, 8192)
n, err := conn.Read(buf)
if err != nil {
log.Errorf("Read error for %s: %v\n", conn.RemoteAddr().String(), err)
s.Lock()
delete(s.Clients, conn.RemoteAddr().String())
s.Unlock()
conn.Close()
return
}
if n > 0 {
if s.ShowRecv {
if s.ShowAsHex {
log.Printf("Recv from %s: %x\n", conn.RemoteAddr().String(), buf[:n])
} else {
log.Printf("Recv from %s: %s\n", conn.RemoteAddr().String(), string(buf[:n]))
}
}
if conn.f != nil {
conn.f.Write([]byte(time.Now().String() + " recv\n"))
conn.f.Write(buf[:n])
conn.f.Write([]byte("\n"))
}
}
}
}
func (s *TcpServer) Stop() {
s.stopFn()
if s.Listen != nil {
s.Close()
}
}

@ -0,0 +1,237 @@
package net
import (
"b612.me/starlog"
"b612.me/starnet"
"context"
"encoding/json"
"fmt"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
"net"
"strings"
"sync/atomic"
"time"
)
func useCustomeDNS(dns []string) {
resolver := net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (conn net.Conn, err error) {
for _, addr := range dns {
if conn, err = net.Dial("udp", addr+":53"); err != nil {
continue
} else {
return conn, nil
}
}
return
},
}
net.DefaultResolver = &resolver
}
func Traceroute(address string, bindaddr string, dns string, maxHops int, timeout time.Duration, ipinfoAddr string, hideIncorrect bool) {
ipinfo := net.ParseIP(address)
if ipinfo == nil {
{
if dns != "" {
useCustomeDNS([]string{dns})
starlog.Infoln("使用自定义DNS服务器", dns)
} else {
starlog.Infoln("使用系统默认DNS服务器")
}
addr, err := net.ResolveIPAddr("ip", address)
if err != nil {
starlog.Errorln("IP地址解析失败", address, err)
return
}
starlog.Infoln("解析IP地址", addr.String())
address = addr.String()
}
}
traceroute(address, bindaddr, maxHops, timeout, ipinfoAddr, hideIncorrect)
}
func traceroute(address string, bindaddr string, maxHops int, timeout time.Duration, ipinfoAddr string, hideIncorrect bool) {
ipinfo := net.ParseIP(address)
if ipinfo == nil {
starlog.Errorln("IP地址解析失败", address)
return
}
var echoType icmp.Type = ipv4.ICMPTypeEcho
var exceededType icmp.Type = ipv4.ICMPTypeTimeExceeded
var replyType icmp.Type = ipv4.ICMPTypeEchoReply
var proto = 1
var network = "ip4:icmp"
var resolveIP = "ip4"
if ipinfo.To4() == nil {
network = "ip6:ipv6-icmp"
resolveIP = "ip6"
echoType = ipv6.ICMPTypeEchoRequest
exceededType = ipv6.ICMPTypeTimeExceeded
replyType = ipv6.ICMPTypeEchoReply
proto = 58
}
if bindaddr == "" {
bindaddr = "0.0.0.0"
}
c, err := icmp.ListenPacket(network, bindaddr)
if err != nil {
fmt.Println(err)
return
}
defer c.Close()
if maxHops == 0 {
maxHops = 32
}
firstTargetHop := int32(maxHops + 1)
if timeout == 0 {
timeout = time.Second * 3
}
exitfor:
for i := 1; i <= maxHops; i++ {
retry := 0
doRetry:
dst, err := net.ResolveIPAddr(resolveIP, address)
if err != nil {
starlog.Errorln("IP地址解析失败", address, err)
return
}
if atomic.LoadInt32(&firstTargetHop) <= int32(i) {
return
}
m := icmp.Message{
Type: echoType, Code: 0,
Body: &icmp.Echo{
ID: i, Seq: i,
Data: []byte("B612.ME-ROUTER-TRACE"),
},
}
b, err := m.Marshal(nil)
if err != nil {
fmt.Printf("%d\tMarshal error: %v\n", i, err)
continue
}
if network == "ip4:icmp" {
if err := c.IPv4PacketConn().SetTTL(i); err != nil {
fmt.Printf("%d\tSetTTL error: %v\n", i, err)
continue
}
} else {
if err := c.IPv6PacketConn().SetHopLimit(i); err != nil {
fmt.Printf("%d\tSetHopLimit error: %v\n", i, err)
continue
}
}
start := time.Now()
n, err := c.WriteTo(b, dst)
if err != nil {
fmt.Printf("%d\tWriteTo error: %v\n", i, err)
continue
} else if n != len(b) {
fmt.Printf("%d\tWrite Short: %v Expected: %v\n", i, n, len(b))
continue
}
now := time.Now()
exitrecheck:
for {
reply := make([]byte, 1500)
err = c.SetReadDeadline(time.Now().Add(timeout))
if err != nil {
fmt.Printf("%d\tSetReadDeadline error: %v\n", i, err)
break
}
n, peer, err := c.ReadFrom(reply)
if err != nil {
fmt.Printf("%d\tReadFrom error: %v\n", i, err)
break
}
duration := time.Since(start)
rm, err := icmp.ParseMessage(proto, reply[:n])
if err != nil {
fmt.Printf("%d\tParseMessage error: %v\n", i, err)
break
}
switch rm.Type {
case exceededType:
fmt.Printf("%d\thops away:\t%s\t(%s) %s\n", i, peer, duration, GetIPInfo(peer.String(), ipinfoAddr))
break exitrecheck
case replyType:
fmt.Printf("%d\thops away:\t%s\t(%s) %s\n", i, peer, duration, GetIPInfo(peer.String(), ipinfoAddr))
if peer.String() == dst.String() {
break exitfor
}
case ipv4.ICMPTypeEcho, ipv6.ICMPTypeEchoRequest:
if time.Now().Sub(now).Seconds() > timeout.Seconds() {
if retry < 1 {
retry++
goto doRetry
}
if !hideIncorrect {
fmt.Printf("%d\tInvalid Echo Request:%s (%s) %s\n", i, peer, duration, GetIPInfo(peer.String(), ipinfoAddr))
}
break exitrecheck
}
case ipv4.ICMPTypeDestinationUnreachable, ipv6.ICMPTypeDestinationUnreachable:
if time.Now().Sub(now).Seconds() > timeout.Seconds() {
if retry < 1 {
retry++
goto doRetry
}
if !hideIncorrect {
fmt.Printf("%d\tInvalid DstInv Request:%s (%s) %s\n", i, peer, duration, GetIPInfo(peer.String(), ipinfoAddr))
}
break exitrecheck
}
default:
if time.Now().Sub(now).Seconds() > timeout.Seconds() {
if retry < 1 {
retry++
goto doRetry
}
if !hideIncorrect {
fmt.Printf("%d\tgot %+v from %v (%s) %s\n", i, rm.Type, peer, duration, GetIPInfo(peer.String(), ipinfoAddr))
}
break exitrecheck
}
}
}
}
}
func GetIPInfo(ip string, addr string) string {
if addr == "" {
return ""
}
uri := strings.ReplaceAll(addr, "{ip}", ip)
res, err := starnet.Curl(starnet.NewRequests(uri, nil, "GET", starnet.WithTimeout(time.Second*2), starnet.WithDialTimeout(time.Second*3)))
if err != nil {
return "获取IP信息失败" + err.Error()
}
var ipinfo IPInfo
err = json.Unmarshal(res.RecvData, &ipinfo)
if err != nil {
return "解析IP信息失败" + err.Error()
}
return fmt.Sprintf("%s %s %s %s %s", ipinfo.CountryName, ipinfo.RegionName, ipinfo.CityName, ipinfo.OwnerDomain, ipinfo.ISP)
}
type IPInfo struct {
CountryName string `json:"country_name"`
RegionName string `json:"region_name"`
CityName string `json:"city_name"`
OwnerDomain string `json:"owner_domain"`
Ip string `json:"ip"`
ISP string `json:"isp_domain"`
Err string `json:"err"`
}

@ -0,0 +1,77 @@
package netforward
import (
"b612.me/stario"
"b612.me/starlog"
"github.com/spf13/cobra"
"os"
"os/signal"
"strings"
"time"
)
var f = new(NetForward)
var dialTimeout, udpTimeout int64
func init() {
CmdNetforward.Flags().IntVarP(&f.DelayToward, "delay-toward", "T", 0, "delay toward milliseconds,0 for both,1 for local,2 for remote")
CmdNetforward.Flags().BoolVarP(&f.StdinMode, "stdin", "s", false, "enable stdin mode")
CmdNetforward.Flags().IntVarP(&f.DelayMilSec, "delay", "S", 0, "delay milliseconds")
CmdNetforward.Flags().StringVarP(&f.LocalAddr, "local", "l", "0.0.0.0", "bind address")
CmdNetforward.Flags().IntVarP(&f.LocalPort, "port", "p", 11270, "local listen port")
CmdNetforward.Flags().BoolVarP(&f.EnableTCP, "enable-tcp-forward", "t", true, "enable tcp forward mode")
CmdNetforward.Flags().BoolVarP(&f.EnableUDP, "enable-udp-forward", "u", true, "enable udp forward mode")
CmdNetforward.Flags().Int64VarP(&dialTimeout, "dial-timeout", "d", 10000, "dial timeout milliseconds")
CmdNetforward.Flags().Int64VarP(&udpTimeout, "udp-timeout", "D", 60000, "udp connection timeout milliseconds")
CmdNetforward.Flags().BoolVarP(&f.UsingKeepAlive, "keepalive", "k", true, "enable keepalive")
CmdNetforward.Flags().IntVarP(&f.KeepAlivePeriod, "keepalive-period", "P", 10, "keepalive retry period (seconds)")
CmdNetforward.Flags().IntVarP(&f.KeepAliveIdel, "keepalive-idel", "I", 15, "keepalive idel time (seconds)")
CmdNetforward.Flags().IntVarP(&f.KeepAliveCount, "keepalive-count", "C", 3, "keepalive count")
CmdNetforward.Flags().IntVarP(&f.UserTimeout, "user-timeout", "U", 0, "user timeout (milliseconds)")
CmdNetforward.Flags().BoolVarP(&f.IgnoreEof, "ignore-eof", "E", false, "ignore eof")
}
var CmdNetforward = &cobra.Command{
Use: "forward",
Short: "端口转发工具",
Long: "端口转发工具支持tcp和udp转发",
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 {
starlog.Errorln("please enter a target uri")
os.Exit(1)
}
f.RemoteURI = strings.TrimSpace(args[0])
if dialTimeout == 0 {
dialTimeout = 10000
}
if udpTimeout == 0 {
udpTimeout = 60000
}
f.DialTimeout = time.Duration(dialTimeout) * time.Millisecond
f.UDPTimeout = time.Duration(udpTimeout) * time.Millisecond
if err := f.Run(); err != nil {
starlog.Errorln("run net forward failed:", err)
os.Exit(2)
}
time.Sleep(time.Millisecond * 500)
sign := make(chan os.Signal)
signal.Notify(sign, os.Interrupt, os.Kill)
for {
select {
case <-sign:
starlog.Noticeln("Recv Stop Signal From User")
f.stopFn()
case <-stario.WaitUntilFinished(func() error {
for {
if f.Status() == 0 {
return nil
}
time.Sleep(time.Second)
}
}):
starlog.Infoln("Service Stoped")
return
}
}
},
}

@ -0,0 +1,357 @@
package netforward
import (
"b612.me/stario"
"b612.me/starlog"
"context"
"errors"
"fmt"
"io"
"net"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
)
type NetForward struct {
LocalAddr string
LocalPort int
RemoteURI string
EnableTCP bool
EnableUDP bool
DelayMilSec int
DelayToward int
StdinMode bool
IgnoreEof bool
DialTimeout time.Duration
UDPTimeout time.Duration
stopCtx context.Context
stopFn context.CancelFunc
running int32
KeepAlivePeriod int
KeepAliveIdel int
KeepAliveCount int
UserTimeout int
UsingKeepAlive bool
}
func (n *NetForward) Close() {
n.stopFn()
}
func (n *NetForward) Status() int32 {
return atomic.LoadInt32(&n.running)
}
func (n *NetForward) Run() error {
if n.running > 0 {
starlog.Errorln("already running")
return errors.New("already running")
}
n.stopCtx, n.stopFn = context.WithCancel(context.Background())
if n.DialTimeout == 0 {
n.DialTimeout = time.Second * 5
}
if n.StdinMode {
go func() {
for {
cmd := strings.TrimSpace(stario.MessageBox("", "").MustString())
for strings.Contains(cmd, " ") {
cmd = strings.Replace(cmd, " ", " ", -1)
}
starlog.Debugf("Recv Command %s\n", cmd)
cmds := strings.Split(cmd, " ")
if len(cmds) < 3 {
starlog.Errorln("Invalid Command", cmd)
continue
}
switch cmds[0] + cmds[1] {
case "setremote":
n.RemoteURI = cmds[2]
starlog.Noticef("Remote URI Set to %s\n", n.RemoteURI)
case "setdelaytoward":
tmp, err := strconv.Atoi(cmds[2])
if err != nil {
starlog.Errorln("Invalid Delay Toward Value", cmds[2])
continue
}
n.DelayToward = tmp
starlog.Noticef("Delay Toward Set to %d\n", n.DelayToward)
case "setdelay":
tmp, err := strconv.Atoi(cmds[2])
if err != nil {
starlog.Errorln("Invalid Delay Value", cmds[2])
continue
}
n.DelayMilSec = tmp
starlog.Noticef("Delay Set to %d\n", n.DelayMilSec)
case "setdialtimeout":
tmp, err := strconv.Atoi(cmds[2])
if err != nil {
starlog.Errorln("Invalid Dial Timeout Value", cmds[2])
continue
}
n.DialTimeout = time.Millisecond * time.Duration(tmp)
starlog.Noticef("Dial Timeout Set to %d\n", n.DialTimeout)
case "setudptimeout":
tmp, err := strconv.Atoi(cmds[2])
if err != nil {
starlog.Errorln("Invalid UDP Timeout Value", cmds[2])
continue
}
n.UDPTimeout = time.Millisecond * time.Duration(tmp)
starlog.Noticef("UDP Timeout Set to %d\n", n.UDPTimeout)
case "setstdin":
if cmds[2] == "off" {
n.StdinMode = false
starlog.Noticef("Stdin Mode Off\n")
return
}
}
}
}()
}
if n.EnableTCP {
go n.runTCP()
}
if n.EnableUDP {
go n.runUDP()
}
return nil
}
func (n *NetForward) runTCP() error {
atomic.AddInt32(&n.running, 1)
defer atomic.AddInt32(&n.running, -1)
listen, err := net.Listen("tcp", fmt.Sprintf("%s:%d", n.LocalAddr, n.LocalPort))
if err != nil {
starlog.Errorln("Listening On Tcp Failed:", err)
return err
}
go func() {
<-n.stopCtx.Done()
listen.Close()
}()
starlog.Infof("Listening TCP on %v\n", fmt.Sprintf("%s:%d", n.LocalAddr, n.LocalPort))
for {
select {
case <-n.stopCtx.Done():
return nil
default:
}
conn, err := listen.Accept()
if err != nil {
continue
}
log := starlog.Std.NewFlag()
log.Infof("Accept New TCP Conn from %v\n", conn.RemoteAddr().String())
if n.DelayMilSec > 0 && (n.DelayToward == 0 || n.DelayToward == 1) {
log.Infof("Delay %d ms\n", n.DelayMilSec)
time.Sleep(time.Millisecond * time.Duration(n.DelayMilSec))
}
err = SetTcpInfo(conn.(*net.TCPConn), n.UsingKeepAlive, n.KeepAliveIdel, n.KeepAlivePeriod, n.KeepAliveCount, n.UserTimeout)
if err != nil {
log.Errorf("SetTcpInfo error for %s: %v\n", conn.RemoteAddr().String(), err)
conn.Close()
continue
}
go func(conn net.Conn) {
rmt, err := net.DialTimeout("tcp", n.RemoteURI, n.DialTimeout)
if err != nil {
log.Errorf("TCP:Dial Remote %s Failed:%v\n", n.RemoteURI, err)
conn.Close()
return
}
err = SetTcpInfo(rmt.(*net.TCPConn), n.UsingKeepAlive, n.KeepAliveIdel, n.KeepAlivePeriod, n.KeepAliveCount, n.UserTimeout)
if err != nil {
log.Errorf("SetTcpInfo error for %s: %v\n", conn.RemoteAddr().String(), err)
rmt.Close()
return
}
log.Infof("TCP Connect %s <==> %s\n", conn.RemoteAddr().String(), rmt.RemoteAddr().String())
n.copy(rmt, conn)
log.Noticef("TCP Connection Closed %s <==> %s\n", conn.RemoteAddr().String(), n.RemoteURI)
conn.Close()
rmt.Close()
}(conn)
}
}
type UDPConn struct {
net.Conn
listen *net.UDPConn
remoteAddr *net.UDPAddr
lastbeat int64
}
func (u UDPConn) Write(p []byte) (n int, err error) {
u.lastbeat = time.Now().Unix()
return u.Conn.Write(p)
}
func (u UDPConn) Read(p []byte) (n int, err error) {
u.lastbeat = time.Now().Unix()
return u.Conn.Read(p)
}
func (u UDPConn) Work(delay int) {
buf := make([]byte, 8192)
for {
if delay > 0 {
time.Sleep(time.Millisecond * time.Duration(delay))
}
count, err := u.Read(buf)
if err != nil {
u.Close()
u.lastbeat = 0
return
}
_, err = u.listen.Write(buf[0:count])
if err != nil {
u.lastbeat = 0
return
}
}
}
func (n *NetForward) runUDP() error {
var mu sync.RWMutex
atomic.AddInt32(&n.running, 1)
defer atomic.AddInt32(&n.running, -1)
udpAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%v", n.LocalAddr, n.LocalPort))
if err != nil {
return err
}
listen, err := net.ListenUDP("udp", udpAddr)
if err != nil {
return err
}
starlog.Infof("Listening UDP on %v\n", fmt.Sprintf("%s:%d", n.LocalAddr, n.LocalPort))
go func() {
<-n.stopCtx.Done()
listen.Close()
}()
udpMap := make(map[string]UDPConn)
go func() {
for {
select {
case <-n.stopCtx.Done():
return
case <-time.After(time.Second * 60):
mu.Lock()
for k, v := range udpMap {
if time.Now().Unix() > int64(n.UDPTimeout.Seconds())+v.lastbeat {
delete(udpMap, k)
starlog.Noticef("UDP Connection Closed %s <==> %s\n", v.remoteAddr.String(), n.RemoteURI)
}
}
mu.Unlock()
}
}
}()
buf := make([]byte, 8192)
for {
select {
case <-n.stopCtx.Done():
return nil
default:
}
count, rmt, err := listen.ReadFromUDP(buf)
if err != nil || rmt.String() == n.RemoteURI {
continue
}
go func(data []byte, rmt *net.UDPAddr) {
log := starlog.Std.NewFlag()
mu.Lock()
addr, ok := udpMap[rmt.String()]
if !ok {
log.Infof("Accept New UDP Conn from %v\n", rmt.String())
conn, err := net.Dial("udp", n.RemoteURI)
if err != nil {
log.Errorf("UDP:Dial Remote %s Failed:%v\n", n.RemoteURI, err)
mu.Unlock()
return
}
addr = UDPConn{
Conn: conn,
remoteAddr: rmt,
listen: listen,
lastbeat: time.Now().Unix(),
}
udpMap[rmt.String()] = addr
go addr.Work(n.DelayMilSec)
log.Infof("UDP Connect %s <==> %s\n", rmt.String(), n.RemoteURI)
}
mu.Unlock()
if n.DelayMilSec > 0 || (n.DelayToward == 0 || n.DelayToward == 1) {
time.Sleep(time.Millisecond * time.Duration(n.DelayMilSec))
}
_, err := addr.Write(data)
if err != nil {
mu.Lock()
addr.Close()
delete(udpMap, addr.remoteAddr.String())
mu.Unlock()
log.Noticef("UDP Connection Closed %s <==> %s\n", rmt.String(), n.RemoteURI)
}
}(buf[0:count], rmt)
}
}
func (n *NetForward) copy(dst, src net.Conn) {
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
bufsize := make([]byte, 32*1024)
for {
count, err := src.Read(bufsize)
if err != nil {
if n.IgnoreEof && err == io.EOF {
continue
}
dst.Close()
src.Close()
return
}
_, err = dst.Write(bufsize[:count])
if err != nil {
src.Close()
dst.Close()
return
}
if n.DelayMilSec > 0 && (n.DelayToward == 0 || n.DelayToward == 1) {
time.Sleep(time.Millisecond * time.Duration(n.DelayMilSec))
}
}
}()
go func() {
defer wg.Done()
bufsize := make([]byte, 32*1024)
for {
count, err := dst.Read(bufsize)
if err != nil {
if n.IgnoreEof && err == io.EOF {
continue
}
src.Close()
dst.Close()
return
}
_, err = src.Write(bufsize[:count])
if err != nil {
src.Close()
dst.Close()
return
}
if n.DelayMilSec > 0 && (n.DelayToward == 0 || n.DelayToward == 2) {
time.Sleep(time.Millisecond * time.Duration(n.DelayMilSec))
}
}
}()
wg.Wait()
}

@ -0,0 +1,33 @@
package netforward
import (
"fmt"
"testing"
"time"
)
func TestForward(t *testing.T) {
var f = NetForward{
LocalAddr: "127.0.0.1",
LocalPort: 22232,
RemoteURI: "192.168.2.1:80",
EnableTCP: true,
EnableUDP: true,
DialTimeout: 6 * time.Second,
UDPTimeout: 7 * time.Second,
}
f.Run()
go func() {
time.Sleep(time.Second * 10)
fmt.Println("closing")
f.Close()
}()
for {
time.Sleep(time.Second * 2)
if f.Status() > 0 {
fmt.Println(f.Status())
continue
}
return
}
}

@ -0,0 +1,35 @@
//go:build darwin
package netforward
import (
"net"
"syscall"
)
func SetTcpInfo(conn *net.TCPConn, usingKeepAlive bool, keepAliveIdel, keepAlivePeriod, keepAliveCount, userTimeout int) error {
rawConn, err := conn.SyscallConn()
if err != nil {
return err
}
if usingKeepAlive {
err = rawConn.Control(func(fd uintptr) {
err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, syscall.TCP_KEEPALIVE, keepAliveIdel)
if err != nil {
return
}
err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, 0x101, keepAlivePeriod)
if err != nil {
return
}
})
} else {
err = conn.SetKeepAlive(false)
}
if userTimeout > 0 {
err = rawConn.Control(func(fd uintptr) {
err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, 0x12, userTimeout)
})
}
return err
}

@ -0,0 +1,39 @@
//go:build !(windows && darwin)
package netforward
import (
"net"
"syscall"
)
func SetTcpInfo(conn *net.TCPConn, usingKeepAlive bool, keepAliveIdel, keepAlivePeriod, keepAliveCount, userTimeout int) error {
rawConn, err := conn.SyscallConn()
if err != nil {
return err
}
if usingKeepAlive {
err = rawConn.Control(func(fd uintptr) {
err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, syscall.TCP_KEEPIDLE, keepAliveIdel)
if err != nil {
return
}
err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, syscall.TCP_KEEPINTVL, keepAlivePeriod)
if err != nil {
return
}
err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, syscall.TCP_KEEPCNT, keepAliveCount)
if err != nil {
return
}
})
} else {
err = conn.SetKeepAlive(false)
}
if userTimeout > 0 {
err = rawConn.Control(func(fd uintptr) {
err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, 0x12, userTimeout)
})
}
return err
}

@ -0,0 +1,33 @@
//go:build windows
package netforward
import (
"net"
"os"
"runtime"
"syscall"
"unsafe"
)
func SetTcpInfo(conn *net.TCPConn, usingKeepAlive bool, keepAliveIdel, keepAlivePeriod, keepAliveCount, userTimeout int) error {
if usingKeepAlive {
rawConn, err := conn.SyscallConn()
if err != nil {
return err
}
err = rawConn.Control(func(fd uintptr) {
ka := syscall.TCPKeepalive{
OnOff: 1,
Time: uint32(keepAliveIdel),
Interval: uint32(keepAlivePeriod),
}
ret := uint32(0)
size := uint32(unsafe.Sizeof(ka))
err = syscall.WSAIoctl(syscall.Handle(fd), syscall.SIO_KEEPALIVE_VALS, (*byte)(unsafe.Pointer(&ka)), size, nil, 0, &ret, nil, 0)
runtime.KeepAlive(fd)
})
return os.NewSyscallError("wsaioctl", err)
}
return conn.SetKeepAlive(false)
}

@ -29,7 +29,7 @@ func init() {
var Cmdc = &cobra.Command{
Use: "rmtc",
Short: "simple remote shell client",
Short: "远程命令执行客户端",
Run: func(cmd *cobra.Command, args []string) {
if rmtRmt == "" {
starlog.Errorln("Please Enter Remote Path")

@ -41,7 +41,7 @@ func init() {
var Cmds = &cobra.Command{
Use: "rmts",
Short: "simple remote shell server",
Short: "远程命令执行服务端",
Run: func(cmd *cobra.Command, args []string) {
if rmtListenPort == "" {
starlog.Errorln("Please Enter Port")

@ -5,23 +5,27 @@ import (
"b612.me/starlog"
"b612.me/startext"
"bufio"
"errors"
"fmt"
"github.com/spf13/cobra"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
)
var stFolder string
var stNum, stMax, stMin int
var stautoGBK bool
var stautoGBK, allowEmoji, onlyShowFileName bool
func init() {
Cmd.Flags().StringVarP(&stFolder, "folder", "f", "./", "搜索的文件夹")
Cmd.Flags().IntVarP(&stNum, "thread-num", "n", 5, "并发搜寻协程数")
Cmd.Flags().BoolVarP(&stautoGBK, "autogbk", "g", true, "自动GBK识别")
Cmd.Flags().BoolVarP(&allowEmoji, "allow-emoji", "e", false, "使用\\U输入Emoji")
Cmd.Flags().BoolVarP(&stautoGBK, "autogbk", "g", false, "自动GBK识别")
Cmd.Flags().BoolVarP(&onlyShowFileName, "only-show-filename", "o", false, "只显示文件名")
Cmd.Flags().IntVar(&stMax, "max", 0, "行最大字数")
Cmd.Flags().IntVar(&stMin, "min", 0, "行最小字数")
}
@ -35,7 +39,10 @@ var Cmd = &cobra.Command{
starlog.Errorln("应当传入两个参数,搜寻文件后缀和搜寻文本")
os.Exit(1)
}
err := searchText(stFolder, args[0], args[1], stNum, stautoGBK, stMax, stMin)
if allowEmoji {
args[1], _ = replaceUnicodeEmoji(args[1])
}
err := searchText(stFolder, args[0], args[1], stNum, stautoGBK, stMax, stMin, onlyShowFileName)
if err != nil {
os.Exit(2)
}
@ -43,7 +50,53 @@ var Cmd = &cobra.Command{
},
}
func searchText(folder string, filematch string, text string, thread int, autoGBK bool, max, min int) error {
func replaceUnicodeEmoji(text string) (string, error) {
// 查找含有 \U 开头的代码点
re := regexp.MustCompile(`\\U([0-9A-Fa-f]{1,8})`)
matches := re.FindAllStringSubmatch(text, -1)
// 如果没有匹配到任何内容,则直接返回原始文本
if matches == nil {
return text, nil
}
// 将代码点替换为相应的表情符号
for _, match := range matches {
emoji, err := unicodeToEmoji(match[1])
if err != nil {
return "", err
}
text = strings.Replace(text, match[0], emoji, -1)
}
return text, nil
}
func unicodeToEmoji(codepoint string) (string, error) {
// 将16进制字符串转换为 uint32 类型
hexcode, err := strconv.ParseUint(codepoint, 16, 32)
if err != nil {
return "", err
}
// 检查代码点是否位于Unicode BMP基本多文本平面
if hexcode > 0x10FFFF {
return "", errors.New("invalid Unicode code point")
}
// 将 uint32 类型的代码点转换为 rune 类型
r := rune(hexcode)
// 将 rune 格式化为 8 位宽度的 16 进制数值并前置 0 填充
emoji := fmt.Sprintf("%08X", hexcode)
// 将 rune 类型的字符转换为对应的字符串表情符号
emoji = string(r)
return emoji, nil
}
func searchText(folder string, filematch string, text string, thread int, autoGBK bool, max, min int, onlyShowFileName bool) error {
data, err := ioutil.ReadDir(folder)
if err != nil {
starlog.Errorln("read folder failed", folder, err)
@ -76,7 +129,11 @@ func searchText(folder string, filematch string, text string, thread int, autoGB
continue
}
if strings.Contains(origin, text) {
fmt.Printf("file:%s line:%d matched:%s\n", filepath, count, origin)
if !onlyShowFileName {
fmt.Printf("file:%s line:%d matched:%s\n", filepath, count, origin)
} else {
fmt.Printf("file:%s line:%d\n", filepath, count)
}
}
if err != nil {
break
@ -85,7 +142,7 @@ func searchText(folder string, filematch string, text string, thread int, autoGB
}
for _, v := range data {
if v.IsDir() {
searchText(filepath.Join(folder, v.Name()), filematch, text, thread, autoGBK, stMax, stMin)
searchText(filepath.Join(folder, v.Name()), filematch, text, thread, autoGBK, stMax, stMin, onlyShowFileName)
}
filepath := filepath.Join(folder, v.Name())
if matched, _ := regexp.MatchString(filematch, filepath); matched {

@ -0,0 +1,16 @@
package search
import (
"fmt"
"testing"
)
func TestEmoji(t *testing.T) {
fmt.Println(replaceUnicodeEmoji("\\U1F441\\UFE0F"))
fmt.Println("👁️")
p, _ := replaceUnicodeEmoji("\\U1F441\\UFE0F")
fmt.Println("👁️" == p)
for _, v := range p {
fmt.Printf("%U", v)
}
}

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2013 Jordan Wright
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

@ -0,0 +1,809 @@
// Package email is designed to provide an "email interface for humans."
// Designed to be robust and flexible, the email package aims to make sending email easy without getting in the way.
package email
import (
"bufio"
"bytes"
"crypto/rand"
"crypto/tls"
"encoding/base64"
"errors"
"fmt"
"io"
"math"
"math/big"
"mime"
"mime/multipart"
"mime/quotedprintable"
"net/mail"
"net/smtp"
"net/textproto"
"os"
"path/filepath"
"strings"
"time"
"unicode"
)
const (
MaxLineLength = 76 // MaxLineLength is the maximum line length per RFC 2045
defaultContentType = "text/plain; charset=us-ascii" // defaultContentType is the default Content-Type according to RFC 2045, section 5.2
)
// ErrMissingBoundary is returned when there is no boundary given for a multipart entity
var ErrMissingBoundary = errors.New("No boundary found for multipart entity")
// ErrMissingContentType is returned when there is no "Content-Type" header for a MIME entity
var ErrMissingContentType = errors.New("No Content-Type found for MIME entity")
// Email is the type used for email messages
type Email struct {
ReplyTo []string
From string
To []string
Bcc []string
Cc []string
Subject string
Text []byte // Plaintext message (optional)
HTML []byte // Html message (optional)
Sender string // override From as SMTP envelope sender (optional)
Headers textproto.MIMEHeader
Attachments []*Attachment
ReadReceipt []string
}
// part is a copyable representation of a multipart.Part
type part struct {
header textproto.MIMEHeader
body []byte
}
// NewEmail creates an Email, and returns the pointer to it.
func NewEmail() *Email {
return &Email{Headers: textproto.MIMEHeader{}}
}
// trimReader is a custom io.Reader that will trim any leading
// whitespace, as this can cause email imports to fail.
type trimReader struct {
rd io.Reader
trimmed bool
}
// Read trims off any unicode whitespace from the originating reader
func (tr *trimReader) Read(buf []byte) (int, error) {
n, err := tr.rd.Read(buf)
if err != nil {
return n, err
}
if !tr.trimmed {
t := bytes.TrimLeftFunc(buf[:n], unicode.IsSpace)
tr.trimmed = true
n = copy(buf, t)
}
return n, err
}
func handleAddressList(v []string) []string {
res := []string{}
for _, a := range v {
w := strings.Split(a, ",")
for _, addr := range w {
decodedAddr, err := (&mime.WordDecoder{}).DecodeHeader(strings.TrimSpace(addr))
if err == nil {
res = append(res, decodedAddr)
} else {
res = append(res, addr)
}
}
}
return res
}
// NewEmailFromReader reads a stream of bytes from an io.Reader, r,
// and returns an email struct containing the parsed data.
// This function expects the data in RFC 5322 format.
func NewEmailFromReader(r io.Reader) (*Email, error) {
e := NewEmail()
s := &trimReader{rd: r}
tp := textproto.NewReader(bufio.NewReader(s))
// Parse the main headers
hdrs, err := tp.ReadMIMEHeader()
if err != nil {
return e, err
}
// Set the subject, to, cc, bcc, and from
for h, v := range hdrs {
switch h {
case "Subject":
e.Subject = v[0]
subj, err := (&mime.WordDecoder{}).DecodeHeader(e.Subject)
if err == nil && len(subj) > 0 {
e.Subject = subj
}
delete(hdrs, h)
case "To":
e.To = handleAddressList(v)
delete(hdrs, h)
case "Cc":
e.Cc = handleAddressList(v)
delete(hdrs, h)
case "Bcc":
e.Bcc = handleAddressList(v)
delete(hdrs, h)
case "Reply-To":
e.ReplyTo = handleAddressList(v)
delete(hdrs, h)
case "From":
e.From = v[0]
fr, err := (&mime.WordDecoder{}).DecodeHeader(e.From)
if err == nil && len(fr) > 0 {
e.From = fr
}
delete(hdrs, h)
}
}
e.Headers = hdrs
body := tp.R
// Recursively parse the MIME parts
ps, err := parseMIMEParts(e.Headers, body)
if err != nil {
return e, err
}
for _, p := range ps {
if ct := p.header.Get("Content-Type"); ct == "" {
return e, ErrMissingContentType
}
ct, _, err := mime.ParseMediaType(p.header.Get("Content-Type"))
if err != nil {
return e, err
}
// Check if part is an attachment based on the existence of the Content-Disposition header with a value of "attachment".
if cd := p.header.Get("Content-Disposition"); cd != "" {
cd, params, err := mime.ParseMediaType(p.header.Get("Content-Disposition"))
if err != nil {
return e, err
}
filename, filenameDefined := params["filename"]
if cd == "attachment" || (cd == "inline" && filenameDefined) {
_, err = e.Attach(bytes.NewReader(p.body), filename, ct)
if err != nil {
return e, err
}
continue
}
}
switch {
case ct == "text/plain":
e.Text = p.body
case ct == "text/html":
e.HTML = p.body
}
}
return e, nil
}
// parseMIMEParts will recursively walk a MIME entity and return a []mime.Part containing
// each (flattened) mime.Part found.
// It is important to note that there are no limits to the number of recursions, so be
// careful when parsing unknown MIME structures!
func parseMIMEParts(hs textproto.MIMEHeader, b io.Reader) ([]*part, error) {
var ps []*part
// If no content type is given, set it to the default
if _, ok := hs["Content-Type"]; !ok {
hs.Set("Content-Type", defaultContentType)
}
ct, params, err := mime.ParseMediaType(hs.Get("Content-Type"))
if err != nil {
return ps, err
}
// If it's a multipart email, recursively parse the parts
if strings.HasPrefix(ct, "multipart/") {
if _, ok := params["boundary"]; !ok {
return ps, ErrMissingBoundary
}
mr := multipart.NewReader(b, params["boundary"])
for {
var buf bytes.Buffer
p, err := mr.NextPart()
if err == io.EOF {
break
}
if err != nil {
return ps, err
}
if _, ok := p.Header["Content-Type"]; !ok {
p.Header.Set("Content-Type", defaultContentType)
}
subct, _, err := mime.ParseMediaType(p.Header.Get("Content-Type"))
if err != nil {
return ps, err
}
if strings.HasPrefix(subct, "multipart/") {
sps, err := parseMIMEParts(p.Header, p)
if err != nil {
return ps, err
}
ps = append(ps, sps...)
} else {
var reader io.Reader
reader = p
const cte = "Content-Transfer-Encoding"
if p.Header.Get(cte) == "base64" {
reader = base64.NewDecoder(base64.StdEncoding, reader)
}
// Otherwise, just append the part to the list
// Copy the part data into the buffer
if _, err := io.Copy(&buf, reader); err != nil {
return ps, err
}
ps = append(ps, &part{body: buf.Bytes(), header: p.Header})
}
}
} else {
// If it is not a multipart email, parse the body content as a single "part"
switch hs.Get("Content-Transfer-Encoding") {
case "quoted-printable":
b = quotedprintable.NewReader(b)
case "base64":
b = base64.NewDecoder(base64.StdEncoding, b)
}
var buf bytes.Buffer
if _, err := io.Copy(&buf, b); err != nil {
return ps, err
}
ps = append(ps, &part{body: buf.Bytes(), header: hs})
}
return ps, nil
}
// Attach is used to attach content from an io.Reader to the email.
// Required parameters include an io.Reader, the desired filename for the attachment, and the Content-Type
// The function will return the created Attachment for reference, as well as nil for the error, if successful.
func (e *Email) Attach(r io.Reader, filename string, c string) (a *Attachment, err error) {
var buffer bytes.Buffer
if _, err = io.Copy(&buffer, r); err != nil {
return
}
at := &Attachment{
Filename: filename,
ContentType: c,
Header: textproto.MIMEHeader{},
Content: buffer.Bytes(),
}
e.Attachments = append(e.Attachments, at)
return at, nil
}
// AttachFile is used to attach content to the email.
// It attempts to open the file referenced by filename and, if successful, creates an Attachment.
// This Attachment is then appended to the slice of Email.Attachments.
// The function will then return the Attachment for reference, as well as nil for the error, if successful.
func (e *Email) AttachFile(filename string) (a *Attachment, err error) {
f, err := os.Open(filename)
if err != nil {
return
}
defer f.Close()
ct := mime.TypeByExtension(filepath.Ext(filename))
basename := filepath.Base(filename)
return e.Attach(f, basename, ct)
}
// msgHeaders merges the Email's various fields and custom headers together in a
// standards compliant way to create a MIMEHeader to be used in the resulting
// message. It does not alter e.Headers.
//
// "e"'s fields To, Cc, From, Subject will be used unless they are present in
// e.Headers. Unless set in e.Headers, "Date" will filled with the current time.
func (e *Email) msgHeaders() (textproto.MIMEHeader, error) {
res := make(textproto.MIMEHeader, len(e.Headers)+6)
if e.Headers != nil {
for _, h := range []string{"Reply-To", "To", "Cc", "From", "Subject", "Date", "Message-Id", "MIME-Version"} {
if v, ok := e.Headers[h]; ok {
res[h] = v
}
}
}
// Set headers if there are values.
if _, ok := res["Reply-To"]; !ok && len(e.ReplyTo) > 0 {
res.Set("Reply-To", strings.Join(e.ReplyTo, ", "))
}
if _, ok := res["To"]; !ok && len(e.To) > 0 {
res.Set("To", strings.Join(e.To, ", "))
}
if _, ok := res["Cc"]; !ok && len(e.Cc) > 0 {
res.Set("Cc", strings.Join(e.Cc, ", "))
}
if _, ok := res["Subject"]; !ok && e.Subject != "" {
res.Set("Subject", e.Subject)
}
if _, ok := res["Message-Id"]; !ok {
id, err := generateMessageID()
if err != nil {
return nil, err
}
res.Set("Message-Id", id)
}
// Date and From are required headers.
if _, ok := res["From"]; !ok {
res.Set("From", e.From)
}
if _, ok := res["Date"]; !ok {
res.Set("Date", time.Now().Format(time.RFC1123Z))
}
if _, ok := res["MIME-Version"]; !ok {
res.Set("MIME-Version", "1.0")
}
for field, vals := range e.Headers {
if _, ok := res[field]; !ok {
res[field] = vals
}
}
return res, nil
}
func writeMessage(buff io.Writer, msg []byte, multipart bool, mediaType string, w *multipart.Writer) error {
if multipart {
header := textproto.MIMEHeader{
"Content-Type": {mediaType + "; charset=UTF-8"},
"Content-Transfer-Encoding": {"quoted-printable"},
}
if _, err := w.CreatePart(header); err != nil {
return err
}
}
qp := quotedprintable.NewWriter(buff)
// Write the text
if _, err := qp.Write(msg); err != nil {
return err
}
return qp.Close()
}
func (e *Email) categorizeAttachments() (htmlRelated, others []*Attachment) {
for _, a := range e.Attachments {
if a.HTMLRelated {
htmlRelated = append(htmlRelated, a)
} else {
others = append(others, a)
}
}
return
}
// Bytes converts the Email object to a []byte representation, including all needed MIMEHeaders, boundaries, etc.
func (e *Email) Bytes() ([]byte, error) {
// TODO: better guess buffer size
buff := bytes.NewBuffer(make([]byte, 0, 4096))
headers, err := e.msgHeaders()
if err != nil {
return nil, err
}
htmlAttachments, otherAttachments := e.categorizeAttachments()
if len(e.HTML) == 0 && len(htmlAttachments) > 0 {
return nil, errors.New("there are HTML attachments, but no HTML body")
}
var (
isMixed = len(otherAttachments) > 0
isAlternative = len(e.Text) > 0 && len(e.HTML) > 0
isRelated = len(e.HTML) > 0 && len(htmlAttachments) > 0
)
var w *multipart.Writer
if isMixed || isAlternative || isRelated {
w = multipart.NewWriter(buff)
}
switch {
case isMixed:
headers.Set("Content-Type", "multipart/mixed;\r\n boundary="+w.Boundary())
case isAlternative:
headers.Set("Content-Type", "multipart/alternative;\r\n boundary="+w.Boundary())
case isRelated:
headers.Set("Content-Type", "multipart/related;\r\n boundary="+w.Boundary())
case len(e.HTML) > 0:
headers.Set("Content-Type", "text/html; charset=UTF-8")
headers.Set("Content-Transfer-Encoding", "quoted-printable")
default:
headers.Set("Content-Type", "text/plain; charset=UTF-8")
headers.Set("Content-Transfer-Encoding", "quoted-printable")
}
headerToBytes(buff, headers)
_, err = io.WriteString(buff, "\r\n")
if err != nil {
return nil, err
}
// Check to see if there is a Text or HTML field
if len(e.Text) > 0 || len(e.HTML) > 0 {
var subWriter *multipart.Writer
if isMixed && isAlternative {
// Create the multipart alternative part
subWriter = multipart.NewWriter(buff)
header := textproto.MIMEHeader{
"Content-Type": {"multipart/alternative;\r\n boundary=" + subWriter.Boundary()},
}
if _, err := w.CreatePart(header); err != nil {
return nil, err
}
} else {
subWriter = w
}
// Create the body sections
if len(e.Text) > 0 {
// Write the text
if err := writeMessage(buff, e.Text, isMixed || isAlternative, "text/plain", subWriter); err != nil {
return nil, err
}
}
if len(e.HTML) > 0 {
messageWriter := subWriter
var relatedWriter *multipart.Writer
if (isMixed || isAlternative) && len(htmlAttachments) > 0 {
relatedWriter = multipart.NewWriter(buff)
header := textproto.MIMEHeader{
"Content-Type": {"multipart/related;\r\n boundary=" + relatedWriter.Boundary()},
}
if _, err := subWriter.CreatePart(header); err != nil {
return nil, err
}
messageWriter = relatedWriter
} else if isRelated && len(htmlAttachments) > 0 {
relatedWriter = w
messageWriter = w
}
// Write the HTML
if err := writeMessage(buff, e.HTML, isMixed || isAlternative || isRelated, "text/html", messageWriter); err != nil {
return nil, err
}
if len(htmlAttachments) > 0 {
for _, a := range htmlAttachments {
a.setDefaultHeaders()
ap, err := relatedWriter.CreatePart(a.Header)
if err != nil {
return nil, err
}
// Write the base64Wrapped content to the part
base64Wrap(ap, a.Content)
}
if isMixed || isAlternative {
relatedWriter.Close()
}
}
}
if isMixed && isAlternative {
if err := subWriter.Close(); err != nil {
return nil, err
}
}
}
// Create attachment part, if necessary
for _, a := range otherAttachments {
a.setDefaultHeaders()
ap, err := w.CreatePart(a.Header)
if err != nil {
return nil, err
}
// Write the base64Wrapped content to the part
base64Wrap(ap, a.Content)
}
if isMixed || isAlternative || isRelated {
if err := w.Close(); err != nil {
return nil, err
}
}
return buff.Bytes(), nil
}
// Send an email using the given host and SMTP auth (optional), returns any error thrown by smtp.SendMail
// This function merges the To, Cc, and Bcc fields and calls the smtp.SendMail function using the Email.Bytes() output as the message
func (e *Email) Send(addr string, a smtp.Auth) error {
// Merge the To, Cc, and Bcc fields
to := make([]string, 0, len(e.To)+len(e.Cc)+len(e.Bcc))
to = append(append(append(to, e.To...), e.Cc...), e.Bcc...)
for i := 0; i < len(to); i++ {
addr, err := mail.ParseAddress(to[i])
if err != nil {
return err
}
to[i] = addr.Address
}
// Check to make sure there is at least one recipient and one "From" address
if e.From == "" || len(to) == 0 {
return errors.New("Must specify at least one From address and one To address")
}
sender, err := e.parseSender()
if err != nil {
return err
}
raw, err := e.Bytes()
if err != nil {
return err
}
return smtp.SendMail(addr, a, sender, to, raw)
}
// Select and parse an SMTP envelope sender address. Choose Email.Sender if set, or fallback to Email.From.
func (e *Email) parseSender() (string, error) {
if e.Sender != "" {
sender, err := mail.ParseAddress(e.Sender)
if err != nil {
return "", err
}
return sender.Address, nil
} else {
from, err := mail.ParseAddress(e.From)
if err != nil {
return "", err
}
return from.Address, nil
}
}
// SendWithTLS sends an email over tls with an optional TLS config.
//
// The TLS Config is helpful if you need to connect to a host that is used an untrusted
// certificate.
func (e *Email) SendWithTLS(addr string, a smtp.Auth, t *tls.Config) error {
// Merge the To, Cc, and Bcc fields
to := make([]string, 0, len(e.To)+len(e.Cc)+len(e.Bcc))
to = append(append(append(to, e.To...), e.Cc...), e.Bcc...)
for i := 0; i < len(to); i++ {
addr, err := mail.ParseAddress(to[i])
if err != nil {
return err
}
to[i] = addr.Address
}
// Check to make sure there is at least one recipient and one "From" address
if e.From == "" || len(to) == 0 {
return errors.New("Must specify at least one From address and one To address")
}
sender, err := e.parseSender()
if err != nil {
return err
}
raw, err := e.Bytes()
if err != nil {
return err
}
conn, err := tls.Dial("tcp", addr, t)
if err != nil {
return err
}
c, err := smtp.NewClient(conn, t.ServerName)
if err != nil {
return err
}
defer c.Close()
if err = c.Hello("localhost"); err != nil {
return err
}
if a != nil {
if ok, _ := c.Extension("AUTH"); ok {
if err = c.Auth(a); err != nil {
return err
}
}
}
if err = c.Mail(sender); err != nil {
return err
}
for _, addr := range to {
if err = c.Rcpt(addr); err != nil {
return err
}
}
w, err := c.Data()
if err != nil {
return err
}
_, err = w.Write(raw)
if err != nil {
return err
}
err = w.Close()
if err != nil {
return err
}
return c.Quit()
}
// SendWithStartTLS sends an email over TLS using STARTTLS with an optional TLS config.
//
// The TLS Config is helpful if you need to connect to a host that is used an untrusted
// certificate.
func (e *Email) SendWithStartTLS(addr string, a smtp.Auth, t *tls.Config) error {
// Merge the To, Cc, and Bcc fields
to := make([]string, 0, len(e.To)+len(e.Cc)+len(e.Bcc))
to = append(append(append(to, e.To...), e.Cc...), e.Bcc...)
for i := 0; i < len(to); i++ {
addr, err := mail.ParseAddress(to[i])
if err != nil {
return err
}
to[i] = addr.Address
}
// Check to make sure there is at least one recipient and one "From" address
if e.From == "" || len(to) == 0 {
return errors.New("Must specify at least one From address and one To address")
}
sender, err := e.parseSender()
if err != nil {
return err
}
raw, err := e.Bytes()
if err != nil {
return err
}
// Taken from the standard library
// https://github.com/golang/go/blob/master/src/net/smtp/smtp.go#L328
c, err := smtp.Dial(addr)
if err != nil {
return err
}
defer c.Close()
if err = c.Hello("localhost"); err != nil {
return err
}
// Use TLS if available
if ok, _ := c.Extension("STARTTLS"); ok {
if err = c.StartTLS(t); err != nil {
return err
}
}
if a != nil {
if ok, _ := c.Extension("AUTH"); ok {
if err = c.Auth(a); err != nil {
return err
}
}
}
if err = c.Mail(sender); err != nil {
return err
}
for _, addr := range to {
if err = c.Rcpt(addr); err != nil {
return err
}
}
w, err := c.Data()
if err != nil {
return err
}
_, err = w.Write(raw)
if err != nil {
return err
}
err = w.Close()
if err != nil {
return err
}
return c.Quit()
}
// Attachment is a struct representing an email attachment.
// Based on the mime/multipart.FileHeader struct, Attachment contains the name, MIMEHeader, and content of the attachment in question
type Attachment struct {
Filename string
ContentType string
Header textproto.MIMEHeader
Content []byte
HTMLRelated bool
}
func (at *Attachment) setDefaultHeaders() {
contentType := "application/octet-stream"
if len(at.ContentType) > 0 {
contentType = at.ContentType
}
at.Header.Set("Content-Type", contentType)
if len(at.Header.Get("Content-Disposition")) == 0 {
disposition := "attachment"
if at.HTMLRelated {
disposition = "inline"
}
at.Header.Set("Content-Disposition", fmt.Sprintf("%s;\r\n filename=\"%s\"", disposition, at.Filename))
}
if len(at.Header.Get("Content-ID")) == 0 {
at.Header.Set("Content-ID", fmt.Sprintf("<%s>", at.Filename))
}
if len(at.Header.Get("Content-Transfer-Encoding")) == 0 {
at.Header.Set("Content-Transfer-Encoding", "base64")
}
}
// base64Wrap encodes the attachment content, and wraps it according to RFC 2045 standards (every 76 chars)
// The output is then written to the specified io.Writer
func base64Wrap(w io.Writer, b []byte) {
// 57 raw bytes per 76-byte base64 line.
const maxRaw = 57
// Buffer for each line, including trailing CRLF.
buffer := make([]byte, MaxLineLength+len("\r\n"))
copy(buffer[MaxLineLength:], "\r\n")
// Process raw chunks until there's no longer enough to fill a line.
for len(b) >= maxRaw {
base64.StdEncoding.Encode(buffer, b[:maxRaw])
w.Write(buffer)
b = b[maxRaw:]
}
// Handle the last chunk of bytes.
if len(b) > 0 {
out := buffer[:base64.StdEncoding.EncodedLen(len(b))]
base64.StdEncoding.Encode(out, b)
out = append(out, "\r\n"...)
w.Write(out)
}
}
// headerToBytes renders "header" to "buff". If there are multiple values for a
// field, multiple "Field: value\r\n" lines will be emitted.
func headerToBytes(buff io.Writer, header textproto.MIMEHeader) {
for field, vals := range header {
for _, subval := range vals {
// bytes.Buffer.Write() never returns an error.
io.WriteString(buff, field)
io.WriteString(buff, ": ")
// Write the encoded header if needed
switch {
case field == "Content-Type" || field == "Content-Disposition":
buff.Write([]byte(subval))
case field == "From" || field == "To" || field == "Cc" || field == "Bcc":
participants := strings.Split(subval, ",")
for i, v := range participants {
addr, err := mail.ParseAddress(v)
if err != nil {
continue
}
participants[i] = addr.String()
}
buff.Write([]byte(strings.Join(participants, ", ")))
default:
buff.Write([]byte(mime.QEncoding.Encode("UTF-8", subval)))
}
io.WriteString(buff, "\r\n")
}
}
}
var maxBigInt = big.NewInt(math.MaxInt64)
// generateMessageID generates and returns a string suitable for an RFC 2822
// compliant Message-ID, e.g.:
// <1444789264909237300.3464.1819418242800517193@DESKTOP01>
//
// The following parameters are used to generate a Message-ID:
// - The nanoseconds since Epoch
// - The calling PID
// - A cryptographically random int64
// - The sending hostname
func generateMessageID() (string, error) {
t := time.Now().UnixNano()
pid := os.Getpid()
rint, err := rand.Int(rand.Reader, maxBigInt)
if err != nil {
return "", err
}
h, err := os.Hostname()
// If we can't get the hostname, we'll use localhost
if err != nil {
h = "localhost.localdomain"
}
msgid := fmt.Sprintf("<%d.%d.%d@%s>", t, pid, rint, h)
return msgid, nil
}

@ -0,0 +1,933 @@
package email
import (
"fmt"
"strings"
"testing"
"bufio"
"bytes"
"crypto/rand"
"io"
"io/ioutil"
"mime"
"mime/multipart"
"mime/quotedprintable"
"net/mail"
"net/smtp"
"net/textproto"
)
func prepareEmail() *Email {
e := NewEmail()
e.From = "Jordan Wright <test@example.com>"
e.To = []string{"test@example.com"}
e.Bcc = []string{"test_bcc@example.com"}
e.Cc = []string{"test_cc@example.com"}
e.Subject = "Awesome Subject"
return e
}
func basicTests(t *testing.T, e *Email) *mail.Message {
raw, err := e.Bytes()
if err != nil {
t.Fatal("Failed to render message: ", e)
}
msg, err := mail.ReadMessage(bytes.NewBuffer(raw))
if err != nil {
t.Fatal("Could not parse rendered message: ", err)
}
expectedHeaders := map[string]string{
"To": "<test@example.com>",
"From": "\"Jordan Wright\" <test@example.com>",
"Cc": "<test_cc@example.com>",
"Subject": "Awesome Subject",
}
for header, expected := range expectedHeaders {
if val := msg.Header.Get(header); val != expected {
t.Errorf("Wrong value for message header %s: %v != %v", header, expected, val)
}
}
return msg
}
func TestEmailText(t *testing.T) {
e := prepareEmail()
e.Text = []byte("Text Body is, of course, supported!\n")
msg := basicTests(t, e)
// Were the right headers set?
ct := msg.Header.Get("Content-type")
mt, _, err := mime.ParseMediaType(ct)
if err != nil {
t.Fatal("Content-type header is invalid: ", ct)
} else if mt != "text/plain" {
t.Fatalf("Content-type expected \"text/plain\", not %v", mt)
}
}
func TestEmailWithHTMLAttachments(t *testing.T) {
e := prepareEmail()
// Set plain text to exercise "mime/alternative"
e.Text = []byte("Text Body is, of course, supported!\n")
e.HTML = []byte("<html><body>This is a text.</body></html>")
// Set HTML attachment to exercise "mime/related".
attachment, err := e.Attach(bytes.NewBufferString("Rad attachment"), "rad.txt", "image/png; charset=utf-8")
if err != nil {
t.Fatal("Could not add an attachment to the message: ", err)
}
attachment.HTMLRelated = true
b, err := e.Bytes()
if err != nil {
t.Fatal("Could not serialize e-mail:", err)
}
// Print the bytes for ocular validation and make sure no errors.
//fmt.Println(string(b))
// TODO: Verify the attachments.
s := &trimReader{rd: bytes.NewBuffer(b)}
tp := textproto.NewReader(bufio.NewReader(s))
// Parse the main headers
hdrs, err := tp.ReadMIMEHeader()
if err != nil {
t.Fatal("Could not parse the headers:", err)
}
// Recursively parse the MIME parts
ps, err := parseMIMEParts(hdrs, tp.R)
if err != nil {
t.Fatal("Could not parse the MIME parts recursively:", err)
}
plainTextFound := false
htmlFound := false
imageFound := false
if expected, actual := 3, len(ps); actual != expected {
t.Error("Unexpected number of parts. Expected:", expected, "Was:", actual)
}
for _, part := range ps {
// part has "header" and "body []byte"
cd := part.header.Get("Content-Disposition")
ct := part.header.Get("Content-Type")
if strings.Contains(ct, "image/png") && strings.HasPrefix(cd, "inline") {
imageFound = true
}
if strings.Contains(ct, "text/html") {
htmlFound = true
}
if strings.Contains(ct, "text/plain") {
plainTextFound = true
}
}
if !plainTextFound {
t.Error("Did not find plain text part.")
}
if !htmlFound {
t.Error("Did not find HTML part.")
}
if !imageFound {
t.Error("Did not find image part.")
}
}
func TestEmailWithHTMLAttachmentsHTMLOnly(t *testing.T) {
e := prepareEmail()
e.HTML = []byte("<html><body>This is a text.</body></html>")
// Set HTML attachment to exercise "mime/related".
attachment, err := e.Attach(bytes.NewBufferString("Rad attachment"), "rad.txt", "image/png; charset=utf-8")
if err != nil {
t.Fatal("Could not add an attachment to the message: ", err)
}
attachment.HTMLRelated = true
b, err := e.Bytes()
if err != nil {
t.Fatal("Could not serialize e-mail:", err)
}
// Print the bytes for ocular validation and make sure no errors.
//fmt.Println(string(b))
// TODO: Verify the attachments.
s := &trimReader{rd: bytes.NewBuffer(b)}
tp := textproto.NewReader(bufio.NewReader(s))
// Parse the main headers
hdrs, err := tp.ReadMIMEHeader()
if err != nil {
t.Fatal("Could not parse the headers:", err)
}
if !strings.HasPrefix(hdrs.Get("Content-Type"), "multipart/related") {
t.Error("Envelope Content-Type is not multipart/related: ", hdrs["Content-Type"])
}
// Recursively parse the MIME parts
ps, err := parseMIMEParts(hdrs, tp.R)
if err != nil {
t.Fatal("Could not parse the MIME parts recursively:", err)
}
htmlFound := false
imageFound := false
if expected, actual := 2, len(ps); actual != expected {
t.Error("Unexpected number of parts. Expected:", expected, "Was:", actual)
}
for _, part := range ps {
// part has "header" and "body []byte"
ct := part.header.Get("Content-Type")
if strings.Contains(ct, "image/png") {
imageFound = true
}
if strings.Contains(ct, "text/html") {
htmlFound = true
}
}
if !htmlFound {
t.Error("Did not find HTML part.")
}
if !imageFound {
t.Error("Did not find image part.")
}
}
func TestEmailHTML(t *testing.T) {
e := prepareEmail()
e.HTML = []byte("<h1>Fancy Html is supported, too!</h1>\n")
msg := basicTests(t, e)
// Were the right headers set?
ct := msg.Header.Get("Content-type")
mt, _, err := mime.ParseMediaType(ct)
if err != nil {
t.Fatalf("Content-type header is invalid: %#v", ct)
} else if mt != "text/html" {
t.Fatalf("Content-type expected \"text/html\", not %v", mt)
}
}
func TestEmailTextAttachment(t *testing.T) {
e := prepareEmail()
e.Text = []byte("Text Body is, of course, supported!\n")
_, err := e.Attach(bytes.NewBufferString("Rad attachment"), "rad.txt", "text/plain; charset=utf-8")
if err != nil {
t.Fatal("Could not add an attachment to the message: ", err)
}
msg := basicTests(t, e)
// Were the right headers set?
ct := msg.Header.Get("Content-type")
mt, params, err := mime.ParseMediaType(ct)
if err != nil {
t.Fatal("Content-type header is invalid: ", ct)
} else if mt != "multipart/mixed" {
t.Fatalf("Content-type expected \"multipart/mixed\", not %v", mt)
}
b := params["boundary"]
if b == "" {
t.Fatalf("Invalid or missing boundary parameter: %#v", b)
}
if len(params) != 1 {
t.Fatal("Unexpected content-type parameters")
}
// Is the generated message parsable?
mixed := multipart.NewReader(msg.Body, params["boundary"])
text, err := mixed.NextPart()
if err != nil {
t.Fatalf("Could not find text component of email: %s", err)
}
// Does the text portion match what we expect?
mt, _, err = mime.ParseMediaType(text.Header.Get("Content-type"))
if err != nil {
t.Fatal("Could not parse message's Content-Type")
} else if mt != "text/plain" {
t.Fatal("Message missing text/plain")
}
plainText, err := ioutil.ReadAll(text)
if err != nil {
t.Fatal("Could not read plain text component of message: ", err)
}
if !bytes.Equal(plainText, []byte("Text Body is, of course, supported!\r\n")) {
t.Fatalf("Plain text is broken: %#q", plainText)
}
// Check attachments.
_, err = mixed.NextPart()
if err != nil {
t.Fatalf("Could not find attachment component of email: %s", err)
}
if _, err = mixed.NextPart(); err != io.EOF {
t.Error("Expected only text and one attachment!")
}
}
func TestEmailTextHtmlAttachment(t *testing.T) {
e := prepareEmail()
e.Text = []byte("Text Body is, of course, supported!\n")
e.HTML = []byte("<h1>Fancy Html is supported, too!</h1>\n")
_, err := e.Attach(bytes.NewBufferString("Rad attachment"), "rad.txt", "text/plain; charset=utf-8")
if err != nil {
t.Fatal("Could not add an attachment to the message: ", err)
}
msg := basicTests(t, e)
// Were the right headers set?
ct := msg.Header.Get("Content-type")
mt, params, err := mime.ParseMediaType(ct)
if err != nil {
t.Fatal("Content-type header is invalid: ", ct)
} else if mt != "multipart/mixed" {
t.Fatalf("Content-type expected \"multipart/mixed\", not %v", mt)
}
b := params["boundary"]
if b == "" {
t.Fatal("Unexpected empty boundary parameter")
}
if len(params) != 1 {
t.Fatal("Unexpected content-type parameters")
}
// Is the generated message parsable?
mixed := multipart.NewReader(msg.Body, params["boundary"])
text, err := mixed.NextPart()
if err != nil {
t.Fatalf("Could not find text component of email: %s", err)
}
// Does the text portion match what we expect?
mt, params, err = mime.ParseMediaType(text.Header.Get("Content-type"))
if err != nil {
t.Fatal("Could not parse message's Content-Type")
} else if mt != "multipart/alternative" {
t.Fatal("Message missing multipart/alternative")
}
mpReader := multipart.NewReader(text, params["boundary"])
part, err := mpReader.NextPart()
if err != nil {
t.Fatal("Could not read plain text component of message: ", err)
}
plainText, err := ioutil.ReadAll(part)
if err != nil {
t.Fatal("Could not read plain text component of message: ", err)
}
if !bytes.Equal(plainText, []byte("Text Body is, of course, supported!\r\n")) {
t.Fatalf("Plain text is broken: %#q", plainText)
}
// Check attachments.
_, err = mixed.NextPart()
if err != nil {
t.Fatalf("Could not find attachment component of email: %s", err)
}
if _, err = mixed.NextPart(); err != io.EOF {
t.Error("Expected only text and one attachment!")
}
}
func TestEmailAttachment(t *testing.T) {
e := prepareEmail()
_, err := e.Attach(bytes.NewBufferString("Rad attachment"), "rad.txt", "text/plain; charset=utf-8")
if err != nil {
t.Fatal("Could not add an attachment to the message: ", err)
}
msg := basicTests(t, e)
// Were the right headers set?
ct := msg.Header.Get("Content-type")
mt, params, err := mime.ParseMediaType(ct)
if err != nil {
t.Fatal("Content-type header is invalid: ", ct)
} else if mt != "multipart/mixed" {
t.Fatalf("Content-type expected \"multipart/mixed\", not %v", mt)
}
b := params["boundary"]
if b == "" {
t.Fatal("Unexpected empty boundary parameter")
}
if len(params) != 1 {
t.Fatal("Unexpected content-type parameters")
}
// Is the generated message parsable?
mixed := multipart.NewReader(msg.Body, params["boundary"])
// Check attachments.
a, err := mixed.NextPart()
if err != nil {
t.Fatalf("Could not find attachment component of email: %s", err)
}
if !strings.HasPrefix(a.Header.Get("Content-Disposition"), "attachment") {
t.Fatalf("Content disposition is not attachment: %s", a.Header.Get("Content-Disposition"))
}
if _, err = mixed.NextPart(); err != io.EOF {
t.Error("Expected only one attachment!")
}
}
func TestHeaderEncoding(t *testing.T) {
cases := []struct {
field string
have string
want string
}{
{
field: "From",
have: "Needs Encóding <encoding@example.com>, Only ASCII <foo@example.com>",
want: "=?utf-8?q?Needs_Enc=C3=B3ding?= <encoding@example.com>, \"Only ASCII\" <foo@example.com>\r\n",
},
{
field: "To",
have: "Keith Moore <moore@cs.utk.edu>, Keld Jørn Simonsen <keld@dkuug.dk>",
want: "\"Keith Moore\" <moore@cs.utk.edu>, =?utf-8?q?Keld_J=C3=B8rn_Simonsen?= <keld@dkuug.dk>\r\n",
},
{
field: "Cc",
have: "Needs Encóding <encoding@example.com>, \"Test :)\" <test@localhost>",
want: "=?utf-8?q?Needs_Enc=C3=B3ding?= <encoding@example.com>, \"Test :)\" <test@localhost>\r\n",
},
{
field: "Subject",
have: "Subject with a 🐟",
want: "=?UTF-8?q?Subject_with_a_=F0=9F=90=9F?=\r\n",
},
{
field: "Subject",
have: "Subject with only ASCII",
want: "Subject with only ASCII\r\n",
},
}
buff := &bytes.Buffer{}
for _, c := range cases {
header := make(textproto.MIMEHeader)
header.Add(c.field, c.have)
buff.Reset()
headerToBytes(buff, header)
want := fmt.Sprintf("%s: %s", c.field, c.want)
got := buff.String()
if got != want {
t.Errorf("invalid utf-8 header encoding. \nwant:%#v\ngot: %#v", want, got)
}
}
}
func TestEmailFromReader(t *testing.T) {
ex := &Email{
Subject: "Test Subject",
To: []string{"Jordan Wright <jmwright798@gmail.com>", "also@example.com"},
From: "Jordan Wright <jmwright798@gmail.com>",
ReplyTo: []string{"Jordan Wright <jmwright798@gmail.com>"},
Cc: []string{"one@example.com", "Two <two@example.com>"},
Bcc: []string{"three@example.com", "Four <four@example.com>"},
Text: []byte("This is a test email with HTML Formatting. It also has very long lines so\nthat the content must be wrapped if using quoted-printable decoding.\n"),
HTML: []byte("<div dir=\"ltr\">This is a test email with <b>HTML Formatting.</b>\u00a0It also has very long lines so that the content must be wrapped if using quoted-printable decoding.</div>\n"),
}
raw := []byte(`
MIME-Version: 1.0
Subject: Test Subject
From: Jordan Wright <jmwright798@gmail.com>
Reply-To: Jordan Wright <jmwright798@gmail.com>
To: Jordan Wright <jmwright798@gmail.com>, also@example.com
Cc: one@example.com, Two <two@example.com>
Bcc: three@example.com, Four <four@example.com>
Content-Type: multipart/alternative; boundary=001a114fb3fc42fd6b051f834280
--001a114fb3fc42fd6b051f834280
Content-Type: text/plain; charset=UTF-8
This is a test email with HTML Formatting. It also has very long lines so
that the content must be wrapped if using quoted-printable decoding.
--001a114fb3fc42fd6b051f834280
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: quoted-printable
<div dir=3D"ltr">This is a test email with <b>HTML Formatting.</b>=C2=A0It =
also has very long lines so that the content must be wrapped if using quote=
d-printable decoding.</div>
--001a114fb3fc42fd6b051f834280--`)
e, err := NewEmailFromReader(bytes.NewReader(raw))
if err != nil {
t.Fatalf("Error creating email %s", err.Error())
}
if e.Subject != ex.Subject {
t.Fatalf("Incorrect subject. %#q != %#q", e.Subject, ex.Subject)
}
if !bytes.Equal(e.Text, ex.Text) {
t.Fatalf("Incorrect text: %#q != %#q", e.Text, ex.Text)
}
if !bytes.Equal(e.HTML, ex.HTML) {
t.Fatalf("Incorrect HTML: %#q != %#q", e.HTML, ex.HTML)
}
if e.From != ex.From {
t.Fatalf("Incorrect \"From\": %#q != %#q", e.From, ex.From)
}
if len(e.To) != len(ex.To) {
t.Fatalf("Incorrect number of \"To\" addresses: %v != %v", len(e.To), len(ex.To))
}
if e.To[0] != ex.To[0] {
t.Fatalf("Incorrect \"To[0]\": %#q != %#q", e.To[0], ex.To[0])
}
if e.To[1] != ex.To[1] {
t.Fatalf("Incorrect \"To[1]\": %#q != %#q", e.To[1], ex.To[1])
}
if len(e.Cc) != len(ex.Cc) {
t.Fatalf("Incorrect number of \"Cc\" addresses: %v != %v", len(e.Cc), len(ex.Cc))
}
if e.Cc[0] != ex.Cc[0] {
t.Fatalf("Incorrect \"Cc[0]\": %#q != %#q", e.Cc[0], ex.Cc[0])
}
if e.Cc[1] != ex.Cc[1] {
t.Fatalf("Incorrect \"Cc[1]\": %#q != %#q", e.Cc[1], ex.Cc[1])
}
if len(e.Bcc) != len(ex.Bcc) {
t.Fatalf("Incorrect number of \"Bcc\" addresses: %v != %v", len(e.Bcc), len(ex.Bcc))
}
if e.Bcc[0] != ex.Bcc[0] {
t.Fatalf("Incorrect \"Bcc[0]\": %#q != %#q", e.Cc[0], ex.Cc[0])
}
if e.Bcc[1] != ex.Bcc[1] {
t.Fatalf("Incorrect \"Bcc[1]\": %#q != %#q", e.Bcc[1], ex.Bcc[1])
}
if len(e.ReplyTo) != len(ex.ReplyTo) {
t.Fatalf("Incorrect number of \"Reply-To\" addresses: %v != %v", len(e.ReplyTo), len(ex.ReplyTo))
}
if e.ReplyTo[0] != ex.ReplyTo[0] {
t.Fatalf("Incorrect \"ReplyTo\": %#q != %#q", e.ReplyTo[0], ex.ReplyTo[0])
}
}
func TestNonAsciiEmailFromReader(t *testing.T) {
ex := &Email{
Subject: "Test Subject",
To: []string{"Anaïs <anais@example.org>"},
Cc: []string{"Patrik Fältström <paf@example.com>"},
From: "Mrs Valérie Dupont <valerie.dupont@example.com>",
Text: []byte("This is a test message!"),
}
raw := []byte(`
MIME-Version: 1.0
Subject: =?UTF-8?Q?Test Subject?=
From: Mrs =?ISO-8859-1?Q?Val=C3=A9rie=20Dupont?= <valerie.dupont@example.com>
To: =?utf-8?q?Ana=C3=AFs?= <anais@example.org>
Cc: =?ISO-8859-1?Q?Patrik_F=E4ltstr=F6m?= <paf@example.com>
Content-type: text/plain; charset=ISO-8859-1
This is a test message!`)
e, err := NewEmailFromReader(bytes.NewReader(raw))
if err != nil {
t.Fatalf("Error creating email %s", err.Error())
}
if e.Subject != ex.Subject {
t.Fatalf("Incorrect subject. %#q != %#q", e.Subject, ex.Subject)
}
if e.From != ex.From {
t.Fatalf("Incorrect \"From\": %#q != %#q", e.From, ex.From)
}
if e.To[0] != ex.To[0] {
t.Fatalf("Incorrect \"To\": %#q != %#q", e.To, ex.To)
}
if e.Cc[0] != ex.Cc[0] {
t.Fatalf("Incorrect \"Cc\": %#q != %#q", e.Cc, ex.Cc)
}
}
func TestNonMultipartEmailFromReader(t *testing.T) {
ex := &Email{
Text: []byte("This is a test message!"),
Subject: "Example Subject (no MIME Type)",
Headers: textproto.MIMEHeader{},
}
ex.Headers.Add("Content-Type", "text/plain; charset=us-ascii")
ex.Headers.Add("Message-ID", "<foobar@example.com>")
raw := []byte(`From: "Foo Bar" <foobar@example.com>
Content-Type: text/plain
To: foobar@example.com
Subject: Example Subject (no MIME Type)
Message-ID: <foobar@example.com>
This is a test message!`)
e, err := NewEmailFromReader(bytes.NewReader(raw))
if err != nil {
t.Fatalf("Error creating email %s", err.Error())
}
if ex.Subject != e.Subject {
t.Errorf("Incorrect subject. %#q != %#q\n", ex.Subject, e.Subject)
}
if !bytes.Equal(ex.Text, e.Text) {
t.Errorf("Incorrect body. %#q != %#q\n", ex.Text, e.Text)
}
if ex.Headers.Get("Message-ID") != e.Headers.Get("Message-ID") {
t.Errorf("Incorrect message ID. %#q != %#q\n", ex.Headers.Get("Message-ID"), e.Headers.Get("Message-ID"))
}
}
func TestBase64EmailFromReader(t *testing.T) {
ex := &Email{
Subject: "Test Subject",
To: []string{"Jordan Wright <jmwright798@gmail.com>"},
From: "Jordan Wright <jmwright798@gmail.com>",
Text: []byte("This is a test email with HTML Formatting. It also has very long lines so that the content must be wrapped if using quoted-printable decoding."),
HTML: []byte("<div dir=\"ltr\">This is a test email with <b>HTML Formatting.</b>\u00a0It also has very long lines so that the content must be wrapped if using quoted-printable decoding.</div>\n"),
}
raw := []byte(`
MIME-Version: 1.0
Subject: Test Subject
From: Jordan Wright <jmwright798@gmail.com>
To: Jordan Wright <jmwright798@gmail.com>
Content-Type: multipart/alternative; boundary=001a114fb3fc42fd6b051f834280
--001a114fb3fc42fd6b051f834280
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: base64
VGhpcyBpcyBhIHRlc3QgZW1haWwgd2l0aCBIVE1MIEZvcm1hdHRpbmcuIEl0IGFsc28gaGFzIHZl
cnkgbG9uZyBsaW5lcyBzbyB0aGF0IHRoZSBjb250ZW50IG11c3QgYmUgd3JhcHBlZCBpZiB1c2lu
ZyBxdW90ZWQtcHJpbnRhYmxlIGRlY29kaW5nLg==
--001a114fb3fc42fd6b051f834280
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: quoted-printable
<div dir=3D"ltr">This is a test email with <b>HTML Formatting.</b>=C2=A0It =
also has very long lines so that the content must be wrapped if using quote=
d-printable decoding.</div>
--001a114fb3fc42fd6b051f834280--`)
e, err := NewEmailFromReader(bytes.NewReader(raw))
if err != nil {
t.Fatalf("Error creating email %s", err.Error())
}
if e.Subject != ex.Subject {
t.Fatalf("Incorrect subject. %#q != %#q", e.Subject, ex.Subject)
}
if !bytes.Equal(e.Text, ex.Text) {
t.Fatalf("Incorrect text: %#q != %#q", e.Text, ex.Text)
}
if !bytes.Equal(e.HTML, ex.HTML) {
t.Fatalf("Incorrect HTML: %#q != %#q", e.HTML, ex.HTML)
}
if e.From != ex.From {
t.Fatalf("Incorrect \"From\": %#q != %#q", e.From, ex.From)
}
}
func TestAttachmentEmailFromReader(t *testing.T) {
ex := &Email{
Subject: "Test Subject",
To: []string{"Jordan Wright <jmwright798@gmail.com>"},
From: "Jordan Wright <jmwright798@gmail.com>",
Text: []byte("Simple text body"),
HTML: []byte("<div dir=\"ltr\">Simple HTML body</div>\n"),
}
a, err := ex.Attach(bytes.NewReader([]byte("Let's just pretend this is raw JPEG data.")), "cat.jpeg", "image/jpeg")
if err != nil {
t.Fatalf("Error attaching image %s", err.Error())
}
b, err := ex.Attach(bytes.NewReader([]byte("Let's just pretend this is raw JPEG data.")), "cat-inline.jpeg", "image/jpeg")
if err != nil {
t.Fatalf("Error attaching inline image %s", err.Error())
}
raw := []byte(`
From: Jordan Wright <jmwright798@gmail.com>
Date: Thu, 17 Oct 2019 08:55:37 +0100
Mime-Version: 1.0
Content-Type: multipart/mixed;
boundary=35d10c2224bd787fe700c2c6f4769ddc936eb8a0b58e9c8717e406c5abb7
To: Jordan Wright <jmwright798@gmail.com>
Subject: Test Subject
--35d10c2224bd787fe700c2c6f4769ddc936eb8a0b58e9c8717e406c5abb7
Content-Type: multipart/alternative;
boundary=b10ca5b1072908cceb667e8968d3af04503b7ab07d61c9f579c15b416d7c
--b10ca5b1072908cceb667e8968d3af04503b7ab07d61c9f579c15b416d7c
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset=UTF-8
Simple text body
--b10ca5b1072908cceb667e8968d3af04503b7ab07d61c9f579c15b416d7c
Content-Transfer-Encoding: quoted-printable
Content-Type: text/html; charset=UTF-8
<div dir=3D"ltr">Simple HTML body</div>
--b10ca5b1072908cceb667e8968d3af04503b7ab07d61c9f579c15b416d7c--
--35d10c2224bd787fe700c2c6f4769ddc936eb8a0b58e9c8717e406c5abb7
Content-Disposition: attachment;
filename="cat.jpeg"
Content-Id: <cat.jpeg>
Content-Transfer-Encoding: base64
Content-Type: image/jpeg
TGV0J3MganVzdCBwcmV0ZW5kIHRoaXMgaXMgcmF3IEpQRUcgZGF0YS4=
--35d10c2224bd787fe700c2c6f4769ddc936eb8a0b58e9c8717e406c5abb7
Content-Disposition: inline;
filename="cat-inline.jpeg"
Content-Id: <cat-inline.jpeg>
Content-Transfer-Encoding: base64
Content-Type: image/jpeg
TGV0J3MganVzdCBwcmV0ZW5kIHRoaXMgaXMgcmF3IEpQRUcgZGF0YS4=
--35d10c2224bd787fe700c2c6f4769ddc936eb8a0b58e9c8717e406c5abb7--`)
e, err := NewEmailFromReader(bytes.NewReader(raw))
if err != nil {
t.Fatalf("Error creating email %s", err.Error())
}
if e.Subject != ex.Subject {
t.Fatalf("Incorrect subject. %#q != %#q", e.Subject, ex.Subject)
}
if !bytes.Equal(e.Text, ex.Text) {
t.Fatalf("Incorrect text: %#q != %#q", e.Text, ex.Text)
}
if !bytes.Equal(e.HTML, ex.HTML) {
t.Fatalf("Incorrect HTML: %#q != %#q", e.HTML, ex.HTML)
}
if e.From != ex.From {
t.Fatalf("Incorrect \"From\": %#q != %#q", e.From, ex.From)
}
if len(e.Attachments) != 2 {
t.Fatalf("Incorrect number of attachments %d != %d", len(e.Attachments), 1)
}
if e.Attachments[0].Filename != a.Filename {
t.Fatalf("Incorrect attachment filename %s != %s", e.Attachments[0].Filename, a.Filename)
}
if !bytes.Equal(e.Attachments[0].Content, a.Content) {
t.Fatalf("Incorrect attachment content %#q != %#q", e.Attachments[0].Content, a.Content)
}
if e.Attachments[1].Filename != b.Filename {
t.Fatalf("Incorrect attachment filename %s != %s", e.Attachments[1].Filename, b.Filename)
}
if !bytes.Equal(e.Attachments[1].Content, b.Content) {
t.Fatalf("Incorrect attachment content %#q != %#q", e.Attachments[1].Content, b.Content)
}
}
func ExampleGmail() {
e := NewEmail()
e.From = "Jordan Wright <test@gmail.com>"
e.To = []string{"test@example.com"}
e.Bcc = []string{"test_bcc@example.com"}
e.Cc = []string{"test_cc@example.com"}
e.Subject = "Awesome Subject"
e.Text = []byte("Text Body is, of course, supported!\n")
e.HTML = []byte("<h1>Fancy Html is supported, too!</h1>\n")
e.Send("smtp.gmail.com:587", smtp.PlainAuth("", e.From, "password123", "smtp.gmail.com"))
}
func ExampleAttach() {
e := NewEmail()
e.AttachFile("test.txt")
}
func Test_base64Wrap(t *testing.T) {
file := "I'm a file long enough to force the function to wrap a\n" +
"couple of lines, but I stop short of the end of one line and\n" +
"have some padding dangling at the end."
encoded := "SSdtIGEgZmlsZSBsb25nIGVub3VnaCB0byBmb3JjZSB0aGUgZnVuY3Rpb24gdG8gd3JhcCBhCmNv\r\n" +
"dXBsZSBvZiBsaW5lcywgYnV0IEkgc3RvcCBzaG9ydCBvZiB0aGUgZW5kIG9mIG9uZSBsaW5lIGFu\r\n" +
"ZApoYXZlIHNvbWUgcGFkZGluZyBkYW5nbGluZyBhdCB0aGUgZW5kLg==\r\n"
var buf bytes.Buffer
base64Wrap(&buf, []byte(file))
if !bytes.Equal(buf.Bytes(), []byte(encoded)) {
t.Fatalf("Encoded file does not match expected: %#q != %#q", string(buf.Bytes()), encoded)
}
}
// *Since the mime library in use by ```email``` is now in the stdlib, this test is deprecated
func Test_quotedPrintEncode(t *testing.T) {
var buf bytes.Buffer
text := []byte("Dear reader!\n\n" +
"This is a test email to try and capture some of the corner cases that exist within\n" +
"the quoted-printable encoding.\n" +
"There are some wacky parts like =, and this input assumes UNIX line breaks so\r\n" +
"it can come out a little weird. Also, we need to support unicode so here's a fish: 🐟\n")
expected := []byte("Dear reader!\r\n\r\n" +
"This is a test email to try and capture some of the corner cases that exist=\r\n" +
" within\r\n" +
"the quoted-printable encoding.\r\n" +
"There are some wacky parts like =3D, and this input assumes UNIX line break=\r\n" +
"s so\r\n" +
"it can come out a little weird. Also, we need to support unicode so here's=\r\n" +
" a fish: =F0=9F=90=9F\r\n")
qp := quotedprintable.NewWriter(&buf)
if _, err := qp.Write(text); err != nil {
t.Fatal("quotePrintEncode: ", err)
}
if err := qp.Close(); err != nil {
t.Fatal("Error closing writer", err)
}
if b := buf.Bytes(); !bytes.Equal(b, expected) {
t.Errorf("quotedPrintEncode generated incorrect results: %#q != %#q", b, expected)
}
}
func TestMultipartNoContentType(t *testing.T) {
raw := []byte(`From: Mikhail Gusarov <dottedmag@dottedmag.net>
To: notmuch@notmuchmail.org
References: <20091117190054.GU3165@dottiness.seas.harvard.edu>
Date: Wed, 18 Nov 2009 01:02:38 +0600
Message-ID: <87iqd9rn3l.fsf@vertex.dottedmag>
MIME-Version: 1.0
Subject: Re: [notmuch] Working with Maildir storage?
Content-Type: multipart/mixed; boundary="===============1958295626=="
--===============1958295626==
Content-Type: multipart/signed; boundary="=-=-=";
micalg=pgp-sha1; protocol="application/pgp-signature"
--=-=-=
Content-Transfer-Encoding: quoted-printable
Twas brillig at 14:00:54 17.11.2009 UTC-05 when lars@seas.harvard.edu did g=
yre and gimble:
--=-=-=
Content-Type: application/pgp-signature
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)
iQIcBAEBAgAGBQJLAvNOAAoJEJ0g9lA+M4iIjLYQAKp0PXEgl3JMOEBisH52AsIK
=/ksP
-----END PGP SIGNATURE-----
--=-=-=--
--===============1958295626==
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: inline
Testing!
--===============1958295626==--
`)
e, err := NewEmailFromReader(bytes.NewReader(raw))
if err != nil {
t.Fatalf("Error when parsing email %s", err.Error())
}
if !bytes.Equal(e.Text, []byte("Testing!")) {
t.Fatalf("Error incorrect text: %#q != %#q\n", e.Text, "Testing!")
}
}
func TestNoMultipartHTMLContentTypeBase64Encoding(t *testing.T) {
raw := []byte(`MIME-Version: 1.0
From: no-reply@example.com
To: tester@example.org
Date: 7 Jan 2021 03:07:44 -0800
Subject: Hello
Content-Type: text/html; charset=utf-8
Content-Transfer-Encoding: base64
Message-Id: <20210107110744.547DD70532@example.com>
PGh0bWw+PGhlYWQ+PHRpdGxlPnRlc3Q8L3RpdGxlPjwvaGVhZD48Ym9keT5IZWxsbyB3
b3JsZCE8L2JvZHk+PC9odG1sPg==
`)
e, err := NewEmailFromReader(bytes.NewReader(raw))
if err != nil {
t.Fatalf("Error when parsing email %s", err.Error())
}
if !bytes.Equal(e.HTML, []byte("<html><head><title>test</title></head><body>Hello world!</body></html>")) {
t.Fatalf("Error incorrect text: %#q != %#q\n", e.Text, "<html>...</html>")
}
}
// *Since the mime library in use by ```email``` is now in the stdlib, this test is deprecated
func Test_quotedPrintDecode(t *testing.T) {
text := []byte("Dear reader!\r\n\r\n" +
"This is a test email to try and capture some of the corner cases that exist=\r\n" +
" within\r\n" +
"the quoted-printable encoding.\r\n" +
"There are some wacky parts like =3D, and this input assumes UNIX line break=\r\n" +
"s so\r\n" +
"it can come out a little weird. Also, we need to support unicode so here's=\r\n" +
" a fish: =F0=9F=90=9F\r\n")
expected := []byte("Dear reader!\r\n\r\n" +
"This is a test email to try and capture some of the corner cases that exist within\r\n" +
"the quoted-printable encoding.\r\n" +
"There are some wacky parts like =, and this input assumes UNIX line breaks so\r\n" +
"it can come out a little weird. Also, we need to support unicode so here's a fish: 🐟\r\n")
qp := quotedprintable.NewReader(bytes.NewReader(text))
got, err := ioutil.ReadAll(qp)
if err != nil {
t.Fatal("quotePrintDecode: ", err)
}
if !bytes.Equal(got, expected) {
t.Errorf("quotedPrintDecode generated incorrect results: %#q != %#q", got, expected)
}
}
func Benchmark_base64Wrap(b *testing.B) {
// Reasonable base case; 128K random bytes
file := make([]byte, 128*1024)
if _, err := rand.Read(file); err != nil {
panic(err)
}
for i := 0; i <= b.N; i++ {
base64Wrap(ioutil.Discard, file)
}
}
func TestParseSender(t *testing.T) {
var cases = []struct {
e Email
want string
haserr bool
}{
{
Email{From: "from@test.com"},
"from@test.com",
false,
},
{
Email{Sender: "sender@test.com", From: "from@test.com"},
"sender@test.com",
false,
},
{
Email{Sender: "bad_address_sender"},
"",
true,
},
{
Email{Sender: "good@sender.com", From: "bad_address_from"},
"good@sender.com",
false,
},
}
for i, testcase := range cases {
got, err := testcase.e.parseSender()
if got != testcase.want || (err != nil) != testcase.haserr {
t.Errorf(`%d: got %s != want %s or error "%t" != "%t"`, i+1, got, testcase.want, err != nil, testcase.haserr)
}
}
}

@ -0,0 +1,367 @@
package email
import (
"crypto/tls"
"errors"
"io"
"net"
"net/mail"
"net/smtp"
"net/textproto"
"sync"
"syscall"
"time"
)
type Pool struct {
addr string
auth smtp.Auth
max int
created int
clients chan *client
rebuild chan struct{}
mut *sync.Mutex
lastBuildErr *timestampedErr
closing chan struct{}
tlsConfig *tls.Config
helloHostname string
}
type client struct {
*smtp.Client
failCount int
}
type timestampedErr struct {
err error
ts time.Time
}
const maxFails = 4
var (
ErrClosed = errors.New("pool closed")
ErrTimeout = errors.New("timed out")
)
func NewPool(address string, count int, auth smtp.Auth, opt_tlsConfig ...*tls.Config) (pool *Pool, err error) {
pool = &Pool{
addr: address,
auth: auth,
max: count,
clients: make(chan *client, count),
rebuild: make(chan struct{}),
closing: make(chan struct{}),
mut: &sync.Mutex{},
}
if len(opt_tlsConfig) == 1 {
pool.tlsConfig = opt_tlsConfig[0]
} else if host, _, e := net.SplitHostPort(address); e != nil {
return nil, e
} else {
pool.tlsConfig = &tls.Config{ServerName: host}
}
return
}
// go1.1 didn't have this method
func (c *client) Close() error {
return c.Text.Close()
}
// SetHelloHostname optionally sets the hostname that the Go smtp.Client will
// use when doing a HELLO with the upstream SMTP server. By default, Go uses
// "localhost" which may not be accepted by certain SMTP servers that demand
// an FQDN.
func (p *Pool) SetHelloHostname(h string) {
p.helloHostname = h
}
func (p *Pool) get(timeout time.Duration) *client {
select {
case c := <-p.clients:
return c
default:
}
if p.created < p.max {
p.makeOne()
}
var deadline <-chan time.Time
if timeout >= 0 {
deadline = time.After(timeout)
}
for {
select {
case c := <-p.clients:
return c
case <-p.rebuild:
p.makeOne()
case <-deadline:
return nil
case <-p.closing:
return nil
}
}
}
func shouldReuse(err error) bool {
// certainly not perfect, but might be close:
// - EOF: clearly, the connection went down
// - textproto.Errors were valid SMTP over a valid connection,
// but resulted from an SMTP error response
// - textproto.ProtocolErrors result from connections going down,
// invalid SMTP, that sort of thing
// - syscall.Errno is probably down connection/bad pipe, but
// passed straight through by textproto instead of becoming a
// ProtocolError
// - if we don't recognize the error, don't reuse the connection
// A false positive will probably fail on the Reset(), and even if
// not will eventually hit maxFails.
// A false negative will knock over (and trigger replacement of) a
// conn that might have still worked.
if err == io.EOF {
return false
}
switch err.(type) {
case *textproto.Error:
return true
case *textproto.ProtocolError, textproto.ProtocolError:
return false
case syscall.Errno:
return false
default:
return false
}
}
func (p *Pool) replace(c *client) {
p.clients <- c
}
func (p *Pool) inc() bool {
if p.created >= p.max {
return false
}
p.mut.Lock()
defer p.mut.Unlock()
if p.created >= p.max {
return false
}
p.created++
return true
}
func (p *Pool) dec() {
p.mut.Lock()
p.created--
p.mut.Unlock()
select {
case p.rebuild <- struct{}{}:
default:
}
}
func (p *Pool) makeOne() {
go func() {
if p.inc() {
if c, err := p.build(); err == nil {
p.clients <- c
} else {
p.lastBuildErr = &timestampedErr{err, time.Now()}
p.dec()
}
}
}()
}
func startTLS(c *client, t *tls.Config) (bool, error) {
if ok, _ := c.Extension("STARTTLS"); !ok {
return false, nil
}
if err := c.StartTLS(t); err != nil {
return false, err
}
return true, nil
}
func addAuth(c *client, auth smtp.Auth) (bool, error) {
if ok, _ := c.Extension("AUTH"); !ok {
return false, nil
}
if err := c.Auth(auth); err != nil {
return false, err
}
return true, nil
}
func (p *Pool) build() (*client, error) {
cl, err := smtp.Dial(p.addr)
if err != nil {
return nil, err
}
// Is there a custom hostname for doing a HELLO with the SMTP server?
if p.helloHostname != "" {
cl.Hello(p.helloHostname)
}
c := &client{cl, 0}
if _, err := startTLS(c, p.tlsConfig); err != nil {
c.Close()
return nil, err
}
if p.auth != nil {
if _, err := addAuth(c, p.auth); err != nil {
c.Close()
return nil, err
}
}
return c, nil
}
func (p *Pool) maybeReplace(err error, c *client) {
if err == nil {
c.failCount = 0
p.replace(c)
return
}
c.failCount++
if c.failCount >= maxFails {
goto shutdown
}
if !shouldReuse(err) {
goto shutdown
}
if err := c.Reset(); err != nil {
goto shutdown
}
p.replace(c)
return
shutdown:
p.dec()
c.Close()
}
func (p *Pool) failedToGet(startTime time.Time) error {
select {
case <-p.closing:
return ErrClosed
default:
}
if p.lastBuildErr != nil && startTime.Before(p.lastBuildErr.ts) {
return p.lastBuildErr.err
}
return ErrTimeout
}
// Send sends an email via a connection pulled from the Pool. The timeout may
// be <0 to indicate no timeout. Otherwise reaching the timeout will produce
// and error building a connection that occurred while we were waiting, or
// otherwise ErrTimeout.
func (p *Pool) Send(e *Email, timeout time.Duration) (err error) {
start := time.Now()
c := p.get(timeout)
if c == nil {
return p.failedToGet(start)
}
defer func() {
p.maybeReplace(err, c)
}()
recipients, err := addressLists(e.To, e.Cc, e.Bcc)
if err != nil {
return
}
msg, err := e.Bytes()
if err != nil {
return
}
from, err := emailOnly(e.From)
if err != nil {
return
}
if err = c.Mail(from); err != nil {
return
}
for _, recip := range recipients {
if err = c.Rcpt(recip); err != nil {
return
}
}
w, err := c.Data()
if err != nil {
return
}
if _, err = w.Write(msg); err != nil {
return
}
err = w.Close()
return
}
func emailOnly(full string) (string, error) {
addr, err := mail.ParseAddress(full)
if err != nil {
return "", err
}
return addr.Address, nil
}
func addressLists(lists ...[]string) ([]string, error) {
length := 0
for _, lst := range lists {
length += len(lst)
}
combined := make([]string, 0, length)
for _, lst := range lists {
for _, full := range lst {
addr, err := emailOnly(full)
if err != nil {
return nil, err
}
combined = append(combined, addr)
}
}
return combined, nil
}
// Close immediately changes the pool's state so no new connections will be
// created, then gets and closes the existing ones as they become available.
func (p *Pool) Close() {
close(p.closing)
for p.created > 0 {
c := <-p.clients
c.Quit()
p.dec()
}
}

@ -0,0 +1,148 @@
package smtpclient
import (
"b612.me/apps/b612/smtpclient/email"
"b612.me/stario"
"b612.me/starlog"
"crypto/tls"
"github.com/spf13/cobra"
"mime"
"net/smtp"
"os"
"path/filepath"
"strings"
)
var Cmd = &cobra.Command{
Use: "smtpc",
Short: "smtp client",
Long: "smtp client",
Run: func(cmd *cobra.Command, args []string) {
run()
},
}
var from, subject, text string
var to, cc, bcc, attachments, replyTo []string
var useHTML bool
var user, pwd, server string
var skipInsecure, usingFile bool
var useTLS int
var hostname string
var autoHostname bool
func init() {
Cmd.Flags().BoolVarP(&usingFile, "file", "F", false, "using file")
Cmd.Flags().StringSliceVarP(&replyTo, "reply-to", "r", nil, "reply to")
Cmd.Flags().StringVarP(&from, "from", "f", "", "from")
Cmd.Flags().StringVarP(&subject, "subject", "s", "", "subject")
Cmd.Flags().StringVarP(&text, "text", "t", "", "text")
Cmd.Flags().StringSliceVarP(&to, "to", "T", nil, "to")
Cmd.Flags().StringSliceVarP(&cc, "cc", "C", nil, "cc")
Cmd.Flags().StringSliceVarP(&bcc, "bcc", "B", nil, "bcc")
Cmd.Flags().StringSliceVarP(&attachments, "attachments", "a", nil, "attachments")
Cmd.Flags().BoolVarP(&useHTML, "html", "H", false, "use html")
Cmd.Flags().StringVarP(&user, "user", "u", "", "user")
Cmd.Flags().StringVarP(&pwd, "pwd", "p", "", "password")
Cmd.Flags().StringVarP(&server, "server", "S", "127.0.0.1:25", "server")
Cmd.Flags().IntVarP(&useTLS, "tls", "l", 0, "use tls,1 means use tls,2 means use starttls,other means not use tls")
Cmd.Flags().BoolVarP(&skipInsecure, "skip-insecure", "i", false, "skip insecure")
Cmd.Flags().StringVarP(&hostname, "hostname", "n", "", "hostname")
Cmd.Flags().BoolVarP(&autoHostname, "auto-hostname", "N", false, "auto hostname")
}
func run() {
{
for from == "" {
from = stario.MessageBox("Please input mail from:", "").MustString()
}
for len(to) == 0 {
to = stario.MessageBox("Please input mail to,split by ,:", "").MustSliceString(",")
}
for subject == "" {
subject = stario.MessageBox("Please input mail subject:", "").MustString()
}
for text == "" {
text = stario.MessageBox("Please input mail text:", "").MustString()
}
for server == "" {
server = stario.MessageBox("Please input mail server:", "").MustString()
}
}
var mail = email.Email{
ReplyTo: replyTo,
From: from,
To: to,
Bcc: bcc,
Cc: cc,
Subject: subject,
}
var txt []byte
var err error
if usingFile {
txt, err = os.ReadFile(text)
if err != nil {
starlog.Errorf("read file %s error:%s\n", text, err)
os.Exit(1)
}
} else {
txt = []byte(text)
}
if useHTML {
mail.HTML = txt
} else {
mail.Text = txt
}
if len(attachments) > 0 {
for _, v := range attachments {
if strings.TrimSpace(v) == "" {
continue
}
f, err := os.Open(v)
if err != nil {
starlog.Errorf("open attach file %s error:%s\n", v, err)
os.Exit(1)
}
stat, err := f.Stat()
if err != nil {
starlog.Errorf("stat attach file %s error:%s\n", v, err)
os.Exit(1)
}
_, err = mail.Attach(f, stat.Name(), mime.TypeByExtension(filepath.Ext(stat.Name())))
if err != nil {
starlog.Errorf("attach file %s error:%s\n", v, err)
os.Exit(1)
}
}
}
if autoHostname && hostname == "" {
hostname = strings.Split(server, ":")[0]
}
var auth smtp.Auth
if user != "" && pwd != "" {
auth = smtp.PlainAuth("", user, pwd, hostname)
}
switch useTLS {
case 1:
starlog.Noticef("Mail send method:TLS InsecureSkipVerify:%v ServerName:%v\n", skipInsecure, hostname)
err = mail.SendWithTLS(server, auth, &tls.Config{
InsecureSkipVerify: skipInsecure,
ServerName: hostname,
})
case 2:
starlog.Noticef("Mail send method:StartTLS InsecureSkipVerify:%v ServerName:%v\n", skipInsecure, hostname)
err = mail.SendWithStartTLS(server, auth, &tls.Config{InsecureSkipVerify: skipInsecure,
ServerName: hostname})
default:
starlog.Noticef("Mail send method:Normal\n")
err = mail.Send(server, auth)
}
if err != nil {
starlog.Errorf("send mail error:%s\n", err)
os.Exit(1)
}
starlog.Infof("send mail to %v success by server %s\n", to, server)
}

@ -0,0 +1,13 @@
package smtpserver
import (
"fmt"
"mime"
"testing"
)
func TestSession_Data(t *testing.T) {
s := `=?UTF-8?q?=E5=85=B7=E8=B6=B3=E8=99=AB=E7=94=B5=E8=B4=BA=E8=A6=81=E8=BE=8A?= =?UTF-8?q?=E6=A5=9Ejhkkj?=`
d := new(mime.WordDecoder)
fmt.Println(d.DecodeHeader(s))
}

@ -0,0 +1,212 @@
package smtpserver
import (
"b612.me/starlog"
"b612.me/startext"
"bytes"
"crypto/tls"
"fmt"
"github.com/spf13/cobra"
"html"
"io"
"io/ioutil"
"mime"
"mime/quotedprintable"
"net/mail"
"os"
"strings"
"time"
"github.com/emersion/go-smtp"
)
var addr string
var user, pass string
var allowAnyuser bool
var output string
var domain string
var cert, key string
var Cmd = &cobra.Command{
Use: "smtps",
Short: "smtp server",
Long: "smtp server",
Run: func(cmd *cobra.Command, args []string) {
run()
},
}
func init() {
Cmd.Flags().StringVarP(&addr, "addr", "a", "0.0.0.0:25", "smtp server listen address")
Cmd.Flags().StringVarP(&user, "user", "u", "admin", "smtp server username")
Cmd.Flags().StringVarP(&pass, "pass", "p", "admin", "smtp server password")
Cmd.Flags().BoolVarP(&allowAnyuser, "allow-anyuser", "A", false, "allow any user")
Cmd.Flags().StringVarP(&output, "output", "o", "", "output mail to html")
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)")
}
type backend struct{}
func (bkd *backend) NewSession(c *smtp.Conn) (smtp.Session, error) {
return &session{}, nil
}
type session struct {
username string
password string
to string
}
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
}
}
return nil
}
func (s *session) Mail(from string, opts *smtp.MailOptions) error {
return nil
}
func (s *session) Rcpt(to string, opts *smtp.RcptOptions) error {
s.to += to + ";"
return nil
}
func (s *session) Data(r io.Reader) error {
mailData, err := ioutil.ReadAll(r)
if err != nil {
return err
}
msg, err := mail.ReadMessage(bytes.NewReader(mailData))
if err != nil {
return err
}
header := msg.Header
subject := header.Get("Subject")
to := header.Get("To")
cc := header.Get("Cc")
from := header.Get("From") // 获取发件人
date := header.Get("Date")
body, err := ioutil.ReadAll(msg.Body)
if err != nil {
return err
}
var bodyStr string
var d = new(mime.WordDecoder)
{
subject, err = d.DecodeHeader(subject)
if err != nil {
starlog.Errorf("Decode subject %s error:%s\n", subject, err)
return err
}
bodyStr, err = bodyDecode(string(body))
if err != nil {
starlog.Errorf("Decode body %s error:%s\n", string(body), err)
return err
}
}
if startext.IsGBK([]byte(bodyStr)) {
tmp, err := startext.GBK2UTF8([]byte(bodyStr))
if err == nil {
bodyStr = string(tmp)
}
}
starlog.Println("From:", from)
starlog.Println("Subject:", subject)
starlog.Println("To ALL:", s.to)
starlog.Println("To:", to)
starlog.Println("Cc:", cc)
starlog.Println("Body:", bodyStr)
if output != "" {
path := fmt.Sprintf("%s/%s_%s.html", output, subject, time.Now().Format("2006_01_02_15_04_05_"))
html := fmt.Sprintf(`<html><head>
<meta charset="utf-8">
<title>%s</title>
</head>
<body>
<h2>%s</h2>
<hr>
<p>auth user:<strong> %s </strong></p>
<p>auth pass:<strong> %s </strong></p>
<hr>
<p>Date:<strong> %v </strong></p>
<p>From:<strong> %s </strong></p>
<p>To All:<strong> %s </strong></p>
<p>To:<strong> %s </strong></p>
<p>Cc:<strong> %s </strong></p>
<hr>
<br />
<br />
%s
</body>
</html>`, html.EscapeString(subject), html.EscapeString(subject), html.EscapeString(s.username), html.EscapeString(s.password),
date, html.EscapeString(from), html.EscapeString(s.to), html.EscapeString(to), html.EscapeString(cc), bodyStr)
os.WriteFile(path, []byte(html), 0644)
}
return nil
}
func (s *session) Reset() {}
func (s *session) Logout() error {
return nil
}
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.AllowInsecureAuth = true
s.Debug = os.Stdout
starlog.Infoln("Starting SMTP server at", addr)
starlog.Errorln(s.ListenAndServe())
}
func bodyDecode(encoded string) (string, error) {
encoded = strings.Replace(encoded, "=\r\n", "", -1) // 对于Windows系统
encoded = strings.Replace(encoded, "=\n", "", -1) // 对于UNIX/Linux系统
// 创建一个新的Quoted-Printable阅读器
reader := quotedprintable.NewReader(strings.NewReader(encoded))
// 读取并解码整个内容
decoded, err := ioutil.ReadAll(reader)
if err != nil {
fmt.Println("Error decoding string:", err)
return string(decoded), err
}
return string(decoded), nil
}

@ -0,0 +1,48 @@
package socks5
import (
"b612.me/starlog"
"github.com/spf13/cobra"
"github.com/things-go/go-socks5"
"log"
"os"
)
var username, password string
var listen string
func init() {
Cmd.Flags().StringVarP(&username, "username", "u", "", "用户名")
Cmd.Flags().StringVarP(&password, "password", "p", "", "密码")
Cmd.Flags().StringVarP(&listen, "listen", "l", ":8000", "监听地址")
}
var Cmd = &cobra.Command{
Use: "socks5",
Short: "socks5代理",
Long: "socks5代理",
Run: func(cmd *cobra.Command, args []string) {
run()
},
}
func run() {
// Create a SOCKS5 server
var opt []socks5.Option
opt = append(opt, socks5.WithLogger(socks5.NewLogger(log.New(os.Stdout, "socks5: ", log.LstdFlags))))
if username != "" && password != "" {
opt = append(opt, socks5.WithAuthMethods([]socks5.Authenticator{
socks5.UserPassAuthenticator{Credentials: socks5.StaticCredentials{username: password}},
}))
}
server := socks5.NewServer(
opt...,
)
starlog.Infof("socks5 server listen on %s", listen)
// Create SOCKS5 proxy on localhost port 8000
if err := server.ListenAndServe("tcp", listen); err != nil {
starlog.Errorln("socks5 server error:", err)
os.Exit(1)
}
}

@ -28,16 +28,16 @@ var (
var Cmd = &cobra.Command{
Use: "tcping",
Short: "tcp/http ping",
Short: "tcp/http ping 工具",
Long: "使用进行Tcp或Http协议进行ping探测",
Example: `
1. ping over tcp
1. dns over tcp
> tcping google.com
2. ping over tcp with custom port
2. dns over tcp with custom port
> tcping google.com 443
3. ping over http
3. dns over http
> tcping -H google.com
4. ping with URI schema
4. dns with URI schema
> tcping http://hui.lu
`,
Run: func(cmd *cobra.Command, args []string) {

@ -36,7 +36,7 @@ func (ping *HTTPing) SetTarget(target *Target) {
}
}
// Start ping
// Start dns
func (ping *HTTPing) Start() <-chan struct{} {
go func() {
t := time.NewTicker(ping.target.Interval)
@ -79,7 +79,7 @@ func (ping *HTTPing) Start() <-chan struct{} {
return ping.done
}
// Result return ping result
// Result return dns result
func (ping *HTTPing) Result() *Result {
return ping.result
}

@ -47,7 +47,7 @@ func NewProtocol(protocol string) (Protocol, error) {
return 0, fmt.Errorf("protocol %s not support", protocol)
}
// Target is a ping
// Target is a dns
type Target struct {
Protocol Protocol
Host string
@ -62,7 +62,7 @@ func (target Target) String() string {
return fmt.Sprintf("%s://%s:%d", target.Protocol, target.Host, target.Port)
}
// Pinger is a ping interface
// Pinger is a dns interface
type Pinger interface {
Start() <-chan struct{}
Stop()
@ -70,7 +70,7 @@ type Pinger interface {
SetTarget(target *Target)
}
// Ping is a ping interface
// Ping is a dns interface
type Ping interface {
Start() <-chan struct{}
@ -95,7 +95,7 @@ type Result struct {
TotalDuration time.Duration
}
// Avg return the average time of ping
// Avg return the average time of dns
func (result Result) Avg() time.Duration {
if result.SuccessCounter == 0 {
return 0

@ -0,0 +1,420 @@
package tls
import (
"b612.me/starlog"
"crypto/ecdsa"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"fmt"
"github.com/spf13/cobra"
"golang.org/x/net/idna"
"golang.org/x/net/proxy"
"net"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"time"
)
var hideDetail bool
var dump string
var reqRawIP int
var timeoutMillSec int
var socks5 string
var socks5Auth string
var showCA bool
func init() {
Cmd.Flags().BoolVarP(&hideDetail, "hide-detail", "H", false, "隐藏证书详细信息")
Cmd.Flags().StringVarP(&dump, "dump", "d", "", "将证书保存到文件")
Cmd.Flags().IntVarP(&reqRawIP, "resolve-ip", "r", 0, "使用解析到的IP地址进行连接输入数字表示使用解析到的第几个IP地址")
Cmd.Flags().IntVarP(&timeoutMillSec, "timeout", "t", 5000, "连接超时时间(毫秒)")
Cmd.Flags().StringVarP(&socks5, "socks5", "p", "", "socks5代理示例127.0.0.1:1080")
Cmd.Flags().StringVarP(&socks5Auth, "socks5-auth", "A", "", "socks5代理认证示例username:password")
Cmd.Flags().BoolVarP(&showCA, "show-ca", "c", false, "显示CA证书")
}
var Cmd = &cobra.Command{
Use: "tls",
Short: "查看TLS证书信息",
Long: "查看TLS证书信息",
Run: func(cmd *cobra.Command, args []string) {
for _, target := range args {
showTls(target, !hideDetail, showCA, reqRawIP, dump, time.Duration(timeoutMillSec)*time.Millisecond)
}
},
}
func showTls(target string, showDetail, showCA bool, reqRawIP int, dumpPath string, timeout time.Duration) {
var err error
{
sp := strings.Split(target, ":")
if len(sp) < 2 {
target = target + ":443"
} else {
if _, err := strconv.Atoi(sp[len(sp)-1]); err != nil {
target = target + ":443"
}
}
}
if timeout == 0 {
timeout = 5 * time.Second
}
hostname := strings.Split(target, ":")[0]
if strings.Count(target, ":") == 2 {
strs := strings.Split(target, ":")
if len(strs) != 3 {
starlog.Errorln("invalid target format")
return
}
target = strs[0] + ":" + strs[2]
hostname = strs[1]
}
if reqRawIP > 0 {
domain := strings.Split(target, ":")[0]
ips, err := net.LookupIP(domain)
if err != nil {
starlog.Errorln("failed to resolve domain: " + err.Error())
return
}
if len(ips) == 0 {
starlog.Errorln("no ip found for domain")
return
}
for _, v := range ips {
starlog.Infof("解析到的IP地址为: %s\n", v.String())
}
if reqRawIP > len(ips) {
reqRawIP = len(ips)
}
target = ips[reqRawIP-1].String() + ":443"
hostname = ips[reqRawIP-1].String()
starlog.Noticeln("使用解析到的IP地址进行连接:", target)
}
starlog.Noticef("将使用如下地址连接:%s ; ServerName: %s\n", target, hostname)
punyCode, err := idna.ToASCII(hostname)
if err == nil {
if punyCode != hostname {
starlog.Infoln("检测到域名中含有非ASCII字符PunyCode转换后为:", punyCode)
hostname = punyCode
}
}
starlog.Infof("正在连接服务器: %s\n", target)
var netDialer = &net.Dialer{
Timeout: timeout,
}
var socksDialer *proxy.Dialer
if socks5 != "" {
var auth *proxy.Auth
if socks5Auth != "" {
up := strings.SplitN(socks5Auth, ":", 2)
if len(up) == 2 {
auth = &proxy.Auth{
User: up[0],
Password: up[1],
}
} else {
starlog.Errorln("socks5认证格式错误")
return
}
}
s5Dial, err := proxy.SOCKS5("tcp", socks5, auth, proxy.Direct)
if err == nil {
socksDialer = &s5Dial
} else {
starlog.Errorln("socks5代理错误:", err)
return
}
}
var verifyErr error
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
var conn *tls.Conn
if socksDialer == nil {
conn, verifyErr = tls.DialWithDialer(netDialer, "tcp", target, &tls.Config{
InsecureSkipVerify: false,
ServerName: hostname,
MinVersion: tls.VersionSSL30,
})
if verifyErr == nil {
conn.Close()
}
} else {
con, err := (*socksDialer).Dial("tcp", target)
if err != nil {
verifyErr = err
return
}
conn = tls.Client(con, &tls.Config{
InsecureSkipVerify: false,
ServerName: hostname,
MinVersion: tls.VersionSSL30,
})
verifyErr = conn.Handshake()
con.Close()
}
}()
var conn *tls.Conn
if socksDialer == nil {
conn, err = tls.DialWithDialer(netDialer, "tcp", target, &tls.Config{
InsecureSkipVerify: true,
ServerName: hostname,
MinVersion: tls.VersionSSL30,
})
if err != nil {
starlog.Errorln("failed to connect: " + err.Error())
return
}
} else {
con, err := (*socksDialer).Dial("tcp", target)
if err != nil {
starlog.Errorln("failed to connect: " + err.Error())
return
}
defer con.Close()
conn = tls.Client(con, &tls.Config{
InsecureSkipVerify: true,
ServerName: hostname,
MinVersion: tls.VersionSSL30,
})
err = conn.Handshake()
if err != nil {
starlog.Errorln("failed to handshake: " + err.Error())
return
}
}
defer conn.Close()
starlog.Infof("连接成功对方IP:%s正在获取证书信息\n", conn.RemoteAddr().String())
certs := conn.ConnectionState().PeerCertificates
if len(certs) == 0 {
starlog.Errorln("no certificate found")
return
}
starlog.Infof("证书获取成功,证书链上共有%d个证书\n", len(certs))
state := conn.ConnectionState()
switch state.Version {
case tls.VersionSSL30:
starlog.Warningln("当前TLS版本: SSL 3.0")
case tls.VersionTLS10:
starlog.Warningln("当前TLS版本: TLS 1.0")
case tls.VersionTLS11:
starlog.Warningln("当前TLS版本: TLS 1.1")
case tls.VersionTLS12:
starlog.Infoln("当前TLS版本: TLS 1.2")
case tls.VersionTLS13:
starlog.Infoln("当前TLS版本: TLS 1.3")
}
switch state.CipherSuite {
case tls.TLS_RSA_WITH_RC4_128_SHA:
starlog.Infoln("当前加密套件: TLS_RSA_WITH_RC4_128_SHA")
case tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA:
starlog.Infoln("当前加密套件: TLS_RSA_WITH_3DES_EDE_CBC_SHA")
case tls.TLS_RSA_WITH_AES_128_CBC_SHA:
starlog.Infoln("当前加密套件: TLS_RSA_WITH_AES_128_CBC_SHA")
case tls.TLS_RSA_WITH_AES_256_CBC_SHA:
starlog.Infoln("当前加密套件: TLS_RSA_WITH_AES_256_CBC_SHA")
case tls.TLS_RSA_WITH_AES_128_CBC_SHA256:
starlog.Infoln("当前加密套件: TLS_RSA_WITH_AES_128_CBC_SHA256")
case tls.TLS_RSA_WITH_AES_128_GCM_SHA256:
starlog.Infoln("当前加密套件: TLS_RSA_WITH_AES_128_GCM_SHA256")
case tls.TLS_RSA_WITH_AES_256_GCM_SHA384:
starlog.Infoln("当前加密套件: TLS_RSA_WITH_AES_256_GCM_SHA384")
case tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
starlog.Infoln("当前加密套件: TLS_ECDHE_ECDSA_WITH_RC4_128_SHA")
case tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
starlog.Infoln("当前加密套件: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA")
case tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
starlog.Infoln("当前加密套件: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA")
case tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA:
starlog.Infoln("当前加密套件: TLS_ECDHE_RSA_WITH_RC4_128_SHA")
case tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
starlog.Infoln("当前加密套件: TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA")
case tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
starlog.Infoln("当前加密套件: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA")
case tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
starlog.Infoln("当前加密套件: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA")
case tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
starlog.Infoln("当前加密套件: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256")
case tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
starlog.Infoln("当前加密套件: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256")
case tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
starlog.Infoln("当前加密套件: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256")
case tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
starlog.Infoln("当前加密套件: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256")
case tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
starlog.Infoln("当前加密套件: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384")
case tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
starlog.Infoln("当前加密套件: TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384")
case tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305:
starlog.Infoln("当前加密套件: TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305")
case tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305:
starlog.Infoln("当前加密套件: TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305")
case tls.TLS_AES_128_GCM_SHA256:
starlog.Infoln("当前加密套件: TLS_AES_128_GCM_SHA256")
case tls.TLS_AES_256_GCM_SHA384:
starlog.Infoln("当前加密套件: TLS_AES_256_GCM_SHA384")
case tls.TLS_CHACHA20_POLY1305_SHA256:
starlog.Infoln("当前加密套件: TLS_CHACHA20_POLY1305_SHA256")
default:
starlog.Infoln("当前加密套件:", state.CipherSuite)
}
starlog.Infoln("服务器名称:", state.ServerName)
wg.Wait()
if verifyErr != nil {
starlog.Red("证书验证失败: " + verifyErr.Error())
} else {
starlog.Green("证书验证成功")
}
if showDetail {
for _, c := range certs {
if c.IsCA && !showCA {
continue
}
fmt.Printf("----------\n")
if c.IsCA {
fmt.Println("这是一个CA证书")
}
fmt.Printf("证书基础信息: %+v\n", c.Subject)
fmt.Printf("证书颁发者: %+v\n", c.Issuer)
fmt.Printf("证书生效时间: %+v 距今:%.1f天\n", c.NotBefore.In(time.Local), time.Since(c.NotBefore).Hours()/24)
fmt.Printf("证书过期时间: %+v 剩余:%.1f天\n", c.NotAfter.In(time.Local), c.NotAfter.Sub(time.Now()).Hours()/24)
fmt.Printf("证书序列号: %s\n", c.SerialNumber.Text(16))
fmt.Printf("证书签名算法: %s\n", c.SignatureAlgorithm)
fmt.Printf("证书公钥算法: %s\n", c.PublicKeyAlgorithm)
switch pub := c.PublicKey.(type) {
case *rsa.PublicKey:
fmt.Printf("RSA公钥位数: %d\n", pub.Size()*8) // RSA公钥的位数
case *ecdsa.PublicKey:
fmt.Printf("ECDSA Curve位数: %d\n", pub.Curve.Params().BitSize) // ECDSA公钥的位数
}
if len(c.DNSNames) != 0 {
fmt.Printf("可选使用的DNS: %s\n", strings.Join(c.DNSNames, ", "))
}
if len(c.IPAddresses) != 0 {
ipAddr := ""
for _, ip := range c.IPAddresses {
ipAddr += ip.String() + ", "
}
ipAddr = ipAddr[:len(ipAddr)-2]
fmt.Printf("可选使用的IP: %s\n", ipAddr)
}
if len(c.EmailAddresses) != 0 {
fmt.Printf("可选使用的Email: %s\n", strings.Join(c.EmailAddresses, ", "))
}
if len(c.URIs) != 0 {
fmt.Printf("可选使用的URI: %v\n", c.URIs)
}
if len(c.PermittedDNSDomains) != 0 {
fmt.Printf("批准使用的DNS: %s\n", strings.Join(c.PermittedDNSDomains, ", "))
}
if len(c.PermittedIPRanges) != 0 {
ipRange := ""
for _, ip := range c.PermittedIPRanges {
ipRange += ip.String() + ", "
}
ipRange = ipRange[:len(ipRange)-2]
fmt.Printf("批准使用的IP: %s\n", ipRange)
}
if len(c.PermittedEmailAddresses) != 0 {
fmt.Printf("批准使用的Email: %s\n", strings.Join(c.PermittedEmailAddresses, ", "))
}
if len(c.PermittedURIDomains) != 0 {
fmt.Printf("批准使用的URI: %s\n", strings.Join(c.PermittedURIDomains, ", "))
}
fmt.Printf("证书密钥用途: %s\n", strings.Join(KeyUsageToString(c.KeyUsage), ", "))
extKeyUsage := []string{}
for _, v := range c.ExtKeyUsage {
switch v {
case x509.ExtKeyUsageAny:
extKeyUsage = append(extKeyUsage, "任何用途")
case x509.ExtKeyUsageServerAuth:
extKeyUsage = append(extKeyUsage, "服务器认证")
case x509.ExtKeyUsageClientAuth:
extKeyUsage = append(extKeyUsage, "客户端认证")
case x509.ExtKeyUsageCodeSigning:
extKeyUsage = append(extKeyUsage, "代码签名")
case x509.ExtKeyUsageEmailProtection:
extKeyUsage = append(extKeyUsage, "电子邮件保护")
case x509.ExtKeyUsageIPSECEndSystem:
extKeyUsage = append(extKeyUsage, "IPSEC终端系统")
case x509.ExtKeyUsageIPSECTunnel:
extKeyUsage = append(extKeyUsage, "IPSEC隧道")
case x509.ExtKeyUsageIPSECUser:
extKeyUsage = append(extKeyUsage, "IPSEC用户")
case x509.ExtKeyUsageTimeStamping:
extKeyUsage = append(extKeyUsage, "时间戳")
case x509.ExtKeyUsageOCSPSigning:
extKeyUsage = append(extKeyUsage, "OCSP签名")
case x509.ExtKeyUsageMicrosoftServerGatedCrypto:
extKeyUsage = append(extKeyUsage, "Microsoft服务器门控加密")
case x509.ExtKeyUsageNetscapeServerGatedCrypto:
extKeyUsage = append(extKeyUsage, "Netscape服务器门控加密")
case x509.ExtKeyUsageMicrosoftCommercialCodeSigning:
extKeyUsage = append(extKeyUsage, "Microsoft商业代码签名")
case x509.ExtKeyUsageMicrosoftKernelCodeSigning:
extKeyUsage = append(extKeyUsage, "Microsoft内核代码签名")
default:
extKeyUsage = append(extKeyUsage, fmt.Sprintf("未知用途(%d)", v))
}
}
fmt.Printf("证书扩展密钥用途: %s\n", strings.Join(extKeyUsage, ", "))
fmt.Printf("证书版本: %d\n----------\n", c.Version)
//fmt.Printf("证书扩展信息: %+v\n\n----------", c.Extensions)
}
if dumpPath != "" {
var data []byte
var name string
for _, c := range certs {
if name == "" {
name = c.Subject.CommonName + ".crt"
}
certBlock := &pem.Block{
Type: "CERTIFICATE",
Bytes: c.Raw,
}
data = append(data, pem.EncodeToMemory(certBlock)...)
}
err = os.WriteFile(filepath.Join(dumpPath, name), data, 0644)
if err != nil {
starlog.Errorln("failed to write file: " + err.Error())
return
}
starlog.Infoln("dumped to " + filepath.Join(dumpPath, name))
}
}
}
func KeyUsageToString(ku x509.KeyUsage) []string {
usages := []string{}
flags := []struct {
Flag x509.KeyUsage
Name string
}{
{x509.KeyUsageDigitalSignature, "数字签名"},
{x509.KeyUsageContentCommitment, "内容承诺"},
{x509.KeyUsageKeyEncipherment, "密钥加密"},
{x509.KeyUsageDataEncipherment, "数据加密"},
{x509.KeyUsageKeyAgreement, "密钥协商"},
{x509.KeyUsageCertSign, "证书签名"},
{x509.KeyUsageCRLSign, "CRL签名"},
{x509.KeyUsageEncipherOnly, "仅加密"},
{x509.KeyUsageDecipherOnly, "仅解密"},
}
for _, flag := range flags {
if ku&flag.Flag != 0 {
usages = append(usages, flag.Name)
}
}
return usages
}

@ -0,0 +1,7 @@
package tls
import "testing"
func TestCert(t *testing.T) {
showTls("139.199.163.65:443", true, "")
}

@ -24,9 +24,9 @@ func init() {
var Cmd = &cobra.Command{
Use: "uac",
Short: "run process with administrator permission",
Example: "vtqe uac 'c:\\program.exe arg1 arg2'",
Version: "2.0.0",
Short: "以管理员权限运行程序",
Example: "b612 uac 'c:\\program.exe arg1 arg2'",
Version: "2.1.0",
Run: func(cmd *cobra.Command, args []string) {
showWindow = !showWindow
workdir, _ = filepath.Abs(workdir)

@ -7,6 +7,6 @@ import "github.com/spf13/cobra"
var Cmd = &cobra.Command{
Use: "uac",
Short: "run process with administrator permission",
Example: "vtqe uac 'c:\\program.exe arg1 arg2'",
Example: "b612 uac 'c:\\program.exe arg1 arg2'",
Hidden: true,
}

@ -21,10 +21,6 @@ var Cmd = &cobra.Command{
pwd, _ := this.Flags().GetString("key")
rep, _ := this.Flags().GetBool("replace")
ext, _ := this.Flags().GetBool("extension")
if len(args) != 2 || args[1] != "sakura" {
starlog.Errorln("ヴィクトリカだけが使えるよ")
return
}
shell := func(pect float64) {
if pect == 100 {
fmt.Println("已处理100.000000%")

@ -0,0 +1,78 @@
package whois
import (
"b612.me/starlog"
"b612.me/staros"
"github.com/likexian/whois"
"github.com/spf13/cobra"
"golang.org/x/net/proxy"
"os"
"strings"
"time"
)
var timeout int
var output string
var whoisServer []string
var socks5 string
var socks5Auth string
func init() {
Cmd.Flags().IntVarP(&timeout, "timeout", "t", 20, "超时时间")
Cmd.Flags().StringVarP(&output, "output", "o", "", "输出文件夹")
Cmd.Flags().StringSliceVarP(&whoisServer, "server", "s", nil, "whois服务器")
Cmd.Flags().StringVarP(&socks5, "socks5", "p", "", "socks5代理示例127.0.0.1:1080")
Cmd.Flags().StringVarP(&socks5Auth, "socks5-auth", "A", "", "socks5代理认证示例username:password")
}
var Cmd = &cobra.Command{
Use: "whois",
Short: "whois查询",
Long: "whois查询",
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 {
cmd.Help()
return
}
if !staros.Exists(output) {
cmd.Println("输出文件夹不存在,将使用标准输出")
output = ""
}
c := whois.NewClient()
if socks5 != "" {
var auth *proxy.Auth
if socks5Auth != "" {
up := strings.SplitN(socks5Auth, ":", 2)
if len(up) == 2 {
auth = &proxy.Auth{
User: up[0],
Password: up[1],
}
} else {
starlog.Errorln("socks5认证格式错误")
return
}
}
s5Dial, err := proxy.SOCKS5("tcp", socks5, auth, proxy.Direct)
if err == nil {
c.SetDialer(s5Dial)
} else {
starlog.Errorln("socks5代理错误:", err)
return
}
}
c.SetTimeout(time.Second * time.Duration(timeout))
for _, v := range args {
data, err := c.Whois(v, whoisServer...)
cmd.Println("Query:", v)
if err != nil {
cmd.Println("查询失败:", err)
cmd.Println("-----------------------------------------------------")
continue
}
cmd.Println(data)
cmd.Println("-----------------------------------------------------")
os.WriteFile(output+"/"+v+".txt", []byte(data), 0644)
}
},
}
Loading…
Cancel
Save