mirror of
https://tildegit.org/solderpunk/molly-brown
synced 2024-05-11 15:26:03 +02:00
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:
parent
e70ec82594
commit
eb85a6e94c
|
@ -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
200
config.go
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 = " "
|
||||||
|
|
10
dynamic.go
10
dynamic.go
|
@ -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"] = ""
|
||||||
|
|
30
handler.go
30
handler.go
|
@ -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
|
||||||
|
|
34
launch.go
34
launch.go
|
@ -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:
|
||||||
|
|
4
main.go
4
main.go
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue