1.http server支持黑白路由名单和子文件夹挂载

2.http反向代理支持同一个端口按host区分不通服务,以及黑白路由名单支持
This commit is contained in:
兔子 2025-06-19 23:47:39 +08:00
parent 6b6b5a6f0f
commit 7650951518
12 changed files with 984 additions and 140 deletions

View File

@ -63,20 +63,26 @@ var Cmd = &cobra.Command{
starlog.Errorln(err) starlog.Errorln(err)
os.Exit(3) os.Exit(3)
} }
reverse := ReverseConfig{ cfg := &SingleReverseConfig{
Name: "web", Name: "web",
Addr: addr,
Host: host, Host: host,
ReverseURL: map[string]*url.URL{ ReverseURL: map[string]any{
"/": u, "/": u,
}, },
Port: port,
UsingSSL: enablessl, UsingSSL: enablessl,
SkipSSLVerify: skipsslverify, SkipSSLVerify: skipsslverify,
Key: key, Key: key,
Cert: cert, Cert: cert,
IPFilterMode: 1, IPFilterMode: 1,
} }
reverse := ReverseConfig{
Addr: addr,
Port: port,
Config: []*SingleReverseConfig{cfg},
routes: map[string]*SingleReverseConfig{
host: cfg,
},
}
go func() { go func() {
sig := make(chan os.Signal) sig := make(chan os.Signal)
signal.Notify(sig, os.Kill, os.Interrupt) signal.Notify(sig, os.Kill, os.Interrupt)

View File

@ -12,21 +12,31 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"path"
"strings" "strings"
"sync" "sync"
) )
type ReverseConfig struct { type ReverseConfig struct {
Name string
Addr string Addr string
ReverseURL map[string]*url.URL
Port int Port int
httpmux http.ServeMux
httpserver http.Server
Config []*SingleReverseConfig
routes map[string]*SingleReverseConfig
autogenCert bool //是否自动生成证书
}
type SingleReverseConfig struct {
Name string
ReverseURL map[string]any
UsingSSL bool UsingSSL bool
AllowHTTPWithHttps bool AllowHTTPWithHttps bool
AutoGenerateCert bool AutoGenerateCert bool
Key string Key string
Cert string Cert string
Host string Host string
ProxyHost string
SkipSSLVerify bool SkipSSLVerify bool
InHeader [][2]string InHeader [][2]string
OutHeader [][2]string OutHeader [][2]string
@ -34,14 +44,13 @@ type ReverseConfig struct {
ReplaceList [][2]string ReplaceList [][2]string
ReplaceOnce bool ReplaceOnce bool
proxy map[string]*rp.ReverseProxy proxy map[string]*rp.ReverseProxy
httpPage map[string]string
IPFilterMode int //0=off 1=useremote 2=add 3=filter IPFilterMode int //0=off 1=useremote 2=add 3=filter
FilterXForward bool FilterXForward bool
FilterRemoteAddr bool FilterRemoteAddr bool
FilterMustKey string FilterMustKey string
FilterSetKey string FilterSetKey string
FilterFile string FilterFile string
httpmux http.ServeMux
httpserver http.Server
CIDR []*net.IPNet CIDR []*net.IPNet
basicAuthUser string basicAuthUser string
@ -51,25 +60,40 @@ type ReverseConfig struct {
whiteip map[string]int whiteip map[string]int
warningpage string warningpage string
warnpagedata []byte warnpagedata []byte
router Router
blackpath Router
whitepath Router
rootLeaf any
} }
type HttpReverseServer struct { type HttpReverseServer struct {
Config []*ReverseConfig Config []*ReverseConfig
} }
func Parse(path string) (HttpReverseServer, error) { func Parse(cfgPath string) (HttpReverseServer, error) {
var res HttpReverseServer var res HttpReverseServer
ini := sysconf.NewIni() ini := sysconf.NewIni()
err := ini.ParseFromFile(path) err := ini.ParseFromFile(cfgPath)
if err != nil { if err != nil {
return res, err return res, err
} }
serverMap := make(map[string]*ReverseConfig)
for _, v := range ini.Data { for _, v := range ini.Data {
var ins = ReverseConfig{ var cfg *ReverseConfig
Name: v.Name, keyID := v.Get("addr") + v.Get("port")
Host: v.Get("host"), if _, ok := serverMap[keyID]; ok {
cfg = serverMap[keyID]
} else {
cfg = &ReverseConfig{
Addr: v.Get("addr"), Addr: v.Get("addr"),
Port: v.Int("port"), Port: v.Int("port"),
routes: make(map[string]*SingleReverseConfig),
}
serverMap[keyID] = cfg
}
var ins = SingleReverseConfig{
Name: v.Name,
Host: v.Get("host"),
UsingSSL: v.Bool("enablessl"), UsingSSL: v.Bool("enablessl"),
AllowHTTPWithHttps: v.Bool("tlsallowhttp"), AllowHTTPWithHttps: v.Bool("tlsallowhttp"),
AutoGenerateCert: v.Bool("autogencert"), AutoGenerateCert: v.Bool("autogencert"),
@ -125,17 +149,22 @@ func Parse(path string) (HttpReverseServer, error) {
ins.warnpagedata = data ins.warnpagedata = data
} }
ins.proxy = make(map[string]*rp.ReverseProxy) ins.proxy = make(map[string]*rp.ReverseProxy)
ins.ReverseURL = make(map[string]*url.URL) ins.ReverseURL = make(map[string]any)
for _, reverse := range v.GetAll("reverse") { for _, reverse := range v.GetAll("reverse") {
kv := strings.SplitN(reverse, "::", 2) kv := strings.SplitN(reverse, "::", 2)
if len(kv) != 2 { if len(kv) != 2 {
return res, errors.New("reverse settings not correct:" + reverse) return res, errors.New("reverse settings not correct:" + reverse)
} }
if !strings.HasPrefix(strings.TrimSpace(kv[1]), "http://") && !strings.HasPrefix(strings.TrimSpace(kv[1]), "https://") {
ins.ReverseURL[strings.TrimSpace(kv[0])] = strings.TrimSpace(kv[1])
} else {
ins.ReverseURL[strings.TrimSpace(kv[0])], err = url.Parse(strings.TrimSpace(kv[1])) ins.ReverseURL[strings.TrimSpace(kv[0])], err = url.Parse(strings.TrimSpace(kv[1]))
if err != nil { if err != nil {
return res, err return res, err
} }
} }
}
for _, header := range v.GetAll("inheader") { for _, header := range v.GetAll("inheader") {
kv := strings.SplitN(header, "::", 2) kv := strings.SplitN(header, "::", 2)
if len(kv) != 2 { if len(kv) != 2 {
@ -154,6 +183,20 @@ func Parse(path string) (HttpReverseServer, error) {
} }
ins.blackip[ip] = cidr ins.blackip[ip] = cidr
} }
for _, blackpath := range v.GetAll("blackpath") {
isStar := path.Base(blackpath) == "*"
if isStar {
blackpath = path.Dir(blackpath)
}
ins.blackpath.AddLeaf(blackpath, isStar)
}
for _, whitepath := range v.GetAll("whitepath") {
isStar := path.Base(whitepath) == "*"
if isStar {
whitepath = path.Dir(whitepath)
}
ins.whitepath.AddLeaf(whitepath, isStar)
}
ins.whiteip = make(map[string]int) ins.whiteip = make(map[string]int)
for _, whiteip := range v.GetAll("whiteip") { for _, whiteip := range v.GetAll("whiteip") {
ip, cidr, err := IPCIDR(whiteip) ip, cidr, err := IPCIDR(whiteip)
@ -183,7 +226,11 @@ func Parse(path string) (HttpReverseServer, error) {
} }
ins.ReplaceList = append(ins.ReplaceList, [2]string{strings.TrimSpace(kv[0]), strings.TrimSpace(kv[1])}) ins.ReplaceList = append(ins.ReplaceList, [2]string{strings.TrimSpace(kv[0]), strings.TrimSpace(kv[1])})
} }
res.Config = append(res.Config, &ins) cfg.routes[ins.Host] = &ins
cfg.Config = append(cfg.Config, &ins)
}
for _, v := range serverMap {
res.Config = append(res.Config, v)
} }
return res, nil return res, nil
} }

117
httpreverse/route.go Normal file
View File

@ -0,0 +1,117 @@
package httpreverse
import "strings"
type Router struct {
Hostname string
Leaf *Leaf
leafMap map[string]*Leaf
}
type Leaf struct {
Name string
Val any
Last *Leaf
Next map[string]*Leaf
FullPath string
}
func NewRouter(hostname string) *Router {
return &Router{
Hostname: hostname,
Leaf: &Leaf{
Name: "/",
Next: make(map[string]*Leaf),
FullPath: "/",
},
leafMap: make(map[string]*Leaf),
}
}
func (r *Router) AddLeaf(path string, val any) {
if r.Leaf == nil {
r.Leaf = &Leaf{
Name: "/",
Next: make(map[string]*Leaf),
FullPath: "/",
}
}
if r.leafMap == nil {
r.leafMap = make(map[string]*Leaf)
r.leafMap["/"] = r.Leaf
}
names := strings.Split(path, "/")
leaf := r.Leaf
for _, name := range names {
if name == "" {
continue
}
if leaf.Next[name] == nil {
fullPath := leaf.FullPath + "/" + name
if leaf.FullPath == "/" {
fullPath = "/" + name
}
leaf.Next[name] = &Leaf{
Name: name,
Next: make(map[string]*Leaf),
FullPath: fullPath,
}
r.leafMap[fullPath] = leaf.Next[name]
}
leaf = leaf.Next[name]
}
leaf.Val = val
}
func (r *Router) GetLeaf(path string) *Leaf {
return r.leafMap[path]
}
func (r *Router) NearestLeaf(path string) *Leaf {
if path == "/" {
return r.Leaf
}
if leaf, ok := r.leafMap[path]; ok {
return leaf
}
parts := strings.Split(path, "/")
leaf := r.Leaf
for _, v := range parts {
if v == "" {
continue
}
if leaf.Next[v] == nil {
return leaf
}
leaf = leaf.Next[v]
}
return leaf
}
func (r *Router) NearestLeafWithVal(path string) *Leaf {
if path == "/" {
return r.Leaf
}
if leaf, ok := r.leafMap[path]; ok && leaf.Val != nil {
return leaf
}
var lastValue *Leaf
parts := strings.Split(path, "/")
leaf := r.Leaf
if leaf.Val != nil {
lastValue = leaf
}
for _, v := range parts {
if v == "" {
continue
}
if leaf.Next[v] == nil {
return lastValue
}
leaf = leaf.Next[v]
if leaf.Val != nil {
lastValue = leaf
}
}
return lastValue
}

View File

@ -3,18 +3,25 @@ package httpreverse
import ( import (
"b612.me/apps/b612/httpreverse/rp" "b612.me/apps/b612/httpreverse/rp"
"b612.me/apps/b612/utils" "b612.me/apps/b612/utils"
"b612.me/starcrypto"
"b612.me/starlog" "b612.me/starlog"
"b612.me/starnet" "b612.me/starnet"
"b612.me/staros"
"bytes" "bytes"
"context" "context"
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"encoding/base64" "encoding/base64"
"errors"
"fmt" "fmt"
"github.com/gabriel-vasile/mimetype"
"io"
"io/ioutil" "io/ioutil"
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
"os"
"path/filepath"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -27,18 +34,25 @@ func (h *ReverseConfig) Run() error {
if err != nil { if err != nil {
return err return err
} }
for key, proxy := range h.proxy { h.httpmux.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
h.httpmux.HandleFunc(key, func(writer http.ResponseWriter, request *http.Request) { c, ok := h.routes[request.Host]
starlog.Infof("<%s> Req Path:%s ListenAddr:%s UA:%s\n", h.Name, request.URL.Path, request.RemoteAddr, request.Header.Get("User-Agent")) if !ok {
if !h.BasicAuth(writer, request) { if _, ok := h.routes[""]; ok {
h.SetResponseHeader(writer) c = h.routes[""]
return } else {
if len(h.routes) > 0 {
for _, v := range h.routes {
c = v
break
} }
if !h.filter(writer, request) { }
h.SetResponseHeader(writer) }
}
rejectWith403 := func(writer http.ResponseWriter, request *http.Request) {
c.SetResponseHeader(writer)
writer.WriteHeader(403) writer.WriteHeader(403)
if len(h.warnpagedata) != 0 { if len(c.warnpagedata) != 0 {
writer.Write(h.warnpagedata) writer.Write(c.warnpagedata)
return return
} }
writer.Write([]byte(` writer.Write([]byte(`
@ -51,41 +65,117 @@ func (h *ReverseConfig) Run() error {
<hr><center>B612 HTTP REVERSE SERVER</center> <hr><center>B612 HTTP REVERSE SERVER</center>
</body> </body>
</html>`)) </html>`))
}
starlog.Infof("<%s> Req Path:%s ListenAddr:%s UA:%s\n", c.Name, request.URL.Path, request.RemoteAddr, request.Header.Get("User-Agent"))
checkPath := request.URL.Path
if strings.HasSuffix(checkPath, "/") && checkPath != "/" {
checkPath = strings.TrimSuffix(checkPath, "/")
}
if c.blackpath.Leaf != nil {
bp := c.blackpath.NearestLeafWithVal(checkPath)
if bp != nil {
if ppr, ok := bp.Val.(bool); ok {
if ppr || (!ppr && bp.FullPath == checkPath) {
starlog.Errorf("<%s> Path:%s is in black path, reject request\n", c.Name, checkPath)
rejectWith403(writer, request)
return return
} }
proxy.ServeHTTP(writer, request)
})
} }
}
}
if c.whitepath.Leaf != nil {
bp := c.whitepath.NearestLeafWithVal(checkPath)
if bp == nil {
starlog.Errorf("<%s> Path:%s is not in the write path, reject request\n", c.Name, checkPath)
rejectWith403(writer, request)
return
}
if ppr, ok := bp.Val.(bool); !ok {
starlog.Errorf("<%s> Path:%s is not in the write path, reject request\n", c.Name, checkPath)
rejectWith403(writer, request)
return
} else {
if !ppr && bp.FullPath != checkPath {
fmt.Println(bp.FullPath, checkPath)
starlog.Errorf("<%s> Path:%s is not in the write path, reject request\n", c.Name, checkPath)
rejectWith403(writer, request)
return
}
}
}
if !c.BasicAuth(writer, request) {
c.SetResponseHeader(writer)
return
}
if !c.filter(writer, request) {
rejectWith403(writer, request)
return
}
leaf := c.router.NearestLeafWithVal(checkPath)
if request.URL.Path == "/" && c.rootLeaf != nil {
leaf = &Leaf{
Name: leaf.Name,
Val: c.rootLeaf,
Last: leaf.Last,
Next: leaf.Next,
FullPath: leaf.FullPath,
}
}
fmt.Println(leaf.Val)
if leaf == nil {
starlog.Errorf("<%s> No Reverse Proxy Found For Path:%s\n", c.Name, request.URL.Path)
writer.WriteHeader(404)
writer.Write([]byte("404 Not Found"))
return
}
if rp, ok := leaf.Val.(*rp.ReverseProxy); ok {
rp.ServeHTTP(writer, request)
return
}
if page, ok := leaf.Val.(string); ok {
h.fileHandle(leaf.FullPath, page, writer, request)
return
}
writer.WriteHeader(500)
})
h.httpserver = http.Server{ h.httpserver = http.Server{
Addr: fmt.Sprintf("%s:%d", h.Addr, h.Port), Addr: fmt.Sprintf("%s:%d", h.Addr, h.Port),
Handler: &h.httpmux, Handler: &h.httpmux,
} }
starlog.Infoln(h.Name + " Listening on " + h.Addr + ":" + strconv.Itoa(h.Port)) var realUsingsSSL bool
if !h.UsingSSL && !h.AutoGenerateCert { var realAllowHTTPWithHttps bool
for _, c := range h.Config {
if c.UsingSSL {
realUsingsSSL = true
}
if c.AutoGenerateCert {
h.autogenCert = true
}
if c.AllowHTTPWithHttps {
realAllowHTTPWithHttps = true
}
starlog.Infoln(c.Name + " Listening on " + h.Addr + ":" + strconv.Itoa(h.Port))
}
if !realUsingsSSL {
if err := h.httpserver.ListenAndServe(); err != http.ErrServerClosed { if err := h.httpserver.ListenAndServe(); err != http.ErrServerClosed {
return err return err
} }
return nil return nil
} }
/*
if !h.AllowHTTPWithHttps && !h.AutoGenerateCert { if !realAllowHTTPWithHttps && !realAutoGenCert {
if h.httpserver.ListenAndServeTLS(h.Cert, h.Key); err != http.ErrServerClosed { if h.httpserver.ListenAndServeTLS(h.Cert, h.Key); err != http.ErrServerClosed {
return err return err
} }
return nil return nil
} }
*/
var lis net.Listener var lis net.Listener
if !h.AutoGenerateCert { lis, err = starnet.ListenTLSWithConfig("tcp", fmt.Sprintf("%s:%d", h.Addr, h.Port), &tls.Config{}, h.getCert, realAllowHTTPWithHttps)
lis, err = starnet.ListenTLS("tcp", fmt.Sprintf("%s:%d", h.Addr, h.Port), h.Cert, h.Key, h.AllowHTTPWithHttps)
if err != nil { if err != nil {
return err return err
} }
} else {
lis, err = starnet.ListenTLSWithConfig("tcp", fmt.Sprintf("%s:%d", h.Addr, h.Port), &tls.Config{}, autoGenCert, h.AllowHTTPWithHttps)
if err != nil {
return err
}
}
defer lis.Close() defer lis.Close()
if err := h.httpserver.Serve(lis); err != http.ErrServerClosed { if err := h.httpserver.Serve(lis); err != http.ErrServerClosed {
return err return err
@ -97,7 +187,242 @@ var certCache = make(map[string]tls.Certificate)
var toolCa *x509.Certificate var toolCa *x509.Certificate
var toolCaKey any var toolCaKey any
func autoGenCert(hostname string) *tls.Config { func (h *ReverseConfig) CalcRange(r *http.Request) (int64, int64) {
var rangeStart, rangeEnd int64 = -1, -1
ranges := r.Header.Get("Range")
if ranges == "" {
return rangeStart, rangeEnd
}
if !strings.Contains(ranges, "bytes=") {
return rangeStart, rangeEnd
}
ranges = strings.TrimPrefix(ranges, "bytes=")
data := strings.Split(ranges, "-")
if len(data) == 0 {
return rangeStart, rangeEnd
}
rangeStart, _ = strconv.ParseInt(data[0], 10, 64)
if len(data) > 1 {
rangeEnd, _ = strconv.ParseInt(data[1], 10, 64)
}
if rangeEnd == 0 {
rangeEnd = -1
}
return rangeStart, rangeEnd
}
func (h *ReverseConfig) BuildHeader(w http.ResponseWriter, r *http.Request, fullpath string) error {
h.SetResponseHeader(w)
if r.Method == "OPTIONS" {
w.Header().Set("Allow", "OPTIONS,GET,HEAD")
w.Header().Set("Content-Length", "0")
}
w.Header().Set("Date", strings.ReplaceAll(time.Now().UTC().Format("Mon, 2 Jan 2006 15:04:05 MST"), "UTC", "GMT"))
if staros.IsFolder(fullpath) {
return nil
}
mime, _ := mimetype.DetectFile(fullpath)
if mime == nil {
w.Header().Set("Content-Type", "application/download")
w.Header().Set("Content-Disposition", "attachment;filename="+filepath.Base(fullpath))
w.Header().Set("Content-Transfer-Encoding", "binary")
} else {
w.Header().Set("Content-Type", mime.String())
}
if staros.Exists(fullpath) {
finfo, err := os.Stat(fullpath)
if err != nil {
w.WriteHeader(502)
w.Write([]byte("Failed to Read " + fullpath + ",reason is " + err.Error()))
return err
}
w.Header().Set("Accept-Ranges", "bytes")
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" {
start, end := h.CalcRange(r)
if start != -1 {
if end == -1 {
w.Header().Set("Content-Length", strconv.FormatInt(finfo.Size()-start, 10))
w.Header().Set("Content-Range", `bytes `+strconv.FormatInt(start, 10)+"-"+strconv.FormatInt(finfo.Size()-1, 10)+"/"+strconv.FormatInt(finfo.Size(), 10))
//w.Header().Set("Content-Length", strconv.FormatInt(fpinfo.Size()-rangeStart, 10))
} else {
w.Header().Set("Content-Length", strconv.FormatInt(end-start+1, 10))
w.Header().Set("Content-Range", `bytes `+strconv.FormatInt(start, 10)+"-"+strconv.FormatInt(end, 10)+"/"+strconv.FormatInt(finfo.Size(), 10))
//w.Header().Set("Content-Length", strconv.FormatInt(1+rangeEnd-rangeStart, 10))
}
} else {
w.Header().Set("Content-Length", strconv.FormatInt(finfo.Size(), 10))
}
}
}
return nil
}
func (h *ReverseConfig) getFile(w http.ResponseWriter, r *http.Request, fullpath string) error {
if !staros.Exists(fullpath) {
w.WriteHeader(404)
return errors.New("File Not Found! 404 ERROR")
}
//starlog.Debugln(r.Header)
startRange, endRange := h.CalcRange(r)
fp, err := os.Open(fullpath)
if err != nil {
starlog.Errorf("Failed to open file %s,reason:%v\n", r.URL.Path, err)
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>`))
return err
}
defer fp.Close()
var transferData int
defer func() {
if transferData != 0 {
var tani string
tani = fmt.Sprintf("%v Byte", transferData)
if f64 := float64(transferData) / 1024; f64 > 1 {
tani = fmt.Sprintf("%v KiB", f64)
if f64 = float64(f64) / 1024; f64 > 1 {
tani = fmt.Sprintf("%v MiB", f64)
if f64 = float64(f64) / 1024; f64 > 1 {
tani = fmt.Sprintf("%v GiB", f64)
}
}
}
starlog.Infof("Tranfered File %s %d bytes (%s) to remote %v\n", r.URL.Path,
transferData, tani, r.RemoteAddr)
}
}()
if startRange == -1 {
w.WriteHeader(200)
for {
buf := make([]byte, 16384)
n, err := fp.Read(buf)
if n != 0 {
ns, err := w.Write(buf[:n])
transferData += ns
if err != nil {
starlog.Errorf("Transfer File %s to Remote Failed:%v\n", fullpath, err)
return err
}
}
if err != nil {
if err == io.EOF {
break
}
starlog.Errorf("Read File %s Failed:%v\n", fullpath, err)
return err
}
}
return nil
}
starlog.Debugf("206 transfer mode for %v %v start %v end %v\n", r.URL.Path, r.RemoteAddr, startRange, endRange)
w.WriteHeader(206)
fp.Seek(int64(startRange), 0)
count := startRange
for {
buf := make([]byte, 16384)
n, err := fp.Read(buf)
if err != nil {
if err == io.EOF {
break
}
starlog.Errorf("Read File %s Failed:%v\n", r.URL.Path, err)
return err
}
if endRange == -1 {
ns, err := w.Write(buf[:n])
transferData += ns
if err != nil {
starlog.Errorf("Transfer File %s to Remote Failed:%v\n", r.URL.Path, err)
return err
}
} else {
if count > endRange {
break
}
writeNum := n
if count+int64(n) > endRange {
writeNum = int(endRange - count + 1)
}
ns, err := w.Write(buf[0:writeNum])
if err != nil {
starlog.Errorln("Transfer Error:", err)
return err
}
transferData += ns
count += int64(n)
}
}
return nil
}
func (h *ReverseConfig) fileHandle(dirPath, diskpath string, writer http.ResponseWriter, request *http.Request) {
var checkUrl = request.URL.Path
if strings.HasSuffix(request.URL.Path, "/") && len(request.URL.Path) > 1 {
checkUrl = strings.TrimSuffix(request.URL.Path, "/")
}
if staros.IsFile(diskpath) {
if checkUrl != dirPath {
h.SetResponseHeader(writer)
writer.WriteHeader(404)
return
}
h.BuildHeader(writer, request, diskpath)
if request.Method == "OPTIONS" {
return
}
h.getFile(writer, request, diskpath)
return
}
reqPath := strings.TrimPrefix(checkUrl, dirPath)
realPath := filepath.Join(diskpath, reqPath)
if !staros.Exists(realPath) {
h.SetResponseHeader(writer)
writer.WriteHeader(404)
return
}
if staros.IsFile(realPath) {
h.BuildHeader(writer, request, realPath)
if request.Method == "OPTIONS" {
return
}
h.getFile(writer, request, realPath)
return
}
h.SetResponseHeader(writer)
writer.WriteHeader(403)
}
func (h *ReverseConfig) getCert(hostname string) *tls.Config {
if h.autogenCert {
return h.autoGenCert(hostname)
}
c, ok := h.routes[hostname]
if !ok {
if _, ok := h.routes[""]; ok {
c = h.routes[""]
} else {
if len(h.routes) > 0 {
for _, v := range h.routes {
c = v
break
}
}
}
}
if c == nil {
return &tls.Config{}
}
cert, err := tls.LoadX509KeyPair(c.Cert, c.Key)
if err != nil {
starlog.Errorln("Load X509 Key Pair Error:", err)
return &tls.Config{}
}
return &tls.Config{
Certificates: []tls.Certificate{cert},
}
}
func (h *ReverseConfig) autoGenCert(hostname string) *tls.Config {
if cert, ok := certCache[hostname]; ok { if cert, ok := certCache[hostname]; ok {
return &tls.Config{Certificates: []tls.Certificate{cert}} return &tls.Config{Certificates: []tls.Certificate{cert}}
} }
@ -136,7 +461,7 @@ func (h *ReverseConfig) Close() error {
return h.httpserver.Shutdown(ctx) return h.httpserver.Shutdown(ctx)
} }
func (h *ReverseConfig) dialTLS(ctx context.Context, network, addr string) (net.Conn, error) { func (h *SingleReverseConfig) dialTLS(ctx context.Context, network, addr string) (net.Conn, error) {
conn, err := net.DialTimeout(network, addr, time.Second*20) conn, err := net.DialTimeout(network, addr, time.Second*20)
if err != nil { if err != nil {
return nil, err return nil, err
@ -146,8 +471,8 @@ func (h *ReverseConfig) dialTLS(ctx context.Context, network, addr string) (net.
if err != nil { if err != nil {
return nil, err return nil, err
} }
if h.Host != "" { if h.ProxyHost != "" {
host = h.Host host = h.ProxyHost
} }
cfg := &tls.Config{ServerName: host} cfg := &tls.Config{ServerName: host}
tlsConn := tls.Client(conn, cfg) tlsConn := tls.Client(conn, cfg)
@ -170,8 +495,36 @@ func (h *ReverseConfig) dialTLS(ctx context.Context, network, addr string) (net.
} }
func (h *ReverseConfig) init() error { func (h *ReverseConfig) init() error {
for _, v := range h.Config {
v.init()
}
return nil
}
func (h *SingleReverseConfig) init() error {
h.proxy = make(map[string]*rp.ReverseProxy) h.proxy = make(map[string]*rp.ReverseProxy)
for key, val := range h.ReverseURL { for key, val := range h.ReverseURL {
switch tp := val.(type) {
case *url.URL:
if err := h.newReverseProxy(key, tp); err != nil {
return fmt.Errorf("new reverse proxy for %s failed: %w", key, err)
}
case string:
if h.httpPage == nil {
h.httpPage = make(map[string]string)
}
h.httpPage[key] = tp
if key == "=/" {
h.rootLeaf = tp
continue
}
h.router.AddLeaf(key, tp)
}
}
return nil
}
func (h *SingleReverseConfig) newReverseProxy(key string, val *url.URL) error {
h.proxy[key] = &rp.ReverseProxy{ h.proxy[key] = &rp.ReverseProxy{
Transport: &http.Transport{DialTLSContext: h.dialTLS}, Transport: &http.Transport{DialTLSContext: h.dialTLS},
} }
@ -179,10 +532,10 @@ func (h *ReverseConfig) init() error {
h.proxy[key].Director = func(req *http.Request) { h.proxy[key].Director = func(req *http.Request) {
targetQuery := val.RawQuery targetQuery := val.RawQuery
req.URL.Scheme = val.Scheme req.URL.Scheme = val.Scheme
if h.Host == "" { if h.ProxyHost != "" {
req.Host = val.Host req.Host = h.ProxyHost
} else { } else {
req.Host = h.Host req.Host = val.Host
} }
req.URL.Host = val.Host req.URL.Host = val.Host
req.URL.Path, req.URL.RawPath = joinURLPath(val, req.URL, key) req.URL.Path, req.URL.RawPath = joinURLPath(val, req.URL, key)
@ -193,7 +546,11 @@ func (h *ReverseConfig) init() error {
} }
h.ModifyRequest(req, val) h.ModifyRequest(req, val)
} }
if key == "=/" {
h.rootLeaf = h.proxy[key]
return nil
} }
h.router.AddLeaf(key, h.proxy[key])
return nil return nil
} }
@ -203,7 +560,13 @@ func (h *ReverseConfig) SetResponseHeader(resp http.ResponseWriter) {
resp.Header().Set("X-Proxy", "B612 Reverse Proxy") resp.Header().Set("X-Proxy", "B612 Reverse Proxy")
} }
func (h *ReverseConfig) ModifyResponse() func(*http.Response) error { func (h *SingleReverseConfig) SetResponseHeader(resp http.ResponseWriter) {
resp.Header().Set("X-Powered-By", "B612.ME")
resp.Header().Set("Server", "B612/"+version)
resp.Header().Set("X-Proxy", "B612 Reverse Proxy")
}
func (h *SingleReverseConfig) ModifyResponse() func(*http.Response) error {
return func(resp *http.Response) error { return func(resp *http.Response) error {
for _, v := range h.OutHeader { for _, v := range h.OutHeader {
resp.Header.Set(v[0], v[1]) resp.Header.Set(v[0], v[1])
@ -236,7 +599,7 @@ func (h *ReverseConfig) ModifyResponse() func(*http.Response) error {
} }
} }
func (h *ReverseConfig) isInCIDR(ip string) bool { func (h *SingleReverseConfig) isInCIDR(ip string) bool {
nip := net.ParseIP(strings.TrimSpace(ip)) nip := net.ParseIP(strings.TrimSpace(ip))
if nip == nil { if nip == nil {
return false return false
@ -249,7 +612,7 @@ func (h *ReverseConfig) isInCIDR(ip string) bool {
return false return false
} }
func (h *ReverseConfig) ModifyRequest(req *http.Request, remote *url.URL) { func (h *SingleReverseConfig) ModifyRequest(req *http.Request, remote *url.URL) {
switch h.IPFilterMode { switch h.IPFilterMode {
case 1: case 1:
req.Header.Set("X-Forwarded-For", strings.Split(req.RemoteAddr, ":")[0]) req.Header.Set("X-Forwarded-For", strings.Split(req.RemoteAddr, ":")[0])
@ -297,7 +660,7 @@ func (h *ReverseConfig) ModifyRequest(req *http.Request, remote *url.URL) {
} }
} }
func (h *ReverseConfig) GiveBasicAuth(w http.ResponseWriter) { func (h *SingleReverseConfig) GiveBasicAuth(w http.ResponseWriter) {
w.Header().Set("WWW-Authenticate", ` Basic realm="Please Enter Passwd"`) w.Header().Set("WWW-Authenticate", ` Basic realm="Please Enter Passwd"`)
w.WriteHeader(401) w.WriteHeader(401)
w.Write([]byte(` w.Write([]byte(`
@ -310,7 +673,7 @@ func (h *ReverseConfig) GiveBasicAuth(w http.ResponseWriter) {
</html>`)) </html>`))
} }
func (h *ReverseConfig) BasicAuth(w http.ResponseWriter, r *http.Request) bool { func (h *SingleReverseConfig) BasicAuth(w http.ResponseWriter, r *http.Request) bool {
if h.basicAuthPwd != "" { if h.basicAuthPwd != "" {
if len(h.protectAuthPage) != 0 { if len(h.protectAuthPage) != 0 {
for _, v := range h.protectAuthPage { for _, v := range h.protectAuthPage {
@ -337,7 +700,7 @@ func (h *ReverseConfig) BasicAuth(w http.ResponseWriter, r *http.Request) bool {
return true return true
} }
func (h *ReverseConfig) filter(w http.ResponseWriter, r *http.Request) bool { func (h *SingleReverseConfig) filter(w http.ResponseWriter, r *http.Request) bool {
if len(h.blackip) == 0 && len(h.whiteip) == 0 { if len(h.blackip) == 0 && len(h.whiteip) == 0 {
return true return true
} }

View File

@ -9,6 +9,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"os" "os"
"os/signal" "os/signal"
"path"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
@ -24,7 +25,8 @@ func init() {
Cmd.Flags().StringVarP(&hooks, "hook", "H", "", "fileget hook for modify") Cmd.Flags().StringVarP(&hooks, "hook", "H", "", "fileget hook for modify")
Cmd.Flags().StringVarP(&s.port, "port", "p", "", "监听端口,http时默认80,https时默认443") Cmd.Flags().StringVarP(&s.port, "port", "p", "", "监听端口,http时默认80,https时默认443")
Cmd.Flags().StringVarP(&s.addr, "ip", "i", "0.0.0.0", "监听ip") Cmd.Flags().StringVarP(&s.addr, "ip", "i", "0.0.0.0", "监听ip")
Cmd.Flags().StringVarP(&s.envPath, "folder", "f", "./", "本地文件地址") Cmd.Flags().StringSliceVarP(&s.EnvPath, "folder", "f", []string{"./"}, "本地文件地址")
Cmd.Flags().StringSliceVarP(&s.SubFolder, "sub-route", "s", []string{}, "挂载的路由,如 /sub与folder对应一一,如果没有指定子文件夹,则默认挂载根路径")
Cmd.Flags().StringVarP(&s.uploadFolder, "upload", "u", "", "文件上传文件夹路径") Cmd.Flags().StringVarP(&s.uploadFolder, "upload", "u", "", "文件上传文件夹路径")
Cmd.Flags().BoolVarP(&daemon, "daemon", "d", false, "以后台进程运行") Cmd.Flags().BoolVarP(&daemon, "daemon", "d", false, "以后台进程运行")
Cmd.Flags().StringVarP(&s.basicAuthUser, "auth", "a", "", "HTTP BASIC AUTH认证(用户名:密码)") Cmd.Flags().StringVarP(&s.basicAuthUser, "auth", "a", "", "HTTP BASIC AUTH认证(用户名:密码)")
@ -41,7 +43,7 @@ func init() {
Cmd.Flags().StringSliceVarP(&s.noListPath, "nolist", "N", []string{}, "禁止列出文件的路径,如/") Cmd.Flags().StringSliceVarP(&s.noListPath, "nolist", "N", []string{}, "禁止列出文件的路径,如/")
Cmd.Flags().StringToStringVarP(&s.listPwd, "listpwd", "L", map[string]string{}, "列出文件的路径的密码,如/=/password") Cmd.Flags().StringToStringVarP(&s.listPwd, "listpwd", "L", map[string]string{}, "列出文件的路径的密码,如/=/password")
Cmd.Flags().BoolVarP(&s.listSameForFile, "list-same", "S", false, "如开启,文件的获取权限将与文件夹保持一致") Cmd.Flags().BoolVarP(&s.listSameForFile, "list-same", "S", false, "如开启,文件的获取权限将与文件夹保持一致")
Cmd.Flags().StringVarP(&speedlimit, "speedlimit", "s", "", "限速如1M意思是1MB/s") Cmd.Flags().StringVarP(&speedlimit, "speedlimit", "e", "", "限速如1M意思是1MB/s")
Cmd.Flags().BoolVarP(&s.allowHttpWithHttps, "allow-http-with-https", "A", false, "同一个端口允许在HTTPS下使用HTTP协议") Cmd.Flags().BoolVarP(&s.allowHttpWithHttps, "allow-http-with-https", "A", false, "同一个端口允许在HTTPS下使用HTTP协议")
Cmd.Flags().BoolVarP(&s.autoGenCert, "autogen-cert", "G", false, "自动生成证书,此时使用--ssl-cert和--ssl-key参数无效") Cmd.Flags().BoolVarP(&s.autoGenCert, "autogen-cert", "G", false, "自动生成证书,此时使用--ssl-cert和--ssl-key参数无效")
@ -49,6 +51,9 @@ func init() {
Cmd.Flags().StringVar(&s.background, "background", "", "背景图片地址") Cmd.Flags().StringVar(&s.background, "background", "", "背景图片地址")
Cmd.Flags().StringVar(&s.mobildBackground, "mbackground", "", "移动端背景图片地址") Cmd.Flags().StringVar(&s.mobildBackground, "mbackground", "", "移动端背景图片地址")
Cmd.Flags().StringSliceVarP(&s.blacklist, "blacklist", "b", nil, "黑名单地址禁止访问的URL地址列表如 /admin,/private")
Cmd.Flags().StringSliceVarP(&s.whitelist, "whitelist", "w", nil, "白名单地址允许访问的URL地址列表如 /public,/shared")
Cmd.Flags().MarkHidden("daeapplied") Cmd.Flags().MarkHidden("daeapplied")
} }
@ -160,6 +165,24 @@ var Cmd = &cobra.Command{
s.basicAuthUser = strings.TrimSpace(tmp[0]) s.basicAuthUser = strings.TrimSpace(tmp[0])
s.basicAuthPwd = tmp[1] s.basicAuthPwd = tmp[1]
} }
if len(s.blacklist) > 0 {
for _, v := range s.blacklist {
isStar := path.Base(v) == "*"
if isStar {
v = path.Dir(v)
}
s.blackpath.AddLeaf(strings.TrimSpace(v), isStar)
}
}
if len(s.whitelist) > 0 {
for _, v := range s.whitelist {
isStar := path.Base(v) == "*"
if isStar {
v = path.Dir(v)
}
s.whitepath.AddLeaf(strings.TrimSpace(v), isStar)
}
}
err := s.Run(ctx) err := s.Run(ctx)
if err != nil { if err != nil {
starlog.Errorln("Http Server Closed by Errors", err) starlog.Errorln("Http Server Closed by Errors", err)

117
httpserver/route.go Normal file
View File

@ -0,0 +1,117 @@
package httpserver
import "strings"
type Router struct {
Hostname string
Leaf *Leaf
leafMap map[string]*Leaf
}
type Leaf struct {
Name string
Val any
Last *Leaf
Next map[string]*Leaf
FullPath string
}
func NewRouter(hostname string) *Router {
return &Router{
Hostname: hostname,
Leaf: &Leaf{
Name: "/",
Next: make(map[string]*Leaf),
FullPath: "/",
},
leafMap: make(map[string]*Leaf),
}
}
func (r *Router) AddLeaf(path string, val any) {
if r.Leaf == nil {
r.Leaf = &Leaf{
Name: "/",
Next: make(map[string]*Leaf),
FullPath: "/",
}
}
if r.leafMap == nil {
r.leafMap = make(map[string]*Leaf)
r.leafMap["/"] = r.Leaf
}
names := strings.Split(path, "/")
leaf := r.Leaf
for _, name := range names {
if name == "" {
continue
}
if leaf.Next[name] == nil {
fullPath := leaf.FullPath + "/" + name
if leaf.FullPath == "/" {
fullPath = "/" + name
}
leaf.Next[name] = &Leaf{
Name: name,
Next: make(map[string]*Leaf),
FullPath: fullPath,
}
r.leafMap[fullPath] = leaf.Next[name]
}
leaf = leaf.Next[name]
}
leaf.Val = val
}
func (r *Router) GetLeaf(path string) *Leaf {
return r.leafMap[path]
}
func (r *Router) NearestLeaf(path string) *Leaf {
if path == "/" {
return r.Leaf
}
if leaf, ok := r.leafMap[path]; ok {
return leaf
}
parts := strings.Split(path, "/")
leaf := r.Leaf
for _, v := range parts {
if v == "" {
continue
}
if leaf.Next[v] == nil {
return leaf
}
leaf = leaf.Next[v]
}
return leaf
}
func (r *Router) NearestLeafWithVal(path string) *Leaf {
if path == "/" {
return r.Leaf
}
if leaf, ok := r.leafMap[path]; ok && leaf.Val != nil {
return leaf
}
var lastValue *Leaf
parts := strings.Split(path, "/")
leaf := r.Leaf
if leaf.Val != nil {
lastValue = leaf
}
for _, v := range parts {
if v == "" {
continue
}
if leaf.Next[v] == nil {
return lastValue
}
leaf = leaf.Next[v]
if leaf.Val != nil {
lastValue = leaf
}
}
return lastValue
}

View File

@ -35,7 +35,8 @@ type HttpServerCfgs func(cfg *HttpServerCfg)
type HttpServerCfg struct { type HttpServerCfg struct {
basicAuthUser string basicAuthUser string
basicAuthPwd string basicAuthPwd string
envPath string EnvPath []string
SubFolder []string
uploadFolder string uploadFolder string
logpath string logpath string
indexFile string indexFile string
@ -61,6 +62,13 @@ type HttpServerCfg struct {
background string background string
mobildBackground string mobildBackground string
blackpath Router
whitepath Router
blacklist []string
whitelist []string
envPath Router
} }
type ServerHook struct { type ServerHook struct {
@ -115,7 +123,7 @@ func NewHttpServer(addr, port, path string, opts ...HttpServerCfgs) *HttpServer
HttpServerCfg: HttpServerCfg{ HttpServerCfg: HttpServerCfg{
addr: addr, addr: addr,
port: port, port: port,
envPath: path, EnvPath: []string{path},
}, },
} }
for _, opt := range opts { for _, opt := range opts {
@ -163,9 +171,17 @@ func (h *HttpServer) Run(ctx context.Context) error {
} }
} }
} }
h.envPath, err = filepath.Abs(h.envPath) for idx, val := range h.EnvPath {
h.EnvPath[idx], err = filepath.Abs(val)
if len(h.SubFolder) > 0 {
h.envPath.AddLeaf(h.SubFolder[idx], h.EnvPath[idx])
}
}
if len(h.EnvPath) > 1 && len(h.EnvPath) != len(h.SubFolder) {
return errors.New("EnvPath and SubFolder length not match")
}
if err != nil { if err != nil {
starlog.Errorln("Failed to get abs path of", h.envPath) starlog.Errorln("Failed to get abs path of", h.EnvPath)
return err return err
} }
uconn, err := net.Dial("udp", "106.55.44.79:80") uconn, err := net.Dial("udp", "106.55.44.79:80")
@ -386,6 +402,82 @@ func (h *HttpServer) debugMode(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(fmt.Sprintf(html, resp))) w.Write([]byte(fmt.Sprintf(html, resp)))
} }
func (h *HttpServer) PermissionCheck(isTls string, w http.ResponseWriter, r *http.Request) bool {
checkPath := r.URL.Path
if strings.HasSuffix(checkPath, "/") && checkPath != "/" {
checkPath = strings.TrimSuffix(checkPath, "/")
}
if h.blackpath.Leaf != nil {
bp := h.blackpath.NearestLeafWithVal(checkPath)
if bp != nil {
if ppr, ok := bp.Val.(bool); ok {
if ppr || (!ppr && bp.FullPath == checkPath) {
starlog.Errorf(isTls+"%s %s From %s %s path is in black path, reject request \n", r.Method, checkPath, r.RemoteAddr, r.Header.Get("User-Agent"))
h.Page403(w)
return false
}
}
}
}
if h.whitepath.Leaf != nil {
bp := h.whitepath.NearestLeafWithVal(checkPath)
if bp == nil {
starlog.Errorf(isTls+"%s %s From %s %s path is not in white path, reject request \n", r.Method, checkPath, r.RemoteAddr, r.Header.Get("User-Agent"))
h.Page403(w)
return false
}
if ppr, ok := bp.Val.(bool); !ok {
starlog.Errorf(isTls+"%s %s From %s %s path is not in white path, reject request \n", r.Method, checkPath, r.RemoteAddr, r.Header.Get("User-Agent"))
h.Page403(w)
return false
} else {
if !ppr && bp.FullPath != checkPath {
starlog.Errorf(isTls+"%s %s From %s %s path is not in white path, reject request \n", r.Method, checkPath, r.RemoteAddr, r.Header.Get("User-Agent"))
h.Page403(w)
return false
}
}
}
return true
}
func (h *HttpServer) getEnvPath(r *http.Request) (string, string) {
if len(h.EnvPath) == 1 {
return "/", h.EnvPath[0]
}
checkPath := r.URL.Path
if strings.HasSuffix(checkPath, "/") && checkPath != "/" {
checkPath = strings.TrimSuffix(checkPath, "/")
}
if h.envPath.Leaf != nil {
leaf := h.envPath.NearestLeafWithVal(checkPath)
if leaf != nil && leaf.Val != nil {
if envPath, ok := leaf.Val.(string); ok {
return leaf.FullPath, envPath
}
}
}
return "", ""
}
func (h *HttpServer) getStrEnvPath(r string) (string, string) {
if len(h.EnvPath) == 1 {
return "/", h.EnvPath[0]
}
checkPath := r
if strings.HasSuffix(checkPath, "/") && checkPath != "/" {
checkPath = strings.TrimSuffix(checkPath, "/")
}
if h.envPath.Leaf != nil {
leaf := h.envPath.NearestLeafWithVal(checkPath)
if leaf != nil && leaf.Val != nil {
if envPath, ok := leaf.Val.(string); ok {
return leaf.FullPath, envPath
}
}
}
return "", ""
}
func (h *HttpServer) Listen(w http.ResponseWriter, r *http.Request) { func (h *HttpServer) Listen(w http.ResponseWriter, r *http.Request) {
log := starlog.Std.NewFlag() log := starlog.Std.NewFlag()
log.SetShowFuncName(false) log.SetShowFuncName(false)
@ -395,6 +487,10 @@ func (h *HttpServer) Listen(w http.ResponseWriter, r *http.Request) {
if !h.BasicAuth(log, w, r) { if !h.BasicAuth(log, w, r) {
return return
} }
isTls := h.isTlsStr(r)
if !h.PermissionCheck(isTls, w, r) {
return
}
path := r.URL.Path path := r.URL.Path
ua := r.Header.Get("User-Agent") ua := r.Header.Get("User-Agent")
if h.httpDebug { if h.httpDebug {
@ -406,11 +502,20 @@ func (h *HttpServer) Listen(w http.ResponseWriter, r *http.Request) {
h.uploadFile(w, r) h.uploadFile(w, r)
return return
} }
fullpath := filepath.Clean(filepath.Join(h.envPath, path)) if h.SetUpload(w, r, path) {
return
}
prefix, envPath := h.getEnvPath(r)
if envPath == "" {
log.Errorf(isTls+"%s %s From %s %s No EnvPath Found\n", r.Method, path, r.RemoteAddr, ua)
h.Page404(w)
return
}
path = "/" + strings.TrimPrefix(path, prefix)
fullpath := filepath.Clean(filepath.Join(envPath, path))
{ {
//security check //security check
if fullpath != h.envPath && !strings.HasPrefix(fullpath, h.envPath) { if fullpath != envPath && !strings.HasPrefix(fullpath, envPath) {
log.Warningf("Invalid Path %s IP:%s Fullpath:%s\n", path, r.RemoteAddr, fullpath) log.Warningf("Invalid Path %s IP:%s Fullpath:%s\n", path, r.RemoteAddr, fullpath)
h.Page403(w) h.Page403(w)
return return
@ -422,11 +527,8 @@ func (h *HttpServer) Listen(w http.ResponseWriter, r *http.Request) {
path = filepath.Join(path, h.indexFile) path = filepath.Join(path, h.indexFile)
} }
} }
isTls := h.isTlsStr(r)
now := time.Now() now := time.Now()
if h.SetUpload(w, r, path) {
return
}
switch r.Method { switch r.Method {
case "OPTIONS", "HEAD": case "OPTIONS", "HEAD":
err := h.BuildHeader(w, r, fullpath) err := h.BuildHeader(w, r, fullpath)
@ -612,7 +714,11 @@ func (h *HttpServer) getFolder(log *starlog.StarLogger, w http.ResponseWriter, r
} }
var upload string var upload string
if h.uploadFolder != "" { if h.uploadFolder != "" {
upload = `<a href=/b612?upload=true>Upload Web Page Is Openned!</a>` prefix := r.URL.Path
if prefix == "/" {
prefix = ""
}
upload = `<a href=` + prefix + `/b612?upload=true>Upload Web Page Is Openned!</a>`
} }
attr := r.URL.Query().Get("list") attr := r.URL.Query().Get("list")
if attr != "" { if attr != "" {
@ -926,8 +1032,14 @@ func (h *HttpServer) uploadFile(w http.ResponseWriter, r *http.Request) {
} }
defer file.Close() defer file.Close()
starlog.Noticef("Uploading %s From %s\n", handler.Filename, r.RemoteAddr) starlog.Noticef("Uploading %s From %s\n", handler.Filename, r.RemoteAddr)
os.MkdirAll(filepath.Join(h.envPath, h.uploadFolder), 0755) _, envPath := h.getStrEnvPath(r.FormValue("path"))
f, err := os.OpenFile(filepath.Join(h.uploadFolder, handler.Filename), os.O_WRONLY|os.O_CREATE, 0755) if envPath == "" {
starlog.Errorf("No EnvPath Found For Upload File %s From %s\n", handler.Filename, r.RemoteAddr)
h.Page404(w)
return
}
os.MkdirAll(filepath.Join(envPath, h.uploadFolder), 0755)
f, err := os.OpenFile(filepath.Join(envPath, h.uploadFolder, handler.Filename), os.O_WRONLY|os.O_CREATE, 0755)
if err != nil { if err != nil {
starlog.Errorf("Open Local File %s Form %v Failed:%v\n", handler.Filename, r.RemoteAddr, err) starlog.Errorf("Open Local File %s Form %v Failed:%v\n", handler.Filename, r.RemoteAddr, err)
return return
@ -939,5 +1051,5 @@ func (h *HttpServer) uploadFile(w http.ResponseWriter, r *http.Request) {
return return
} }
starlog.Infof("Write File %s Form %v Finished\n", handler.Filename, r.RemoteAddr) starlog.Infof("Write File %s Form %v Finished\n", handler.Filename, r.RemoteAddr)
fmt.Fprintf(w, `<html><body><p>%v</p><h2><a href="/b612?upload=true">Return To Web Page</a></h2></body></html>`, handler.Header) //fmt.Fprintf(w, `<html><body><p>%v</p><h2><a href="./b612?upload=true">Return To Web Page</a></h2></body></html>`, handler.Header)
} }

View File

@ -43,6 +43,7 @@ func init() {
CmdNetforward.Flags().BoolVarP(&f.inTlsAutoGen, "in-autogen", "G", false, "auto generate input tls cert") CmdNetforward.Flags().BoolVarP(&f.inTlsAutoGen, "in-autogen", "G", false, "auto generate input tls cert")
CmdNetforward.Flags().StringSliceVar(&f.CaCerts, "ca", []string{}, "tls ca certs") CmdNetforward.Flags().StringSliceVar(&f.CaCerts, "ca", []string{}, "tls ca certs")
CmdNetforward.Flags().BoolVarP(&f.allowNoTls, "allow-no-tls", "A", false, "allow no tls connection") CmdNetforward.Flags().BoolVarP(&f.allowNoTls, "allow-no-tls", "A", false, "allow no tls connection")
CmdNetforward.Flags().StringVarP(&f.serverName, "server-name", "H", "", "dial server name")
} }
var CmdNetforward = &cobra.Command{ var CmdNetforward = &cobra.Command{

View File

@ -63,6 +63,7 @@ type NetForward struct {
toolCaKey any toolCaKey any
caPool *x509.CertPool caPool *x509.CertPool
outTlsCertCache tls.Certificate outTlsCertCache tls.Certificate
serverName string
} }
func (n *NetForward) UdpListener() *net.UDPConn { func (n *NetForward) UdpListener() *net.UDPConn {
@ -311,11 +312,13 @@ func (n *NetForward) runTCP() error {
} }
log.Infof("TCP Connect %s <==> %s\n", conn.RemoteAddr().String(), rmt.RemoteAddr().String()) log.Infof("TCP Connect %s <==> %s\n", conn.RemoteAddr().String(), rmt.RemoteAddr().String())
if n.outTls { if n.outTls {
serverName, _, _ := net.SplitHostPort(n.RemoteURI) if n.serverName == "" {
n.serverName, _, _ = net.SplitHostPort(n.RemoteURI)
}
tlsConfig := &tls.Config{ tlsConfig := &tls.Config{
InsecureSkipVerify: n.outTlsSkipVerify, InsecureSkipVerify: n.outTlsSkipVerify,
RootCAs: n.caPool, RootCAs: n.caPool,
ServerName: serverName, ServerName: n.serverName,
} }
if n.outTlsCert != "" && n.outTlsKey != "" { if n.outTlsCert != "" && n.outTlsKey != "" {
tlsConfig.Certificates = []tls.Certificate{n.outTlsCertCache} tlsConfig.Certificates = []tls.Certificate{n.outTlsCertCache}

View File

@ -14,7 +14,6 @@ import (
"net" "net"
"os" "os"
"path/filepath" "path/filepath"
"strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -27,55 +26,72 @@ var timeoutMillSec int
var socks5 string var socks5 string
var socks5Auth string var socks5Auth string
var showCA bool var showCA bool
var skipVerify bool
func init() { func init() {
Cmd.Flags().BoolVarP(&hideDetail, "hide-detail", "H", false, "隐藏证书详细信息") Cmd.Flags().BoolVarP(&hideDetail, "hide-detail", "H", false, "隐藏证书详细信息")
Cmd.Flags().StringVarP(&dump, "dump", "d", "", "将证书保存到文件") Cmd.Flags().StringVarP(&dump, "dump", "d", "", "将证书保存到文件")
Cmd.Flags().IntVarP(&reqRawIP, "resolve-ip", "r", 0, "使用解析到的IP地址进行连接输入数字表示使用解析到的第几个IP地址") Cmd.Flags().IntVarP(&reqRawIP, "resolve-ip", "r", 0, "使用解析到的IP地址作为SNI进行tls连接输入数字表示使用解析到的第几个IP地址")
Cmd.Flags().IntVarP(&timeoutMillSec, "timeout", "t", 5000, "连接超时时间(毫秒)") Cmd.Flags().IntVarP(&timeoutMillSec, "timeout", "t", 5000, "连接超时时间(毫秒)")
Cmd.Flags().StringVarP(&socks5, "socks5", "p", "", "socks5代理示例127.0.0.1:1080") Cmd.Flags().StringVarP(&socks5, "socks5", "p", "", "socks5代理示例127.0.0.1:1080")
Cmd.Flags().StringVarP(&socks5Auth, "socks5-auth", "A", "", "socks5代理认证示例username:password") Cmd.Flags().StringVarP(&socks5Auth, "socks5-auth", "A", "", "socks5代理认证示例username:password")
Cmd.Flags().BoolVarP(&showCA, "show-ca", "c", false, "显示CA证书") Cmd.Flags().BoolVarP(&showCA, "show-ca", "c", false, "显示CA证书")
Cmd.Flags().BoolVarP(&skipVerify, "skip-verify", "k", false, "跳过证书验证")
} }
var Cmd = &cobra.Command{ var Cmd = &cobra.Command{
Use: "tls", Use: "tls",
Short: "查看TLS证书信息", Short: "查看TLS证书信息",
Long: "查看TLS证书信息", Long: `查看TLS证书信息
用法: b612 tls [-d|-r|-t|-p|-A-c] [域名:端口] [域名:端口/服务器名] [域名/服务器名]
如果使用IP直接连接对于IPv6地址需要使用方括号包裹[::1]:443
示例b612 tls -d cert.pem example.com:443/test.example.com
含义连接example.com:443SNI设置为test.example.com并将证书保存到cert.pem文件中
示例b612 tls -r 1 example.com:443
含义连接example.com:443使用解析到的第一个IP地址进行连接并且这个IP地址作为SNI进行连接
示例b612 tls -r 1 example.com:443/example.com
含义连接example.com:443使用解析到的第一个IP地址进行连接并使用example.com作为SNI进行连接`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
for _, target := range args { for _, target := range args {
showTls(target, !hideDetail, showCA, reqRawIP, dump, time.Duration(timeoutMillSec)*time.Millisecond) showTls(target, !hideDetail, showCA, reqRawIP, dump, skipVerify, time.Duration(timeoutMillSec)*time.Millisecond)
} }
}, },
} }
func showTls(target string, showDetail, showCA bool, reqRawIP int, dumpPath string, timeout time.Duration) { func showTls(target string, showDetail, showCA bool, reqRawIP int, dumpPath string, skipVerify bool, timeout time.Duration) {
var err error var err error
{ {
sp := strings.Split(target, ":") sp := strings.Split(target, "/")
if len(sp) < 2 { hostname, port, err := net.SplitHostPort(sp[0])
target = target + ":443" if port == "" || err != nil {
} else { if hostname == "" {
if _, err := strconv.Atoi(sp[len(sp)-1]); err != nil { hostname = sp[0]
target = target + ":443"
} }
port = "443"
sp[0] = hostname + ":443"
target = strings.Join(sp, "/")
} }
} }
if timeout == 0 { if timeout == 0 {
timeout = 5 * time.Second timeout = 5 * time.Second
} }
hostname := strings.Split(target, ":")[0] hostname, _, _ := net.SplitHostPort(strings.Split(target, "/")[0])
if strings.Count(target, ":") == 2 { hasHostname := false
strs := strings.Split(target, ":") if strings.Count(target, "/") > 0 {
if len(strs) != 3 { strs := strings.Split(target, "/")
if len(strs) != 2 {
starlog.Errorln("invalid target format") starlog.Errorln("invalid target format")
return return
} }
target = strs[0] + ":" + strs[2] target = strs[0]
hostname = strs[1] hostname = strs[1]
hasHostname = true
} }
if reqRawIP > 0 { if reqRawIP > 0 {
domain := strings.Split(target, ":")[0] domain, port, _ := net.SplitHostPort(strings.Split(target, "/")[0])
ips, err := net.LookupIP(domain) ips, err := net.LookupIP(domain)
if err != nil { if err != nil {
starlog.Errorln("failed to resolve domain: " + err.Error()) starlog.Errorln("failed to resolve domain: " + err.Error())
@ -91,8 +107,14 @@ func showTls(target string, showDetail, showCA bool, reqRawIP int, dumpPath stri
if reqRawIP > len(ips) { if reqRawIP > len(ips) {
reqRawIP = len(ips) reqRawIP = len(ips)
} }
target = ips[reqRawIP-1].String() + ":443" targetIp := ips[reqRawIP-1].String()
if ips[reqRawIP-1].To16() != nil {
targetIp = "[" + targetIp + "]"
}
target = targetIp + ":" + port
if !hasHostname {
hostname = ips[reqRawIP-1].String() hostname = ips[reqRawIP-1].String()
}
starlog.Noticeln("使用解析到的IP地址进行连接:", target) starlog.Noticeln("使用解析到的IP地址进行连接:", target)
} }
starlog.Noticef("将使用如下地址连接:%s ; ServerName: %s\n", target, hostname) starlog.Noticef("将使用如下地址连接:%s ; ServerName: %s\n", target, hostname)
@ -135,6 +157,9 @@ func showTls(target string, showDetail, showCA bool, reqRawIP int, dumpPath stri
wg.Add(1) wg.Add(1)
go func() { go func() {
defer wg.Done() defer wg.Done()
if !skipVerify {
return
}
var conn *tls.Conn var conn *tls.Conn
if socksDialer == nil { if socksDialer == nil {
conn, verifyErr = tls.DialWithDialer(netDialer, "tcp", target, &tls.Config{ conn, verifyErr = tls.DialWithDialer(netDialer, "tcp", target, &tls.Config{
@ -164,7 +189,7 @@ func showTls(target string, showDetail, showCA bool, reqRawIP int, dumpPath stri
if socksDialer == nil { if socksDialer == nil {
conn, err = tls.DialWithDialer(netDialer, "tcp", target, &tls.Config{ conn, err = tls.DialWithDialer(netDialer, "tcp", target, &tls.Config{
InsecureSkipVerify: true, InsecureSkipVerify: skipVerify,
ServerName: hostname, ServerName: hostname,
MinVersion: tls.VersionSSL30, MinVersion: tls.VersionSSL30,
}) })
@ -180,7 +205,7 @@ func showTls(target string, showDetail, showCA bool, reqRawIP int, dumpPath stri
} }
defer con.Close() defer con.Close()
conn = tls.Client(con, &tls.Config{ conn = tls.Client(con, &tls.Config{
InsecureSkipVerify: true, InsecureSkipVerify: skipVerify,
ServerName: hostname, ServerName: hostname,
MinVersion: tls.VersionSSL30, MinVersion: tls.VersionSSL30,
}) })
@ -293,6 +318,7 @@ func showTls(target string, showDetail, showCA bool, reqRawIP int, dumpPath stri
switch pub := c.PublicKey.(type) { switch pub := c.PublicKey.(type) {
case *rsa.PublicKey: case *rsa.PublicKey:
fmt.Printf("RSA公钥位数: %d\n", pub.Size()*8) // RSA公钥的位数 fmt.Printf("RSA公钥位数: %d\n", pub.Size()*8) // RSA公钥的位数
fmt.Printf("RSA公钥指数: %d\n", pub.E) // RSA公钥的指数
case *ecdsa.PublicKey: case *ecdsa.PublicKey:
fmt.Printf("ECDSA Curve位数: %d\n", pub.Curve.Params().BitSize) // ECDSA公钥的位数 fmt.Printf("ECDSA Curve位数: %d\n", pub.Curve.Params().BitSize) // ECDSA公钥的位数
} }
@ -316,6 +342,9 @@ func showTls(target string, showDetail, showCA bool, reqRawIP int, dumpPath stri
if len(c.PermittedDNSDomains) != 0 { if len(c.PermittedDNSDomains) != 0 {
fmt.Printf("批准使用的DNS: %s\n", strings.Join(c.PermittedDNSDomains, ", ")) fmt.Printf("批准使用的DNS: %s\n", strings.Join(c.PermittedDNSDomains, ", "))
} }
if len(c.ExcludedDNSDomains) != 0 {
fmt.Printf("禁止使用的DNS: %s\n", strings.Join(c.ExcludedDNSDomains, ", "))
}
if len(c.PermittedIPRanges) != 0 { if len(c.PermittedIPRanges) != 0 {
ipRange := "" ipRange := ""
for _, ip := range c.PermittedIPRanges { for _, ip := range c.PermittedIPRanges {
@ -324,12 +353,26 @@ func showTls(target string, showDetail, showCA bool, reqRawIP int, dumpPath stri
ipRange = ipRange[:len(ipRange)-2] ipRange = ipRange[:len(ipRange)-2]
fmt.Printf("批准使用的IP: %s\n", ipRange) fmt.Printf("批准使用的IP: %s\n", ipRange)
} }
if len(c.ExcludedIPRanges) != 0 {
ipRange := ""
for _, ip := range c.ExcludedIPRanges {
ipRange += ip.String() + ", "
}
ipRange = ipRange[:len(ipRange)-2]
fmt.Printf("禁止使用的IP: %s\n", ipRange)
}
if len(c.PermittedEmailAddresses) != 0 { if len(c.PermittedEmailAddresses) != 0 {
fmt.Printf("批准使用的Email: %s\n", strings.Join(c.PermittedEmailAddresses, ", ")) fmt.Printf("批准使用的Email: %s\n", strings.Join(c.PermittedEmailAddresses, ", "))
} }
if len(c.ExcludedEmailAddresses) != 0 {
fmt.Printf("禁止使用的Email: %s\n", strings.Join(c.ExcludedEmailAddresses, ", "))
}
if len(c.PermittedURIDomains) != 0 { if len(c.PermittedURIDomains) != 0 {
fmt.Printf("批准使用的URI: %s\n", strings.Join(c.PermittedURIDomains, ", ")) fmt.Printf("批准使用的URI: %s\n", strings.Join(c.PermittedURIDomains, ", "))
} }
if len(c.ExcludedURIDomains) != 0 {
fmt.Printf("禁止使用的URI: %s\n", strings.Join(c.ExcludedURIDomains, ", "))
}
fmt.Printf("证书密钥用途: %s\n", strings.Join(KeyUsageToString(c.KeyUsage), ", ")) fmt.Printf("证书密钥用途: %s\n", strings.Join(KeyUsageToString(c.KeyUsage), ", "))
extKeyUsage := []string{} extKeyUsage := []string{}
for _, v := range c.ExtKeyUsage { for _, v := range c.ExtKeyUsage {
@ -367,6 +410,24 @@ func showTls(target string, showDetail, showCA bool, reqRawIP int, dumpPath stri
} }
} }
fmt.Printf("证书扩展密钥用途: %s\n", strings.Join(extKeyUsage, ", ")) fmt.Printf("证书扩展密钥用途: %s\n", strings.Join(extKeyUsage, ", "))
if len(c.CRLDistributionPoints) > 0 {
fmt.Printf("证书CRL分发点: %s\n", strings.Join(c.CRLDistributionPoints, ","))
}
if len(c.OCSPServer) > 0 {
fmt.Printf("证书OCSP服务器: %s\n", strings.Join(c.OCSPServer, ", "))
}
if len(c.SubjectKeyId) > 0 {
fmt.Printf("证书主题密钥ID: %x\n", c.SubjectKeyId)
}
if len(c.AuthorityKeyId) > 0 {
fmt.Printf("证书颁发者密钥ID: %x\n", c.AuthorityKeyId)
}
if c.MaxPathLenZero {
fmt.Println("证书最大路径长度为0")
}
if c.MaxPathLen > 0 {
fmt.Printf("证书最大路径长度: %d\n", c.MaxPathLen)
}
fmt.Printf("证书版本: %d\n----------\n", c.Version) fmt.Printf("证书版本: %d\n----------\n", c.Version)
//fmt.Printf("证书扩展信息: %+v\n\n----------", c.Extensions) //fmt.Printf("证书扩展信息: %+v\n\n----------", c.Extensions)
} }

View File

@ -6,7 +6,6 @@ import (
"crypto/ecdh" "crypto/ecdh"
"crypto/ed25519" "crypto/ed25519"
"crypto/elliptic" "crypto/elliptic"
"crypto/pbkdf2"
"crypto/rand" "crypto/rand"
"crypto/sha512" "crypto/sha512"
"crypto/tls" "crypto/tls"
@ -16,6 +15,7 @@ import (
"encoding/pem" "encoding/pem"
"errors" "errors"
"fmt" "fmt"
"golang.org/x/crypto/pbkdf2"
"io" "io"
"math/big" "math/big"
"net" "net"
@ -245,12 +245,9 @@ func Encode(data []byte, key string) ([]byte, error) {
if key == "" { if key == "" {
return nil, errors.New("key is empty") return nil, errors.New("key is empty")
} }
aesKey, err := pbkdf2.Key(sha512.New, key, []byte("b612.me"), 923876, 32) aesKey := pbkdf2.Key([]byte(key), []byte("b612.me"), 923876, 32, sha512.New)
if err != nil {
return nil, fmt.Errorf("failed to generate AES key: %w", err)
}
var iv = make([]byte, 16) var iv = make([]byte, 16)
_, err = io.ReadFull(rand.Reader, iv) _, err := io.ReadFull(rand.Reader, iv)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to generate IV: %w", err) return nil, fmt.Errorf("failed to generate IV: %w", err)
} }
@ -269,10 +266,7 @@ func Decode(data []byte, key string) ([]byte, error) {
if key == "" { if key == "" {
return nil, errors.New("key is empty") return nil, errors.New("key is empty")
} }
aesKey, err := pbkdf2.Key(sha512.New, key, []byte("b612.me"), 923876, 32) aesKey := pbkdf2.Key([]byte(key), []byte("b612.me"), 923876, 32, sha512.New)
if err != nil {
return nil, fmt.Errorf("failed to generate AES key: %w", err)
}
iv := data[:16] iv := data[:16]
ciphertext := data[16:] ciphertext := data[16:]
plaintext, err := starcrypto.CustomDecryptAesCFBNoBlock(ciphertext, aesKey, iv) plaintext, err := starcrypto.CustomDecryptAesCFBNoBlock(ciphertext, aesKey, iv)

View File

@ -86,7 +86,7 @@ func TestGenerateMiddleCA(t *testing.T) {
MaxPathLen: 0, MaxPathLen: 0,
MaxPathLenZero: true, MaxPathLenZero: true,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageAny, x509.ExtKeyUsageServerAuth, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageAny, x509.ExtKeyUsageServerAuth,
x509.ExtKeyUsageClientAuth}, x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageOCSPSigning},
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement | x509.KeyUsageDigitalSignature, KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement | x509.KeyUsageDigitalSignature,
} }
rsa, _, err := starcrypto.GenerateRsaKey(4096) rsa, _, err := starcrypto.GenerateRsaKey(4096)