add go module support #34

Closed
tjp wants to merge 7 commits from tjp:module into master
5 changed files with 91 additions and 16 deletions

View File

@ -55,9 +55,10 @@ Molly Brown is known to run on:
Please let us know if you get it to work on some other platform!
Molly Brown only has a single dependency beyond the Go standard
library, which is [this TOML parsing
library](https://github.com/BurntSushi/toml).
Molly Brown only has two dependencies beyond the Go standard
library, which are [this TOML parsing
library](https://github.com/BurntSushi/toml) and [this shell command
lexer](https://github.com/google/shlex).
## Installation
@ -285,17 +286,14 @@ a request URL includes components after the path to an executable
executable (e.g. `/var/gemini/cgi-bin/scripty.py`) while the variable
`PATH_INFO` will contain the remainder (e.g. `foo/bar/baz`).
It is very important to be aware that programs written in Go are
*unable* to reliably change their UID once started, due to how
goroutines are implemented on unix systems. As an unavoidable
consequence of this, CGI processes started by Molly Brown are run as
the same user as the server process. This means CGI processes
necessarily have read and write access to the server logs and to the
TLS private key. There is no way to work around this. As such you
must be extremely careful about only running trustworthy CGI
applications, ideally only applications you have carefully written
yourself. Allowing untrusted users to upload arbitrary executable
files into a CGI path is a serious security vulnerability.
Be aware that CGI processes started by Molly Brown are run as the same
user as the server process. This means that by default, CGI processes
have read and write access to the server logs and to the TLS private
key. Set a `CGICommand` to something that drop privileges before
running `$SCRIPT_PATH` to add guard rails to CGI processes provided by
other system users. Otherwise, allowing untrusted users to upload
arbitrary executable files into a CGI path is a serious security
vulnerability.
SCGI applications must be started separately (i.e. Molly Brown expects
them to already be running and will not attempt to start them itself),
@ -314,6 +312,17 @@ startup, database connection etc. on each request).
if wildcards are used, the path should *not* end in a trailing slash
- this appears to be a peculiarity of the Go standard library's
`filepath.Glob` function.
* `CGICommand`: An executable command which will be run instead of
cgi programs. Arguments specified in `CGICommand` (though not the
initial command itself) will have a few simple string replacements
performed on them: `{}` and `{prg}` are populated with the same
value as `$SCRIPT_PATH`, and `{user}` with the `~username` in the
URL. Ultimately, the command should typically end up running the
CGI program anwyay, either as `{}` or `{prg}` in the `CGICommand`,
or as `$SCRIPT_PATH` in a script that `CGICommand` points to. But
this facility gives a server admin the opportunity to change the
effective user, chroot into a limited filesystem, or take any other
appropriate steps to sandbox user-provided programs.
* `SCGIPaths`: In this section of the config file, keys are URL path
prefixes and values are filesystem paths to unix domain sockets.
Any request for a URL whose path begins with one of the specified

View File

@ -2,10 +2,12 @@ package main
import (
"errors"
"github.com/BurntSushi/toml"
"log"
"os"
"path/filepath"
"github.com/BurntSushi/toml"
"github.com/google/shlex"
)
type Config struct {
@ -24,11 +26,14 @@ type Config struct {
PermRedirects map[string]string
MimeOverrides map[string]string
CGIPaths []string
CGICommand string
SCGIPaths map[string]string
CertificateZones map[string][]string
DirectorySort string
DirectoryReverse bool
DirectoryTitles bool
cgiCommandArgs []string
}
type MollyFile struct {
@ -61,6 +66,7 @@ func getConfig(filename string) (Config, error) {
config.TempRedirects = make(map[string]string)
config.PermRedirects = make(map[string]string)
config.CGIPaths = make([]string, 0)
config.CGICommand = ""
config.SCGIPaths = make(map[string]string)
config.DirectorySort = "Name"
@ -93,6 +99,15 @@ func getConfig(filename string) (Config, error) {
}
config.CGIPaths = cgiPaths
if config.CGICommand != "" {
spl, err := shlex.Split(config.CGICommand)
if err != nil {
return config, err
}
config.CGICommand = spl[0]
config.cgiCommandArgs = spl[1:]
}
return config, nil
}

View File

@ -10,6 +10,7 @@ import (
"net/url"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"time"
@ -47,10 +48,18 @@ func handleCGI(config Config, path string, cgiPath string, URL *url.URL, log *Lo
// Prepare environment variables
vars := prepareCGIVariables(config, URL, conn, scriptPath, pathInfo)
// Use the CGICommand if provided
var script []string
if config.CGICommand != "" {
script = prepareCGICommand(config, scriptPath)
} else {
script = []string{scriptPath}
}
// Spawn process
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, scriptPath)
cmd := exec.CommandContext(ctx, script[0], script[1:]...)
cmd.Env = []string{}
for key, value := range vars {
cmd.Env = append(cmd.Env, key+"="+value)
@ -148,6 +157,36 @@ func handleSCGI(URL *url.URL, scgiPath string, scgiSocket string, config Config,
}
}
func prepareCGICommand(config Config, scriptPath string) []string {
if config.CGICommand == "" {
return []string{scriptPath}
}
cmd := make([]string, len(config.cgiCommandArgs)+1)
cmd[0] = config.CGICommand
substitutions := map[string]string{
"{}": scriptPath,
"{prg}": scriptPath,
}
base := filepath.Join(config.DocBase, config.HomeDocBase) + string(filepath.Separator)
if strings.HasPrefix(scriptPath, base) {
segments := strings.Split(scriptPath[len(base):], string(filepath.Separator))
if len(segments) > 1 {
substitutions["{user}"] = segments[0]
}
}
for i, arg := range config.cgiCommandArgs {
for key, val := range substitutions {
arg = strings.ReplaceAll(arg, key, val)
}
cmd[i+1] = arg
}
return cmd
}
func prepareCGIVariables(config Config, URL *url.URL, conn net.Conn, script_path string, path_info string) map[string]string {
vars := prepareGatewayVariables(config, URL, conn)
vars["GATEWAY_INTERFACE"] = "CGI/1.1"

8
go.mod Normal file
View File

@ -0,0 +1,8 @@
module mollybrown
go 1.18
require (
github.com/BurntSushi/toml v1.1.0
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
)

4
go.sum Normal file
View File

@ -0,0 +1,4 @@
github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=