Another big refactor, splitting the Config struct in two.

The split reflects that between variables which can and cannot be
overridden by .molly files, and this greatly simplifies the
processing of said files, getting rid of the need for lots of
ugly temporary variable thrashing.
This commit is contained in:
Solderpunk 2023-02-25 11:29:13 +01:00
parent e70ec82594
commit eb85a6e94c
12 changed files with 129 additions and 167 deletions

View File

@ -27,7 +27,7 @@ func enforceCertificateValidity(clientCerts []*x509.Certificate, conn net.Conn,
} }
} }
func handleCertificateZones(URL *url.URL, clientCerts []*x509.Certificate, config Config, conn net.Conn, logEntry *LogEntry) { func handleCertificateZones(URL *url.URL, clientCerts []*x509.Certificate, config UserConfig, conn net.Conn, logEntry *LogEntry) {
authorised := true authorised := true
for zone, allowedFingerprints := range config.CertificateZones { for zone, allowedFingerprints := range config.CertificateZones {
matched, err := regexp.Match(zone, []byte(URL.Path)) matched, err := regexp.Match(zone, []byte(URL.Path))

200
config.go
View File

@ -9,195 +9,167 @@ import (
"strings" "strings"
) )
type Config struct { type SysConfig struct {
Port int Port int
Hostname string Hostname string
CertPath string CertPath string
KeyPath string KeyPath string
DocBase string
HomeDocBase string
GeminiExt string
DefaultLang string
DefaultEncoding string
AccessLog string AccessLog string
ErrorLog string ErrorLog string
ReadMollyFiles bool DocBase string
TempRedirects map[string]string HomeDocBase string
PermRedirects map[string]string
MimeOverrides map[string]string
CGIPaths []string CGIPaths []string
SCGIPaths map[string]string SCGIPaths map[string]string
CertificateZones map[string][]string ReadMollyFiles bool
AllowTLS12 bool AllowTLS12 bool
DirectorySort string
DirectorySubdirsFirst bool
DirectoryReverse bool
DirectoryTitles bool
} }
type MollyFile struct { type UserConfig struct {
GeminiExt string GeminiExt string
DefaultLang string
DefaultEncoding string
TempRedirects map[string]string TempRedirects map[string]string
PermRedirects map[string]string PermRedirects map[string]string
MimeOverrides map[string]string MimeOverrides map[string]string
CertificateZones map[string][]string CertificateZones map[string][]string
DefaultLang string
DefaultEncoding string
DirectorySort string DirectorySort string
DirectorySubdirsFirst bool DirectorySubdirsFirst bool
DirectoryReverse bool DirectoryReverse bool
DirectoryTitles bool DirectoryTitles bool
} }
func getConfig(filename string) (Config, error) { func getConfig(filename string) (SysConfig, UserConfig, error) {
var config Config var sysConfig SysConfig
var userConfig UserConfig
// Defaults // Defaults
config.Port = 1965 sysConfig.Port = 1965
config.Hostname = "localhost" sysConfig.Hostname = "localhost"
config.CertPath = "cert.pem" sysConfig.CertPath = "cert.pem"
config.KeyPath = "key.pem" sysConfig.KeyPath = "key.pem"
config.DocBase = "/var/gemini/" sysConfig.AccessLog = "access.log"
config.HomeDocBase = "users" sysConfig.ErrorLog = ""
config.GeminiExt = "gmi" sysConfig.DocBase = "/var/gemini/"
config.DefaultLang = "" sysConfig.HomeDocBase = "users"
config.DefaultEncoding = "" sysConfig.CGIPaths = make([]string, 0)
config.AccessLog = "access.log" sysConfig.SCGIPaths = make(map[string]string)
config.ErrorLog = "" sysConfig.ReadMollyFiles = false
config.TempRedirects = make(map[string]string) sysConfig.AllowTLS12 = true
config.PermRedirects = make(map[string]string)
config.CGIPaths = make([]string, 0) userConfig.GeminiExt = "gmi"
config.SCGIPaths = make(map[string]string) userConfig.DefaultLang = ""
config.AllowTLS12 = true userConfig.DefaultEncoding = ""
config.DirectorySort = "Name" userConfig.TempRedirects = make(map[string]string)
config.DirectorySubdirsFirst = false userConfig.PermRedirects = make(map[string]string)
userConfig.DirectorySort = "Name"
userConfig.DirectorySubdirsFirst = false
// Return defaults if no filename given // Return defaults if no filename given
if filename == "" { if filename == "" {
return config, nil return sysConfig, userConfig, nil
} }
// Attempt to overwrite defaults from file // Attempt to overwrite defaults from file
_, err := toml.DecodeFile(filename, &config) _, err := toml.DecodeFile(filename, &sysConfig)
if err != nil { if err != nil {
return config, err return sysConfig, userConfig, err
}
_, err = toml.DecodeFile(filename, &userConfig)
if err != nil {
return sysConfig, userConfig, err
} }
// Force hostname to lowercase // Force hostname to lowercase
config.Hostname = strings.ToLower(config.Hostname) sysConfig.Hostname = strings.ToLower(sysConfig.Hostname)
// Validate pseudo-enums // Validate pseudo-enums
switch config.DirectorySort { switch userConfig.DirectorySort {
case "Name", "Size", "Time": case "Name", "Size", "Time":
default: default:
return config, errors.New("Invalid DirectorySort value.") return sysConfig, userConfig, errors.New("Invalid DirectorySort value.")
} }
// Absolutise paths // Absolutise paths
config.DocBase, err = filepath.Abs(config.DocBase) sysConfig.DocBase, err = filepath.Abs(sysConfig.DocBase)
if err != nil { if err != nil {
return config, err return sysConfig, userConfig, err
} }
config.CertPath, err = filepath.Abs(config.CertPath) sysConfig.CertPath, err = filepath.Abs(sysConfig.CertPath)
if err != nil { if err != nil {
return config, err return sysConfig, userConfig, err
} }
config.KeyPath, err = filepath.Abs(config.KeyPath) sysConfig.KeyPath, err = filepath.Abs(sysConfig.KeyPath)
if err != nil { if err != nil {
return config, err return sysConfig, userConfig, err
} }
if config.AccessLog != "" && config.AccessLog != "-" { if sysConfig.AccessLog != "" && sysConfig.AccessLog != "-" {
config.AccessLog, err = filepath.Abs(config.AccessLog) sysConfig.AccessLog, err = filepath.Abs(sysConfig.AccessLog)
if err != nil { if err != nil {
return config, err return sysConfig, userConfig, err
} }
} }
if config.ErrorLog != "" { if sysConfig.ErrorLog != "" {
config.ErrorLog, err = filepath.Abs(config.ErrorLog) sysConfig.ErrorLog, err = filepath.Abs(sysConfig.ErrorLog)
if err != nil { if err != nil {
return config, err return sysConfig, userConfig, err
} }
} }
// Absolutise CGI paths // Absolutise CGI paths
for index, cgiPath := range config.CGIPaths { for index, cgiPath := range sysConfig.CGIPaths {
if !filepath.IsAbs(cgiPath) { if !filepath.IsAbs(cgiPath) {
config.CGIPaths[index] = filepath.Join(config.DocBase, cgiPath) sysConfig.CGIPaths[index] = filepath.Join(sysConfig.DocBase, cgiPath)
} }
} }
// Expand CGI paths // Expand CGI paths
var cgiPaths []string var cgiPaths []string
for _, cgiPath := range config.CGIPaths { for _, cgiPath := range sysConfig.CGIPaths {
expandedPaths, err := filepath.Glob(cgiPath) expandedPaths, err := filepath.Glob(cgiPath)
if err != nil { if err != nil {
return config, errors.New("Error expanding CGI path glob " + cgiPath + ": " + err.Error()) return sysConfig, userConfig, errors.New("Error expanding CGI path glob " + cgiPath + ": " + err.Error())
} }
cgiPaths = append(cgiPaths, expandedPaths...) cgiPaths = append(cgiPaths, expandedPaths...)
} }
config.CGIPaths = cgiPaths sysConfig.CGIPaths = cgiPaths
// Absolutise SCGI paths // Absolutise SCGI paths
for index, scgiPath := range config.SCGIPaths { for index, scgiPath := range sysConfig.SCGIPaths {
config.SCGIPaths[index], err = filepath.Abs( scgiPath) sysConfig.SCGIPaths[index], err = filepath.Abs( scgiPath)
if err != nil { if err != nil {
return config, err return sysConfig, userConfig, err
} }
} }
// Validate redirects // Validate redirects
for _, value := range config.TempRedirects { for _, value := range userConfig.TempRedirects {
if strings.Contains(value, "://") && !strings.HasPrefix(value, "gemini://") { if strings.Contains(value, "://") && !strings.HasPrefix(value, "gemini://") {
return config, errors.New("Invalid cross-protocol redirect to " + value) return sysConfig, userConfig, errors.New("Invalid cross-protocol redirect to " + value)
} }
} }
for _, value := range config.PermRedirects { for _, value := range userConfig.PermRedirects {
if strings.Contains(value, "://") && !strings.HasPrefix(value, "gemini://") { if strings.Contains(value, "://") && !strings.HasPrefix(value, "gemini://") {
return config, errors.New("Ignoring cross-protocol redirect to " + value) return sysConfig, userConfig, errors.New("Ignoring cross-protocol redirect to " + value)
} }
} }
return config, nil return sysConfig, userConfig, nil
} }
func parseMollyFiles(path string, config *Config) { func parseMollyFiles(path string, docBase string, config UserConfig) UserConfig {
// Replace config variables which use pointers with new ones, // Replace config variables which use pointers with new ones,
// so that changes made here aren't reflected everywhere. // so that changes made here aren't reflected everywhere.
newTempRedirects := make(map[string]string) config.TempRedirects = make(map[string]string)
for key, value := range config.TempRedirects { config.PermRedirects = make(map[string]string)
newTempRedirects[key] = value config.MimeOverrides = make(map[string]string)
} config.CertificateZones = make(map[string][]string)
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 // Build list of directories to check
var dirs []string var dirs []string
dirs = append(dirs, path) dirs = append(dirs, path)
for { for {
if path == filepath.Clean(config.DocBase) { if path == filepath.Clean(docBase) {
break break
} }
subpath := filepath.Dir(path) subpath := filepath.Dir(path)
@ -219,38 +191,28 @@ func parseMollyFiles(path string, config *Config) {
continue continue
} }
// If the file exists and we can read it, try to parse it // If the file exists and we can read it, try to parse it
_, err = toml.DecodeFile(mollyPath, &mollyFile) _, err = toml.DecodeFile(mollyPath, &config)
if err != nil { if err != nil {
log.Println("Error parsing .molly file " + mollyPath + ": " + err.Error()) log.Println("Error parsing .molly file " + mollyPath + ": " + err.Error())
continue continue
} }
// Overwrite main Config using MollyFile
config.GeminiExt = mollyFile.GeminiExt for key, value := range config.TempRedirects {
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://") { if strings.Contains(value, "://") && !strings.HasPrefix(value, "gemini://") {
log.Println("Ignoring cross-protocol redirect to " + value + " in .molly file " + mollyPath) log.Println("Ignoring cross-protocol redirect to " + value + " in .molly file " + mollyPath)
continue continue
} }
config.TempRedirects[key] = value config.TempRedirects[key] = value
} }
for key, value := range mollyFile.PermRedirects { for key, value := range config.PermRedirects {
if strings.Contains(value, "://") && !strings.HasPrefix(value, "gemini://") { if strings.Contains(value, "://") && !strings.HasPrefix(value, "gemini://") {
log.Println("Ignoring cross-protocol redirect to " + value + " in .molly file " + mollyPath) log.Println("Ignoring cross-protocol redirect to " + value + " in .molly file " + mollyPath)
continue continue
} }
config.PermRedirects[key] = value config.PermRedirects[key] = value
} }
for key, value := range mollyFile.MimeOverrides {
config.MimeOverrides[key] = value
}
for key, value := range mollyFile.CertificateZones {
config.CertificateZones[key] = value
}
} }
return config
} }

View File

@ -11,7 +11,7 @@ import (
"strings" "strings"
) )
func generateDirectoryListing(URL *url.URL, path string, config Config) (string, error) { func generateDirectoryListing(URL *url.URL, path string, config UserConfig) (string, error) {
var listing string var listing string
files, err := ioutil.ReadDir(path) files, err := ioutil.ReadDir(path)
if err != nil { if err != nil {
@ -82,7 +82,7 @@ func generateDirectoryListing(URL *url.URL, path string, config Config) (string,
return listing, nil return listing, nil
} }
func generatePrettyFileLabel(info os.FileInfo, path string, config Config) string { func generatePrettyFileLabel(info os.FileInfo, path string, config UserConfig) string {
var size string var size string
if info.IsDir() { if info.IsDir() {
size = " " size = " "

View File

@ -15,7 +15,7 @@ import (
"time" "time"
) )
func handleCGI(config Config, path string, cgiPath string, URL *url.URL, logEntry *LogEntry, conn net.Conn) { func handleCGI(config SysConfig, path string, cgiPath string, URL *url.URL, logEntry *LogEntry, conn net.Conn) {
// Find the shortest leading part of path which maps to an executable file. // Find the shortest leading part of path which maps to an executable file.
// Call this part scriptPath, and everything after it pathInfo. // Call this part scriptPath, and everything after it pathInfo.
components := strings.Split(path, "/") components := strings.Split(path, "/")
@ -86,7 +86,7 @@ func handleCGI(config Config, path string, cgiPath string, URL *url.URL, logEntr
conn.Write(response) conn.Write(response)
} }
func handleSCGI(URL *url.URL, scgiPath string, scgiSocket string, config Config, logEntry *LogEntry, conn net.Conn) { func handleSCGI(URL *url.URL, scgiPath string, scgiSocket string, config SysConfig, logEntry *LogEntry, conn net.Conn) {
// Connect to socket // Connect to socket
socket, err := net.Dial("unix", scgiSocket) socket, err := net.Dial("unix", scgiSocket)
@ -148,7 +148,7 @@ func handleSCGI(URL *url.URL, scgiPath string, scgiSocket string, config Config,
} }
} }
func prepareCGIVariables(config Config, URL *url.URL, conn net.Conn, script_path string, path_info string) map[string]string { func prepareCGIVariables(config SysConfig, URL *url.URL, conn net.Conn, script_path string, path_info string) map[string]string {
vars := prepareGatewayVariables(config, URL, conn) vars := prepareGatewayVariables(config, URL, conn)
vars["GATEWAY_INTERFACE"] = "CGI/1.1" vars["GATEWAY_INTERFACE"] = "CGI/1.1"
vars["SCRIPT_PATH"] = script_path vars["SCRIPT_PATH"] = script_path
@ -156,7 +156,7 @@ func prepareCGIVariables(config Config, URL *url.URL, conn net.Conn, script_path
return vars return vars
} }
func prepareSCGIVariables(config Config, URL *url.URL, scgiPath string, conn net.Conn) map[string]string { func prepareSCGIVariables(config SysConfig, URL *url.URL, scgiPath string, conn net.Conn) map[string]string {
vars := prepareGatewayVariables(config, URL, conn) vars := prepareGatewayVariables(config, URL, conn)
vars["SCGI"] = "1" vars["SCGI"] = "1"
vars["CONTENT_LENGTH"] = "0" vars["CONTENT_LENGTH"] = "0"
@ -165,7 +165,7 @@ func prepareSCGIVariables(config Config, URL *url.URL, scgiPath string, conn net
return vars return vars
} }
func prepareGatewayVariables(config Config, URL *url.URL, conn net.Conn) map[string]string { func prepareGatewayVariables(config SysConfig, URL *url.URL, conn net.Conn) map[string]string {
vars := make(map[string]string) vars := make(map[string]string)
vars["QUERY_STRING"] = URL.RawQuery vars["QUERY_STRING"] = URL.RawQuery
vars["REQUEST_METHOD"] = "" vars["REQUEST_METHOD"] = ""

View File

@ -36,7 +36,7 @@ func isSubdir(subdir, superdir string) (bool, error) {
return false, nil return false, nil
} }
func handleGeminiRequest(conn net.Conn, config Config, accessLogEntries chan LogEntry, wg *sync.WaitGroup) { func handleGeminiRequest(conn net.Conn, sysConfig SysConfig, config UserConfig, accessLogEntries chan LogEntry, wg *sync.WaitGroup) {
defer conn.Close() defer conn.Close()
defer wg.Done() defer wg.Done()
var tlsConn (*tls.Conn) = conn.(*tls.Conn) var tlsConn (*tls.Conn) = conn.(*tls.Conn)
@ -75,7 +75,7 @@ func handleGeminiRequest(conn net.Conn, config Config, accessLogEntries chan Log
if strings.HasSuffix(requestedHost, ".") { if strings.HasSuffix(requestedHost, ".") {
requestedHost = requestedHost[:len(requestedHost)-1] requestedHost = requestedHost[:len(requestedHost)-1]
} }
if requestedHost != config.Hostname || (URL.Port() != "" && URL.Port() != strconv.Itoa(config.Port)) { if requestedHost != sysConfig.Hostname || (URL.Port() != "" && URL.Port() != strconv.Itoa(sysConfig.Port)) {
conn.Write([]byte("53 No proxying to other hosts or ports!\r\n")) conn.Write([]byte("53 No proxying to other hosts or ports!\r\n"))
logEntry.Status = 53 logEntry.Status = 53
return return
@ -89,7 +89,7 @@ func handleGeminiRequest(conn net.Conn, config Config, accessLogEntries chan Log
} }
// Resolve URI path to actual filesystem path, including following symlinks // Resolve URI path to actual filesystem path, including following symlinks
raw_path := resolvePath(URL.Path, config) raw_path := resolvePath(URL.Path, sysConfig)
path, err := filepath.EvalSymlinks(raw_path) path, err := filepath.EvalSymlinks(raw_path)
if err!= nil { if err!= nil {
log.Println("Error evaluating path " + raw_path + " for symlinks: " + err.Error()) log.Println("Error evaluating path " + raw_path + " for symlinks: " + err.Error())
@ -99,7 +99,7 @@ func handleGeminiRequest(conn net.Conn, config Config, accessLogEntries chan Log
// If symbolic links have been used to escape the intended document directory, // If symbolic links have been used to escape the intended document directory,
// deny all knowledge // deny all knowledge
isSub, err := isSubdir(path, config.DocBase) isSub, err := isSubdir(path, sysConfig.DocBase)
if err != nil { if err != nil {
log.Println("Error testing whether path " + path + " is below DocBase: " + err.Error()) log.Println("Error testing whether path " + path + " is below DocBase: " + err.Error())
} }
@ -114,15 +114,15 @@ func handleGeminiRequest(conn net.Conn, config Config, accessLogEntries chan Log
// Paranoid security measures: // Paranoid security measures:
// Fail ASAP if the URL has mapped to a sensitive file // Fail ASAP if the URL has mapped to a sensitive file
if path == config.CertPath || path == config.KeyPath || path == config.AccessLog || path == config.ErrorLog || filepath.Base(path) == ".molly" { if path == sysConfig.CertPath || path == sysConfig.KeyPath || path == sysConfig.AccessLog || path == sysConfig.ErrorLog || filepath.Base(path) == ".molly" {
conn.Write([]byte("51 Not found!\r\n")) conn.Write([]byte("51 Not found!\r\n"))
logEntry.Status = 51 logEntry.Status = 51
return return
} }
// Read Molly files // Read Molly files
if config.ReadMollyFiles { if sysConfig.ReadMollyFiles {
parseMollyFiles(path, &config) config = parseMollyFiles(path, sysConfig.DocBase, config)
} }
// Check whether this URL is in a certificate zone // Check whether this URL is in a certificate zone
@ -138,17 +138,17 @@ func handleGeminiRequest(conn net.Conn, config Config, accessLogEntries chan Log
} }
// Check whether this URL is mapped to an SCGI app // Check whether this URL is mapped to an SCGI app
for scgiPath, scgiSocket := range config.SCGIPaths { for scgiPath, scgiSocket := range sysConfig.SCGIPaths {
if strings.HasPrefix(URL.Path, scgiPath) { if strings.HasPrefix(URL.Path, scgiPath) {
handleSCGI(URL, scgiPath, scgiSocket, config, &logEntry, conn) handleSCGI(URL, scgiPath, scgiSocket, sysConfig, &logEntry, conn)
return return
} }
} }
// Check whether this URL is in a configured CGI path // Check whether this URL is in a configured CGI path
for _, cgiPath := range config.CGIPaths { for _, cgiPath := range sysConfig.CGIPaths {
if strings.HasPrefix(path, cgiPath) { if strings.HasPrefix(path, cgiPath) {
handleCGI(config, path, cgiPath, URL, &logEntry, conn) handleCGI(sysConfig, path, cgiPath, URL, &logEntry, conn)
if logEntry.Status != 0 { if logEntry.Status != 0 {
return return
} }
@ -212,7 +212,7 @@ func readRequest(conn net.Conn, logEntry *LogEntry) (*url.URL, error) {
return URL, nil return URL, nil
} }
func resolvePath(path string, config Config) string { func resolvePath(path string, config SysConfig) string {
// Handle tildes // Handle tildes
if strings.HasPrefix(path, "/~") { if strings.HasPrefix(path, "/~") {
bits := strings.Split(path, "/") bits := strings.Split(path, "/")
@ -226,7 +226,7 @@ func resolvePath(path string, config Config) string {
return path return path
} }
func handleRedirects(URL *url.URL, config Config, conn net.Conn, logEntry *LogEntry) { func handleRedirects(URL *url.URL, config UserConfig, conn net.Conn, logEntry *LogEntry) {
handleRedirectsInner(URL, config.TempRedirects, 30, conn, logEntry) handleRedirectsInner(URL, config.TempRedirects, 30, conn, logEntry)
handleRedirectsInner(URL, config.PermRedirects, 31, conn, logEntry) handleRedirectsInner(URL, config.PermRedirects, 31, conn, logEntry)
} }
@ -252,7 +252,7 @@ func handleRedirectsInner(URL *url.URL, redirects map[string]string, status int,
} }
} }
func serveDirectory(URL *url.URL, path string, logEntry *LogEntry, conn net.Conn, config Config) { func serveDirectory(URL *url.URL, path string, logEntry *LogEntry, conn net.Conn, config UserConfig) {
// Redirect to add trailing slash if missing // Redirect to add trailing slash if missing
// (otherwise relative links don't work properly) // (otherwise relative links don't work properly)
if !strings.HasSuffix(URL.Path, "/") { if !strings.HasSuffix(URL.Path, "/") {
@ -281,7 +281,7 @@ func serveDirectory(URL *url.URL, path string, logEntry *LogEntry, conn net.Conn
} }
} }
func serveFile(path string, logEntry *LogEntry, conn net.Conn, config Config) { func serveFile(path string, logEntry *LogEntry, conn net.Conn, config UserConfig) {
// Get MIME type of files // Get MIME type of files
ext := filepath.Ext(path) ext := filepath.Ext(path)
var mimeType string var mimeType string

View File

@ -16,12 +16,12 @@ import (
var VERSION = "0.0.0" var VERSION = "0.0.0"
func launch(config Config, privInfo userInfo) int { func launch(sysConfig SysConfig, userConfig UserConfig, privInfo userInfo) int {
var err error var err error
// Open log files // Open log files
if config.ErrorLog != "" { if sysConfig.ErrorLog != "" {
errorLogFile, err := os.OpenFile(config.ErrorLog, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) errorLogFile, err := os.OpenFile(sysConfig.ErrorLog, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil { if err != nil {
log.Println("Error opening error log file: " + err.Error()) log.Println("Error opening error log file: " + err.Error())
return 1 return 1
@ -32,10 +32,10 @@ func launch(config Config, privInfo userInfo) int {
log.SetFlags(log.Ldate|log.Ltime) log.SetFlags(log.Ldate|log.Ltime)
var accessLogFile *os.File var accessLogFile *os.File
if config.AccessLog == "-" { if sysConfig.AccessLog == "-" {
accessLogFile = os.Stdout accessLogFile = os.Stdout
} else if config.AccessLog != "" { } else if sysConfig.AccessLog != "" {
accessLogFile, err = os.OpenFile(config.AccessLog, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) accessLogFile, err = os.OpenFile(sysConfig.AccessLog, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil { if err != nil {
log.Println("Error opening access log file: " + err.Error()) log.Println("Error opening access log file: " + err.Error())
return 1 return 1
@ -45,22 +45,22 @@ func launch(config Config, privInfo userInfo) int {
// Read TLS files, create TLS config // Read TLS files, create TLS config
// Check key file permissions first // Check key file permissions first
info, err := os.Stat(config.KeyPath) info, err := os.Stat(sysConfig.KeyPath)
if err != nil { if err != nil {
log.Println("Error opening TLS key file: " + err.Error()) log.Println("Error opening TLS key file: " + err.Error())
return 1 return 1
} }
if uint64(info.Mode().Perm())&0444 == 0444 { if uint64(info.Mode().Perm())&0444 == 0444 {
log.Println("Refusing to use world-readable TLS key file " + config.KeyPath) log.Println("Refusing to use world-readable TLS key file " + sysConfig.KeyPath)
return 1 return 1
} }
// Check certificate hostname matches server hostname // Check certificate hostname matches server hostname
info, err = os.Stat(config.CertPath) info, err = os.Stat(sysConfig.CertPath)
if err != nil { if err != nil {
log.Println("Error opening TLS certificate file: " + err.Error()) log.Println("Error opening TLS certificate file: " + err.Error())
return 1 return 1
} }
certFile, err := os.Open(config.CertPath) certFile, err := os.Open(sysConfig.CertPath)
if err != nil { if err != nil {
log.Println("Error opening TLS certificate file: " + err.Error()) log.Println("Error opening TLS certificate file: " + err.Error())
return 1 return 1
@ -76,7 +76,7 @@ func launch(config Config, privInfo userInfo) int {
return 1 return 1
} }
certx509, err := x509.ParseCertificate(certDer.Bytes) certx509, err := x509.ParseCertificate(certDer.Bytes)
err = certx509.VerifyHostname(config.Hostname) err = certx509.VerifyHostname(sysConfig.Hostname)
if err != nil { if err != nil {
log.Println("Invalid TLS certificate: " + err.Error()) log.Println("Invalid TLS certificate: " + err.Error())
return 1 return 1
@ -88,7 +88,7 @@ func launch(config Config, privInfo userInfo) int {
} }
// Load certificate and private key // Load certificate and private key
cert, err := tls.LoadX509KeyPair(config.CertPath, config.KeyPath) cert, err := tls.LoadX509KeyPair(sysConfig.CertPath, sysConfig.KeyPath)
if err != nil { if err != nil {
log.Println("Error loading TLS keypair: " + err.Error()) log.Println("Error loading TLS keypair: " + err.Error())
return 1 return 1
@ -96,7 +96,7 @@ func launch(config Config, privInfo userInfo) int {
var tlscfg tls.Config var tlscfg tls.Config
tlscfg.Certificates = []tls.Certificate{cert} tlscfg.Certificates = []tls.Certificate{cert}
tlscfg.ClientAuth = tls.RequestClientCert tlscfg.ClientAuth = tls.RequestClientCert
if config.AllowTLS12 { if sysConfig.AllowTLS12 {
tlscfg.MinVersion = tls.VersionTLS12 tlscfg.MinVersion = tls.VersionTLS12
} else { } else {
tlscfg.MinVersion = tls.VersionTLS13 tlscfg.MinVersion = tls.VersionTLS13
@ -110,14 +110,14 @@ func launch(config Config, privInfo userInfo) int {
} }
// Apply security restrictions // Apply security restrictions
err = enableSecurityRestrictions(config, privInfo) err = enableSecurityRestrictions(sysConfig, privInfo)
if err != nil { if err != nil {
log.Println("Exiting due to failure to apply security restrictions.") log.Println("Exiting due to failure to apply security restrictions.")
return 1 return 1
} }
// Create TLS listener // Create TLS listener
listener, err := tls.Listen("tcp", ":"+strconv.Itoa(config.Port), &tlscfg) listener, err := tls.Listen("tcp", ":"+strconv.Itoa(sysConfig.Port), &tlscfg)
if err != nil { if err != nil {
log.Println("Error creating TLS listener: " + err.Error()) log.Println("Error creating TLS listener: " + err.Error())
return 1 return 1
@ -126,7 +126,7 @@ func launch(config Config, privInfo userInfo) int {
// Start log handling routines // Start log handling routines
var accessLogEntries chan LogEntry var accessLogEntries chan LogEntry
if config.AccessLog == "" { if sysConfig.AccessLog == "" {
accessLogEntries = nil accessLogEntries = nil
} else { } else {
accessLogEntries = make(chan LogEntry, 10) accessLogEntries = make(chan LogEntry, 10)
@ -156,7 +156,7 @@ func launch(config Config, privInfo userInfo) int {
conn, err := listener.Accept() conn, err := listener.Accept()
if err == nil { if err == nil {
wg.Add(1) wg.Add(1)
go handleGeminiRequest(conn, config, accessLogEntries, &wg) go handleGeminiRequest(conn, sysConfig, userConfig, accessLogEntries, &wg)
} else { } else {
select { select {
case <-shutdown: case <-shutdown:

View File

@ -25,12 +25,12 @@ func main() {
} }
// Read config // Read config
config, err := getConfig(conf_file) sysConfig, userConfig, err := getConfig(conf_file)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
// Run server and exit // Run server and exit
var dummy userInfo var dummy userInfo
os.Exit(launch(config, dummy)) os.Exit(launch(sysConfig, userConfig, dummy))
} }

View File

@ -30,7 +30,7 @@ func main() {
} }
// Read config // Read config
config, err := getConfig(conf_file) sysConfig, userConfig, err := getConfig(conf_file)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -51,5 +51,5 @@ func main() {
} }
// Run server and exit // Run server and exit
os.Exit(launch(config, privInfo)) os.Exit(launch(sysConfig, userConfig, privInfo))
} }

View File

@ -9,6 +9,6 @@ type userInfo struct {
// This is intended to be called immediately prior to accepting client // This is intended to be called immediately prior to accepting client
// connections and may be used to establish a security "jail" for the molly // connections and may be used to establish a security "jail" for the molly
// brown executable. // brown executable.
func enableSecurityRestrictions(config Config, ui userInfo) error { func enableSecurityRestrictions(config SysConfig, ui userInfo) error {
return nil return nil
} }

View File

@ -7,7 +7,7 @@ import (
"os" "os"
) )
func enableSecurityRestrictions(config Config, ui userInfo) error { func enableSecurityRestrictions(config SysConfig, ui userInfo) error {
// Prior to Go 1.6, setuid did not work reliably on Linux // Prior to Go 1.6, setuid did not work reliably on Linux
// So, absolutely refuse to run as root // So, absolutely refuse to run as root

View File

@ -11,7 +11,7 @@ import (
// operations available to the molly brown executable. Please note that (S)CGI // operations available to the molly brown executable. Please note that (S)CGI
// processes that molly brown spawns or communicates with are unrestricted // processes that molly brown spawns or communicates with are unrestricted
// and should pledge their own restrictions and unveil their own files. // and should pledge their own restrictions and unveil their own files.
func enableSecurityRestrictions(config Config, ui userInfo) error { func enableSecurityRestrictions(config SysConfig, ui userInfo) error {
// Setuid to an unprivileged user // Setuid to an unprivileged user
err := DropPrivs(ui) err := DropPrivs(ui)

View File

@ -2,7 +2,7 @@
package main package main
func enableSecurityRestrictions(config Config, ui userInfo) error { func enableSecurityRestrictions(config SysConfig, ui userInfo) error {
// Setuid to an unprivileged user // Setuid to an unprivileged user
return DropPrivs(ui) return DropPrivs(ui)