Compare commits

...

4 Commits

@ -79,7 +79,7 @@ func fileFinder(path string, reg string, onlyFilterName bool, outpath string, sh
} }
return "" return ""
} }
fp := new(os.File) var fp *os.File
if outpath != "" { if outpath != "" {
fp, err = os.Create(outpath) fp, err = os.Create(outpath)
if err != nil { if err != nil {

@ -25,6 +25,7 @@ require (
github.com/spf13/cobra v1.8.0 github.com/spf13/cobra v1.8.0
github.com/things-go/go-socks5 v0.0.5 github.com/things-go/go-socks5 v0.0.5
golang.org/x/crypto v0.21.0 golang.org/x/crypto v0.21.0
golang.org/x/net v0.21.0
software.sslmate.com/src/go-pkcs12 v0.4.0 software.sslmate.com/src/go-pkcs12 v0.4.0
) )
@ -39,7 +40,6 @@ require (
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/image v0.6.0 // indirect golang.org/x/image v0.6.0 // indirect
golang.org/x/mod v0.14.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/sys v0.18.0 // indirect
golang.org/x/term v0.18.0 // indirect golang.org/x/term v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect golang.org/x/text v0.14.0 // indirect

@ -12,4 +12,9 @@ authuser=b612
authpasswd=b612 authpasswd=b612
whiteip= whiteip=
blackip= blackip=
wanringpage= wanringpage=
ipfiltermode=3
filterxforward=
filterremoteaddr=
filtermustkey=
filterfile=

@ -75,7 +75,7 @@ var Cmd = &cobra.Command{
SkipSSLVerify: skipsslverify, SkipSSLVerify: skipsslverify,
Key: key, Key: key,
Cert: cert, Cert: cert,
XForwardMode: 1, IPFilterMode: 1,
} }
go func() { go func() {
sig := make(chan os.Signal) sig := make(chan os.Signal)

@ -1,35 +1,46 @@
package httpreverse package httpreverse
import ( import (
"b612.me/apps/b612/httpreverse/rp"
"b612.me/starlog"
"b612.me/staros/sysconf" "b612.me/staros/sysconf"
"bufio"
"errors" "errors"
"io"
"io/ioutil" "io/ioutil"
"net"
"net/http" "net/http"
"net/http/httputil"
"net/url" "net/url"
"os"
"strings" "strings"
"sync" "sync"
) )
type ReverseConfig struct { type ReverseConfig struct {
Name string Name string
Addr string Addr string
ReverseURL map[string]*url.URL ReverseURL map[string]*url.URL
Port int Port int
UsingSSL bool UsingSSL bool
Key string Key string
Cert string Cert string
Host string Host string
SkipSSLVerify bool SkipSSLVerify bool
InHeader [][2]string InHeader [][2]string
OutHeader [][2]string OutHeader [][2]string
Cookie [][3]string //[3]string should contains path::key::value Cookie [][3]string //[3]string should contains path::key::value
ReplaceList [][2]string ReplaceList [][2]string
ReplaceOnce bool ReplaceOnce bool
proxy map[string]*httputil.ReverseProxy proxy map[string]*rp.ReverseProxy
XForwardMode int //0=off 1=useremote 2=add IPFilterMode int //0=off 1=useremote 2=add 3=filter
httpmux http.ServeMux FilterXForward bool
httpserver http.Server FilterRemoteAddr bool
FilterMustKey string
FilterSetKey string
FilterFile string
httpmux http.ServeMux
httpserver http.Server
CIDR []*net.IPNet
basicAuthUser string basicAuthUser string
basicAuthPwd string basicAuthPwd string
@ -53,18 +64,54 @@ func Parse(path string) (HttpReverseServer, error) {
} }
for _, v := range ini.Data { for _, v := range ini.Data {
var ins = ReverseConfig{ var ins = ReverseConfig{
Name: v.Name, Name: v.Name,
Host: v.Get("host"), Host: v.Get("host"),
Addr: v.Get("addr"), Addr: v.Get("addr"),
Port: v.Int("port"), Port: v.Int("port"),
UsingSSL: v.Bool("enablessl"), UsingSSL: v.Bool("enablessl"),
Key: v.Get("key"), Key: v.Get("key"),
Cert: v.Get("cert"), Cert: v.Get("cert"),
ReplaceOnce: v.Bool("replaceonce"), ReplaceOnce: v.Bool("replaceonce"),
XForwardMode: v.Int("xforwardmode"), IPFilterMode: v.Int("ipfiltermode"),
basicAuthUser: v.Get("authuser"), FilterXForward: v.Bool("filterxforward"),
basicAuthPwd: v.Get("authpasswd"), FilterRemoteAddr: v.Bool("filterremoteaddr"),
warningpage: v.Get("warnpage"), FilterMustKey: v.Get("filtermustkey"),
FilterSetKey: v.Get("filtersetkey"),
FilterFile: v.Get("filterfile"),
basicAuthUser: v.Get("authuser"),
basicAuthPwd: v.Get("authpasswd"),
warningpage: v.Get("warnpage"),
}
if ins.IPFilterMode == 3 && ins.FilterFile != "" {
starlog.Infoln("IP Filter Mode 3, Load IP Filter File", ins.FilterFile)
f, err := os.Open(ins.FilterFile)
if err != nil {
return res, err
}
buf := bufio.NewReader(f)
count := 0
for {
line, err := buf.ReadString('\n')
if err != nil {
if err == io.EOF {
f.Close()
break
}
f.Close()
return res, err
}
line = strings.TrimSpace(line)
if !strings.Contains(line, "/") {
line += "/32" //todo区分IPV6
}
_, cidr, err := net.ParseCIDR(line)
if err != nil {
return res, err
}
ins.CIDR = append(ins.CIDR, cidr)
count++
}
starlog.Infoln("Load", count, "CIDR")
} }
if ins.warningpage != "" { if ins.warningpage != "" {
data, err := ioutil.ReadFile(ins.warningpage) data, err := ioutil.ReadFile(ins.warningpage)
@ -73,7 +120,7 @@ func Parse(path string) (HttpReverseServer, error) {
} }
ins.warnpagedata = data ins.warnpagedata = data
} }
ins.proxy = make(map[string]*httputil.ReverseProxy) ins.proxy = make(map[string]*rp.ReverseProxy)
ins.ReverseURL = make(map[string]*url.URL) ins.ReverseURL = make(map[string]*url.URL)
for _, reverse := range v.GetAll("reverse") { for _, reverse := range v.GetAll("reverse") {
kv := strings.SplitN(reverse, "::", 2) kv := strings.SplitN(reverse, "::", 2)

@ -2,9 +2,18 @@ package httpreverse
import ( import (
"fmt" "fmt"
"net"
"testing" "testing"
) )
func TestCIDR(t *testing.T) {
_, c, err := net.ParseCIDR("108.162.192.0/18")
if err != nil {
t.Fatal(err)
}
fmt.Println(c.Contains(net.ParseIP("108.162.245.124")))
}
func TestReverseParse(t *testing.T) { func TestReverseParse(t *testing.T) {
data, err := Parse("./cfg.ini") data, err := Parse("./cfg.ini")
if err != nil { if err != nil {

@ -0,0 +1,852 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// HTTP reverse proxy handler
package rp
import (
"context"
"errors"
"fmt"
"io"
"log"
"mime"
"net"
"net/http"
"net/http/httptrace"
"net/textproto"
"net/url"
"strings"
"sync"
"time"
"golang.org/x/net/http/httpguts"
)
func lower(b byte) byte {
if 'A' <= b && b <= 'Z' {
return b + ('a' - 'A')
}
return b
}
func EqualFold(s, t string) bool {
if len(s) != len(t) {
return false
}
for i := 0; i < len(s); i++ {
if lower(s[i]) != lower(t[i]) {
return false
}
}
return true
}
func IsPrint(s string) bool {
for i := 0; i < len(s); i++ {
if s[i] < ' ' || s[i] > '~' {
return false
}
}
return true
}
// A ProxyRequest contains a request to be rewritten by a ReverseProxy.
type ProxyRequest struct {
// In is the request received by the proxy.
// The Rewrite function must not modify In.
In *http.Request
// Out is the request which will be sent by the proxy.
// The Rewrite function may modify or replace this request.
// Hop-by-hop headers are removed from this request
// before Rewrite is called.
Out *http.Request
}
// SetURL routes the outbound request to the scheme, host, and base path
// provided in target. If the target's path is "/base" and the incoming
// request was for "/dir", the target request will be for "/base/dir".
//
// SetURL rewrites the outbound Host header to match the target's host.
// To preserve the inbound request's Host header (the default behavior
// of NewSingleHostReverseProxy):
//
// rewriteFunc := func(r *httputil.ProxyRequest) {
// r.SetURL(url)
// r.Out.Host = r.In.Host
// }
func (r *ProxyRequest) SetURL(target *url.URL) {
rewriteRequestURL(r.Out, target)
r.Out.Host = ""
}
// SetXForwarded sets the X-Forwarded-For, X-Forwarded-Host, and
// X-Forwarded-Proto headers of the outbound request.
//
// - The X-Forwarded-For header is set to the client IP address.
// - The X-Forwarded-Host header is set to the host name requested
// by the client.
// - The X-Forwarded-Proto header is set to "http" or "https", depending
// on whether the inbound request was made on a TLS-enabled connection.
//
// If the outbound request contains an existing X-Forwarded-For header,
// SetXForwarded appends the client IP address to it. To append to the
// inbound request's X-Forwarded-For header (the default behavior of
// ReverseProxy when using a Director function), copy the header
// from the inbound request before calling SetXForwarded:
//
// rewriteFunc := func(r *httputil.ProxyRequest) {
// r.Out.Header["X-Forwarded-For"] = r.In.Header["X-Forwarded-For"]
// r.SetXForwarded()
// }
func (r *ProxyRequest) SetXForwarded() {
clientIP, _, err := net.SplitHostPort(r.In.RemoteAddr)
if err == nil {
prior := r.Out.Header["X-Forwarded-For"]
if len(prior) > 0 {
clientIP = strings.Join(prior, ", ") + ", " + clientIP
}
r.Out.Header.Set("X-Forwarded-For", clientIP)
} else {
r.Out.Header.Del("X-Forwarded-For")
}
r.Out.Header.Set("X-Forwarded-Host", r.In.Host)
if r.In.TLS == nil {
r.Out.Header.Set("X-Forwarded-Proto", "http")
} else {
r.Out.Header.Set("X-Forwarded-Proto", "https")
}
}
// ReverseProxy is an HTTP Handler that takes an incoming request and
// sends it to another server, proxying the response back to the
// client.
//
// 1xx responses are forwarded to the client if the underlying
// transport supports ClientTrace.Got1xxResponse.
type ReverseProxy struct {
// Rewrite must be a function which modifies
// the request into a new request to be sent
// using Transport. Its response is then copied
// back to the original client unmodified.
// Rewrite must not access the provided ProxyRequest
// or its contents after returning.
//
// The Forwarded, X-Forwarded, X-Forwarded-Host,
// and X-Forwarded-Proto headers are removed from the
// outbound request before Rewrite is called. See also
// the ProxyRequest.SetXForwarded method.
//
// Unparsable query parameters are removed from the
// outbound request before Rewrite is called.
// The Rewrite function may copy the inbound URL's
// RawQuery to the outbound URL to preserve the original
// parameter string. Note that this can lead to security
// issues if the proxy's interpretation of query parameters
// does not match that of the downstream server.
//
// At most one of Rewrite or Director may be set.
Rewrite func(*ProxyRequest)
// Director is a function which modifies
// the request into a new request to be sent
// using Transport. Its response is then copied
// back to the original client unmodified.
// Director must not access the provided Request
// after returning.
//
// By default, the X-Forwarded-For header is set to the
// value of the client IP address. If an X-Forwarded-For
// header already exists, the client IP is appended to the
// existing values. As a special case, if the header
// exists in the Request.Header map but has a nil value
// (such as when set by the Director func), the X-Forwarded-For
// header is not modified.
//
// To prevent IP spoofing, be sure to delete any pre-existing
// X-Forwarded-For header coming from the client or
// an untrusted proxy.
//
// Hop-by-hop headers are removed from the request after
// Director returns, which can remove headers added by
// Director. Use a Rewrite function instead to ensure
// modifications to the request are preserved.
//
// Unparsable query parameters are removed from the outbound
// request if Request.Form is set after Director returns.
//
// At most one of Rewrite or Director may be set.
Director func(*http.Request)
// The transport used to perform proxy requests.
// If nil, http.DefaultTransport is used.
Transport http.RoundTripper
// FlushInterval specifies the flush interval
// to flush to the client while copying the
// response body.
// If zero, no periodic flushing is done.
// A negative value means to flush immediately
// after each write to the client.
// The FlushInterval is ignored when ReverseProxy
// recognizes a response as a streaming response, or
// if its ContentLength is -1; for such responses, writes
// are flushed to the client immediately.
FlushInterval time.Duration
// ErrorLog specifies an optional logger for errors
// that occur when attempting to proxy the request.
// If nil, logging is done via the log package's standard logger.
ErrorLog *log.Logger
// BufferPool optionally specifies a buffer pool to
// get byte slices for use by io.CopyBuffer when
// copying HTTP response bodies.
BufferPool BufferPool
// ModifyResponse is an optional function that modifies the
// Response from the backend. It is called if the backend
// returns a response at all, with any HTTP status code.
// If the backend is unreachable, the optional ErrorHandler is
// called without any call to ModifyResponse.
//
// If ModifyResponse returns an error, ErrorHandler is called
// with its error value. If ErrorHandler is nil, its default
// implementation is used.
ModifyResponse func(*http.Response) error
// ErrorHandler is an optional function that handles errors
// reaching the backend or errors from ModifyResponse.
//
// If nil, the default is to log the provided error and return
// a 502 Status Bad Gateway response.
ErrorHandler func(http.ResponseWriter, *http.Request, error)
}
// A BufferPool is an interface for getting and returning temporary
// byte slices for use by io.CopyBuffer.
type BufferPool interface {
Get() []byte
Put([]byte)
}
func singleJoiningSlash(a, b string) string {
aslash := strings.HasSuffix(a, "/")
bslash := strings.HasPrefix(b, "/")
switch {
case aslash && bslash:
return a + b[1:]
case !aslash && !bslash:
return a + "/" + b
}
return a + b
}
func joinURLPath(a, b *url.URL) (path, rawpath string) {
if a.RawPath == "" && b.RawPath == "" {
return singleJoiningSlash(a.Path, b.Path), ""
}
// Same as singleJoiningSlash, but uses EscapedPath to determine
// whether a slash should be added
apath := a.EscapedPath()
bpath := b.EscapedPath()
aslash := strings.HasSuffix(apath, "/")
bslash := strings.HasPrefix(bpath, "/")
switch {
case aslash && bslash:
return a.Path + b.Path[1:], apath + bpath[1:]
case !aslash && !bslash:
return a.Path + "/" + b.Path, apath + "/" + bpath
}
return a.Path + b.Path, apath + bpath
}
// NewSingleHostReverseProxy returns a new ReverseProxy that routes
// URLs to the scheme, host, and base path provided in target. If the
// target's path is "/base" and the incoming request was for "/dir",
// the target request will be for /base/dir.
//
// NewSingleHostReverseProxy does not rewrite the Host header.
//
// To customize the ReverseProxy behavior beyond what
// NewSingleHostReverseProxy provides, use ReverseProxy directly
// with a Rewrite function. The ProxyRequest SetURL method
// may be used to route the outbound request. (Note that SetURL,
// unlike NewSingleHostReverseProxy, rewrites the Host header
// of the outbound request by default.)
//
// proxy := &ReverseProxy{
// Rewrite: func(r *ProxyRequest) {
// r.SetURL(target)
// r.Out.Host = r.In.Host // if desired
// }
// }
func NewSingleHostReverseProxy(target *url.URL) *ReverseProxy {
director := func(req *http.Request) {
rewriteRequestURL(req, target)
}
return &ReverseProxy{Director: director}
}
func rewriteRequestURL(req *http.Request, target *url.URL) {
targetQuery := target.RawQuery
req.URL.Scheme = target.Scheme
req.URL.Host = target.Host
req.URL.Path, req.URL.RawPath = joinURLPath(target, req.URL)
if targetQuery == "" || req.URL.RawQuery == "" {
req.URL.RawQuery = targetQuery + req.URL.RawQuery
} else {
req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
}
}
func copyHeader(dst, src http.Header) {
for k, vv := range src {
for _, v := range vv {
dst.Add(k, v)
}
}
}
// Hop-by-hop headers. These are removed when sent to the backend.
// As of RFC 7230, hop-by-hop headers are required to appear in the
// Connection header field. These are the headers defined by the
// obsoleted RFC 2616 (section 13.5.1) and are used for backward
// compatibility.
var hopHeaders = []string{
"Connection",
"Proxy-Connection", // non-standard but still sent by libcurl and rejected by e.g. google
"Keep-Alive",
"Proxy-Authenticate",
"Proxy-Authorization",
"Te", // canonicalized version of "TE"
"Trailer", // not Trailers per URL above; https://www.rfc-editor.org/errata_search.php?eid=4522
"Transfer-Encoding",
"Upgrade",
}
func (p *ReverseProxy) defaultErrorHandler(rw http.ResponseWriter, req *http.Request, err error) {
p.logf("http: proxy error: %v", err)
rw.WriteHeader(http.StatusBadGateway)
}
func (p *ReverseProxy) getErrorHandler() func(http.ResponseWriter, *http.Request, error) {
if p.ErrorHandler != nil {
return p.ErrorHandler
}
return p.defaultErrorHandler
}
// modifyResponse conditionally runs the optional ModifyResponse hook
// and reports whether the request should proceed.
func (p *ReverseProxy) modifyResponse(rw http.ResponseWriter, res *http.Response, req *http.Request) bool {
if p.ModifyResponse == nil {
return true
}
if err := p.ModifyResponse(res); err != nil {
res.Body.Close()
p.getErrorHandler()(rw, req, err)
return false
}
return true
}
func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
transport := p.Transport
if transport == nil {
transport = http.DefaultTransport
}
ctx := req.Context()
if ctx.Done() != nil {
// CloseNotifier predates context.Context, and has been
// entirely superseded by it. If the request contains
// a Context that carries a cancellation signal, don't
// bother spinning up a goroutine to watch the CloseNotify
// channel (if any).
//
// If the request Context has a nil Done channel (which
// means it is either context.Background, or a custom
// Context implementation with no cancellation signal),
// then consult the CloseNotifier if available.
} else if cn, ok := rw.(http.CloseNotifier); ok {
var cancel context.CancelFunc
ctx, cancel = context.WithCancel(ctx)
defer cancel()
notifyChan := cn.CloseNotify()
go func() {
select {
case <-notifyChan:
cancel()
case <-ctx.Done():
}
}()
}
outreq := req.Clone(ctx)
if req.ContentLength == 0 {
outreq.Body = nil // Issue 16036: nil Body for http.Transport retries
}
if outreq.Body != nil {
// Reading from the request body after returning from a handler is not
// allowed, and the RoundTrip goroutine that reads the Body can outlive
// this handler. This can lead to a crash if the handler panics (see
// Issue 46866). Although calling Close doesn't guarantee there isn't
// any Read in flight after the handle returns, in practice it's safe to
// read after closing it.
defer outreq.Body.Close()
}
if outreq.Header == nil {
outreq.Header = make(http.Header) // Issue 33142: historical behavior was to always allocate
}
if (p.Director != nil) == (p.Rewrite != nil) {
p.getErrorHandler()(rw, req, errors.New("ReverseProxy must have exactly one of Director or Rewrite set"))
return
}
if p.Director != nil {
p.Director(outreq)
if outreq.Form != nil {
outreq.URL.RawQuery = cleanQueryParams(outreq.URL.RawQuery)
}
}
outreq.Close = false
reqUpType := upgradeType(outreq.Header)
if !IsPrint(reqUpType) {
p.getErrorHandler()(rw, req, fmt.Errorf("client tried to switch to invalid protocol %q", reqUpType))
return
}
removeHopByHopHeaders(outreq.Header)
// Issue 21096: tell backend applications that care about trailer support
// that we support trailers. (We do, but we don't go out of our way to
// advertise that unless the incoming client request thought it was worth
// mentioning.) Note that we look at req.Header, not outreq.Header, since
// the latter has passed through removeHopByHopHeaders.
if httpguts.HeaderValuesContainsToken(req.Header["Te"], "trailers") {
outreq.Header.Set("Te", "trailers")
}
// After stripping all the hop-by-hop connection headers above, add back any
// necessary for protocol upgrades, such as for websockets.
if reqUpType != "" {
outreq.Header.Set("Connection", "Upgrade")
outreq.Header.Set("Upgrade", reqUpType)
}
if p.Rewrite != nil {
// Strip client-provided forwarding headers.
// The Rewrite func may use SetXForwarded to set new values
// for these or copy the previous values from the inbound request.
outreq.Header.Del("Forwarded")
outreq.Header.Del("X-Forwarded-For")
outreq.Header.Del("X-Forwarded-Host")
outreq.Header.Del("X-Forwarded-Proto")
// Remove unparsable query parameters from the outbound request.
outreq.URL.RawQuery = cleanQueryParams(outreq.URL.RawQuery)
pr := &ProxyRequest{
In: req,
Out: outreq,
}
p.Rewrite(pr)
outreq = pr.Out
}
if _, ok := outreq.Header["User-Agent"]; !ok {
// If the outbound request doesn't have a User-Agent header set,
// don't send the default Go HTTP client User-Agent.
outreq.Header.Set("User-Agent", "")
}
trace := &httptrace.ClientTrace{
Got1xxResponse: func(code int, header textproto.MIMEHeader) error {
h := rw.Header()
copyHeader(h, http.Header(header))
rw.WriteHeader(code)
// Clear headers, it's not automatically done by ResponseWriter.WriteHeader() for 1xx responses
for k := range h {
delete(h, k)
}
return nil
},
}
outreq = outreq.WithContext(httptrace.WithClientTrace(outreq.Context(), trace))
res, err := transport.RoundTrip(outreq)
if err != nil {
p.getErrorHandler()(rw, outreq, err)
return
}
// Deal with 101 Switching Protocols responses: (WebSocket, h2c, etc)
if res.StatusCode == http.StatusSwitchingProtocols {
if !p.modifyResponse(rw, res, outreq) {
return
}
p.handleUpgradeResponse(rw, outreq, res)
return
}
removeHopByHopHeaders(res.Header)
if !p.modifyResponse(rw, res, outreq) {
return
}
copyHeader(rw.Header(), res.Header)
// The "Trailer" header isn't included in the Transport's response,
// at least for *http.Transport. Build it up from Trailer.
announcedTrailers := len(res.Trailer)
if announcedTrailers > 0 {
trailerKeys := make([]string, 0, len(res.Trailer))
for k := range res.Trailer {
trailerKeys = append(trailerKeys, k)
}
rw.Header().Add("Trailer", strings.Join(trailerKeys, ", "))
}
rw.WriteHeader(res.StatusCode)
err = p.copyResponse(rw, res.Body, p.flushInterval(res))
if err != nil {
defer res.Body.Close()
// Since we're streaming the response, if we run into an error all we can do
// is abort the request. Issue 23643: ReverseProxy should use ErrAbortHandler
// on read error while copying body.
if !shouldPanicOnCopyError(req) {
p.logf("suppressing panic for copyResponse error in test; copy error: %v", err)
return
}
panic(http.ErrAbortHandler)
}
res.Body.Close() // close now, instead of defer, to populate res.Trailer
if len(res.Trailer) > 0 {
// Force chunking if we saw a response trailer.
// This prevents net/http from calculating the length for short
// bodies and adding a Content-Length.
if fl, ok := rw.(http.Flusher); ok {
fl.Flush()
}
}
if len(res.Trailer) == announcedTrailers {
copyHeader(rw.Header(), res.Trailer)
return
}
for k, vv := range res.Trailer {
k = http.TrailerPrefix + k
for _, v := range vv {
rw.Header().Add(k, v)
}
}
}
var inOurTests bool // whether we're in our own tests
// shouldPanicOnCopyError reports whether the reverse proxy should
// panic with http.ErrAbortHandler. This is the right thing to do by
// default, but Go 1.10 and earlier did not, so existing unit tests
// weren't expecting panics. Only panic in our own tests, or when
// running under the HTTP server.
func shouldPanicOnCopyError(req *http.Request) bool {
if inOurTests {
// Our tests know to handle this panic.
return true
}
if req.Context().Value(http.ServerContextKey) != nil {
// We seem to be running under an HTTP server, so
// it'll recover the panic.
return true
}
// Otherwise act like Go 1.10 and earlier to not break
// existing tests.
return false
}
// removeHopByHopHeaders removes hop-by-hop headers.
func removeHopByHopHeaders(h http.Header) {
// RFC 7230, section 6.1: Remove headers listed in the "Connection" header.
for _, f := range h["Connection"] {
for _, sf := range strings.Split(f, ",") {
if sf = textproto.TrimString(sf); sf != "" {
h.Del(sf)
}
}
}
// RFC 2616, section 13.5.1: Remove a set of known hop-by-hop headers.
// This behavior is superseded by the RFC 7230 Connection header, but
// preserve it for backwards compatibility.
for _, f := range hopHeaders {
h.Del(f)
}
}
// flushInterval returns the p.FlushInterval value, conditionally
// overriding its value for a specific request/response.
func (p *ReverseProxy) flushInterval(res *http.Response) time.Duration {
resCT := res.Header.Get("Content-Type")
// For Server-Sent Events responses, flush immediately.
// The MIME type is defined in https://www.w3.org/TR/eventsource/#text-event-stream
if baseCT, _, _ := mime.ParseMediaType(resCT); baseCT == "text/event-stream" {
return -1 // negative means immediately
}
// We might have the case of streaming for which Content-Length might be unset.
if res.ContentLength == -1 {
return -1
}
return p.FlushInterval
}
func (p *ReverseProxy) copyResponse(dst io.Writer, src io.Reader, flushInterval time.Duration) error {
if flushInterval != 0 {
if wf, ok := dst.(writeFlusher); ok {
mlw := &maxLatencyWriter{
dst: wf,
latency: flushInterval,
}
defer mlw.stop()
// set up initial timer so headers get flushed even if body writes are delayed
mlw.flushPending = true
mlw.t = time.AfterFunc(flushInterval, mlw.delayedFlush)
dst = mlw
}
}
var buf []byte
if p.BufferPool != nil {
buf = p.BufferPool.Get()
defer p.BufferPool.Put(buf)
}
_, err := p.copyBuffer(dst, src, buf)
return err
}
// copyBuffer returns any write errors or non-EOF read errors, and the amount
// of bytes written.
func (p *ReverseProxy) copyBuffer(dst io.Writer, src io.Reader, buf []byte) (int64, error) {
if len(buf) == 0 {
buf = make([]byte, 32*1024)
}
var written int64
for {
nr, rerr := src.Read(buf)
if rerr != nil && rerr != io.EOF && rerr != context.Canceled {
p.logf("httputil: ReverseProxy read error during body copy: %v", rerr)
}
if nr > 0 {
nw, werr := dst.Write(buf[:nr])
if nw > 0 {
written += int64(nw)
}
if werr != nil {
return written, werr
}
if nr != nw {
return written, io.ErrShortWrite
}
}
if rerr != nil {
if rerr == io.EOF {
rerr = nil
}
return written, rerr
}
}
}
func (p *ReverseProxy) logf(format string, args ...any) {
if p.ErrorLog != nil {
p.ErrorLog.Printf(format, args...)
} else {
log.Printf(format, args...)
}
}
type writeFlusher interface {
io.Writer
http.Flusher
}
type maxLatencyWriter struct {
dst writeFlusher
latency time.Duration // non-zero; negative means to flush immediately
mu sync.Mutex // protects t, flushPending, and dst.Flush
t *time.Timer
flushPending bool
}
func (m *maxLatencyWriter) Write(p []byte) (n int, err error) {
m.mu.Lock()
defer m.mu.Unlock()
n, err = m.dst.Write(p)
if m.latency < 0 {
m.dst.Flush()
return
}
if m.flushPending {
return
}
if m.t == nil {
m.t = time.AfterFunc(m.latency, m.delayedFlush)
} else {
m.t.Reset(m.latency)
}
m.flushPending = true
return
}
func (m *maxLatencyWriter) delayedFlush() {
m.mu.Lock()
defer m.mu.Unlock()
if !m.flushPending { // if stop was called but AfterFunc already started this goroutine
return
}
m.dst.Flush()
m.flushPending = false
}
func (m *maxLatencyWriter) stop() {
m.mu.Lock()
defer m.mu.Unlock()
m.flushPending = false
if m.t != nil {
m.t.Stop()
}
}
func upgradeType(h http.Header) string {
if !httpguts.HeaderValuesContainsToken(h["Connection"], "Upgrade") {
return ""
}
return h.Get("Upgrade")
}
func (p *ReverseProxy) handleUpgradeResponse(rw http.ResponseWriter, req *http.Request, res *http.Response) {
reqUpType := upgradeType(req.Header)
resUpType := upgradeType(res.Header)
if !IsPrint(resUpType) { // We know reqUpType is ASCII, it's checked by the caller.
p.getErrorHandler()(rw, req, fmt.Errorf("backend tried to switch to invalid protocol %q", resUpType))
}
if !EqualFold(reqUpType, resUpType) {
p.getErrorHandler()(rw, req, fmt.Errorf("backend tried to switch protocol %q when %q was requested", resUpType, reqUpType))
return
}
hj, ok := rw.(http.Hijacker)
if !ok {
p.getErrorHandler()(rw, req, fmt.Errorf("can't switch protocols using non-Hijacker ResponseWriter type %T", rw))
return
}
backConn, ok := res.Body.(io.ReadWriteCloser)
if !ok {
p.getErrorHandler()(rw, req, fmt.Errorf("internal error: 101 switching protocols response with non-writable body"))
return
}
backConnCloseCh := make(chan bool)
go func() {
// Ensure that the cancellation of a request closes the backend.
// See issue https://golang.org/issue/35559.
select {
case <-req.Context().Done():
case <-backConnCloseCh:
}
backConn.Close()
}()
defer close(backConnCloseCh)
conn, brw, err := hj.Hijack()
if err != nil {
p.getErrorHandler()(rw, req, fmt.Errorf("Hijack failed on protocol switch: %v", err))
return
}
defer conn.Close()
copyHeader(rw.Header(), res.Header)
res.Header = rw.Header()
res.Body = nil // so res.Write only writes the headers; we have res.Body in backConn above
if err := res.Write(brw); err != nil {
p.getErrorHandler()(rw, req, fmt.Errorf("response write: %v", err))
return
}
if err := brw.Flush(); err != nil {
p.getErrorHandler()(rw, req, fmt.Errorf("response flush: %v", err))
return
}
errc := make(chan error, 1)
spc := switchProtocolCopier{user: conn, backend: backConn}
go spc.copyToBackend(errc)
go spc.copyFromBackend(errc)
<-errc
}
// switchProtocolCopier exists so goroutines proxying data back and
// forth have nice names in stacks.
type switchProtocolCopier struct {
user, backend io.ReadWriter
}
func (c switchProtocolCopier) copyFromBackend(errc chan<- error) {
_, err := io.Copy(c.user, c.backend)
errc <- err
}
func (c switchProtocolCopier) copyToBackend(errc chan<- error) {
_, err := io.Copy(c.backend, c.user)
errc <- err
}
func cleanQueryParams(s string) string {
reencode := func(s string) string {
v, _ := url.ParseQuery(s)
return v.Encode()
}
for i := 0; i < len(s); {
switch s[i] {
case ';':
return reencode(s)
case '%':
if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) {
return reencode(s)
}
i += 3
default:
i++
}
}
return s
}
func ishex(c byte) bool {
switch {
case '0' <= c && c <= '9':
return true
case 'a' <= c && c <= 'f':
return true
case 'A' <= c && c <= 'F':
return true
}
return false
}

@ -1,6 +1,7 @@
package httpreverse package httpreverse
import ( import (
"b612.me/apps/b612/httpreverse/rp"
"b612.me/starlog" "b612.me/starlog"
"bytes" "bytes"
"context" "context"
@ -10,7 +11,6 @@ import (
"io/ioutil" "io/ioutil"
"net" "net"
"net/http" "net/http"
"net/http/httputil"
"net/url" "net/url"
"strconv" "strconv"
"strings" "strings"
@ -27,7 +27,6 @@ func (h *ReverseConfig) Run() error {
for key, proxy := range h.proxy { for key, proxy := range h.proxy {
h.httpmux.HandleFunc(key, func(writer http.ResponseWriter, request *http.Request) { h.httpmux.HandleFunc(key, func(writer http.ResponseWriter, request *http.Request) {
starlog.Infof("<%s> Req Path:%s ListenAddr:%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) { if !h.BasicAuth(writer, request) {
h.SetResponseHeader(writer) h.SetResponseHeader(writer)
return return
@ -111,31 +110,27 @@ func (h *ReverseConfig) dialTLS(ctx context.Context, network, addr string) (net.
} }
func (h *ReverseConfig) init() error { func (h *ReverseConfig) init() error {
h.proxy = make(map[string]*httputil.ReverseProxy) h.proxy = make(map[string]*rp.ReverseProxy)
for key, val := range h.ReverseURL { for key, val := range h.ReverseURL {
h.proxy[key] = &httputil.ReverseProxy{ h.proxy[key] = &rp.ReverseProxy{
Transport: &http.Transport{DialTLSContext: h.dialTLS}, 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() h.proxy[key].ModifyResponse = h.ModifyResponse()
originalDirector := h.proxy[key].Director
h.proxy[key].Director = func(req *http.Request) { h.proxy[key].Director = func(req *http.Request) {
originalDirector(req) 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, key)
if targetQuery == "" || req.URL.RawQuery == "" {
req.URL.RawQuery = targetQuery + req.URL.RawQuery
} else {
req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
}
h.ModifyRequest(req, val) h.ModifyRequest(req, val)
} }
} }
@ -181,13 +176,54 @@ func (h *ReverseConfig) ModifyResponse() func(*http.Response) error {
} }
} }
func (h *ReverseConfig) isInCIDR(ip string) bool {
nip := net.ParseIP(strings.TrimSpace(ip))
if nip == nil {
return false
}
for _, c := range h.CIDR {
if c.Contains(nip) {
return true
}
}
return false
}
func (h *ReverseConfig) ModifyRequest(req *http.Request, remote *url.URL) { func (h *ReverseConfig) ModifyRequest(req *http.Request, remote *url.URL) {
if h.XForwardMode == 1 { switch h.IPFilterMode {
case 1:
req.Header.Set("X-Forwarded-For", strings.Split(req.RemoteAddr, ":")[0]) req.Header.Set("X-Forwarded-For", strings.Split(req.RemoteAddr, ":")[0])
} else if h.XForwardMode == 2 { case 2:
xforward := strings.Split(strings.TrimSpace(req.Header.Get("X-Forwarded-For")), ",") xforward := strings.Split(strings.TrimSpace(req.Header.Get("X-Forwarded-For")), ",")
xforward = append(xforward, strings.Split(req.RemoteAddr, ":")[0]) xforward = append(xforward, strings.Split(req.RemoteAddr, ":")[0])
req.Header.Set("X-Forwarded-For", strings.Join(xforward, ", ")) req.Header.Set("X-Forwarded-For", strings.Join(xforward, ", "))
case 3:
var lastForwardIP string
var xforward []string
if h.FilterMustKey != "" && req.Header.Get(h.FilterMustKey) != "" {
lastForwardIP = req.Header.Get(h.FilterMustKey)
xforward = []string{lastForwardIP}
} else {
for _, ip := range append(strings.Split(strings.TrimSpace(req.Header.Get("X-Forwarded-For")), ","), strings.Split(req.RemoteAddr, ":")[0]) {
ip = strings.TrimSpace(ip)
if !h.isInCIDR(ip) {
xforward = append(xforward, ip)
lastForwardIP = ip
}
}
}
if lastForwardIP == "" {
lastForwardIP = strings.Split(req.RemoteAddr, ":")[0]
}
if h.FilterXForward {
req.Header.Set("X-Forwarded-For", strings.Join(xforward, ", "))
}
if h.FilterRemoteAddr {
req.Header.Set("X-Real-IP", lastForwardIP)
}
if h.FilterSetKey != "" {
req.Header.Set(h.FilterSetKey, lastForwardIP)
}
} }
for _, v := range h.Cookie { for _, v := range h.Cookie {
req.AddCookie(&http.Cookie{ req.AddCookie(&http.Cookie{
@ -196,20 +232,6 @@ func (h *ReverseConfig) ModifyRequest(req *http.Request, remote *url.URL) {
Path: v[0], Path: v[0],
}) })
} }
host := h.Host
if host == "" {
host = remote.Host
}
targetQuery := remote.RawQuery
req.URL.Scheme = remote.Scheme
req.URL.Host = remote.Host
req.Host = host
req.URL.Path, req.URL.RawPath = joinURLPath(remote, req.URL)
if targetQuery == "" || req.URL.RawQuery == "" {
req.URL.RawQuery = targetQuery + req.URL.RawQuery
} else {
req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
}
for _, v := range h.InHeader { for _, v := range h.InHeader {
req.Header.Set(v[0], v[1]) req.Header.Set(v[0], v[1])
} }
@ -281,12 +303,17 @@ func (h *ReverseConfig) filter(w http.ResponseWriter, r *http.Request) bool {
return true return true
} }
func joinURLPath(a, b *url.URL) (path, rawpath string) { func joinURLPath(a, b *url.URL, hpath string) (path, rawpath string) {
if hpath != "/" {
b.Path = strings.TrimPrefix(b.Path, hpath)
b.RawPath = strings.TrimPrefix(b.RawPath, hpath)
}
if a.RawPath == "" && b.RawPath == "" { if a.RawPath == "" && b.RawPath == "" {
return singleJoiningSlash(a.Path, b.Path), "" return singleJoiningSlash(a.Path, b.Path), ""
} }
// Same as singleJoiningSlash, but uses EscapedPath to determine // Same as singleJoiningSlash, but uses EscapedPath to determine
// whether a slash should be added // whether a slash should be added
apath := a.EscapedPath() apath := a.EscapedPath()
bpath := b.EscapedPath() bpath := b.EscapedPath()

@ -30,6 +30,10 @@ func init() {
Cmd.Flags().StringVarP(&s.key, "ssl-key", "k", "", "TLS密钥路径") Cmd.Flags().StringVarP(&s.key, "ssl-key", "k", "", "TLS密钥路径")
Cmd.Flags().BoolVarP(&s.disableMIME, "disablemime", "m", false, "停止解析MIME全部按下载文件处理") Cmd.Flags().BoolVarP(&s.disableMIME, "disablemime", "m", false, "停止解析MIME全部按下载文件处理")
Cmd.Flags().StringSliceVarP(&s.protectAuthPage, "protect-page", "P", []string{}, "Basic Auth 开启白名单") Cmd.Flags().StringSliceVarP(&s.protectAuthPage, "protect-page", "P", []string{}, "Basic Auth 开启白名单")
Cmd.Flags().StringVar(&s.page401, "401", "", "自定义401页面地址")
Cmd.Flags().StringVar(&s.page403, "403", "", "自定义403页面地址")
Cmd.Flags().StringVar(&s.page404, "404", "", "自定义404页面地址")
Cmd.Flags().BoolVarP(&s.httpDebug, "debug", "D", false, "开启调试模式")
Cmd.Flags().Bool("daeapplied", false, "") Cmd.Flags().Bool("daeapplied", false, "")
Cmd.Flags().MarkHidden("daeapplied") Cmd.Flags().MarkHidden("daeapplied")
} }

@ -37,10 +37,14 @@ type HttpServerCfg struct {
key string key string
addr string addr string
port string port string
page404 string
page403 string
page401 string
protectAuthPage []string protectAuthPage []string
disableMIME bool disableMIME bool
ctx context.Context ctx context.Context
hooks []ServerHook hooks []ServerHook
httpDebug bool
} }
type ServerHook struct { type ServerHook struct {
@ -338,15 +342,27 @@ func (h *HttpServer) Run(ctx context.Context) error {
if strings.Contains(v.Flags.String(), "up") { if strings.Contains(v.Flags.String(), "up") {
addrs, err := v.Addrs() addrs, err := v.Addrs()
if err == nil { if err == nil {
var ips []string
for _, ip := range addrs { for _, ip := range addrs {
ips = append(ips, ip.String()) starlog.Cyan("Name:%s\tIP:%s\n", v.Name, ip)
} }
starlog.Noticef("Name:%s IP:%s MAC:%s\n", v.Name, strings.Join(ips, ","), v.HardwareAddr)
} }
} }
} }
} }
h.envPath, err = filepath.Abs(h.envPath)
if err != nil {
starlog.Errorln("Failed to get abs path of", h.envPath)
return err
}
uconn, err := net.Dial("udp", "106.55.44.79:80")
if err == nil {
schema := "http://"
if h.cert != "" {
schema = "https://"
}
starlog.Infof("Visit: %s%s:%s\n", schema, uconn.LocalAddr().(*net.UDPAddr).IP.String(), h.port)
uconn.Close()
}
starlog.Infoln("Listening on " + h.addr + ":" + h.port) starlog.Infoln("Listening on " + h.addr + ":" + h.port)
if h.cert == "" { if h.cert == "" {
if err := server.ListenAndServe(); err != http.ErrServerClosed { if err := server.ListenAndServe(); err != http.ErrServerClosed {
@ -364,9 +380,39 @@ func (h *HttpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.Listen(w, r) h.Listen(w, r)
} }
func (h *HttpServer) GiveBasicAuth(w http.ResponseWriter) { func (h *HttpServer) Page404(w http.ResponseWriter) {
w.Header().Set("WWW-Authenticate", ` Basic realm="Please Enter Passwd"`) w.WriteHeader(404)
if h.page404 != "" {
data, err := os.ReadFile(h.page404)
if err == nil {
w.Write(data)
return
}
}
w.Write([]byte(`<html><title>B612 Http Server</title><body><h1 "style="text-align: center;">404 NOT FOUND</h1><hr ></body></html>`))
}
func (h *HttpServer) Page403(w http.ResponseWriter) {
w.WriteHeader(403)
if h.page403 != "" {
data, err := os.ReadFile(h.page403)
if err == nil {
w.Write(data)
return
}
}
w.Write([]byte(`<html><title>B612 Http Server</title><body><h1 "style="text-align: center;">403 Forbidden</h1><hr ></body></html>`))
}
func (h *HttpServer) Page401(w http.ResponseWriter) {
w.WriteHeader(401) w.WriteHeader(401)
if h.page401 != "" {
data, err := os.ReadFile(h.page401)
if err == nil {
w.Write(data)
return
}
}
w.Write([]byte(` w.Write([]byte(`
<html> <html>
<head><title>401 Authorization Required</title></head> <head><title>401 Authorization Required</title></head>
@ -377,6 +423,11 @@ func (h *HttpServer) GiveBasicAuth(w http.ResponseWriter) {
</html>`)) </html>`))
} }
func (h *HttpServer) GiveBasicAuth(w http.ResponseWriter) {
w.Header().Set("WWW-Authenticate", ` Basic realm="Please Enter Passwd"`)
h.Page401(w)
}
func (h *HttpServer) BasicAuth(log *starlog.StarLogger, w http.ResponseWriter, r *http.Request) bool { func (h *HttpServer) BasicAuth(log *starlog.StarLogger, w http.ResponseWriter, r *http.Request) bool {
if h.basicAuthPwd != "" { if h.basicAuthPwd != "" {
if len(h.protectAuthPage) != 0 { if len(h.protectAuthPage) != 0 {
@ -427,26 +478,74 @@ func (h *HttpServer) SetUpload(w http.ResponseWriter, r *http.Request, path stri
} }
return false return false
} }
func (h *HttpServer) debugMode(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
w.WriteHeader(200)
html := `<html><head><meta charset="utf-8"><title>B612 Http Server</title></head><body><h1 "style="text-align: center;">Debug Mode</h1><hr >%s</body></html>`
resp := "<h2>Url</h2>"
resp += "<p>" + r.Method + " " + r.URL.Path + "</p>"
resp += "<p> query " + r.URL.RawQuery + "</p>"
resp += "<p> fragment " + r.URL.Fragment + "</p>"
resp += "<p> FullUrl " + r.URL.String() + "</p>"
resp += "<h2>Query</h2>"
for k, v := range r.URL.Query() {
resp += fmt.Sprintf("<p>%s:%s</p>", k, v)
}
resp += "<h2>Header</h2>"
for key, val := range r.Header {
for _, v := range val {
resp += fmt.Sprintf("<p>%s:%s</p>", key, v)
}
}
resp += "<h2>Cookie</h2>"
for _, c := range r.Cookies() {
resp += fmt.Sprintf("<p>%s:%s</p>", c.Name, c.Value)
}
resp += "<h2>RemoteAddr</h2>"
resp += "<p>" + r.RemoteAddr + "</p>"
resp += "<h2>Proto</h2>"
resp += "<p>" + r.Proto + "</p>"
w.Write([]byte(fmt.Sprintf(html, resp)))
}
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.SetShowOriginFile(false)
w.Header().Set("X-Powered-By", "B612.ME") w.Header().Set("X-Powered-By", "B612.ME")
w.Header().Set("Server", "B612/"+version) w.Header().Set("Server", "B612/"+version)
if !h.BasicAuth(log, w, r) { if !h.BasicAuth(log, w, r) {
return return
} }
path := r.URL.Path path := r.URL.Path
ua := r.Header.Get("User-Agent")
if h.httpDebug {
log.Infof("debug mode:%s %s From %s %s\n", r.Method, path, r.RemoteAddr, ua)
h.debugMode(w, r)
return
}
if h.uploadFolder != "" && path == "/recv" && len(r.URL.Query()["upload"]) != 0 { if h.uploadFolder != "" && path == "/recv" && len(r.URL.Query()["upload"]) != 0 {
h.uploadFile(w, r) h.uploadFile(w, r)
return return
} }
fullpath := filepath.Join(h.envPath, path) fullpath := filepath.Clean(filepath.Join(h.envPath, path))
{
//security check
if fullpath != h.envPath && !strings.HasPrefix(fullpath, h.envPath) {
log.Warningf("Invalid Path %s IP:%s Fullpath:%s\n", path, r.RemoteAddr, fullpath)
h.Page403(w)
return
}
}
if h.indexFile != "" && staros.IsFolder(fullpath) { if h.indexFile != "" && staros.IsFolder(fullpath) {
if staros.Exists(filepath.Join(fullpath, h.indexFile)) { if staros.Exists(filepath.Join(fullpath, h.indexFile)) {
fullpath = filepath.Join(fullpath, h.indexFile) fullpath = filepath.Join(fullpath, h.indexFile)
path = filepath.Join(path, h.indexFile) path = filepath.Join(path, h.indexFile)
} }
} }
log.Noticef("Start Method:%s Path:%s IP:%s\n", r.Method, path, r.RemoteAddr) now := time.Now()
if h.SetUpload(w, r, path) { if h.SetUpload(w, r, path) {
return return
} }
@ -454,9 +553,9 @@ func (h *HttpServer) Listen(w http.ResponseWriter, r *http.Request) {
case "OPTIONS", "HEAD": case "OPTIONS", "HEAD":
err := h.BuildHeader(w, r, fullpath) err := h.BuildHeader(w, r, fullpath)
if err != nil { if err != nil {
log.Warningf("Finished Method:%s Path:%s IP:%s Err:%v\n", r.Method, path, r.RemoteAddr, err) log.Warningf("%s %s From %s %s %.2fs %v\n", r.Method, path, r.RemoteAddr, ua, time.Since(now).Seconds(), err)
} else { } else {
log.Infof("Finished Method:%s Path:%s IP:%s\n", r.Method, path, r.RemoteAddr) log.Infof("%s %s From %s %s %.2fs \n", r.Method, path, r.RemoteAddr, ua, time.Since(now).Seconds())
} }
case "GET": case "GET":
err := h.BuildHeader(w, r, fullpath) err := h.BuildHeader(w, r, fullpath)
@ -465,12 +564,12 @@ func (h *HttpServer) Listen(w http.ResponseWriter, r *http.Request) {
} }
err = h.ResponseGet(log, w, r, fullpath) err = h.ResponseGet(log, w, r, fullpath)
if err != nil { if err != nil {
log.Warningf("Finished Method %s Path:%s IP:%s Err:%v\n", r.Method, path, r.RemoteAddr, err) log.Warningf("%s %s From %s %s %.2fs %v\n", r.Method, path, r.RemoteAddr, ua, time.Since(now).Seconds(), err)
return return
} }
log.Infof("Finished Method:%s Path:%s IP:%s\n", r.Method, path, r.RemoteAddr) log.Infof("%s %s From %s %s %.2fs\n", r.Method, path, r.RemoteAddr, ua, time.Since(now).Seconds())
default: default:
log.Warningf("Invalid Method %s Path:%s IP:%s\n", r.Method, path, r.RemoteAddr) log.Errorf("Invalid %s %s From %s %s %.2fs\n", r.Method, path, r.RemoteAddr, ua, time.Since(now).Seconds())
return return
} }
} }
@ -619,8 +718,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 { func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r *http.Request, fullpath string) error {
if !staros.Exists(fullpath) { if !staros.Exists(fullpath) {
w.WriteHeader(404) h.Page404(w)
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") return errors.New("File Not Found! 404 ERROR")
} }
//starlog.Debugln(r.Header) //starlog.Debugln(r.Header)
@ -711,7 +809,7 @@ func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r *
} }
return nil return nil
} }
log.Debugf("206 transfer mode for %v %v\n", r.URL.Path, r.RemoteAddr) log.Debugf("206 transfer mode for %v %v start %v end %v\n", r.URL.Path, r.RemoteAddr, startRange, endRange)
w.WriteHeader(206) w.WriteHeader(206)
fp.Seek(int64(startRange), 0) fp.Seek(int64(startRange), 0)
count := startRange count := startRange

@ -41,7 +41,7 @@ import (
var cmdRoot = &cobra.Command{ var cmdRoot = &cobra.Command{
Use: "b612", Use: "b612",
Version: "2.1.0.beta.4", Version: "2.1.0.beta.8",
} }
func init() { func init() {

@ -52,7 +52,7 @@ func init() {
CmdNetTrace.Flags().BoolVarP(&disableIpInfo, "disable-ipinfo", "D", false, "禁用ip信息查询") CmdNetTrace.Flags().BoolVarP(&disableIpInfo, "disable-ipinfo", "D", false, "禁用ip信息查询")
CmdNetTrace.Flags().StringVarP(&bindAddr, "bind", "b", "0.0.0.0", "绑定地址") CmdNetTrace.Flags().StringVarP(&bindAddr, "bind", "b", "0.0.0.0", "绑定地址")
CmdNetTrace.Flags().BoolVarP(&hideIncorrect, "hide-incorrect", "H", false, "隐藏错误节点") CmdNetTrace.Flags().BoolVarP(&hideIncorrect, "hide-incorrect", "H", false, "隐藏错误节点")
Cmd.AddCommand(CmdNetTrace) Cmd.AddCommand(CmdNetTrace, cmdSSHJar)
} }

@ -25,5 +25,5 @@ func TestNat(t *testing.T) {
} }
func TestTrace(t *testing.T) { func TestTrace(t *testing.T) {
Traceroute("b612.me", "", 32, time.Millisecond*800, "https://ip.b612.me/{ip}/detail") //Traceroute("b612.me", "", 32, time.Millisecond*800, "https://ip.b612.me/{ip}/detail")
} }

@ -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()
}
}

@ -23,6 +23,12 @@ func init() {
CmdNetforward.Flags().BoolVarP(&f.EnableUDP, "enable-udp-forward", "u", true, "enable udp 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(&dialTimeout, "dial-timeout", "d", 10000, "dial timeout milliseconds")
CmdNetforward.Flags().Int64VarP(&udpTimeout, "udp-timeout", "D", 60000, "udp connection 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{ var CmdNetforward = &cobra.Command{

@ -6,6 +6,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"io"
"net" "net"
"strconv" "strconv"
"strings" "strings"
@ -23,11 +24,18 @@ type NetForward struct {
DelayMilSec int DelayMilSec int
DelayToward int DelayToward int
StdinMode bool StdinMode bool
IgnoreEof bool
DialTimeout time.Duration DialTimeout time.Duration
UDPTimeout time.Duration UDPTimeout time.Duration
stopCtx context.Context stopCtx context.Context
stopFn context.CancelFunc stopFn context.CancelFunc
running int32 running int32
KeepAlivePeriod int
KeepAliveIdel int
KeepAliveCount int
UserTimeout int
UsingKeepAlive bool
} }
func (n *NetForward) Close() { func (n *NetForward) Close() {
@ -145,6 +153,12 @@ func (n *NetForward) runTCP() error {
log.Infof("Delay %d ms\n", n.DelayMilSec) log.Infof("Delay %d ms\n", n.DelayMilSec)
time.Sleep(time.Millisecond * time.Duration(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) { go func(conn net.Conn) {
rmt, err := net.DialTimeout("tcp", n.RemoteURI, n.DialTimeout) rmt, err := net.DialTimeout("tcp", n.RemoteURI, n.DialTimeout)
if err != nil { if err != nil {
@ -152,9 +166,17 @@ func (n *NetForward) runTCP() error {
conn.Close() conn.Close()
return 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()) log.Infof("TCP Connect %s <==> %s\n", conn.RemoteAddr().String(), rmt.RemoteAddr().String())
n.copy(rmt, conn) n.copy(rmt, conn)
log.Noticef("TCP Connection Closed %s <==> %s\n", conn.RemoteAddr().String(), n.RemoteURI) log.Noticef("TCP Connection Closed %s <==> %s\n", conn.RemoteAddr().String(), n.RemoteURI)
conn.Close()
rmt.Close()
}(conn) }(conn)
} }
} }
@ -289,6 +311,9 @@ func (n *NetForward) copy(dst, src net.Conn) {
for { for {
count, err := src.Read(bufsize) count, err := src.Read(bufsize)
if err != nil { if err != nil {
if n.IgnoreEof && err == io.EOF {
continue
}
dst.Close() dst.Close()
src.Close() src.Close()
return return
@ -310,6 +335,9 @@ func (n *NetForward) copy(dst, src net.Conn) {
for { for {
count, err := dst.Read(bufsize) count, err := dst.Read(bufsize)
if err != nil { if err != nil {
if n.IgnoreEof && err == io.EOF {
continue
}
src.Close() src.Close()
dst.Close() dst.Close()
return 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)
}

@ -28,6 +28,8 @@ var useHTML bool
var user, pwd, server string var user, pwd, server string
var skipInsecure, usingFile bool var skipInsecure, usingFile bool
var useTLS int var useTLS int
var hostname string
var autoHostname bool
func init() { func init() {
Cmd.Flags().BoolVarP(&usingFile, "file", "F", false, "using file") Cmd.Flags().BoolVarP(&usingFile, "file", "F", false, "using file")
@ -45,6 +47,8 @@ func init() {
Cmd.Flags().StringVarP(&server, "server", "S", "127.0.0.1:25", "server") 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().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().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() { func run() {
@ -112,16 +116,28 @@ func run() {
} }
} }
} }
if autoHostname && hostname == "" {
hostname = strings.Split(server, ":")[0]
}
var auth smtp.Auth var auth smtp.Auth
if user != "" && pwd != "" { if user != "" && pwd != "" {
auth = smtp.PlainAuth("", user, pwd, server) auth = smtp.PlainAuth("", user, pwd, hostname)
} }
switch useTLS { switch useTLS {
case 1: case 1:
err = mail.SendWithTLS(server, auth, &tls.Config{InsecureSkipVerify: skipInsecure}) 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: case 2:
err = mail.SendWithStartTLS(server, auth, &tls.Config{InsecureSkipVerify: skipInsecure}) 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: default:
starlog.Noticef("Mail send method:Normal\n")
err = mail.Send(server, auth) err = mail.Send(server, auth)
} }
if err != nil { if err != nil {

@ -9,18 +9,33 @@ import (
"encoding/pem" "encoding/pem"
"fmt" "fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"golang.org/x/net/idna"
"golang.org/x/net/proxy"
"net"
"os" "os"
"path/filepath" "path/filepath"
"strconv"
"strings" "strings"
"sync"
"time" "time"
) )
var hideDetail bool var hideDetail bool
var dump string var dump string
var reqRawIP int
var timeoutMillSec int
var socks5 string
var socks5Auth string
var showCA 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(&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{ var Cmd = &cobra.Command{
@ -29,25 +44,155 @@ var Cmd = &cobra.Command{
Long: "查看TLS证书信息", Long: "查看TLS证书信息",
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, dump) showTls(target, !hideDetail, showCA, reqRawIP, dump, time.Duration(timeoutMillSec)*time.Millisecond)
} }
}, },
} }
func showTls(target string, showDetail bool, dumpPath string) { func showTls(target string, showDetail, showCA bool, reqRawIP int, dumpPath string, timeout time.Duration) {
if !strings.Contains(target, ":") { var err error
target += ":443" {
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) starlog.Infof("正在连接服务器: %s\n", target)
conn, err := tls.Dial("tcp", target, &tls.Config{ var netDialer = &net.Dialer{
InsecureSkipVerify: true, Timeout: timeout,
}) }
if err != nil { var socksDialer *proxy.Dialer
starlog.Errorln("failed to connect: " + err.Error()) if socks5 != "" {
return 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() defer conn.Close()
starlog.Infoln("连接成功,正在获取证书信息") starlog.Infof("连接成功对方IP:%s正在获取证书信息\n", conn.RemoteAddr().String())
certs := conn.ConnectionState().PeerCertificates certs := conn.ConnectionState().PeerCertificates
if len(certs) == 0 { if len(certs) == 0 {
starlog.Errorln("no certificate found") starlog.Errorln("no certificate found")
@ -58,11 +203,11 @@ func showTls(target string, showDetail bool, dumpPath string) {
switch state.Version { switch state.Version {
case tls.VersionSSL30: case tls.VersionSSL30:
starlog.Infoln("当前TLS版本: SSL 3.0") starlog.Warningln("当前TLS版本: SSL 3.0")
case tls.VersionTLS10: case tls.VersionTLS10:
starlog.Infoln("当前TLS版本: TLS 1.0") starlog.Warningln("当前TLS版本: TLS 1.0")
case tls.VersionTLS11: case tls.VersionTLS11:
starlog.Infoln("当前TLS版本: TLS 1.1") starlog.Warningln("当前TLS版本: TLS 1.1")
case tls.VersionTLS12: case tls.VersionTLS12:
starlog.Infoln("当前TLS版本: TLS 1.2") starlog.Infoln("当前TLS版本: TLS 1.2")
case tls.VersionTLS13: case tls.VersionTLS13:
@ -123,13 +268,22 @@ func showTls(target string, showDetail bool, dumpPath string) {
starlog.Infoln("当前加密套件:", state.CipherSuite) starlog.Infoln("当前加密套件:", state.CipherSuite)
} }
starlog.Infoln("服务器名称:", state.ServerName) starlog.Infoln("服务器名称:", state.ServerName)
wg.Wait()
if verifyErr != nil {
starlog.Red("证书验证失败: " + verifyErr.Error())
} else {
starlog.Green("证书验证成功")
}
if showDetail { if showDetail {
for _, c := range certs { for _, c := range certs {
if c.IsCA { if c.IsCA && !showCA {
continue continue
} }
fmt.Printf("----------\n证书基础信息: %+v\n", c.Subject) fmt.Printf("----------\n")
if c.IsCA {
fmt.Println("这是一个CA证书")
}
fmt.Printf("证书基础信息: %+v\n", c.Subject)
fmt.Printf("证书颁发者: %+v\n", c.Issuer) 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.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("证书过期时间: %+v 剩余:%.1f天\n", c.NotAfter.In(time.Local), c.NotAfter.Sub(time.Now()).Hours()/24)

@ -1,19 +1,28 @@
package whois package whois
import ( import (
"b612.me/starlog"
"b612.me/staros" "b612.me/staros"
"github.com/likexian/whois" "github.com/likexian/whois"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"golang.org/x/net/proxy"
"os" "os"
"strings"
"time" "time"
) )
var timeout int var timeout int
var output string var output string
var whoisServer []string
var socks5 string
var socks5Auth string
func init() { func init() {
Cmd.Flags().IntVarP(&timeout, "timeout", "t", 20, "超时时间") Cmd.Flags().IntVarP(&timeout, "timeout", "t", 20, "超时时间")
Cmd.Flags().StringVarP(&output, "output", "o", "", "输出文件夹") 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{ var Cmd = &cobra.Command{
@ -30,9 +39,31 @@ var Cmd = &cobra.Command{
output = "" output = ""
} }
c := whois.NewClient() 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)) c.SetTimeout(time.Second * time.Duration(timeout))
for _, v := range args { for _, v := range args {
data, err := c.Whois(v) data, err := c.Whois(v, whoisServer...)
cmd.Println("Query:", v) cmd.Println("Query:", v)
if err != nil { if err != nil {
cmd.Println("查询失败:", err) cmd.Println("查询失败:", err)

Loading…
Cancel
Save