twins/config.go

269 lines
5.6 KiB
Go
Raw Normal View History

2020-10-29 20:35:48 +00:00
package main
import (
"crypto/tls"
2020-10-29 20:35:48 +00:00
"errors"
"io/ioutil"
2020-10-30 00:17:23 +00:00
"log"
2020-11-04 20:48:55 +00:00
"net/url"
2020-10-29 20:35:48 +00:00
"os"
"regexp"
"strconv"
"strings"
2020-10-29 20:35:48 +00:00
"github.com/kballard/go-shellquote"
2020-11-04 20:48:55 +00:00
"github.com/yookoala/gofast"
2020-10-29 20:35:48 +00:00
"gopkg.in/yaml.v3"
)
type pathConfig struct {
// Path to match
Path string
2020-10-30 00:17:23 +00:00
// Resource to serve
2020-11-19 17:24:12 +00:00
Root string
Command string
Proxy string
Redirect string
2020-10-29 20:35:48 +00:00
// Request input
Input string
// Request sensitive input
SensitiveInput string
// Follow symbolic links
SymLinks bool
// Serve hidden files and directories
2020-11-19 16:57:28 +00:00
Hidden bool
2020-11-16 17:17:42 +00:00
// List directory
List bool
2020-10-31 01:31:13 +00:00
2020-11-04 20:48:55 +00:00
// Content type
Type string
2020-11-10 04:10:53 +00:00
// Cache duration
2020-11-10 05:12:22 +00:00
Cache string
2020-11-10 04:10:53 +00:00
2020-11-04 20:48:55 +00:00
// FastCGI server address
FastCGI string
2020-11-12 17:56:59 +00:00
// Log file
Log string
2020-11-10 05:12:22 +00:00
r *regexp.Regexp
cmd []string
cache int64
2020-10-29 20:35:48 +00:00
}
type hostConfig struct {
Cert string
Key string
Paths []*pathConfig
cert *tls.Certificate
}
2020-10-29 20:35:48 +00:00
type serverConfig struct {
Listen string
2020-11-17 19:31:22 +00:00
Types map[string]string
Hosts map[string]*hostConfig
DisableSize bool
2020-11-05 20:57:28 +00:00
SaneEOL bool
2020-11-04 20:48:55 +00:00
hostname string
port int
2020-11-04 21:07:06 +00:00
fcgiPools map[string]gofast.ConnFactory
2020-10-29 20:35:48 +00:00
}
2020-11-10 05:12:22 +00:00
const cacheUnset = -1965
var config *serverConfig
2020-10-29 20:35:48 +00:00
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)
2020-10-29 20:35:48 +00:00
if err != nil {
return err
}
config = newConfig
if config.Listen == "" {
log.Fatal("listen address must be specified")
}
2020-10-29 20:35:48 +00:00
2020-11-17 19:31:22 +00:00
if config.Types == nil {
config.Types = make(map[string]string)
}
2020-11-05 20:57:28 +00:00
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)
2020-10-29 20:35:48 +00:00
}
}
2020-11-17 19:31:22 +00:00
// Default content types
if config.Types[".htm"] == "" {
config.Types[".htm"] = htmlType
}
if config.Types[".html"] == "" {
config.Types[".html"] = htmlType
}
if config.Types[".gmi"] == "" {
config.Types[".gmi"] = geminiType
}
if config.Types[".gemini"] == "" {
config.Types[".gemini"] = geminiType
}
defaultHost := config.Hosts["default"]
delete(config.Hosts, "default")
2020-11-04 21:07:06 +00:00
config.fcgiPools = make(map[string]gofast.ConnFactory)
2020-11-04 20:48:55 +00:00
for hostname, host := range config.Hosts {
hostname = strings.ToLower(hostname)
if defaultHost != nil {
if host.Cert == "" {
host.Cert = defaultHost.Cert
}
if host.Key == "" {
host.Key = defaultHost.Key
}
if len(defaultHost.Paths) == 1 {
defaultPath := defaultHost.Paths[0]
for _, serve := range host.Paths {
if defaultPath.Root != "" && serve.Root == "" {
serve.Root = defaultPath.Root
}
if defaultPath.Command != "" && serve.Command == "" {
serve.Command = defaultPath.Command
}
2020-11-19 17:24:12 +00:00
if defaultPath.Proxy != "" && serve.Proxy == "" {
serve.Proxy = defaultPath.Proxy
}
if defaultPath.SymLinks {
serve.SymLinks = defaultPath.SymLinks
}
2020-11-19 16:57:28 +00:00
if defaultPath.Hidden {
serve.Hidden = defaultPath.Hidden
}
2020-11-16 17:17:42 +00:00
if defaultPath.List {
serve.List = defaultPath.List
}
if defaultPath.Cache != "" && serve.Cache == "" {
serve.Cache = defaultPath.Cache
}
if defaultPath.FastCGI != "" && serve.FastCGI == "" {
serve.FastCGI = defaultPath.FastCGI
}
2020-11-12 17:56:59 +00:00
if defaultPath.Log != "" && serve.Log == "" {
serve.Log = defaultPath.Log
}
}
} else if len(defaultHost.Paths) > 1 {
log.Fatal("only one path may be defined for the default host")
}
}
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 == "" {
2020-11-19 17:24:12 +00:00
log.Fatal("a path must be specified in each serve entry")
}
if serve.Path[0] == '^' {
serve.r = regexp.MustCompile(serve.Path)
}
2020-11-19 17:24:12 +00:00
var resources int
if serve.Root != "" {
resources++
}
if serve.Command != "" {
resources++
}
if serve.Proxy != "" {
resources++
}
if serve.Redirect != "" {
resources++
}
if resources == 0 {
log.Fatalf("a resource must specified for path %s%s", hostname, serve.Path)
} else if resources > 1 {
log.Fatalf("only one resource (root, command, proxy or redirect) may specified for path %s%s", hostname, serve.Path)
}
2020-11-10 05:12:22 +00:00
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)
}
}
2020-11-19 17:24:12 +00:00
if serve.Command != "" {
serve.cmd, err = shellquote.Split(serve.Command)
if err != nil {
log.Fatalf("failed to parse command %s: %s", serve.cmd, err)
}
} else if serve.FastCGI != "" {
2020-11-04 20:48:55 +00:00
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)
}
2020-11-04 21:07:06 +00:00
config.fcgiPools[serve.FastCGI] = gofast.SimpleConnFactory(f.Scheme, f.Host+f.Path)
2020-11-04 20:48:55 +00:00
}
}
}
2020-10-29 20:35:48 +00:00
}
return nil
}