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-05 22:46:50 +00:00
"time"
2020-05-03 21:42:51 +00:00
"io"
)
2020-05-08 15:52:17 +00:00
/* Setup initial (i.e. constant) gophermap / command environment variables */
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-08 15:52:17 +00:00
/* Setup initial (i.e. constant) CGI environment variables */
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-08 15:52:17 +00:00
envKeyValue ( "GATEWAY_INTERFACE" , "CGI/1.1" ) , /* MUST be set to the dialect of CGI being used by the server */
2020-05-04 17:19:56 +00:00
envKeyValue ( "SERVER_SOFTWARE" , "gophor/" + GophorVersion ) , /* MUST be set to name and version of server software serving this request */
2020-05-08 15:52:17 +00:00
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 */
2020-05-04 17:19:56 +00:00
/* 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-09 16:49:31 +00:00
/* Generate CGI environment */
func generateCgiEnvironment ( responder * Responder ) [ ] string {
2020-05-04 17:19:56 +00:00
/* Get initial CgiEnv variables */
2020-05-09 16:49:31 +00:00
env := Config . CgiEnv
env = append ( env , envKeyValue ( "SERVER_NAME" , responder . Host . Name ( ) ) ) /* MUST be set to name of server host client is connecting to */
env = append ( env , envKeyValue ( "SERVER_PORT" , responder . Host . Port ( ) ) ) /* MUST be set to the server port that client is connecting to */
env = append ( env , 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
}
2020-05-09 16:49:31 +00:00
env = append ( env , envKeyValue ( "QUERY_STRING" , queryString ) ) /* URL encoded search or parameter string, MUST be set even if empty */
env = append ( env , envKeyValue ( "SCRIPT_NAME" , "/" + responder . Request . Path . Relative ( ) ) ) /* URI path (not URL encoded) which could identify the CGI script (rather than script's output) */
env = append ( env , envKeyValue ( "SCRIPT_FILENAME" , responder . Request . Path . Absolute ( ) ) ) /* Basically SCRIPT_NAME absolute path */
env = append ( env , envKeyValue ( "SELECTOR" , responder . Request . Path . Selector ( ) ) )
env = append ( env , envKeyValue ( "DOCUMENT_ROOT" , responder . Request . Path . RootDir ( ) ) )
env = append ( env , envKeyValue ( "REQUEST_URI" , "/" + responder . Request . Path . Relative ( ) + responder . Request . Parameters [ 0 ] ) )
return env
}
/* Execute a CGI script (pointer to correct function) */
var executeCgi func ( * Responder ) * GophorError
/* Execute CGI script and serve as-is */
func executeCgiNoHttp ( responder * Responder ) * GophorError {
return execute ( responder . Writer , generateCgiEnvironment ( responder ) , responder . Request . Path . Absolute ( ) , nil )
}
/* Execute CGI script and strip HTTP headers */
func executeCgiStripHttp ( responder * Responder ) * GophorError {
/* HTTP header stripping writer that also parses HTTP status codes */
2020-05-09 15:33:38 +00:00
httpStripWriter := NewHttpStripWriter ( responder . Writer )
2020-05-07 16:08:06 +00:00
2020-05-09 16:49:31 +00:00
/* Execute the CGI script using the new httpStripWriter */
gophorErr := execute ( httpStripWriter , generateCgiEnvironment ( responder ) , responder . Request . Path . Absolute ( ) , nil )
/* httpStripWriter's error takes priority as it might have parsed the status code */
cgiStatusErr := httpStripWriter . FinishUp ( )
if cgiStatusErr != nil {
return cgiStatusErr
2020-05-09 15:33:38 +00:00
} else {
2020-05-09 16:49:31 +00:00
return gophorErr
2020-05-09 15:33:38 +00:00
}
2020-05-04 17:19:56 +00:00
}
2020-05-08 15:52:17 +00:00
/* Execute any file (though only allowed are gophermaps) */
2020-05-07 07:59:53 +00:00
func executeFile ( responder * Responder ) * GophorError {
2020-05-08 15:52:17 +00:00
return execute ( responder . Writer , Config . Env , responder . Request . Path . Absolute ( ) , responder . Request . Parameters )
2020-05-04 17:19:56 +00:00
}
2020-05-08 15:52:17 +00:00
/* Execute a supplied path with arguments and environment, to writer */
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-09 16:49:31 +00:00
exitError , ok := err . ( * exec . ExitError )
if ok {
waitStatus := exitError . Sys ( ) . ( syscall . WaitStatus )
exitCode = waitStatus . ExitStatus ( )
} else {
exitCode = 1
}
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
2020-05-08 15:52:17 +00:00
/* Just neatens creating an environment KEY=VALUE string */
2020-05-03 22:49:19 +00:00
func envKeyValue ( key , value string ) string {
return key + "=" + value
}