-
Notifications
You must be signed in to change notification settings - Fork 0
/
dynamic.go
130 lines (115 loc) · 3.35 KB
/
dynamic.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package main
import (
"bufio"
"context"
"io"
"log"
"net"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"time"
)
func handleCGI(conf *Config, req *Request, cgiPath string) (ok bool) {
ok = true
path := req.filePath
conn := req.conn
scriptPath := filepath.Join(conf.RootDir, req.filePath)
if req.user != "" {
scriptPath = filepath.Join("/home", req.user, conf.UserDir, req.filePath)
}
info, err := os.Stat(scriptPath)
if err != nil {
log.Println(err.Error())
ok = false
return
}
if !(info.Mode().Perm()&0555 == 0555) {
log.Println("File not executable")
ok = false
return
}
// Prepare environment variables
vars := prepareCGIVariables(conf, req, scriptPath)
log.Println("Running script:", scriptPath)
// Spawn process
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, scriptPath)
// Put input data into stdin pipe
stdin, err := cmd.StdinPipe()
if err != nil {
log.Println("Error creating a stdin pipe:", err.Error())
ok = false
return
}
io.WriteString(stdin, req.data)
stdin.Close()
// Set environment variables
cmd.Env = []string{}
for key, value := range vars {
cmd.Env = append(cmd.Env, key+"="+value)
}
// Manually change the uid/gid for the command
// Fetch user info
// user, err := user.Lookup(req.user)
// if err == nil {
// tmp, _ := strconv.ParseUint(user.Uid, 10, 32)
// uid := uint32(tmp)
// tmp, _ = strconv.ParseUint(user.Gid, 10, 32)
// gid := uint32(tmp)
// cmd.SysProcAttr = &syscall.SysProcAttr{}
// cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uid, Gid: gid}
// }
// Fetch and check output
response, err := cmd.Output()
if ctx.Err() == context.DeadlineExceeded {
log.Println("Terminating CGI process " + path + " due to exceeding 10 second runtime limit.")
conn.Write([]byte("5 CGI process timed out!\r\n"))
return
}
if err != nil {
log.Println("Error running CGI program " + path + ": " + err.Error())
if strings.Contains(err.Error(), "permission denied") {
ok = false
return
}
if err, ok := err.(*exec.ExitError); ok {
log.Println("↳ stderr output: " + string(err.Stderr))
}
conn.Write([]byte("5 CGI error\r\n"))
return
}
// Extract response header
header, _, err := bufio.NewReader(strings.NewReader(string(response))).ReadLine()
_, err2 := strconv.Atoi(strings.Fields(string(header))[0])
if err != nil || err2 != nil {
log.Println("Unable to parse first line of output from CGI process " + path + " as valid Gemini response header. Line was: " + string(header))
conn.Write([]byte("5 CGI error\r\n"))
return
}
log.Println("Returning CGI output")
// Write response
conn.Write(response)
return
}
func prepareCGIVariables(conf *Config, req *Request, script_path string) map[string]string {
vars := prepareGatewayVariables(conf, req)
vars["GATEWAY_INTERFACE"] = "CGI/1.1"
vars["SCRIPT_PATH"] = script_path
return vars
}
func prepareGatewayVariables(conf *Config, req *Request) map[string]string {
vars := make(map[string]string)
vars["REQUEST_METHOD"] = ""
vars["SERVER_NAME"] = conf.Hostname
vars["SERVER_PORT"] = strconv.Itoa(conf.Port)
vars["SERVER_PROTOCOL"] = "SPARTAN"
vars["SERVER_SOFTWARE"] = "SPSRV"
vars["DATA_LENGTH"] = strconv.Itoa(req.dataLen)
host, _, _ := net.SplitHostPort((*req.netConn).RemoteAddr().String())
vars["REMOTE_ADDR"] = host
return vars
}