1
1
Fork 0
mirror of https://tildegit.org/solderpunk/molly-brown synced 2024-05-13 10:36:02 +02:00
molly-brown/config.go
Solderpunk 212c9f79fb A rather extensive refactor.
Basically the function formerly known as do_main() in main.go has
been renamed launch() and moved into launch.go.  Now there are
main.go and main_unix.go files implementing minmial main()
functions which load a config and pass it to launch.  This allows
separating unix-specific security stuff (both the actual system
calls which won't compile on other platforms and the definition
of command line switches) out from the platform agnostic
implementation of the main server logic.  It also simplifies the
interaction of relative paths in config files with chrooting.

Docs still need updating...
2023-02-23 18:49:15 +01:00

255 lines
7.2 KiB
Go

package main
import (
"errors"
"github.com/BurntSushi/toml"
"log"
"os"
"path/filepath"
"strings"
)
type Config struct {
Port int
Hostname string
CertPath string
KeyPath string
DocBase string
HomeDocBase string
GeminiExt string
DefaultLang string
DefaultEncoding string
AccessLog string
ErrorLog string
ReadMollyFiles bool
TempRedirects map[string]string
PermRedirects map[string]string
MimeOverrides map[string]string
CGIPaths []string
SCGIPaths map[string]string
CertificateZones map[string][]string
DirectorySort string
DirectorySubdirsFirst bool
DirectoryReverse bool
DirectoryTitles bool
}
type MollyFile struct {
GeminiExt string
TempRedirects map[string]string
PermRedirects map[string]string
MimeOverrides map[string]string
CertificateZones map[string][]string
DefaultLang string
DefaultEncoding string
DirectorySort string
DirectorySubdirsFirst bool
DirectoryReverse bool
DirectoryTitles bool
}
func getConfig(filename string) (Config, error) {
var config Config
// Defaults
config.Port = 1965
config.Hostname = "localhost"
config.CertPath = "cert.pem"
config.KeyPath = "key.pem"
config.DocBase = "/var/gemini/"
config.HomeDocBase = "users"
config.GeminiExt = "gmi"
config.DefaultLang = ""
config.DefaultEncoding = ""
config.AccessLog = "access.log"
config.ErrorLog = ""
config.TempRedirects = make(map[string]string)
config.PermRedirects = make(map[string]string)
config.CGIPaths = make([]string, 0)
config.SCGIPaths = make(map[string]string)
config.DirectorySort = "Name"
config.DirectorySubdirsFirst = false
// Return defaults if no filename given
if filename == "" {
return config, nil
}
// Attempt to overwrite defaults from file
_, err := toml.DecodeFile(filename, &config)
if err != nil {
return config, err
}
// Force hostname to lowercase
config.Hostname = strings.ToLower(config.Hostname)
// Validate pseudo-enums
switch config.DirectorySort {
case "Name", "Size", "Time":
default:
return config, errors.New("Invalid DirectorySort value.")
}
// Absolutise paths
config.DocBase, err = filepath.Abs(config.DocBase)
if err != nil {
return config, err
}
config.CertPath, err = filepath.Abs(config.CertPath)
if err != nil {
return config, err
}
config.KeyPath, err = filepath.Abs(config.KeyPath)
if err != nil {
return config, err
}
if config.AccessLog != "" && config.AccessLog != "-" {
config.AccessLog, err = filepath.Abs(config.AccessLog)
if err != nil {
return config, err
}
}
if config.ErrorLog != "" {
config.ErrorLog, err = filepath.Abs(config.ErrorLog)
if err != nil {
return config, err
}
}
// Absolutise CGI paths
for index, cgiPath := range config.CGIPaths {
if !filepath.IsAbs(cgiPath) {
config.CGIPaths[index] = filepath.Join(config.DocBase, cgiPath)
}
}
// Expand CGI paths
var cgiPaths []string
for _, cgiPath := range config.CGIPaths {
expandedPaths, err := filepath.Glob(cgiPath)
if err != nil {
return config, errors.New("Error expanding CGI path glob " + cgiPath + ": " + err.Error())
}
cgiPaths = append(cgiPaths, expandedPaths...)
}
config.CGIPaths = cgiPaths
// Absolutise SCGI paths
for index, scgiPath := range config.SCGIPaths {
config.SCGIPaths[index], err = filepath.Abs( scgiPath)
if err != nil {
return config, err
}
}
// Validate redirects
for _, value := range config.TempRedirects {
if strings.Contains(value, "://") && !strings.HasPrefix(value, "gemini://") {
return config, errors.New("Invalid cross-protocol redirect to " + value)
}
}
for _, value := range config.PermRedirects {
if strings.Contains(value, "://") && !strings.HasPrefix(value, "gemini://") {
return config, errors.New("Ignoring cross-protocol redirect to " + value)
}
}
return config, nil
}
func parseMollyFiles(path string, config *Config) {
// Replace config variables which use pointers with new ones,
// so that changes made here aren't reflected everywhere.
newTempRedirects := make(map[string]string)
for key, value := range config.TempRedirects {
newTempRedirects[key] = value
}
config.TempRedirects = newTempRedirects
newPermRedirects := make(map[string]string)
for key, value := range config.PermRedirects {
newPermRedirects[key] = value
}
config.PermRedirects = newPermRedirects
newMimeOverrides := make(map[string]string)
for key, value := range config.MimeOverrides {
newMimeOverrides[key] = value
}
config.MimeOverrides = newMimeOverrides
newCertificateZones := make(map[string][]string)
for key, value := range config.CertificateZones {
newCertificateZones[key] = value
}
config.CertificateZones = newCertificateZones
// Initialise MollyFile using main Config
var mollyFile MollyFile
mollyFile.GeminiExt = config.GeminiExt
mollyFile.DefaultLang = config.DefaultLang
mollyFile.DefaultEncoding = config.DefaultEncoding
mollyFile.DirectorySort = config.DirectorySort
mollyFile.DirectorySubdirsFirst = config.DirectorySubdirsFirst
mollyFile.DirectoryReverse = config.DirectoryReverse
mollyFile.DirectoryTitles = config.DirectoryTitles
// Build list of directories to check
var dirs []string
dirs = append(dirs, path)
for {
if path == filepath.Clean(config.DocBase) {
break
}
subpath := filepath.Dir(path)
dirs = append(dirs, subpath)
path = subpath
}
// Parse files in reverse order
for i := len(dirs) - 1; i >= 0; i-- {
dir := dirs[i]
// Break out of the loop if a directory doesn't exist
_, err := os.Stat(dir)
if os.IsNotExist(err) {
break
}
// Construct path for a .molly file in this dir
mollyPath := filepath.Join(dir, ".molly")
_, err = os.Stat(mollyPath)
if err != nil {
continue
}
// If the file exists and we can read it, try to parse it
_, err = toml.DecodeFile(mollyPath, &mollyFile)
if err != nil {
log.Println("Error parsing .molly file " + mollyPath + ": " + err.Error())
continue
}
// Overwrite main Config using MollyFile
config.GeminiExt = mollyFile.GeminiExt
config.DefaultLang = mollyFile.DefaultLang
config.DefaultEncoding = mollyFile.DefaultEncoding
config.DirectorySort = mollyFile.DirectorySort
config.DirectorySubdirsFirst = mollyFile.DirectorySubdirsFirst
config.DirectoryReverse = mollyFile.DirectoryReverse
config.DirectoryTitles = mollyFile.DirectoryTitles
for key, value := range mollyFile.TempRedirects {
if strings.Contains(value, "://") && !strings.HasPrefix(value, "gemini://") {
log.Println("Ignoring cross-protocol redirect to " + value + " in .molly file " + mollyPath)
continue
}
config.TempRedirects[key] = value
}
for key, value := range mollyFile.PermRedirects {
if strings.Contains(value, "://") && !strings.HasPrefix(value, "gemini://") {
log.Println("Ignoring cross-protocol redirect to " + value + " in .molly file " + mollyPath)
continue
}
config.PermRedirects[key] = value
}
for key, value := range mollyFile.MimeOverrides {
config.MimeOverrides[key] = value
}
for key, value := range mollyFile.CertificateZones {
config.CertificateZones[key] = value
}
}
}