diff --git a/Makefile b/Makefile index c2663e0..9e6ccef 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,13 @@ all: deps check build -format: +format : find . -iname "*.go" -exec gofmt -s -l -w {} \; -check: +check : go vet ./... -run: +run : go run cmd/HellPot/*.go deps: go mod tidy -v -build: - go build -trimpath -ldflags "-s -w -X main.version=`git tag --sort=-version:refname | head -n 1`" cmd/HellPot/*.go +test : + go test -v ./... +build : + go build -x -trimpath -ldflags "-s -w -X main.version=`git tag --sort=-version:refname | head -n 1`" cmd/HellPot/*.go diff --git a/README.md b/README.md index 443dfba..a70da84 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Clients (hopefully bots) that disregard `robots.txt` and connect to your instanc HellPot will send an infinite stream of data that is _just close enough_ to being a real website that they might just stick around until their soul is ripped apart and they cease to exist. -Under the hood of this eternal suffering is a markov engine that chucks bits and pieces of [The Birth of Tragedy (Hellenism and Pessimism)](https://www.gutenberg.org/files/51356/51356-h/51356-h.htm) by Friedrich Nietzsche at the client using [fasthttp](https://github.com/valyala/fasthttp). +Under the hood of this eternal suffering is a markov engine that chucks bits and pieces of [The Birth of Tragedy (Hellenism and Pessimism)](https://www.gutenberg.org/files/51356/51356-h/51356-h.htm) by Friedrich Nietzsche at the client~~~~ using [fasthttp](https://github.com/valyala/fasthttp), or optionally you may synchronize HellPot with your nightmares by using the `-g`/`--grimoire` flag ## Building From Source diff --git a/cmd/HellPot/HellPot.go b/cmd/HellPot/HellPot.go index b2b3055..1f23794 100644 --- a/cmd/HellPot/HellPot.go +++ b/cmd/HellPot/HellPot.go @@ -2,58 +2,26 @@ package main import ( "os" - "os/signal" - "syscall" - "github.com/rs/zerolog" - - "github.com/yunginnanet/HellPot/internal/config" - "github.com/yunginnanet/HellPot/internal/extra" "github.com/yunginnanet/HellPot/internal/http" ) -var ( - log zerolog.Logger - version string // set by linker -) - -func init() { - if version != "" { - config.Version = version[1:] - } - config.Init() - if config.BannerOnly { - extra.Banner() - os.Exit(0) - } +func main() { + stopChan := make(chan os.Signal, 1) + log, resolvedConf, realConf, err := setup(stopChan) - switch config.DockerLogging { - case true: - config.CurrentLogFile = "/dev/stdout" - config.NoColor = true - log = config.StartLogger(false, os.Stdout) - default: - log = config.StartLogger(true) + if err != nil { + println("failed to start: " + err.Error()) + os.Exit(1) } - extra.Banner() - - log.Info().Str("caller", "config").Str("file", config.Filename).Msg(config.Filename) - log.Info().Str("caller", "logger").Msg(config.CurrentLogFile) - log.Debug().Str("caller", "logger").Msg("debug enabled") - log.Trace().Str("caller", "logger").Msg("trace enabled") - -} - -func main() { - stopChan := make(chan os.Signal, 1) - signal.Notify(stopChan, syscall.SIGINT, syscall.SIGTERM) + printInfo(log, resolvedConf, realConf) go func() { - log.Fatal().Err(http.Serve()).Msg("HTTP error") + log.Fatal().Err(http.Serve(realConf)).Msg("HTTP error") }() - <-stopChan // wait for SIGINT - log.Warn().Msg("Shutting down server...") - + sig := <-stopChan // wait for SIGINT + log.Warn().Interface("signal_received", sig). + Msg("Shutting down server...") } diff --git a/cmd/HellPot/HellPot_test.go b/cmd/HellPot/HellPot_test.go new file mode 100644 index 0000000..86eb303 --- /dev/null +++ b/cmd/HellPot/HellPot_test.go @@ -0,0 +1,102 @@ +package main + +import ( + "os" + "path/filepath" + "strconv" + "strings" + "testing" + "time" + + "github.com/yunginnanet/HellPot/internal/config" + "github.com/yunginnanet/HellPot/internal/http" + "github.com/yunginnanet/HellPot/internal/logger" +) + +func testMain(t *testing.T) (string, chan os.Signal, *logger.Log, *config.Parameters, error) { + t.Setenv("HELLPOT_TEST_MODE", "true") + t.Helper() + stopChan := make(chan os.Signal, 1) + + log, resolvedConf, realConfig, err := setup(stopChan) + if err == nil { + printInfo(log, resolvedConf, realConfig) + go func() { + terr := http.Serve(realConfig) + if terr != nil { + t.Error("failed to serve HTTP: " + terr.Error()) + return + } + }() + } + //goland:noinspection GoNilness + return resolvedConf, stopChan, log, realConfig, err +} + +func TestHellPot(t *testing.T) { + tDir := filepath.Join(t.TempDir(), strconv.Itoa(int(time.Now().Unix()))) + logDir := filepath.Join(tDir, "logs") + if err := os.MkdirAll(logDir, 0755); err != nil { + t.Fatal(err) + } + confFile := filepath.Join(tDir, "HellPot_test.toml") + t.Setenv("HELLPOT_LOGGER_DIRECTORY", logDir) + t.Setenv("HELLPOT_LOGGER_RSYSLOG__ADDRESS", "local") + t.Setenv("HELLPOT_LOGGER_DEBUG", "true") + t.Setenv("HELLPOT_CONFIG", confFile) + + resolvedConf, stopChan, log, realConfig, err := testMain(t) + + if err != nil { + t.Fatal(err) + } + if log == nil { + t.Fatal("log is nil") + } + if realConfig == nil { + t.Fatal("realConfig is nil") + } + + logFile := log.Config.ActiveLogFileName + + if stopChan == nil { + t.Fatal("stopChan is nil") + } + if resolvedConf == "" { + t.Fatal("resolvedConf is empty") + } + if logFile == "" { + t.Fatal("logFile is empty") + } + if _, err = os.Stat(logFile); err != nil { + t.Fatal(err) + } + if resolvedConf != confFile { + t.Errorf("expected %s, got %s", confFile, resolvedConf) + } + if logFile != filepath.Join(logDir, "HellPot.log") { + t.Errorf("expected %s, got %s", filepath.Join(logDir, "HellPot.log"), logFile) + } + time.Sleep(25 * time.Millisecond) // sync maybe + logDat, err := os.ReadFile(logFile) + if err != nil { + t.Error(err) + } + if !strings.Contains(string(logDat), "🔥 Starting HellPot 🔥") { + t.Errorf("expected log to contain '🔥 Starting HellPot 🔥', got %s", logDat) + } + if !strings.Contains(string(logDat), logFile) { + t.Errorf("expected log to contain '%s'", logFile) + } + if !strings.Contains(string(logDat), resolvedConf) { + t.Errorf("expected log to contain '%s'", resolvedConf) + } + if !strings.Contains(string(logDat), strconv.Itoa(os.Getpid())+",") { + t.Errorf("expected log to contain 'PID: %d', got %s", os.Getpid(), logDat) + } + t.Log("resolvedConf: ", resolvedConf) + t.Log("logFile: ", logFile) + t.Log("realConfig: ", realConfig) + time.Sleep(100 * time.Millisecond) + stopChan <- os.Interrupt +} diff --git a/cmd/HellPot/boot.go b/cmd/HellPot/boot.go new file mode 100644 index 0000000..968e0f8 --- /dev/null +++ b/cmd/HellPot/boot.go @@ -0,0 +1,254 @@ +package main + +import ( + "errors" + "fmt" + "io" + "os" + "os/signal" + "path/filepath" + "strconv" + "syscall" + "time" + + "github.com/yunginnanet/HellPot/internal/config" + "github.com/yunginnanet/HellPot/internal/extra" + "github.com/yunginnanet/HellPot/internal/logger" +) + +const ( + defaultConfigWarningDelaySecs = 10 + red = "\033[31m" + reset = "\033[0m" +) + +func writeConfig(target string) (*config.Parameters, bool) { + var f *os.File + var err error + f, err = os.Create(target) // #nosec G304 -- go home gosec, you're drunk + if err != nil { + println("failed to create config file: " + err.Error()) + return nil, false + } + if _, err = io.Copy(f, config.Defaults.IO); err != nil { + println("failed to write default config to file: " + err.Error()) + _ = f.Close() + return nil, false + } + if err = f.Sync(); err != nil { + panic(err) + } + println("wrote default config to " + target) + var newConf *config.Parameters + if newConf, err = config.Setup(f); err != nil { + println("failed to setup config with newly written file: " + err.Error()) + _ = f.Close() + return nil, false + } + _ = f.Close() + newConf.UsingDefaults = true + return newConf, true +} + +func searchConfig() string { + var resolvedConf string + uconf, _ := os.UserConfigDir() + if uconf == "" && os.Getenv("HOME") != "" { + uconf = filepath.Join(os.Getenv("HOME"), ".config") + } + + for _, path := range []string{ + "/etc/HellPot/config.toml", + "/usr/local/etc/HellPot/config.toml", + "./config.toml", + filepath.Join(uconf, "HellPot", "config.toml"), + } { + if _, err := os.Stat(path); err == nil { + resolvedConf = path + break + } + } + return resolvedConf +} + +func readConfig(resolvedConf string) (*config.Parameters, error) { + var err error + var setupErr error + var f *os.File + + if resolvedConf == "" { + return nil, fmt.Errorf("%w: provided config file is an empty string", io.EOF) + } + + var runningConfig *config.Parameters + + f, err = os.Open(resolvedConf) // #nosec G304 go home gosec, you're drunk + defer func() { + if f != nil { + _ = f.Close() + } + }() + if err == nil { + runningConfig, setupErr = config.Setup(f) + } + switch { + case setupErr != nil: + println("failed to setup config: " + setupErr.Error()) + err = setupErr + case err != nil: + println("failed to open config file for reading: " + err.Error()) + println("trying to create it....") + newRunningConfig, wroteOK := writeConfig(resolvedConf) + if wroteOK { + return newRunningConfig, nil + } + println("failed to create config file, cannot continue") + return nil, fmt.Errorf("failed to create config file: %w", err) + case runningConfig == nil: + err = errors.New("unknown failure resulting in missing configuration, cannot continue") + return nil, err + } + + return runningConfig, err +} + +type configDetail struct { + runningConfig *config.Parameters + usingDefaults bool + resolvedConf string +} + +func loadCLIConfig() (detail configDetail, err error) { + conf := config.CLIFlags.Lookup("config") + detail = configDetail{} + detail.resolvedConf = conf.Value.String() + detail.runningConfig, err = readConfig(detail.resolvedConf) + detail.usingDefaults = false + return +} + +func loadEnvConfig() (detail configDetail, err error) { + detail = configDetail{} + detail.resolvedConf = os.Getenv("HELLPOT_CONFIG_FILE") + detail.runningConfig, err = readConfig(detail.resolvedConf) + detail.usingDefaults = false + return +} + +func resolveConfig() (runningConfig *config.Parameters, usingDefaults bool, resolvedConf string, err error) { + var cliConfigPath string + if config.CLIFlags != nil && config.CLIFlags.Lookup("config") != nil { + cliConfigPath = config.CLIFlags.Lookup("config").Value.String() + } + + var detail configDetail + +try: + switch { + case cliConfigPath != "": + detail, err = loadCLIConfig() + if err == nil { + runningConfig = detail.runningConfig + usingDefaults = detail.usingDefaults + resolvedConf = detail.resolvedConf + return + } + println("failed to load config from CLI path: " + err.Error()) + cliConfigPath = "" + goto try + case os.Getenv("HELLPOT_CONFIG_FILE") != "": + detail, err = loadEnvConfig() + if err == nil { + runningConfig = detail.runningConfig + usingDefaults = detail.usingDefaults + resolvedConf = detail.resolvedConf + return + } + println("failed to load config from env path: " + err.Error()) + fallthrough + default: + if resolvedConf = searchConfig(); resolvedConf != "" { + usingDefaults = false + } + } + + if runningConfig, err = readConfig(resolvedConf); err != nil && !errors.Is(err, io.EOF) { + return runningConfig, false, "", err + } + + if runningConfig == nil { + if runningConfig, err = config.Setup(nil); err != nil || runningConfig == nil { + if err == nil { + err = errors.New("unknown failure resulting in missing configuration, cannot continue") + } + return runningConfig, false, "", err + } + return runningConfig, true, "", nil + } + + return runningConfig, false, resolvedConf, nil +} + +func initLogger(runningConfig *config.Parameters) (log *logger.Log, err error) { + if log, err = logger.New(&runningConfig.Logger); err != nil { + return + } + + return +} + +func setup(stopChan chan os.Signal) (log *logger.Log, + resolvedConf string, runningConfig *config.Parameters, err error) { + + config.InitCLI() + + var usingDefaults bool + + defer func() { + if runningConfig == nil && err == nil { + err = errors.New("running configuration is nil, cannot continue") + return + } + if (runningConfig.GetLogger() == nil || runningConfig.GetLogger().Config == nil) && err == nil { + err = errors.New("running configuration logger is nil, cannot continue") + return + } + if usingDefaults && os.Getenv("HELLPOT_TEST_MODE") == "" { + log.Warn().Msg("using default configuration!") + print(red + "continuing with default configuration in ") + for i := defaultConfigWarningDelaySecs; i > 0; i-- { + print(strconv.Itoa(i)) + for i := 0; i < 5; i++ { + time.Sleep(200 * time.Millisecond) + print(".") + } + } + print(reset + "\n") + } + signal.Notify(stopChan, syscall.SIGINT, syscall.SIGTERM) + + if //goland:noinspection GoNilness + !runningConfig.GetLogger().Config.NoColor { + extra.Banner() + } + if absResolvedConf, err := filepath.Abs(resolvedConf); err == nil { + resolvedConf = absResolvedConf + } + }() + + switch { + case config.CLIFlags.Lookup("config") == nil, config.CLIFlags.Lookup("genconfig").Value.String() == "": + if runningConfig, usingDefaults, resolvedConf, err = resolveConfig(); err != nil { + return + } + log, err = initLogger(runningConfig) + default: + println("loading configuration file: " + config.CLIFlags.Lookup("config").Value.String()) + if runningConfig, err = readConfig(config.CLIFlags.Lookup("config").Value.String()); err != nil { + return + } + resolvedConf = config.CLIFlags.Lookup("config").Value.String() + log, err = initLogger(runningConfig) + } + return +} diff --git a/cmd/HellPot/util.go b/cmd/HellPot/util.go new file mode 100644 index 0000000..7c123b6 --- /dev/null +++ b/cmd/HellPot/util.go @@ -0,0 +1,42 @@ +package main + +import ( + "os" + + "github.com/rs/zerolog" + + "github.com/yunginnanet/HellPot/internal/config" + "github.com/yunginnanet/HellPot/internal/logger" + "github.com/yunginnanet/HellPot/internal/version" +) + +func printInfo(log *logger.Log, resolvedConf string, realConfig *config.Parameters) { + log.Info(). + Str("version", version.Version). + Int("pid", os.Getpid()). + Str("config_file", resolvedConf). + Str("log_file", log.Config.ActiveLogFileName). + Msg("🔥 Starting HellPot 🔥") + if realConfig.UsingDefaults { + log.Warn().Msg("Using default configuration! Please edit the configuration file to suit your needs.") + } + + var eventer func() *zerolog.Event + + if realConfig.Logger.Debug && !realConfig.Logger.Trace { + eventer = log.Debug + } + if realConfig.Logger.Trace { + eventer = log.Trace + } + + if eventer == nil { + return + } + + eventer().Bool("docker_logging", true). + Bool("debug", true).Bool("trace", false). + Str("rsyslog", realConfig.Logger.RSyslog). + Str("log_file", realConfig.Logger.ActiveLogFileName). + Msg("logging engine started") +} diff --git a/go.mod b/go.mod index 42bd8e5..c477423 100644 --- a/go.mod +++ b/go.mod @@ -6,18 +6,15 @@ require ( git.tcp.direct/kayos/common v0.9.7 github.com/fasthttp/router v1.5.1 github.com/knadh/koanf/parsers/toml v0.1.0 + github.com/knadh/koanf/providers/basicflag v1.0.0 github.com/knadh/koanf/providers/env v0.1.0 - github.com/knadh/koanf/providers/file v1.0.0 github.com/knadh/koanf/v2 v2.1.1 github.com/rs/zerolog v1.33.0 - github.com/spf13/afero v1.11.0 github.com/valyala/fasthttp v1.55.0 - golang.org/x/term v0.21.0 ) require ( github.com/andybalholm/brotli v1.1.0 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect github.com/klauspost/compress v1.17.9 // indirect github.com/knadh/koanf/maps v0.1.1 // indirect @@ -29,5 +26,5 @@ require ( github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect golang.org/x/sys v0.21.0 // indirect - golang.org/x/text v0.16.0 // indirect + nullprogram.com/x/rng v1.1.0 // indirect ) diff --git a/go.sum b/go.sum index e4c7525..0f4f395 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,6 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/fasthttp/router v1.5.1 h1:uViy8UYYhm5npJSKEZ4b/ozM//NGzVCfJbh6VJ0VKr8= github.com/fasthttp/router v1.5.1/go.mod h1:WrmsLo3mrerZP2VEXRV1E8nL8ymJFYCDTr4HmnB8+Zs= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 h1:TQcrn6Wq+sKGkpyPvppOz99zsMBaUOKXq6HSv655U1c= github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -17,10 +15,10 @@ github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NI github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI= github.com/knadh/koanf/parsers/toml v0.1.0 h1:S2hLqS4TgWZYj4/7mI5m1CQQcWurxUz6ODgOub/6LCI= github.com/knadh/koanf/parsers/toml v0.1.0/go.mod h1:yUprhq6eo3GbyVXFFMdbfZSo928ksS+uo0FFqNMnO18= +github.com/knadh/koanf/providers/basicflag v1.0.0 h1:qB0es/9fYsLuYnrKazxNCuWtkv3JFX1lI1druUsDDvY= +github.com/knadh/koanf/providers/basicflag v1.0.0/go.mod h1:n0NlnaxXUCER/WIzRroT9q3Np+FiZ9pSjrC6A/OozI8= github.com/knadh/koanf/providers/env v0.1.0 h1:LqKteXqfOWyx5Ab9VfGHmjY9BvRXi+clwyZozgVRiKg= github.com/knadh/koanf/providers/env v0.1.0/go.mod h1:RE8K9GbACJkeEnkl8L/Qcj8p4ZyPXZIQ191HJi44ZaQ= -github.com/knadh/koanf/providers/file v1.0.0 h1:DtPvSQBeF+N0QLPMz0yf2bx0nFSxUcncpqQvzCxfCyk= -github.com/knadh/koanf/providers/file v1.0.0/go.mod h1:/faSBcv2mxPVjFrXck95qeoyoZ5myJ6uxN8OOVNJJCI= github.com/knadh/koanf/v2 v2.1.1 h1:/R8eXqasSTsmDCsAyYj+81Wteg8AqrV9CP6gvsTsOmM= github.com/knadh/koanf/v2 v2.1.1/go.mod h1:4mnTRbZCK+ALuBXHZMjDfG9y714L7TykVnZkXbMU3Es= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -41,8 +39,6 @@ github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 h1:KanIMPX0QdEdB4R3CiimCAbxFrhB3j7h0/OvpYGVQa8= github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg= -github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= -github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= @@ -53,9 +49,6 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= -golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= nullprogram.com/x/rng v1.1.0 h1:SMU7DHaQSWtKJNTpNFIFt8Wd/KSmOuSDPXrMFp/UMro= +nullprogram.com/x/rng v1.1.0/go.mod h1:glGw6V87vyfawxCzqOABL3WfL95G65az9Z2JZCylCkg= diff --git a/heffalump/heffalump.go b/heffalump/heffalump.go index 4e7ce3b..ed97f21 100644 --- a/heffalump/heffalump.go +++ b/heffalump/heffalump.go @@ -6,16 +6,12 @@ package heffalump import ( "bufio" + "fmt" "io" "sync" - - "github.com/yunginnanet/HellPot/internal/config" ) -var log = config.GetLogger() - -// DefaultHeffalump represents a Heffalump type -var DefaultHeffalump *Heffalump +const DefaultBuffSize = 100 * 1 << 10 // Heffalump represents our buffer pool and markov map from Heffalump type Heffalump struct { @@ -36,18 +32,20 @@ func NewHeffalump(mm MarkovMap, buffsize int) *Heffalump { } } +// NewDefaultHeffalump instantiates a new default Heffalump from a MarkovMap created using using the default source text. +func NewDefaultHeffalump() *Heffalump { + return NewHeffalump(NewDefaultMarkovMap(), DefaultBuffSize) +} + // WriteHell writes markov chain heffalump hell to the provided io.Writer func (h *Heffalump) WriteHell(bw *bufio.Writer) (int64, error) { var n int64 var err error - defer func() { - if r := recover(); r != nil { - log.Error().Interface("caller", r).Msg("panic recovered!") - } - }() - - buf := h.pool.Get().([]byte) + buf, ok := h.pool.Get().([]byte) + if !ok { + panic("buffer pool type assertion failed, retrieved type is a " + fmt.Sprintf("%T", buf)) + } if _, err = bw.WriteString("\n
\n"); err != nil { h.pool.Put(buf) diff --git a/heffalump/markov.go b/heffalump/markov.go index ca522ab..ef70066 100644 --- a/heffalump/markov.go +++ b/heffalump/markov.go @@ -5,16 +5,21 @@ import ( "io" "math/rand" "strings" + "time" "unicode" "unicode/utf8" + "git.tcp.direct/kayos/common/entropy" "git.tcp.direct/kayos/common/squish" ) -var DefaultMarkovMap MarkovMap +const ( + sp = " " + nl = "\n" +) -func init() { - // DefaultMarkovMap is a Markov chain based on src. +// NewDefaultMarkovMap creates a new MarkovMap from the default source text. +func NewDefaultMarkovMap() MarkovMap { src, err := squish.UnpackStr(srcGz) if err != nil { panic(err) @@ -22,8 +27,8 @@ func init() { if len(src) < 1 { panic("failed to unpack source") } - DefaultMarkovMap = MakeMarkovMap(strings.NewReader(src)) - DefaultHeffalump = NewHeffalump(DefaultMarkovMap, 100*1<<10) + + return MakeMarkovMap(strings.NewReader(src)) } // ScanHTML is a basic split function for a Scanner that returns each @@ -72,11 +77,15 @@ func ScanHTML(data []byte, atEOF bool) (advance int, token []byte, err error) { type tokenPair [2]string // MarkovMap is a map that acts as a Markov chain generator. -type MarkovMap map[tokenPair][]string +type MarkovMap struct { + m map[tokenPair][]string + r *rand.Rand +} // MakeMarkovMap makes an empty MakeMarkov and fills it with r. func MakeMarkovMap(r io.Reader) MarkovMap { - m := MarkovMap{} + m := MarkovMap{m: make(map[tokenPair][]string)} + m.r = rand.New(rand.NewSource(entropy.GetOptimizedRand().Int63())) // #nosec: G404 : speed not security m.Fill(r) return m } @@ -99,13 +108,13 @@ func (mm MarkovMap) Fill(r io.Reader) { // Add adds a three token sequence to the map. func (mm MarkovMap) Add(w1, w2, w3 string) { p := tokenPair{w1, w2} - mm[p] = append(mm[p], w3) + mm.m[p] = append(mm.m[p], w3) } // Get pseudo-randomly chooses a possible suffix to w1 and w2. func (mm MarkovMap) Get(w1, w2 string) string { p := tokenPair{w1, w2} - suffix, ok := mm[p] + suffix, ok := mm.m[p] if !ok { return "" } @@ -121,10 +130,15 @@ func (mm MarkovMap) Read(p []byte) (n int, err error) { for { w3 = mm.Get(w1, w2) if n+len(w3)+1 >= len(p) { + n += copy(p[n:], nl) break } n += copy(p[n:], w3) - n += copy(p[n:], "\n") + if time.Now().UnixNano()%10 == 0 { + n += copy(p[n:], nl) + } else { + n += copy(p[n:], sp) + } w1, w2 = w2, w3 } return diff --git a/internal/config/arguments.go b/internal/config/arguments.go deleted file mode 100644 index 89fe1fd..0000000 --- a/internal/config/arguments.go +++ /dev/null @@ -1,37 +0,0 @@ -package config - -import ( - "os" -) - -var ( - forceDebug = false - forceTrace = false -) - -var argBoolMap = map[string]*bool{ - "--debug": &forceDebug, "-v": &forceDebug, "--trace": &forceTrace, "-vv": &forceTrace, - "--nocolor": &noColorForce, "--banner": &BannerOnly, "--genconfig": &GenConfig, -} - -// TODO: should probably just make a proper CLI with flags or something -func argParse() { - for i, arg := range os.Args { - if t, ok := argBoolMap[arg]; ok { - *t = true - continue - } - switch arg { - case "-h": - CLI.printUsage() - case "-c", "--config": - if len(os.Args) < i+2 { - println("missing config file after -c/--config") - os.Exit(1) - } - loadCustomConfig(os.Args[i+1]) - default: - continue - } - } -} diff --git a/internal/config/client_rules.go b/internal/config/client_rules.go new file mode 100644 index 0000000..7b880c3 --- /dev/null +++ b/internal/config/client_rules.go @@ -0,0 +1,83 @@ +package config + +import ( + "bytes" + "fmt" + "regexp" +) + +type ClientRules struct { + // See: https://github.com/yunginnanet/HellPot/issues/23 + UseragentDisallowStrings []string `koanf:"user_agent_disallow_strings"` + useragentDisallowStrBytes [][]byte + UseragentDisallowRegex []string `koanf:"user_agent_disallow_regex"` + useragentDisallowRegex []*regexp.Regexp +} + +func NewClientRules(strs []string, regex []string) (*ClientRules, error) { + if strs == nil && regex == nil { + return &ClientRules{}, nil + } + if regex == nil { + regex = make([]string, 0) + } + if strs == nil { + strs = make([]string, 0) + } + cr := &ClientRules{ + UseragentDisallowStrings: strs, + UseragentDisallowRegex: regex, + } + return cr, cr.compile() +} + +func (c *ClientRules) compile() error { + dupes := make(map[string]struct{}) + for _, v := range c.UseragentDisallowRegex { + if v == "" { + continue + } + if _, ok := dupes[v]; ok { + continue + } + dupes[v] = struct{}{} + var compd *regexp.Regexp + var err error + if compd, err = regexp.Compile(v); err != nil { + return fmt.Errorf("failed to compile regex '%s': %w", v, err) + } + c.useragentDisallowRegex = append(c.useragentDisallowRegex, compd) + } + + newStrs := make([]string, 0) + for _, v := range c.UseragentDisallowStrings { + if v == "" { + continue + } + if _, ok := dupes[v]; ok { + continue + } + dupes[v] = struct{}{} + newStrs = append(newStrs, v) + } + c.UseragentDisallowStrings = newStrs + c.useragentDisallowStrBytes = make([][]byte, len(c.UseragentDisallowStrings)) + for i, v := range c.UseragentDisallowStrings { + c.useragentDisallowStrBytes[i] = []byte(v) + } + return nil +} + +func (c *ClientRules) MatchUseragent(ua []byte) bool { + for _, v := range c.useragentDisallowRegex { + if v.Match(ua) { + return true + } + } + for _, v := range c.useragentDisallowStrBytes { + if bytes.Contains(ua, v) { + return true + } + } + return false +} diff --git a/internal/config/client_rules_test.go b/internal/config/client_rules_test.go new file mode 100644 index 0000000..c8b080f --- /dev/null +++ b/internal/config/client_rules_test.go @@ -0,0 +1,47 @@ +package config + +import "testing" + +func TestCompileRules(t *testing.T) { + if _, err := NewClientRules(nil, nil); err != nil { + t.Error(err) + } + rules, err := NewClientRules([]string{"test", "test"}, nil) + if err != nil { + t.Error(err) + } + if len(rules.UseragentDisallowStrings) != 1 { + t.Error("expected 1 got", len(rules.UseragentDisallowStrings)) + } + if rules.UseragentDisallowStrings[0] != "test" { + t.Error("expected test got", rules.UseragentDisallowStrings[0]) + } + rules, err = NewClientRules( + []string{"yeeterson", "", "", "", "yeeterson", "mc", "mc", "geeterson"}, + []string{"^y..terson$", "^mc", "^mc", "^g..ters.n$"}, + ) + if err != nil { + t.Error(err) + } + if len(rules.useragentDisallowRegex) != 3 { + t.Error("expected 3 got", len(rules.useragentDisallowRegex)) + } + if len(rules.UseragentDisallowStrings) != 3 { + t.Error("expected 3 got", len(rules.UseragentDisallowStrings)) + } + if !rules.MatchUseragent([]byte("yeeterson")) { + t.Error("expected true got false") + } + if !rules.MatchUseragent([]byte("mc")) { + t.Error("expected true got false") + } + if !rules.MatchUseragent([]byte("yooterson")) { + t.Error("expected true got false") + } + if !rules.MatchUseragent([]byte("gooters%n")) { + t.Error("expected true got false") + } + if rules.MatchUseragent([]byte("yootersongooterson")) { + t.Error("expected false got true") + } +} diff --git a/internal/config/command_line.go b/internal/config/command_line.go new file mode 100644 index 0000000..a74361e --- /dev/null +++ b/internal/config/command_line.go @@ -0,0 +1,142 @@ +package config + +import ( + "flag" + "io" + "os" + "slices" + "strconv" + "strings" + + "github.com/yunginnanet/HellPot/internal/extra" + "github.com/yunginnanet/HellPot/internal/version" +) + +var CLIFlags = flag.NewFlagSet("config", flag.ContinueOnError) + +var ( + sliceDefs = make(map[string][]string) + slicePtrs = make(map[string]*string) +) + +func addCLIFlags() { + parse := func(k string, v interface{}, nestedName string) { + switch casted := v.(type) { + case bool: + CLIFlags.Bool(nestedName, casted, "set "+k) + case string: + CLIFlags.String(nestedName, casted, "set "+k) + case int: + CLIFlags.Int(nestedName, casted, "set "+k) + case float64: + CLIFlags.Float64(nestedName, casted, "set "+k) + case []string: + sliceDefs[nestedName] = casted + joined := strings.Join(sliceDefs[nestedName], ",") + slicePtrs[nestedName] = CLIFlags.String(nestedName, joined, "set "+k) + } + } + + for key, val := range Defaults.val { + if _, ok := val.(map[string]interface{}); !ok { + parse(key, val, key) + continue + } + nested, ok := val.(map[string]interface{}) + if !ok { + // linter was confused by the above check + panic("unreachable, if you see this you have entered a real life HellPot") + } + for k, v := range nested { + nestedName := key + "." + k + parse(k, v, nestedName) + } + } +} + +var replacer = map[string][]string{ + "-h": {"-help"}, + "-v": {"-version"}, + "-c": {"-config"}, + "-g": {"-bespoke.enable_grimoire", "true", "-bespoke.grimoire_file"}, +} + +func InitCLI() { + newArgs := make([]string, 0) + for _, arg := range os.Args { + if repl, ok := replacer[arg]; ok { + newArgs = append(newArgs, repl...) + continue + } + // check for unit test flags + if !strings.HasPrefix(arg, "-test.") { + newArgs = append(newArgs, arg) + } + } + + newArgs = slices.Compact(newArgs) + + CLIFlags.Bool("banner", false, "show banner and version then exit") + CLIFlags.Bool("genconfig", false, "write default config to stdout then exit") + CLIFlags.Bool("help", false, "show this help and exit") + CLIFlags.String("config", "", "specify config file") + CLIFlags.String("version", "", "show version and exit") + + addCLIFlags() + + if err := CLIFlags.Parse(newArgs[1:]); err != nil { + println(err.Error()) + os.Exit(2) + } + + for defaultKey, defaultVal := range Defaults.val { + switch defaultVal.(type) { + case bool: + parsedBool, pErr := strconv.ParseBool(CLIFlags.Lookup(defaultKey).Value.String()) + if pErr != nil { + continue + } + if parsedBool == Defaults.val[defaultKey].(bool) { + fl := CLIFlags.Lookup(defaultKey) + *fl = flag.Flag{} + fl = nil + } + case string: + if CLIFlags.Lookup(defaultKey).Value.String() == Defaults.val[defaultKey].(string) || + CLIFlags.Lookup(defaultKey).Value.String() == "" { + fl := CLIFlags.Lookup(defaultKey) + *fl = flag.Flag{} + fl = nil + } + } + + } + + if os.Getenv("HELLPOT_CONFIG") != "" { + if err := CLIFlags.Set("config", os.Getenv("HELLPOT_CONFIG")); err != nil { + panic(err) + } + } + if CLIFlags.Lookup("help").Value.String() == "true" { + CLIFlags.Usage() + os.Exit(0) + } + if CLIFlags.Lookup("version").Value.String() == "true" { + _, _ = os.Stdout.WriteString("HellPot version: " + version.Version + "\n") + os.Exit(0) + } + if CLIFlags.Lookup("genconfig").Value.String() == "true" { + if n, err := io.Copy(os.Stdout, Defaults.IO); err != nil || n == 0 { + if err == nil { + err = io.EOF + } + panic(err) + } + os.Exit(0) + } + if CLIFlags.Lookup("banner").Value.String() == "true" { + extra.Banner() + os.Exit(0) + } + +} diff --git a/internal/config/config.go b/internal/config/config.go deleted file mode 100644 index 925bde3..0000000 --- a/internal/config/config.go +++ /dev/null @@ -1,236 +0,0 @@ -package config - -import ( - "errors" - "fmt" - "os" - "path/filepath" - "runtime" - "strconv" - "strings" - - "github.com/knadh/koanf/providers/env" - "github.com/knadh/koanf/providers/file" - "github.com/rs/zerolog" - - "github.com/knadh/koanf/parsers/toml" - viper "github.com/knadh/koanf/v2" -) - -// generic vars -var ( - noColorForce = false - customconfig = false - home string - snek = viper.New(".") -) - -func init() { - home, _ = os.UserHomeDir() - if home == "" { - home = os.Getenv("HOME") - } - if home == "" { - println("WARNING: could not determine home directory") - } -} - -// exported generic vars -var ( - // Trace is the value of our trace (extra verbose) on/off toggle as per the current configuration. - Trace bool - // Debug is the value of our debug (verbose) on/off toggle as per the current configuration. - Debug bool - // Filename returns the current location of our toml config file. - Filename string -) - -func writeConfig() string { - prefConfigLocation, _ := os.UserConfigDir() - if prefConfigLocation != "" { - prefConfigLocation = filepath.Join(prefConfigLocation, Title) - } - - if prefConfigLocation == "" { - home, _ = os.UserHomeDir() - prefConfigLocation = filepath.Join(home, ".config", Title) - } - - if _, err := os.Stat(prefConfigLocation); os.IsNotExist(err) { - if err = os.MkdirAll(prefConfigLocation, 0o750); err != nil && !errors.Is(err, os.ErrExist) { - println("error writing new config: " + err.Error()) - os.Exit(1) - } - } - - Filename = filepath.Join(prefConfigLocation, "config.toml") - - tomld, terr := toml.Parser().Marshal(snek.All()) - if terr != nil { - fmt.Println("Failed to marshal new configuration file: " + terr.Error()) - os.Exit(1) - } - - if err := os.WriteFile(Filename, tomld, 0o600); err != nil { - println("error writing new config: " + err.Error()) - os.Exit(1) - } - - return Filename -} - -// Init will initialize our toml configuration engine and define our default configuration values which can be written to a new configuration file if desired -func Init() { - argParse() - - if customconfig { - associateExportedVariables() - return - } - - setDefaults() - - chosen := "" - - uconf, _ := os.UserConfigDir() - - switch runtime.GOOS { - case "windows": - // - default: - if _, err := os.Stat(filepath.Join("/etc/", Title, "config.toml")); err == nil { - chosen = filepath.Join("/etc/", Title, "config.toml") - } - } - - if chosen == "" && uconf == "" && home != "" { - uconf = filepath.Join(home, ".config") - } - - if chosen == "" && uconf != "" { - _ = os.MkdirAll(filepath.Join(uconf, Title), 0750) - chosen = filepath.Join(uconf, Title, "config.toml") - } - - if chosen == "" { - pwd, _ := os.Getwd() - if _, err := os.Stat("./config.toml"); err == nil { - chosen = "./config.toml" - } else { - if _, err := os.Stat(filepath.Join(pwd, "config.toml")); err == nil { - chosen = filepath.Join(pwd, "config.toml") - } - } - } - - loadErr := snek.Load(file.Provider(chosen), toml.Parser()) - - if chosen == "" || loadErr != nil { - println("No configuration file found, writing new configuration file...") - chosen = writeConfig() - } - Filename = chosen - - if loadErr = snek.Load(file.Provider(chosen), toml.Parser()); loadErr != nil { - fmt.Println("failed to load default config file: ", loadErr.Error()) - os.Exit(1) - } - - /* snek.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_")) - snek.SetEnvPrefix(Title) - snek.AutomaticEnv() - */ - associateExportedVariables() -} - -func loadCustomConfig(path string) { - Filename, _ = filepath.Abs(path) - - if err := snek.Load(file.Provider(Filename), toml.Parser()); err != nil { - fmt.Println("failed to load specified config file: ", err.Error()) - os.Exit(1) - } - - customconfig = true -} - -func processOpts() { - // string options and their exported variables - stringOpt := map[string]*string{ - "http.bind_addr": &HTTPBind, - "http.bind_port": &HTTPPort, - "http.real_ip_header": &HeaderName, - "logger.directory": &logDir, - "logger.console_time_format": &ConsoleTimeFormat, - "deception.server_name": &FakeServerName, - } - // string slice options and their exported variables - strSliceOpt := map[string]*[]string{ - "http.router.paths": &Paths, - "http.uagent_string_blacklist": &UseragentBlacklistMatchers, - } - // bool options and their exported variables - boolOpt := map[string]*bool{ - "performance.restrict_concurrency": &RestrictConcurrency, - "http.use_unix_socket": &UseUnixSocket, - "logger.debug": &Debug, - "logger.trace": &Trace, - "logger.nocolor": &NoColor, - "logger.docker_logging": &DockerLogging, - "http.router.makerobots": &MakeRobots, - "http.router.catchall": &CatchAll, - } - // integer options and their exported variables - intOpt := map[string]*int{ - "performance.max_workers": &MaxWorkers, - } - - for key, opt := range stringOpt { - *opt = snek.String(key) - } - for key, opt := range strSliceOpt { - *opt = snek.Strings(key) - } - for key, opt := range boolOpt { - *opt = snek.Bool(key) - } - for key, opt := range intOpt { - *opt = snek.Int(key) - } -} - -func associateExportedVariables() { - _ = snek.Load(env.Provider("HELLPOT_", ".", func(s string) string { - s = strings.TrimPrefix(s, "HELLPOT_") - s = strings.ToLower(s) - s = strings.ReplaceAll(s, "__", " ") - s = strings.ReplaceAll(s, "_", ".") - s = strings.ReplaceAll(s, " ", "_") - return s - }), nil) - - processOpts() - - if noColorForce { - NoColor = true - } - - if UseUnixSocket { - UnixSocketPath = snek.String("http.unix_socket_path") - parsedPermissions, err := strconv.ParseUint(snek.String("http.unix_socket_permissions"), 8, 32) - if err == nil { - UnixSocketPermissions = uint32(parsedPermissions) - } - } - - // We set exported variables here so that it tracks when accessed from other packages. - - if Debug || forceDebug { - zerolog.SetGlobalLevel(zerolog.DebugLevel) - Debug = true - } - if Trace || forceTrace { - zerolog.SetGlobalLevel(zerolog.TraceLevel) - Trace = true - } -} diff --git a/internal/config/defaults.go b/internal/config/defaults.go index 0d0241a..4e4c6f7 100644 --- a/internal/config/defaults.go +++ b/internal/config/defaults.go @@ -1,35 +1,68 @@ package config import ( - "io" - "os" + "bytes" "runtime" "time" "github.com/knadh/koanf/parsers/toml" - "github.com/spf13/afero" ) -var ( - configSections = []string{"logger", "http", "performance", "deception", "ssh"} - defNoColor = false -) +var Defaults = &Preset{val: defOpts} + +func init() { + Defaults.IO = &PresetIO{p: Defaults} +} + +type Preset struct { + val map[string]interface{} + IO *PresetIO +} + +type PresetIO struct { + p *Preset + buf *bytes.Buffer +} + +func (pre *Preset) ReadBytes() ([]byte, error) { + return toml.Parser().Marshal(pre.val) //nolint:wrapcheck +} + +func (shim *PresetIO) Read(p []byte) (int, error) { + if shim.buf != nil && shim.buf.Len() > 0 { + return shim.buf.Read(p) //nolint:wrapcheck + } + data, err := shim.p.ReadBytes() + if err != nil { + return 0, err + } + if shim.buf == nil { + shim.buf = bytes.NewBuffer(data) + } + return shim.buf.Read(p) //nolint:wrapcheck +} -var defOpts = map[string]map[string]interface{}{ - "logger": { +func (pre *Preset) Read() (map[string]interface{}, error) { + return pre.val, nil +} + +var defOpts = map[string]interface{}{ + "logger": map[string]interface{}{ "debug": true, "trace": false, - "nocolor": defNoColor, - "use_date_filename": true, + "nocolor": runtime.GOOS == "windows", + "noconsole": false, + "use_date_filename": false, "docker_logging": false, + "rsyslog_address": "", "console_time_format": time.Kitchen, }, - "http": { + "http": map[string]interface{}{ "use_unix_socket": false, "unix_socket_path": "/var/run/hellpot", "unix_socket_permissions": "0666", "bind_addr": "127.0.0.1", - "bind_port": "8080", + "bind_port": int64(8080), //nolint:gomnd "real_ip_header": "X-Real-IP", "router": map[string]interface{}{ @@ -44,67 +77,15 @@ var defOpts = map[string]map[string]interface{}{ "Cloudflare-Traffic-Manager", }, }, - "performance": { + "performance": map[string]interface{}{ "restrict_concurrency": false, - "max_workers": 256, + "max_workers": 256, //nolint:gomnd }, - "deception": { + "deception": map[string]interface{}{ "server_name": "nginx", }, -} - -func gen(memfs afero.Fs) { - var ( - dat []byte - err error - f afero.File - ) - if dat, err = snek.Marshal(toml.Parser()); err != nil { - println(err.Error()) - os.Exit(1) - } - if f, err = memfs.Create("config.toml"); err != nil { - println(err.Error()) - os.Exit(1) - } - var n int - if n, err = f.Write(dat); err != nil || n != len(dat) { - if err == nil { - err = io.ErrShortWrite - } - println(err.Error()) - os.Exit(1) - } - println("Default config written to config.toml") - os.Exit(0) -} - -func setDefaults() { - memfs := afero.NewMemMapFs() - //goland:noinspection GoBoolExpressions - if runtime.GOOS == "windows" { - defNoColor = true - } - for _, def := range configSections { - for key, val := range defOpts[def] { - if _, ok := val.(map[string]interface{}); !ok { - if err := snek.Set(def+"."+key, val); err != nil { - println(err.Error()) - os.Exit(1) - } - continue - } - for k, v := range val.(map[string]interface{}) { - if err := snek.Set(def+"."+key+"."+k, v); err != nil { - println(err.Error()) - os.Exit(1) - } - } - continue - } - } - - if GenConfig { - gen(memfs) - } + "bespoke": map[string]interface{}{ + "grimoire_file": "", + "enable_grimoire": false, + }, } diff --git a/internal/config/defaults_test.go b/internal/config/defaults_test.go new file mode 100644 index 0000000..535f8e1 --- /dev/null +++ b/internal/config/defaults_test.go @@ -0,0 +1,58 @@ +package config + +import ( + "bytes" + "testing" +) + +func TestDefaults(t *testing.T) { + t.Run("ReadBytes", func(t *testing.T) { + t.Parallel() + bs, err := Defaults.ReadBytes() + if err != nil { + t.Fatal(err) + } + if len(bs) == 0 { + t.Fatal("expected non-empty byte slice") + } + total := 0 + for _, needle := range []string{ + "logger", + "http", + "performance", + "deception", + "bespoke", + } { + total += bytes.Count(bs, []byte(needle)) + 3 // name plus brackets and newline + if !bytes.Contains(bs, []byte(needle)) { + t.Errorf("expected %q in byte slice", needle) + } + } + if len(bs) <= total { + t.Errorf("default byte slice seems too short to contain any default values") + } + }) + t.Run("Read", func(t *testing.T) { + t.Parallel() + m, err := Defaults.Read() + if err != nil { + t.Fatal(err) + } + if len(m) == 0 { + t.Fatal("expected non-empty map") + } + for _, needle := range []string{ + "logger", + "http", + "performance", + "deception", + } { + if _, ok := m[needle]; !ok { + t.Errorf("expected %q in map", needle) + } + if m[needle] == nil { + t.Errorf("expected non-nil value for %q", needle) + } + } + }) +} diff --git a/internal/config/globals.go b/internal/config/globals.go deleted file mode 100644 index 1c82dfb..0000000 --- a/internal/config/globals.go +++ /dev/null @@ -1,86 +0,0 @@ -package config - -import ( - "runtime/debug" -) - -// Title is the name of the application used throughout the configuration process. -const Title = "HellPot" - -var Version = "dev" - -func init() { - if Version != "dev" { - return - } - binInfo := make(map[string]string) - info, ok := debug.ReadBuildInfo() - if !ok { - return - } - for _, v := range info.Settings { - binInfo[v.Key] = v.Value - } - if gitrev, ok := binInfo["vcs.revision"]; ok { - Version = gitrev[:7] - } -} - -var ( - // BannerOnly when toggled causes HellPot to only print the banner and version then exit. - BannerOnly = false - // GenConfig when toggled causes HellPot to write its default config to the cwd and then exit. - GenConfig = false - // NoColor when true will disable the banner and any colored console output. - NoColor bool - // DockerLogging when true will disable the banner and any colored console output, as well as disable the log file. - // Assumes NoColor == true. - DockerLogging bool - // MakeRobots when false will not respond to requests for robots.txt. - MakeRobots bool - // CatchAll when true will cause HellPot to respond to all paths. - // Note that this will override MakeRobots. - CatchAll bool - // ConsoleTimeFormat sets the time format for the console. The string is passed to time.Format() down the line. - ConsoleTimeFormat string -) - -// "http" -var ( - // HTTPBind is defined via our toml configuration file. It is the address that HellPot listens on. - HTTPBind string - // HTTPPort is defined via our toml configuration file. It is the port that HellPot listens on. - HTTPPort string - // HeaderName is defined via our toml configuration file. It is the HTTP Header containing the original IP of the client, - // in traditional reverse Proxy deployments. - HeaderName string - - // 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 determines if we will listen for HTTP connections on a unix socket. - UseUnixSocket bool - - // UnixSocketPath is defined via our toml configuration file. It is the path of the socket HellPot listens on - // if UseUnixSocket, also defined via our toml configuration file, is set to true. - UnixSocketPath = "" - UnixSocketPermissions uint32 - - // UseragentBlacklistMatchers contains useragent matches checked for with strings.Contains() that - // prevent HellPot from firing off. - // See: https://github.com/yunginnanet/HellPot/issues/23 - UseragentBlacklistMatchers []string -) - -// "performance" -var ( - RestrictConcurrency bool - MaxWorkers int -) - -// "deception" -var ( - // FakeServerName is our configured value for the "Server: " response header when serving HTTP clients - FakeServerName string -) diff --git a/internal/config/help.go b/internal/config/help.go deleted file mode 100644 index 8c50059..0000000 --- a/internal/config/help.go +++ /dev/null @@ -1,123 +0,0 @@ -package config - -import ( - "io" - "os" - "strings" - - "golang.org/x/term" -) - -type help struct { - title, version string - usage map[int][]string - out io.Writer -} - -var CLI = help{ - title: Title, - version: Version, - usage: map[int][]string{ - 0: {0: "--config", 1: "