run substitutions on args in CGICommand

- `{}` -> `$SCRIPT_PATH`
- `{prg}` -> `$SCRIPT_PATH`
- `{user}` -> `~username` (without the tilde) from the URL, if there
  is one
This commit is contained in:
Travis J Parker 2022-06-15 17:47:38 +02:00
parent 50f9d49e6c
commit 505ad17819
2 changed files with 42 additions and 5 deletions

View File

@ -312,10 +312,16 @@ startup, database connection etc. on each request).
- 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. It can still run the cgi program by executing
`$SCRIPT_PATH`, but before doing so it should change the effective
user, chroot into a limited file system, and take any other steps
appropriate to sandbox user-provided programs.
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

@ -10,6 +10,7 @@ import (
"net/url"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"time"
@ -50,7 +51,7 @@ func handleCGI(config Config, path string, cgiPath string, URL *url.URL, log *Lo
// Use the CGICommand if provided
var script []string
if config.CGICommand != "" {
script = append([]string{config.CGICommand}, config.cgiCommandArgs...)
script = prepareCGICommand(config, scriptPath)
} else {
script = []string{scriptPath}
}
@ -156,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 := filepath.SplitList(scriptPath[len(base):])
if len(segments) > 1 {
substitutions["{user}"] = segments[0][1:]
}
}
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"