forked from tslocum/twins
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
271 lines
5.8 KiB
271 lines
5.8 KiB
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 |
|
Command string |
|
Proxy string |
|
Redirect string |
|
|
|
// Cache duration |
|
Cache string |
|
|
|
// FastCGI server address |
|
FastCGI string |
|
|
|
// Serve hidden files and directories |
|
Hidden bool |
|
|
|
// Request input |
|
Input string |
|
|
|
// Language |
|
Lang string |
|
|
|
// List directory |
|
List bool |
|
|
|
// Log file |
|
Log string |
|
|
|
// Request sensitive input |
|
SensitiveInput string |
|
|
|
// Follow symbolic links |
|
SymLinks bool |
|
|
|
// Content type |
|
Type 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 |
|
Types map[string]string |
|
Hosts map[string]*hostConfig |
|
DisableHTTPS bool |
|
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.Types == nil { |
|
config.Types = make(map[string]string) |
|
} |
|
|
|
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) |
|
} |
|
} |
|
|
|
// 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") |
|
|
|
config.fcgiPools = make(map[string]gofast.ConnFactory) |
|
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 { |
|
// Resources |
|
if defaultPath.Root != "" && serve.Root == "" { |
|
serve.Root = defaultPath.Root |
|
} else if defaultPath.Command != "" && serve.Command == "" { |
|
serve.Command = defaultPath.Command |
|
} else if defaultPath.Proxy != "" && serve.Proxy == "" { |
|
serve.Proxy = defaultPath.Proxy |
|
} |
|
// Attributes |
|
if defaultPath.Cache != "" && serve.Cache == "" { |
|
serve.Cache = defaultPath.Cache |
|
} |
|
if defaultPath.FastCGI != "" && serve.FastCGI == "" { |
|
serve.FastCGI = defaultPath.FastCGI |
|
} |
|
if defaultPath.Hidden { |
|
serve.Hidden = defaultPath.Hidden |
|
} |
|
if defaultPath.Lang != "" && serve.Lang == "" { |
|
serve.Lang = defaultPath.Lang |
|
} |
|
if defaultPath.List { |
|
serve.List = defaultPath.List |
|
} |
|
if defaultPath.Log != "" && serve.Log == "" { |
|
serve.Log = defaultPath.Log |
|
} |
|
if defaultPath.SymLinks { |
|
serve.SymLinks = defaultPath.SymLinks |
|
} |
|
} |
|
} 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 == "" { |
|
log.Fatal("a path must be specified in each serve entry") |
|
} |
|
if serve.Path[0] == '^' { |
|
serve.r = regexp.MustCompile(serve.Path) |
|
} |
|
|
|
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) |
|
} |
|
|
|
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.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 != "" { |
|
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) |
|
} |
|
} |
|
} |
|
} |
|
|
|
return nil |
|
}
|
|
|