package main import ( "crypto/tls" "errors" "io/ioutil" "log" "net/url" "os" "regexp" "strconv" "strings" "github.com/kballard/go-shellquote" "github.com/yookoala/gofast" "gopkg.in/yaml.v3" ) type pathConfig struct { // Path to match Path string // Resource to serve Root string Proxy string Command string // Request input Input string // Request sensitive input SensitiveInput string // List directory entries ListDirectory bool // Content type Type string // Cache duration Cache string // FastCGI server address FastCGI string r *regexp.Regexp cmd []string cache int64 } type hostConfig struct { Cert string Key string Paths []*pathConfig cert *tls.Certificate } type serverConfig struct { Listen string Hosts map[string]*hostConfig DisableSize bool SaneEOL bool hostname string port int fcgiPools map[string]gofast.ConnFactory } const cacheUnset = -1965 var config *serverConfig func readconfig(configPath string) error { if configPath == "" { return errors.New("file unspecified") } if _, err := os.Stat(configPath); os.IsNotExist(err) { return errors.New("file not found") } configData, err := ioutil.ReadFile(configPath) if err != nil { return err } var newConfig *serverConfig err = yaml.Unmarshal(configData, &newConfig) if err != nil { return err } config = newConfig if config.Listen == "" { log.Fatal("listen address must be specified") } if config.SaneEOL { newLine = "\n" } else { newLine = "\r\n" } split := strings.Split(config.Listen, ":") if len(split) != 2 { config.hostname = config.Listen config.Listen += ":1965" } else { config.hostname = split[0] config.port, err = strconv.Atoi(split[1]) if err != nil { log.Fatalf("invalid port specified: %s", err) } } config.fcgiPools = make(map[string]gofast.ConnFactory) for hostname, host := range config.Hosts { if host.Cert == "" || host.Key == "" { log.Fatal("a certificate must be specified for each domain (gemini requires TLS for all connections)") } cert, err := tls.LoadX509KeyPair(host.Cert, host.Key) if err != nil { log.Fatalf("failed to load certificate: %s", err) } host.cert = &cert for _, serve := range host.Paths { if serve.Path == "" { log.Fatal("path must be specified in serve entry") } else if (serve.Root != "" && (serve.Proxy != "" || serve.Command != "")) || (serve.Proxy != "" && (serve.Root != "" || serve.Command != "")) || (serve.Command != "" && (serve.Root != "" || serve.Proxy != "")) { log.Fatal("only one root, proxy or command resource may specified for a path") } if serve.Path[0] == '^' { serve.r = regexp.MustCompile(serve.Path) } serve.cache = cacheUnset if serve.Cache != "" { serve.cache, err = strconv.ParseInt(serve.Cache, 10, 64) if err != nil { log.Fatalf("failed to parse cache duration for path %s: %s", serve.Path, err) } } if serve.FastCGI != "" { if serve.Root == "" { log.Fatalf("root must be specified to use fastcgi resource %s of path %s%s", serve.FastCGI, hostname, serve.Path) } if config.fcgiPools[serve.FastCGI] == nil { f, err := url.Parse(serve.FastCGI) if err != nil { log.Fatalf("failed to parse fastcgi resource %s: %s", serve.FastCGI, err) } config.fcgiPools[serve.FastCGI] = gofast.SimpleConnFactory(f.Scheme, f.Host+f.Path) } } else if serve.Command != "" { serve.cmd, err = shellquote.Split(serve.Command) if err != nil { log.Fatalf("failed to parse command %s: %s", serve.cmd, err) } } } } return nil }