diff --git a/cmd/HellPot/main.go b/cmd/HellPot/main.go index 82b96b3..9c8e808 100644 --- a/cmd/HellPot/main.go +++ b/cmd/HellPot/main.go @@ -1,6 +1,10 @@ package main import ( + "os" + "os/signal" + "syscall" + "github.com/rs/zerolog" "github.com/yunginnanet/HellPot/config" @@ -17,9 +21,19 @@ func init() { if config.Debug { log.Debug().Msg("debug enabled") } - log.Info().Str("config", config.Filename).Msg("initialization finished") + log.Info().Str("file", config.Filename).Msg("current config") + log.Info().Str("file", config.CurrentLogFile).Msg("current log") } func main() { - log.Error().Err(http.Serve()).Msg("HTTP err") + stopChan := make(chan os.Signal, 1) + signal.Notify(stopChan, syscall.SIGINT, syscall.SIGTERM) + + go func() { + log.Error().Err(http.Serve()).Msg("HTTP err") + }() + + <-stopChan // wait for SIGINT + log.Warn().Msg("Shutting down server...") + } diff --git a/config/config.go b/config/config.go index 4b2aff9..fdc6a6f 100644 --- a/config/config.go +++ b/config/config.go @@ -16,6 +16,7 @@ const ( Title = "HellPot" ) +// "http" var ( // BindAddr is defined via our toml configuration file. It is the address that HellPot listens on. BindAddr string @@ -24,6 +25,21 @@ var ( // Paths are defined via our toml configuration file. These are the paths that HellPot will present for "robots.txt" // These are also the paths that HellPot will respond for. Other paths will throw a warning and will serve a 404. Paths []string + + UseUnixSocket bool + UnixSocketPath = "" +) + +// "performance" +var ( + RestrictConcurrency bool + MaxWorkers int +) + +// "diception" +var ( + // FakeServerName is our configured value for the "Server: " response header when serving HTTP clients + FakeServerName string ) var ( @@ -124,7 +140,7 @@ func Init() { func setDefaults() { var ( - configSections = []string{"logger", "http"} + configSections = []string{"logger", "http", "performance", "diception"} deflogdir = home + "/.config/" + Title + "/logs/" defNoColor = false ) @@ -141,13 +157,22 @@ func setDefaults() { "use_date_filename": true, } Opt["http"] = map[string]interface{}{ - "bind_addr": "127.0.0.1", - "bind_port": "8080", + "use_unix_socket": false, + "unix_socket": "/var/run/hellpot", + "bind_addr": "127.0.0.1", + "bind_port": "8080", "paths": []string{ "wp-login.php", "wp-login", }, } + Opt["performance"] = map[string]interface{}{ + "restrict_concurrency": false, + "max_workers": 256, + } + Opt["diception"] = map[string]interface{}{ + "server_name": "nginx", + } for _, def := range configSections { snek.SetDefault(def, Opt[def]) @@ -221,6 +246,14 @@ func associate() { BindAddr = snek.GetString("http.bind_addr") BindPort = snek.GetString("http.bind_port") Paths = snek.GetStringSlice("http.paths") + UseUnixSocket = snek.GetBool("http.use_unix_socket") + FakeServerName = snek.GetString("diception.server_name") + RestrictConcurrency = snek.GetBool("performance.restrict_concurrency") + MaxWorkers = snek.GetInt("performance.max_workers") + + if UseUnixSocket { + UnixSocketPath = snek.GetString("http.unix_socket_path") + } if Debug { zerolog.SetGlobalLevel(zerolog.DebugLevel) diff --git a/heffalump/heffalump.go b/heffalump/heffalump.go index 35890e1..6e96162 100644 --- a/heffalump/heffalump.go +++ b/heffalump/heffalump.go @@ -49,6 +49,12 @@ func (h *Heffalump) putBuffer(buf []byte) { // WriteHell writes markov chain heffalump hell to the provided io.Writer // https://github.com/carlmjohnson/heffalump func (h *Heffalump) WriteHell(w io.Writer) int64 { + defer func() { + if r := recover(); r != nil { + log.Error().Interface("caller", r).Msg("panic recovered!") + } + }() + buf := h.getBuffer() defer h.putBuffer(buf) diff --git a/http/hellpot.go b/http/hellpot.go deleted file mode 100644 index dca9c28..0000000 --- a/http/hellpot.go +++ /dev/null @@ -1,81 +0,0 @@ -package http - -import ( - "net" - "net/http" - "os" - "os/signal" - "strings" - "syscall" - - "github.com/yunginnanet/HellPot/config" -) - -var log = config.GetLogger() - -func listenOnUnixSocket(addr string, srv *http.Server) error { - var err error - var unixAddr *net.UnixAddr - var unixListener *net.UnixListener - socketPath := strings.TrimPrefix(addr, "unix:") - unixAddr, err = net.ResolveUnixAddr("unix", socketPath) - if err == nil { - // Always unlink sockets before listening on them - _ = syscall.Unlink(socketPath) - unixListener, err = net.ListenUnix("unix", unixAddr) - if err == nil { - err = srv.Serve(unixListener) - } - } - return err -} - -func listen() { - addr := config.BindAddr - port := config.BindPort - //listenOnUnixDomain := true - - log.Info().Str("bind_addr", addr).Str("bind_port", port). - Msg("Listening and serving HTTP...") - -/* if !strings.HasPrefix(addr, "unix:") { - listenOnUnixDomain = false - } - - if listenOnUnixDomain { - err = listenOnUnixSocket(addr, srv) - } else { - err = srv.ListenAndServe() - }*/ - - // log.Warn().Err(err).Msg("HTTP_STOP") -} - -func StartPot() { - - // subscribe to SIGINT signals - stopChan := make(chan os.Signal, 1) - signal.Notify(stopChan, syscall.SIGINT, syscall.SIGTERM) - - /*r := mux.NewRouter() - - r.HandleFunc("/{path}", heffalump.DefaultHoneypot) - - srv := &http.Server{Handler: r} - -*/ - go listen() - - <-stopChan // wait for SIGINT - log.Warn().Msg("Shutting down server...") - - /* - // shut down gracefully, but wait no longer than 5 seconds before halting - ctx, c := context.WithTimeout(context.Background(), 5*time.Second) - defer c() - if err := srv.Shutdown(ctx); err != nil { - log.Debug().Caller().Err(err) - } - log.Info().Msg("Server gracefully stopped") - */ -} diff --git a/http/robots.go b/http/robots.go index 1bd204a..23cd636 100644 --- a/http/robots.go +++ b/http/robots.go @@ -23,5 +23,4 @@ func robotsTXT(ctx *fasthttp.RequestCtx) { if _, err := fmt.Fprintf(ctx, robotsTxt+paths+"\r\n"); err != nil { log.Error().Err(err).Msg("SERVE_ROBOTS_ERROR") } - return } diff --git a/http/router.go b/http/router.go index 75a9ef9..ba9b9b5 100644 --- a/http/router.go +++ b/http/router.go @@ -1,15 +1,21 @@ package http import ( + "fmt" + "net" + "syscall" "time" "github.com/fasthttp/router" + "github.com/rs/zerolog" "github.com/valyala/fasthttp" "github.com/yunginnanet/HellPot/config" "github.com/yunginnanet/HellPot/heffalump" ) +var log zerolog.Logger + func getRealRemote(ctx *fasthttp.RequestCtx) string { xrealip := string(ctx.Request.Header.Peek("X-Real-IP")) if len(xrealip) > 0 { @@ -53,10 +59,83 @@ func scopeCheck(ctx *fasthttp.RequestCtx) { } +func listenOnUnixSocket(addr string, r *router.Router) error { + var err error + var unixAddr *net.UnixAddr + var unixListener *net.UnixListener + unixAddr, err = net.ResolveUnixAddr("unix", addr) + if err == nil { + // Always unlink sockets before listening on them + if err2 := syscall.Unlink(addr); err2 != nil { + panic(err2) + } + unixListener, err = net.ListenUnix("unix", unixAddr) + if err == nil { + err = fasthttp.Serve(unixListener, r.Handler) + } + } + return err +} + // Serve starts our HTTP server and request router func Serve() error { + log = config.GetLogger() + + l := fmt.Sprintf("%s:%s", config.BindAddr, config.BindPort) + r := router.New() r.GET("/robots.txt", robotsTXT) r.GET("/{path}", scopeCheck) - return fasthttp.ListenAndServe(":8080", r.Handler) + + if !config.RestrictConcurrency { + config.MaxWorkers = fasthttp.DefaultConcurrency + } + + srv := fasthttp.Server{ + // User defined server name + // Likely not useful if behind a reverse proxy without additional configuration of the proxy server. + Name: config.FakeServerName, + + /* + from fasthttp docs: "By default request read timeout is unlimited." + My thinking here is avoiding some sort of weird oversized GET query just in case. + */ + ReadTimeout: 5 * time.Second, + MaxRequestBodySize: 1 * 1024 * 1024, + + // Help curb abuse of HellPot (we've always needed this badly) + MaxConnsPerIP: 1, + MaxRequestsPerConn: 2, + Concurrency: config.MaxWorkers, + + // only accept GET requests + GetOnly: true, + + // we don't care if a request ends up being handled by a different handler + // it shouldn't for now, but this may prevent future bugs + KeepHijackedConns: true, + + CloseOnShutdown: true, + + + + // No need to keepalive, our response is a sort of keep-alive ;) + DisableKeepalive: true, + + + + Handler: r.Handler, + } + + if !config.UseUnixSocket { + log.Info().Str("caller", l).Msg("Listening and serving HTTP...") + return srv.ListenAndServe(l) + } + + if len(config.UnixSocketPath) < 1 { + log.Fatal().Msg("unix_socket_path configuration directive appears to be empty") + } + + log.Info().Str("caller", config.UnixSocketPath).Msg("Listening and serving HTTP...") + return listenOnUnixSocket(config.UnixSocketPath, r) }