2020-05-03 21:42:51 +00:00
package main
import (
"os/exec"
"syscall"
2020-05-04 17:19:56 +00:00
"strconv"
2020-05-07 16:08:06 +00:00
"bytes"
2020-05-05 22:46:50 +00:00
"time"
2020-05-03 21:42:51 +00:00
"io"
)
2020-05-07 16:08:06 +00:00
func setupExecEnviron ( path string ) [ ] string {
2020-05-04 17:19:56 +00:00
return [ ] string {
2020-05-07 16:08:06 +00:00
envKeyValue ( "PATH" , path ) ,
2020-05-04 17:19:56 +00:00
}
}
2020-05-07 16:08:06 +00:00
func setupInitialCgiEnviron ( path string ) [ ] string {
2020-05-03 22:49:19 +00:00
return [ ] string {
/* RFC 3875 standard */
2020-05-04 17:19:56 +00:00
envKeyValue ( "GATEWAY_INTERFACE" , "CGI/1.1" ) , /* MUST be set to the dialect of CGI being used by the server */
envKeyValue ( "SERVER_SOFTWARE" , "gophor/" + GophorVersion ) , /* MUST be set to name and version of server software serving this request */
envKeyValue ( "SERVER_PROTOCOL" , "RFC1436" ) , /* MUST be set to name and version of application protocol used for this request */
envKeyValue ( "CONTENT_LENGTH" , "0" ) , /* Contains size of message-body attached (always 0 so we set here) */
envKeyValue ( "REQUEST_METHOD" , "GET" ) , /* MUST be set to method by which script should process request. Always GET */
/* Non-standard */
2020-05-07 16:08:06 +00:00
envKeyValue ( "PATH" , path ) ,
2020-05-04 17:19:56 +00:00
envKeyValue ( "COLUMNS" , strconv . Itoa ( Config . PageWidth ) ) ,
2020-05-03 22:49:19 +00:00
envKeyValue ( "GOPHER_CHARSET" , Config . CharSet ) ,
}
}
2020-05-07 07:59:53 +00:00
func executeCgi ( responder * Responder ) * GophorError {
2020-05-04 17:19:56 +00:00
/* Get initial CgiEnv variables */
cgiEnv := Config . CgiEnv
2020-05-07 07:59:53 +00:00
cgiEnv = append ( cgiEnv , envKeyValue ( "SERVER_NAME" , responder . Host . Name ( ) ) ) /* MUST be set to name of server host client is connecting to */
cgiEnv = append ( cgiEnv , envKeyValue ( "SERVER_PORT" , responder . Host . Port ( ) ) ) /* MUST be set to the server port that client is connecting to */
cgiEnv = append ( cgiEnv , envKeyValue ( "REMOTE_ADDR" , responder . Client . Ip ( ) ) ) /* Remote client addr, MUST be set */
2020-05-04 17:19:56 +00:00
2020-05-05 22:46:50 +00:00
/* We store the query string in Parameters[0]. Ensure we git without initial delimiter */
2020-05-04 19:00:53 +00:00
var queryString string
2020-05-07 20:48:16 +00:00
if len ( responder . Request . Parameters [ 0 ] ) > 0 {
queryString = responder . Request . Parameters [ 0 ] [ 1 : ]
2020-05-04 19:00:53 +00:00
} else {
2020-05-07 20:48:16 +00:00
queryString = responder . Request . Parameters [ 0 ]
2020-05-04 19:00:53 +00:00
}
cgiEnv = append ( cgiEnv , envKeyValue ( "QUERY_STRING" , queryString ) ) /* URL encoded search or parameter string, MUST be set even if empty */
2020-05-07 20:48:16 +00:00
cgiEnv = append ( cgiEnv , envKeyValue ( "SCRIPT_NAME" , "/" + responder . Request . RelPath ( ) ) ) /* URI path (not URL encoded) which could identify the CGI script (rather than script's output) */
cgiEnv = append ( cgiEnv , envKeyValue ( "SCRIPT_FILENAME" , responder . Request . AbsPath ( ) ) ) /* Basically SCRIPT_NAME absolute path */
cgiEnv = append ( cgiEnv , envKeyValue ( "SELECTOR" , responder . Request . SelectorPath ( ) ) )
cgiEnv = append ( cgiEnv , envKeyValue ( "DOCUMENT_ROOT" , responder . Request . RootDir ( ) ) )
cgiEnv = append ( cgiEnv , envKeyValue ( "REQUEST_URI" , "/" + responder . Request . RelPath ( ) + responder . Request . Parameters [ 0 ] ) )
2020-05-04 19:31:24 +00:00
2020-05-05 22:46:50 +00:00
/* Fuck it. For now, we don't support PATH_INFO. It's a piece of shit variable */
2020-05-07 07:59:53 +00:00
// cgiEnv = append(cgiEnv, envKeyValue("PATH_INFO", responder.Parameters[0])) /* Sub-resource to be fetched by script, derived from path hierarch portion of URI. NOT URL encoded */
// cgiEnv = append(cgiEnv, envKeyValue("PATH_TRANSLATED", responder.AbsPath())) /* Take PATH_INFO, parse as local URI and append root dir */
2020-05-05 22:46:50 +00:00
2020-05-04 19:00:53 +00:00
/* We ignore these due to just CBA and we're not implementing authorization yet */
2020-05-04 17:19:56 +00:00
// cgiEnv = append(cgiEnv, envKeyValue("AUTH_TYPE", "")) /* Any method used my server to authenticate user, MUST be set if auth'd */
// cgiEnv = append(cgiEnv, envKeyValue("CONTENT_TYPE", "")) /* Only a MUST if HTTP content-type set (so never for gopher) */
// cgiEnv = append(cgiEnv, envKeyValue("REMOTE_IDENT", "")) /* Remote client identity information */
// cgiEnv = append(cgiEnv, envKeyValue("REMOTE_HOST", "")) /* Remote client domain name */
// cgiEnv = append(cgiEnv, envKeyValue("REMOTE_USER", "")) /* Remote user ID, if AUTH_TYPE, MUST be set */
2020-05-07 16:08:06 +00:00
contentTypeReached := true
skipPrefixWriter := NewSkipPrefixWriter (
responder . Writer ,
[ ] byte ( DOSLineEnd + DOSLineEnd ) ,
func ( skipBuffer [ ] byte ) bool {
split := bytes . Split ( skipBuffer , [ ] byte ( DOSLineEnd ) )
for _ , header := range split {
header = bytes . ToLower ( header )
if bytes . HasPrefix ( header , [ ] byte ( "content-type:" ) ) {
contentTypeReached = true
break
}
}
return contentTypeReached
} ,
)
2020-05-07 20:48:16 +00:00
gophorErr := execute ( skipPrefixWriter , cgiEnv , responder . Request . AbsPath ( ) , nil )
2020-05-07 16:08:06 +00:00
if gophorErr != nil {
return gophorErr
} else if ! contentTypeReached {
return & GophorError { CgiOutputErr , nil }
} else {
return nil
}
2020-05-04 17:19:56 +00:00
}
2020-05-07 07:59:53 +00:00
func executeFile ( responder * Responder ) * GophorError {
return execute ( responder . Writer , Config . Env , responder . Request . AbsPath ( ) , responder . Request . Parameters )
2020-05-04 17:19:56 +00:00
}
2020-05-07 07:59:53 +00:00
func executeCommand ( responder * Responder ) * GophorError {
if isRestrictedCommand ( responder . Request . AbsPath ( ) ) {
2020-05-05 22:46:50 +00:00
return & GophorError { RestrictedCommandErr , nil }
}
2020-05-07 07:59:53 +00:00
return execute ( responder . Writer , Config . Env , responder . Request . AbsPath ( ) , responder . Request . Parameters )
2020-05-04 17:19:56 +00:00
}
2020-05-05 22:46:50 +00:00
func execute ( writer io . Writer , env [ ] string , path string , args [ ] string ) * GophorError {
2020-05-07 20:48:16 +00:00
/* If CGI disbabled, just return error */
if ! Config . CgiEnabled {
return & GophorError { CgiDisabledErr , nil }
}
2020-05-03 21:42:51 +00:00
/* Setup command */
var cmd * exec . Cmd
if args != nil {
2020-05-04 17:19:56 +00:00
cmd = exec . Command ( path , args ... )
2020-05-03 21:42:51 +00:00
} else {
2020-05-04 17:19:56 +00:00
cmd = exec . Command ( path )
2020-05-03 21:42:51 +00:00
}
2020-05-05 22:46:50 +00:00
/* Set new proccess group id */
cmd . SysProcAttr = & syscall . SysProcAttr { Setpgid : true }
2020-05-04 17:19:56 +00:00
/* Setup cmd env */
cmd . Env = env
2020-05-03 21:42:51 +00:00
2020-05-04 17:19:56 +00:00
/* Setup out buffer */
2020-05-05 22:46:50 +00:00
cmd . Stdout = writer
2020-05-03 21:42:51 +00:00
/* Start executing! */
err := cmd . Start ( )
if err != nil {
2020-05-05 22:46:50 +00:00
return & GophorError { CommandStartErr , err }
2020-05-03 21:42:51 +00:00
}
2020-05-05 22:46:50 +00:00
/* Setup timer goroutine to kill cmd after x time */
go func ( ) {
2020-05-07 16:08:06 +00:00
time . Sleep ( Config . MaxExecRunTime )
2020-05-05 22:46:50 +00:00
if cmd . ProcessState != nil {
/* We've already finished */
return
}
/* Get process group id */
pgid , err := syscall . Getpgid ( cmd . Process . Pid )
if err != nil {
Config . SysLog . Fatal ( "" , "Process unfinished, PGID not found!\n" )
}
/* Kill process group! */
err = syscall . Kill ( - pgid , syscall . SIGTERM )
if err != nil {
Config . SysLog . Fatal ( "" , "Error stopping process group %d: %s\n" , pgid , err . Error ( ) )
}
} ( )
2020-05-03 21:42:51 +00:00
/* Wait for command to finish, get exit code */
err = cmd . Wait ( )
exitCode := 0
if err != nil {
/* Error, try to get exit code */
2020-05-05 22:46:50 +00:00
exitError , _ := err . ( * exec . ExitError )
waitStatus := exitError . Sys ( ) . ( syscall . WaitStatus )
exitCode = waitStatus . ExitStatus ( )
2020-05-03 21:42:51 +00:00
} else {
/* No error! Get exit code direct from command */
waitStatus := cmd . ProcessState . Sys ( ) . ( syscall . WaitStatus )
exitCode = waitStatus . ExitStatus ( )
}
2020-05-05 22:46:50 +00:00
2020-05-03 21:42:51 +00:00
if exitCode != 0 {
2020-05-04 17:19:56 +00:00
/* If non-zero exit code return error */
2020-05-07 07:59:53 +00:00
Config . SysLog . Error ( "" , "Error executing: %s\n" , path )
2020-05-05 22:46:50 +00:00
return & GophorError { CommandExitCodeErr , err }
2020-05-03 21:42:51 +00:00
} else {
2020-05-05 22:46:50 +00:00
return nil
2020-05-03 21:42:51 +00:00
}
}
2020-05-03 22:49:19 +00:00
func envKeyValue ( key , value string ) string {
return key + "=" + value
}