TeeHee: Essentially rewrite HellPot :^)

This commit is contained in:
kayos@tcp.direct 2024-06-20 21:42:24 -07:00
parent 3024b2d0c3
commit 0a18454ea0
No known key found for this signature in database
GPG Key ID: 4B841471B4BEE979
25 changed files with 825 additions and 716 deletions

View File

@ -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), or optionally you may synchronize HellPot with your nightmares by using the `-g`/`--grimoire` flag
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

View File

@ -1,59 +1,135 @@
package main
import (
"io"
"os"
"os/signal"
"path/filepath"
"strconv"
"syscall"
"github.com/rs/zerolog"
"time"
"github.com/yunginnanet/HellPot/internal/config"
"github.com/yunginnanet/HellPot/internal/extra"
"github.com/yunginnanet/HellPot/internal/http"
"github.com/yunginnanet/HellPot/internal/logger"
"github.com/yunginnanet/HellPot/internal/version"
)
var (
log zerolog.Logger
version string // set by linker
runningConfig *config.Parameters
)
func init() {
if version != "" {
config.Version = version[1:]
func writeConfig(target string) bool {
var f *os.File
var err error
if f, err = os.Create(target); err != nil {
println("failed to create config file: " + err.Error())
return false
}
config.Init()
if config.BannerOnly {
extra.Banner()
os.Exit(0)
if _, err = io.Copy(f, config.Defaults.IO); err != nil {
println("failed to write default config to file: " + err.Error())
_ = f.Close()
return false
}
switch config.DockerLogging {
case true:
config.CurrentLogFile = "/dev/stdout"
config.NoColor = true
log = config.StartLogger(false, os.Stdout)
default:
log = config.StartLogger(true)
}
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")
println("wrote default config to " + target)
runningConfig, _ = config.Setup(f)
_ = f.Close()
return true
}
func main() {
conf := config.CLIFlags.Lookup("config").Value
if conf.String() == "" {
conf = config.CLIFlags.Lookup("c").Value
}
usingDefaults := true
resolvedConf := conf.String()
uconf, _ := os.UserConfigDir()
if uconf == "" && os.Getenv("HOME") != "" {
uconf = filepath.Join(os.Getenv("HOME"), ".config")
}
if resolvedConf == "" {
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
}
}
}
var setupErr error
var err error
var f *os.File
f, err = os.Open(resolvedConf)
if err == nil {
runningConfig, setupErr = config.Setup(f)
}
switch {
case setupErr != nil:
println("failed to setup config: " + setupErr.Error())
case err != nil:
println("failed to open config file for reading: " + err.Error())
println("trying to create it....")
wroteOK := writeConfig(resolvedConf)
if wroteOK {
break
}
case runningConfig != nil:
usingDefaults = false
_ = f.Close()
}
if runningConfig == nil {
if runningConfig, err = config.Setup(nil); err != nil || runningConfig == nil {
panic("failed to setup default config...\n" + err.Error())
return // unreachable, but the linter doesn't seem to realize that
}
}
log, _ := logger.New(runningConfig.Logger)
if usingDefaults {
log.Warn().Msg("continuing with default configuration in ")
for i := 5; i > 0; i-- {
print(strconv.Itoa(i))
for i := 0; i < 5; i++ {
time.Sleep(200 * time.Millisecond)
print(".")
}
}
}
if !runningConfig.Logger.NoColor {
extra.Banner()
}
log.Info().Msg("🔥 Starting HellPot 🔥")
log.Info().Msg("Version: " + version.Version)
log.Info().Msg("PID: " + strconv.Itoa(os.Getpid()))
log.Info().Msg("Using config file: " + resolvedConf)
if usingDefaults {
log.Warn().Msg("Using default configuration")
}
log.Debug().Msg("Debug logging enabled")
log.Trace().Msg("Trace logging enabled")
stopChan := make(chan os.Signal, 1)
signal.Notify(stopChan, syscall.SIGINT, syscall.SIGTERM)
go func() {
log.Fatal().Err(http.Serve()).Msg("HTTP error")
log.Fatal().Err(http.Serve(runningConfig)).Msg("HTTP error")
}()
<-stopChan // wait for SIGINT
log.Warn().Msg("Shutting down server...")
}

6
go.mod
View File

@ -7,17 +7,16 @@ require (
github.com/fasthttp/router v1.5.1
github.com/knadh/koanf/parsers/toml v0.1.0
github.com/knadh/koanf/providers/env v0.1.0
github.com/knadh/koanf/providers/file v0.1.0
github.com/knadh/koanf/providers/structs v0.1.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.6.0 // indirect
github.com/fatih/structs v1.1.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 +28,4 @@ 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
)

13
go.sum
View File

@ -6,8 +6,8 @@ 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.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
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=
@ -19,8 +19,8 @@ github.com/knadh/koanf/parsers/toml v0.1.0 h1:S2hLqS4TgWZYj4/7mI5m1CQQcWurxUz6OD
github.com/knadh/koanf/parsers/toml v0.1.0/go.mod h1:yUprhq6eo3GbyVXFFMdbfZSo928ksS+uo0FFqNMnO18=
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 v0.1.0 h1:fs6U7nrV58d3CFAFh8VTde8TM262ObYf3ODrc//Lp+c=
github.com/knadh/koanf/providers/file v0.1.0/go.mod h1:rjJ/nHQl64iYCtAW2QQnF0eSmDEX/YZ/eNFj5yR6BvA=
github.com/knadh/koanf/providers/structs v0.1.0 h1:wJRteCNn1qvLtE5h8KQBvLJovidSdntfdyIbbCzEyE0=
github.com/knadh/koanf/providers/structs v0.1.0/go.mod h1:sw2YZ3txUcqA3Z27gPlmmBzWn1h8Nt9O6EP/91MkcWE=
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,22 +41,17 @@ 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=
github.com/valyala/fasthttp v1.55.0 h1:Zkefzgt6a7+bVKHnu/YaYSOPfNYNisSVBo/unVCf8k8=
github.com/valyala/fasthttp v1.55.0/go.mod h1:NkY9JtkrpPKmgwV3HTaS2HWaJss9RSIsRVfcxxoHiOM=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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=

View File

@ -8,12 +8,8 @@ import (
"bufio"
"io"
"sync"
"github.com/yunginnanet/HellPot/internal/config"
)
var log = config.GetLogger()
const DefaultBuffSize = 100 * 1 << 10
// Heffalump represents our buffer pool and markov map from Heffalump
@ -46,11 +42,11 @@ func (h *Heffalump) WriteHell(bw *bufio.Writer) (int64, error) {
var n int64
var err error
defer func() {
/* defer func() {
if r := recover(); r != nil {
log.Error().Interface("caller", r).Msg("panic recovered!")
}
}()
}()*/
buf := h.pool.Get().([]byte)

View File

@ -1,44 +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", "--help":
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])
case "-g", "--grimoire":
if len(os.Args) < i+2 {
println("missing source of suffering file after -g/--grimoire")
os.Exit(1)
}
Grimoire = os.Args[i+1]
UseCustomHeffalump = true
default:
continue
}
}
}

View File

@ -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 `koanf:"-"`
UseragentDisallowRegex []string `koanf:"user_agent_disallow_regex"`
useragentDisallowRegex []*regexp.Regexp `koanf:"-"`
}
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
}

View File

@ -0,0 +1,52 @@
package config
import (
"flag"
"io"
"os"
"github.com/yunginnanet/HellPot/internal/extra"
"github.com/yunginnanet/HellPot/internal/version"
)
var CLIFlags = flag.NewFlagSet("cli", flag.ExitOnError)
func init() {
CLIFlags.Bool("logger-debug", false, "force debug logging")
CLIFlags.Bool("logger-trace", false, "force trace logging")
CLIFlags.Bool("logger-nocolor", false, "force no color logging")
CLIFlags.String("bespoke-grimoire", "", "specify a custom file used for text generation")
CLIFlags.Bool("banner", false, "show banner and version then exit")
CLIFlags.Bool("genconfig", false, "write default config to stdout then exit")
CLIFlags.Bool("h", false, "show this help and exit")
CLIFlags.Bool("help", false, "show this help and exit")
CLIFlags.String("c", "", "specify config file")
CLIFlags.String("config", "", "specify config file")
CLIFlags.String("version", "", "show version and exit")
CLIFlags.String("v", "", "show version and exit")
if err := CLIFlags.Parse(os.Args[1:]); err != nil {
println(err.Error())
// flag.ExitOnError will call os.Exit(2)
}
if CLIFlags.Lookup("h").Value.String() == "true" || CLIFlags.Lookup("help").Value.String() == "true" {
CLIFlags.Usage()
os.Exit(0)
}
if CLIFlags.Lookup("version").Value.String() == "true" || CLIFlags.Lookup("v").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)
}
}

View File

@ -1,206 +0,0 @@
package config
import (
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"github.com/rs/zerolog"
"github.com/spf13/viper"
)
// generic vars
var (
noColorForce = false
customconfig = false
home string
prefConfigLocation string
snek = viper.New()
)
// 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
// UseCustomHeffalump decides if a custom Heffalump is to be used
UseCustomHeffalump = false
// Grimoire returns the current location of a possible source of suffering file
Grimoire string
)
func writeConfig() {
if _, err := os.Stat(prefConfigLocation); os.IsNotExist(err) {
if err = os.MkdirAll(prefConfigLocation, 0o750); err != nil {
println("error writing new config: " + err.Error())
os.Exit(1)
}
}
Filename = prefConfigLocation + "/" + "config.toml"
if err := snek.SafeWriteConfigAs(Filename); err != nil {
fmt.Println("Failed to write new configuration file to '" + Filename + "': " + err.Error())
os.Exit(1)
}
}
// 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() {
snek.SetConfigType("toml")
snek.SetConfigName("config")
argParse()
if customconfig {
associateExportedVariables()
return
}
setDefaults()
for _, loc := range getConfigPaths() {
snek.AddConfigPath(loc)
}
if err := snek.MergeInConfig(); err != nil {
println("Error reading configuration file: " + err.Error())
println("Writing new configuration file...")
writeConfig()
}
if len(Filename) < 1 {
Filename = snek.ConfigFileUsed()
}
snek.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
snek.SetEnvPrefix(Title)
snek.AutomaticEnv()
associateExportedVariables()
}
func getConfigPaths() (paths []string) {
paths = append(paths, "./")
//goland:noinspection GoBoolExpressions
if runtime.GOOS != "windows" {
paths = append(paths,
prefConfigLocation, "/etc/"+Title+"/", "../", "../../")
}
return
}
func loadCustomConfig(path string) {
/* #nosec */
cf, err := os.Open(path)
if err != nil {
println("Error opening specified config file: " + path)
println(err.Error())
os.Exit(1)
}
Filename, err = filepath.Abs(path)
if len(Filename) < 1 || err != nil {
Filename = path
}
defer func(f *os.File) {
if fcerr := f.Close(); fcerr != nil {
fmt.Println("failed to close file handler for config file: ", fcerr.Error())
}
}(cf)
buf, err1 := io.ReadAll(cf)
err2 := snek.ReadConfig(bytes.NewBuffer(buf))
switch {
case err1 != nil:
fmt.Println("config file read fatal error during i/o: ", err1.Error())
os.Exit(1)
case err2 != nil:
fmt.Println("config file read fatal error during parse: ", err2.Error())
os.Exit(1)
default:
break
}
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,
"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.GetString(key)
}
for key, opt := range strSliceOpt {
*opt = snek.GetStringSlice(key)
}
for key, opt := range boolOpt {
*opt = snek.GetBool(key)
}
for key, opt := range intOpt {
*opt = snek.GetInt(key)
}
}
func associateExportedVariables() {
processOpts()
if noColorForce {
NoColor = true
}
if UseUnixSocket {
UnixSocketPath = snek.GetString("http.unix_socket_path")
parsedPermissions, err := strconv.ParseUint(snek.GetString("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
}
}

View File

@ -1,46 +1,66 @@
package config
import (
"fmt"
"io"
"os"
"path"
"bytes"
"runtime"
"time"
"github.com/spf13/afero"
"github.com/knadh/koanf/parsers/toml"
)
var Defaults = &Preset{val: defOpts}
func init() {
var err error
if home, err = os.UserHomeDir(); err != nil {
panic(err)
}
if len(defOpts) == 0 {
panic("default options map is empty")
}
defOpts["logger"]["directory"] = path.Join(home, ".local", "share", Title, "logs")
prefConfigLocation = path.Join(home, ".config", Title)
Defaults.IO = &PresetIO{p: Defaults}
}
var (
configSections = []string{"logger", "http", "performance", "deception", "ssh"}
defNoColor = false
)
type Preset struct {
val map[string]interface{}
IO *PresetIO
}
var defOpts = map[string]map[string]interface{}{
"logger": {
"debug": true,
"trace": false,
"nocolor": defNoColor,
"use_date_filename": true,
"docker_logging": false,
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.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
}
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": runtime.GOOS == "windows",
"use_date_filename": true,
"docker_logging": false,
"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{}{
@ -55,52 +75,11 @@ 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) {
target := fmt.Sprintf("%s.toml", Title)
if err := snek.SafeWriteConfigAs("config.toml"); err != nil {
print(err.Error())
os.Exit(1)
}
var f afero.File
var err error
f, err = memfs.Open("config.toml")
if err != nil {
println(err.Error())
os.Exit(1)
}
nf, err := os.Create(target) // #nosec G304
if err != nil {
println(err.Error())
os.Exit(1)
}
if _, err = io.Copy(nf, f); err != nil {
println(err.Error())
os.Exit(1)
}
println("default configuration successfully written to " + target)
os.Exit(0)
}
func setDefaults() {
memfs := afero.NewMemMapFs()
//goland:noinspection GoBoolExpressions
if runtime.GOOS == "windows" {
defNoColor = true
}
for _, def := range configSections {
snek.SetDefault(def, defOpts[def])
}
if GenConfig {
snek.SetFs(memfs)
gen(memfs)
}
}

View File

@ -0,0 +1,57 @@
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",
} {
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)
}
}
})
}

View File

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

View File

@ -1,124 +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: "-c, --config", 1: "<file>", 2: "Specify config file"},
1: {0: "--nocolor", 1: "disable color and banner"},
2: {0: "--banner", 1: "show banner + version and exit"},
3: {0: "--genconfig", 1: "write default config to " + Title + ".toml then exit"},
4: {0: "-g, --grimoire", 1: "<file>", 2: "Specify a custom file used for text generation"},
5: {0: "-h,--help", 1: "show this help and exit"},
},
out: os.Stdout,
}
func (cli help) secondColStart(index int) (max int) {
l := cli.firstColEnd() + 2
if len(cli.usage[index]) > 2 && cli.usage[index][2] != "" {
l -= len(cli.usage[index][1])
}
if l > max {
max = l
}
return max
}
func (cli help) firstColEnd() (max int) {
for n := range cli.usage {
l := len(cli.usage[n][0])
if l > max {
max = l
}
}
return max
}
func (cli help) stdout(s ...string) {
for _, v := range s {
_, _ = cli.out.Write([]byte(v))
}
}
func (cli help) lb(n int) {
for n > 0 {
cli.stdout("\n")
n--
}
}
func (cli help) printUsage() {
if !term.IsTerminal(int(os.Stdout.Fd())) {
os.Exit(1)
}
cli.header()
for n := 0; n < len(cli.usage); n++ {
line := &strings.Builder{}
buf := &strings.Builder{}
usageAt := 1
tlen := cli.secondColStart(n)
switch {
case cli.usage[n][0] == "":
cli.lb(1)
case cli.usage[n][1] == "":
cli.stdout(cli.usage[n][0])
cli.lb(2)
case len(cli.usage[n]) > 2 && cli.usage[n][2] != "":
tlen = cli.firstColEnd() - len(cli.usage[n][1])
usageAt = 2
fallthrough
default:
buf.WriteString(cli.usage[n][0])
}
if tlen < 0 {
tlen = 2
}
tab := strings.Repeat(" ", tlen)
line.WriteString(" ")
if buf.Len() < cli.firstColEnd() {
line.WriteString(strings.Repeat(" ", cli.firstColEnd()-buf.Len()))
}
if usageAt == 2 {
buf.WriteString(strings.Repeat(" ", tlen/2))
buf.WriteString(cli.usage[n][1])
}
buf.WriteString(tab)
buf.Write([]byte(" (" + cli.usage[n][usageAt] + ")"))
buf.Write([]byte{'\n'})
line.Write([]byte(buf.String()))
cli.stdout(line.String())
}
os.Exit(0)
}
func (cli help) header() {
cli.stdout("\n")
s := &strings.Builder{}
s.Write([]byte(cli.title))
s.Write([]byte(" v["))
s.Write([]byte(cli.version))
s.Write([]byte("]"))
tab := cli.firstColEnd() - (s.Len() % 2) + 1
if tab > 0 {
cli.stdout(strings.Repeat(" ", tab))
}
cli.stdout(s.String())
cli.lb(2)
}

View File

@ -1,71 +0,0 @@
package config
import (
"io"
"os"
"path"
"path/filepath"
"strings"
"time"
"github.com/rs/zerolog"
)
var (
// CurrentLogFile is used for accessing the location of the currently used log file across packages.
CurrentLogFile string
logFile io.Writer
logDir string
logger zerolog.Logger
)
func prepLogDir() {
logDir = snek.String("logger.directory")
if logDir == "" {
logDir = filepath.Join(home, ".local", "share", Title, "logs")
}
_ = os.MkdirAll(logDir, 0750)
}
// StartLogger instantiates an instance of our zerolog loggger so we can hook it in our main package.
// While this does return a logger, it should not be used for additional retrievals of the logger. Use GetLogger().
func StartLogger(pretty bool, targets ...io.Writer) zerolog.Logger {
logFileName := "HellPot"
if snek.Bool("logger.use_date_filename") {
tn := strings.ReplaceAll(time.Now().Format(time.RFC822), " ", "_")
tn = strings.ReplaceAll(tn, ":", "-")
logFileName = logFileName + "_" + tn
}
var err error
switch {
case len(targets) > 0:
logFile = io.MultiWriter(targets...)
default:
prepLogDir()
CurrentLogFile = path.Join(logDir, logFileName+".log")
//nolint:lll
logFile, err = os.OpenFile(CurrentLogFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o666) // #nosec G304 G302 -- we are not using user input to create the file
if err != nil {
println("cannot create log file: " + err.Error())
os.Exit(1)
}
}
var logWriter = logFile
if pretty {
logWriter = zerolog.MultiLevelWriter(zerolog.ConsoleWriter{TimeFormat: ConsoleTimeFormat, NoColor: NoColor, Out: os.Stdout}, logFile)
}
logger = zerolog.New(logWriter).With().Timestamp().Logger()
return logger
}
// GetLogger retrieves our global logger object.
func GetLogger() *zerolog.Logger {
// future logic here
return &logger
}

87
internal/config/models.go Normal file
View File

@ -0,0 +1,87 @@
package config
import (
"sync"
"github.com/knadh/koanf/v2"
"github.com/rs/zerolog"
"github.com/yunginnanet/HellPot/internal/logger"
)
// Parameters represents the configuration for HellPot.
type Parameters struct {
HTTP HTTP `koanf:"http"`
Logger *logger.Configuration `koanf:"logger"`
Bespoke Customization `koanf:"bespoke"`
Perf Performance `koanf:"performance"`
Liar Deception `koanf:"deception"`
source *koanf.Koanf `koanf:"-"`
logger *zerolog.Logger
}
var once = &sync.Once{}
func (p *Parameters) GetLogger() *zerolog.Logger {
once.Do(func() {
p.logger = logger.GetLoggerOnce()
})
return p.logger
}
type Deception struct {
// FakeServerName is our configured value for the "Server: " response header when serving HTTP clients
FakeServerName string `koanf:"fake_server_name"`
}
type Performance struct {
ConcurrencyCap bool `koanf:"limit_concurrency"`
MaxWorkers int `koanf:"max_workers"`
}
// UnixSocket represents the configuration for the Unix socket.
type UnixSocket struct {
// UnixSocketPath is the path to the Unix socket that HellPot will listen on if UseUnixSocket is set to true.
UnixSocketPath string `koanf:"unix_socket_path"`
// UseUnixSocket determines if we will listen for HTTP connections on a unix socket.
UseUnixSocket bool `koanf:"use_unix_socket"`
// UnixSocketPermissions are the octal permissions for the Unix socket.
UnixSocketPermissions uint32 `koanf:"unix_socket_permissions"`
}
// Router represents the configuration for the HTTP router.
type Router struct {
// 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 `koanf:"paths"`
CatchAll bool `koanf:"catchall"`
MakeRobots bool `koanf:"makerobots"`
ClientRules ClientRules `koanf:"client_rules"`
}
// HTTP represents the configuration for the HTTP server.
type HTTP struct {
Bind string `koanf:"bind_addr"`
Port int64 `koanf:"port"`
// ProxiedIPHeader is the HTTP Header containing the original IP of the client
// for usage by traditional reverse Proxy deployments.
ProxiedIPHeader string `koanf:"proxied_ip_header"`
Router Router `koanf:"router"`
UnixSocket UnixSocket `koanf:"unix_socket"`
ClientRules ClientRules `koanf:"client_rules"`
Experimental DevilsPlaythings `koanf:"experimental"`
}
// DevilsPlaythings - nothing to see here, move along.
type DevilsPlaythings struct {
// POSTMimicry when true will cause HellPot to respond to POST requests to the configured roads to hell
// with the content of the POST request entangled within the response. (Experimental)
POSTMimicry bool `koanf:"post_mimicry"`
}
// Customization represents the configuration for the customizations.
type Customization struct {
CustomHeffalump bool `koanf:"custom_heffalump"`
Grimoire string `koanf:"grimoire"`
}

View File

@ -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("yeeterson") {
t.Error("expected true got false")
}
if !rules.MatchUseragent("mc") {
t.Error("expected true got false")
}
if !rules.MatchUseragent("yooterson") {
t.Error("expected true got false")
}
if !rules.MatchUseragent("gooters%n") {
t.Error("expected true got false")
}
if rules.MatchUseragent("yootersongooterson") {
t.Error("expected false got true")
}
}

63
internal/config/setup.go Normal file
View File

@ -0,0 +1,63 @@
package config
import (
"fmt"
"io"
"os"
"strings"
"github.com/knadh/koanf/parsers/toml"
"github.com/knadh/koanf/providers/env"
"github.com/knadh/koanf/v2"
)
type readerProvider struct {
source io.Reader
}
func (r *readerProvider) ReadBytes() ([]byte, error) {
return io.ReadAll(r.source)
}
func (r *readerProvider) Read() (map[string]interface{}, error) {
b, err := r.ReadBytes()
if err != nil {
return nil, err
}
return toml.Parser().Unmarshal(b) //nolint:wrapcheck
}
func Setup(source io.Reader) (*Parameters, error) {
k := koanf.New(".")
if err := k.Load(Defaults, nil); err != nil {
return nil, fmt.Errorf("failed to load defaults: %w", err)
}
if source != nil {
if err := k.Load(&readerProvider{source}, toml.Parser()); err != nil {
return nil, fmt.Errorf("failed to read config: %w", err)
}
}
_ = k.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)
p := &Parameters{
source: k,
}
if err := k.Unmarshal("", p); err != nil {
return nil, fmt.Errorf("failed to unmarshal config: %w", err)
}
p.Logger.Outputs = append(p.Logger.Outputs, os.Stdout)
return p, nil
}

View File

@ -0,0 +1,125 @@
package config
import (
"bytes"
"testing"
)
func TestSetup(t *testing.T) {
t.Run("Success", SetupSuccess)
t.Run("NoFailureOnNilSource", SetupNoFailureOnNilSource)
t.Run("FailureOnReadConfig", SetupFailureOnReadConfig)
}
func SetupSuccess(t *testing.T) {
source := bytes.NewBufferString(`
[http]
port = 55
bind_addr = "5.5.5.5"
[http.router]
catchall = true
makerobots = false
`)
params, err := Setup(source)
if err != nil {
t.Errorf("Expected no error, got %v", err)
}
if params == nil {
t.Fatal("Expected params to be not nil")
}
if params.source.Get("http.port") != int64(55) {
t.Errorf("Expected 55, got (%T) %v", params.source.Get("http.port"), params.source.Get("http.port"))
}
if params.HTTP.Port != int64(55) {
t.Errorf("Expected 55, got %v", params.HTTP.Port)
}
if params.source.Get("http.bind_addr") != "5.5.5.5" {
t.Errorf("Expected 5.5.5.5, got %v", params.source.Get("http.bind_addr"))
}
if params.HTTP.Bind != "5.5.5.5" {
t.Errorf("Expected 5.5.5.5, got %v", params.HTTP.Bind)
}
if params.source.Get("http.router.catchall") != true {
t.Errorf("Expected true, got %v", params.source.Get("http.router.catchall"))
}
if params.HTTP.Router.CatchAll != true {
t.Errorf("Expected true, got %v", params.HTTP.Router.CatchAll)
}
}
func SetupNoFailureOnNilSource(t *testing.T) {
params, err := Setup(nil)
if err != nil {
t.Errorf("Expected no error, got %v", err)
}
if params == nil {
t.Fatal("Expected params to be not nil")
}
t.Run("DefaultsWithNilSource", func(t *testing.T) {
for _, needle := range []string{
"logger",
"http",
"performance",
"deception",
"http.router",
"http.router.paths",
} {
if params.source.Get(needle) == nil {
t.Errorf("Expected %q in map", needle)
}
}
// nolint:forcetypeassert
if params.HTTP.Port != Defaults.val["http"].(map[string]interface{})["port"].(int64) {
t.Errorf("Expected %v, got %v",
// nolint:forcetypeassert
Defaults.val["http"].(map[string]interface{})["port"].(int64), params.HTTP.Port,
)
}
// nolint:forcetypeassert
if params.HTTP.Bind != Defaults.val["http"].(map[string]interface{})["bind_addr"].(string) {
t.Errorf("Expected %v, got %v",
// nolint:forcetypeassert
Defaults.val["http"].(map[string]interface{})["bind_addr"].(string), params.HTTP.Bind,
)
}
if params.HTTP.Router.CatchAll !=
// nolint:forcetypeassert
Defaults.val["http"].(map[string]interface{})["router"].(map[string]interface{})["catchall"].(bool) {
t.Errorf("Expected %v, got %v",
// nolint:forcetypeassert
Defaults.val["http"].(map[string]interface{})["router"].(map[string]interface{})["catchall"].(bool),
params.HTTP.Router.CatchAll,
)
}
if len(params.HTTP.Router.Paths) !=
// nolint:forcetypeassert
len(Defaults.val["http"].(map[string]interface{})["router"].(map[string]interface{})["paths"].([]string)) {
t.Errorf("Expected %v, got %v",
// nolint:forcetypeassert
Defaults.val["http"].(map[string]interface{})["router"].(map[string]interface{})["paths"].([]string),
params.HTTP.Router.Paths,
)
}
})
}
func SetupFailureOnReadConfig(t *testing.T) {
source := bytes.NewBufferString(`{eeeeeeeeeeeeeeeeeeEE: 1}`)
params, err := Setup(source)
if err == nil {
t.Error("Expected error, got nil")
}
if params != nil {
t.Error("Expected params to be nil")
}
}

View File

@ -5,13 +5,12 @@ import (
"encoding/binary"
"fmt"
"os"
"runtime"
"strings"
"time"
"git.tcp.direct/kayos/common/squish"
"github.com/yunginnanet/HellPot/internal/config"
"github.com/yunginnanet/HellPot/internal/version"
)
const hellpot = "H4sIAAAAAAACA8VXvW7bQAze9QpZOGQUZNXntBD6Ahm7Gx1cx0jdRnKRKAUCdPDgQavOgB/QTxLZ1P3oRJ5Obo0CtnE5feSR30fylOhmfjv9PEtzwIXIj4dds/xw2jsequNB2gizXd3Mxad2O81PX7AAe+UNGneuR8aUOuTsqQUDXAMv1cJE5Tfbn6GaKz45kpid+lQc3zoNY5zmEUEt+jCGNZUjeYr0StZYmbwtwNavuCaUFWA8MjxVIImjNas6TPQT9Tnq4MnYJF0zkhVU4rLvqflscU/ox0Lg45qKTjoSmiLQPA+ZuTT7BbrckpfWKMkUquTErIPEYbPoKjamy6SjR0feGssPUMYTCDWEnrR8c0m7hJ2B4jekK2KUsBfa7bpTD0ftnmKPE9nN2IzcLc99vxhIUbszlwqrJoklpQWlI6AeQh9nDHXj2ldOvyat/vZdDxVfzZdbSuspRUe/+IKZtxq2GWlbZzS6jnrnDEXGCkXUGnahuTgAA+DY9HU8FUoYH3ji/q84HetDWmT/Y3ml6oX21/eCNzB46+6UuVTSQHXgGmzUTJT/zeNQ3zCvysEBuH3hER9CbhNa6FoLHSBfT2gmK/rFKCj/K1nTfcBduKHVwgjo+Y+HilXBEAqhKg1X6lQzMaIF6ZK6ipVILR0Awh16SWy9KsxvZXWbL34oGpNmMcPNdYFmiE40+qV9cg4Logjm2uXjukzK5a/kYf28WpaTn4u3zcvkfvX09GVTnuFfEYzBNujvr9+S5SafvL0Wj+uiWBSrsov/I6axmMXiLhYf40zE2TTOZnF2F2fNn2n0DpcvBxhQEAAA"
@ -21,23 +20,23 @@ func rc(s []string) string {
}
func process(in string) (s string) {
var v = strings.Split(config.Version, "")
var v = strings.Split(version.Version, "")
var maj, min, smin = "", "", ""
if len(config.Version) > 0 {
if len(version.Version) > 0 {
maj = v[0]
}
if len(config.Version) > 2 {
if len(version.Version) > 2 {
min = v[2]
}
if len(config.Version) > 4 {
if len(version.Version) > 4 {
smin = v[4]
}
defl8, _ := squish.UnpackStr(in)
sp := strings.Split(defl8, "|")
s = sp[0]
if smin == "" || len(config.Version) == 7 || config.Version == "dev" {
if smin == "" || len(version.Version) == 7 || version.Version == "dev" {
s = strings.ReplaceAll(s, "$1;40m.", "$1;40m")
if len(config.Version) == 7 || config.Version == "dev" {
if len(version.Version) == 7 || version.Version == "dev" {
s = strings.ReplaceAll(s, "$3;40m.", "$3;40m")
}
}
@ -53,8 +52,8 @@ func process(in string) (s string) {
for n := 1; n < 5; n++ {
s = cproc(s, fmt.Sprintf("%d", n))
}
if len(config.Version) == 7 || config.Version == "dev" {
maj = "[" + config.Version + "]"
if len(version.Version) == 7 || version.Version == "dev" {
maj = "[" + version.Version + "]"
min = ""
smin = ""
}
@ -72,8 +71,8 @@ func ru() uint32 {
return binary.LittleEndian.Uint32(b)
}
// printBanner prints our entropic banner
func printBanner() {
// Banner prints our entropic banner
func Banner() {
time.Sleep(5 * time.Millisecond)
println("\n" + process(hellpot) + "\n\n")
time.Sleep(5 * time.Millisecond)
@ -88,13 +87,3 @@ func bannerFail(errs ...error) {
}
os.Exit(1)
}
// Banner prints out our banner (using spooky magic)
func Banner() {
//goland:noinspection GoBoolExpressions
if runtime.GOOS == "windows" || config.NoColor {
_, _ = os.Stdout.Write([]byte(config.Title + " " + config.Version + "\n\n"))
return
}
printBanner()
}

View File

@ -2,32 +2,35 @@ package http
import (
"fmt"
"strings"
"git.tcp.direct/kayos/common/pool"
"github.com/valyala/fasthttp"
"github.com/yunginnanet/HellPot/internal/config"
)
var strs = pool.NewStringFactory()
func robotsTXT(ctx *fasthttp.RequestCtx) {
config := runningConfig.HTTP.Router
slog := log.With().
Str("USERAGENT", string(ctx.UserAgent())).
Str("REMOTE_ADDR", getRealRemote(ctx)).
Interface("URL", string(ctx.RequestURI())).Logger()
paths := &strings.Builder{}
paths.WriteString("User-agent: *\r\n")
pathBuf := strs.Get()
pathBuf.MustWriteString("User-agent: *\r\n")
for _, p := range config.Paths {
paths.WriteString("Disallow: ")
paths.WriteString(p)
paths.WriteString("\r\n")
pathBuf.MustWriteString("Disallow: ")
pathBuf.MustWriteString(p)
pathBuf.MustWriteString("\r\n")
}
paths.WriteString("\r\n")
pathBuf.MustWriteString("\r\n")
paths := pathBuf.String()
strs.MustPut(pathBuf)
slog.Debug().
Strs("PATHS", config.Paths).
Msg("SERVE_ROBOTS")
if _, err := fmt.Fprintf(ctx, paths.String()); err != nil {
if _, err := fmt.Fprintf(ctx, paths); err != nil {
slog.Error().Err(err).Msg("SERVE_ROBOTS_ERROR")
}
}

View File

@ -6,6 +6,7 @@ import (
"net/http"
"os"
"runtime"
"strconv"
"strings"
"time"
@ -20,10 +21,11 @@ import (
var (
log *zerolog.Logger
hellpotHeffalump *heffalump.Heffalump
runningConfig *config.Parameters
)
func getRealRemote(ctx *fasthttp.RequestCtx) string {
xrealip := string(ctx.Request.Header.Peek(config.HeaderName))
xrealip := string(ctx.Request.Header.Peek(runningConfig.HTTP.ProxiedIPHeader))
if len(xrealip) > 0 {
return xrealip
}
@ -43,15 +45,13 @@ func hellPot(ctx *fasthttp.RequestCtx) {
Str("REMOTE_ADDR", remoteAddr).
Interface("URL", string(ctx.RequestURI())).Logger()
for _, denied := range config.UseragentBlacklistMatchers {
if strings.Contains(string(ctx.UserAgent()), denied) {
slog.Trace().Msg("Ignoring useragent")
ctx.Error("Not found", http.StatusNotFound)
return
}
if runningConfig.HTTP.ClientRules.MatchUseragent(ctx.UserAgent()) {
slog.Trace().Msg("Ignoring useragent")
ctx.Error("Not found", http.StatusNotFound)
return
}
if config.Trace {
if runningConfig.Logger.Trace {
slog = slog.With().Str("caller", path).Logger()
}
@ -81,31 +81,29 @@ func hellPot(ctx *fasthttp.RequestCtx) {
}
func getSrv(r *router.Router) fasthttp.Server {
if !config.RestrictConcurrency {
config.MaxWorkers = fasthttp.DefaultConcurrency
if !runningConfig.Perf.ConcurrencyCap {
runningConfig.Perf.MaxWorkers = fasthttp.DefaultConcurrency
}
log = config.GetLogger()
log = runningConfig.GetLogger()
return fasthttp.Server{
// User defined server name
// Likely not useful if behind a reverse proxy without additional configuration of the proxy server.
Name: config.FakeServerName,
Name: runningConfig.Liar.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.
Nope.
*/
ReadTimeout: 5 * time.Second,
MaxRequestBodySize: 1 * 1024 * 1024,
// Help curb abuse of HellPot (we've always needed this badly)
MaxConnsPerIP: 10,
MaxConnsPerIP: 3,
MaxRequestsPerConn: 2,
Concurrency: config.MaxWorkers,
Concurrency: runningConfig.Perf.MaxWorkers,
// only accept GET requests
GetOnly: true,
// GetOnly: true,
// we don't care if a request ends up being handled by a different handler (in fact it probably will)
KeepHijackedConns: true,
@ -121,18 +119,19 @@ func getSrv(r *router.Router) fasthttp.Server {
}
// Serve starts our HTTP server and request router
func Serve() error {
func Serve(config *config.Parameters) error {
log = config.GetLogger()
runningConfig = config
switch config.UseCustomHeffalump {
switch config.Bespoke.CustomHeffalump {
case true:
content, err := os.ReadFile(config.Grimoire)
content, err := os.ReadFile(config.Bespoke.Grimoire)
if err != nil {
panic(err)
}
// Wasteful, but only done once at startup
src := string(content)
log.Info().Msgf("Using custom grimoire file '%s'", config.Grimoire)
log.Info().Msgf("Using custom grimoire file '%s'", config.Bespoke.Grimoire)
if len(src) < 1 {
panic("grimoire file was empty!")
@ -145,16 +144,16 @@ func Serve() error {
hellpotHeffalump = heffalump.NewDefaultHeffalump()
}
l := config.HTTPBind + ":" + config.HTTPPort
l := config.HTTP.Bind + ":" + strconv.Itoa(int(config.HTTP.Port))
r := router.New()
if config.MakeRobots && !config.CatchAll {
if config.HTTP.Router.MakeRobots && !config.HTTP.Router.CatchAll {
r.GET("/robots.txt", robotsTXT)
}
if !config.CatchAll {
for _, p := range config.Paths {
if !config.HTTP.Router.CatchAll {
for _, p := range config.HTTP.Router.Paths {
log.Trace().Str("caller", "router").Msgf("Add route: %s", p)
r.GET(fmt.Sprintf("/%s", p), hellPot)
}
@ -166,15 +165,15 @@ func Serve() error {
srv := getSrv(r)
//goland:noinspection GoBoolExpressions
if !config.UseUnixSocket || runtime.GOOS == "windows" {
if !config.HTTP.UnixSocket.UseUnixSocket || runtime.GOOS == "windows" {
log.Info().Str("caller", l).Msg("Listening and serving HTTP...")
return srv.ListenAndServe(l)
}
if len(config.UnixSocketPath) < 1 {
if len(config.HTTP.UnixSocket.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)
log.Info().Str("caller", config.HTTP.UnixSocket.UnixSocketPath).Msg("Listening and serving HTTP...")
return listenOnUnixSocket(config.HTTP.UnixSocket.UnixSocketPath, r)
}

View File

@ -9,11 +9,10 @@ import (
"github.com/fasthttp/router"
"github.com/valyala/fasthttp"
"github.com/yunginnanet/HellPot/internal/config"
)
func listenOnUnixSocket(addr string, r *router.Router) error {
config := runningConfig.HTTP.UnixSocket
var err error
var unixAddr *net.UnixAddr
var unixListener *net.UnixListener

63
internal/logger/logger.go Normal file
View File

@ -0,0 +1,63 @@
package logger
import (
"errors"
"io"
"os"
"sync"
"github.com/rs/zerolog"
)
// Configuration represents the configuration for the logger.
type Configuration struct {
Directory string `koanf:"directory"`
Debug bool `koanf:"debug"`
Trace bool `koanf:"trace"`
NoColor bool `koanf:"nocolor"`
DockerLogging bool `koanf:"docker_logging"`
// ConsoleTimeFormat sets the time format for the console.
// The string is passed to time.Format() down the line.
ConsoleTimeFormat string
Outputs []io.Writer `koanf:"-"`
}
var once = &sync.Once{}
func GetLoggerOnce() *zerolog.Logger {
var ret *zerolog.Logger
once.Do(func() {
ret = &_log
})
if ret == nil {
panic("i said once you fool")
}
return ret
}
var ErrNoOutputs = errors.New("no outputs provided")
var _log zerolog.Logger
func New(conf *Configuration) (zerolog.Logger, error) {
if len(conf.Outputs) == 0 {
return zerolog.Logger{}, ErrNoOutputs
}
for i, output := range conf.Outputs {
if output == os.Stdout || output == os.Stderr {
cw := zerolog.ConsoleWriter{Out: output, TimeFormat: conf.ConsoleTimeFormat, NoColor: conf.NoColor}
conf.Outputs = append(conf.Outputs[:i], conf.Outputs[i+1:]...)
conf.Outputs = append(conf.Outputs, cw)
}
}
_log = zerolog.New(zerolog.MultiLevelWriter(conf.Outputs...)).With().Timestamp().Logger()
_log = _log.Level(zerolog.InfoLevel)
if conf.Debug {
_log = _log.Level(zerolog.DebugLevel)
}
if conf.Trace {
_log = _log.Level(zerolog.TraceLevel)
}
return _log, nil
}

View File

@ -0,0 +1,3 @@
package hellscope
func Measured

View File

@ -0,0 +1,26 @@
package version
import (
"runtime/debug"
)
const HP = "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]
}
}