1 | /* |
---|
2 | * Copyright (c) 2004-2005 Sergey Lyubka <valenok@gmail.com> |
---|
3 | * All rights reserved |
---|
4 | * |
---|
5 | * "THE BEER-WARE LICENSE" (Revision 42): |
---|
6 | * Sergey Lyubka wrote this file. As long as you retain this notice you |
---|
7 | * can do whatever you want with this stuff. If we meet some day, and you think |
---|
8 | * this stuff is worth it, you can buy me a beer in return. |
---|
9 | */ |
---|
10 | |
---|
11 | #include "defs.h" |
---|
12 | |
---|
13 | #if !defined(NO_CGI) |
---|
14 | struct env_block { |
---|
15 | char buf[ENV_MAX]; /* Environment buffer */ |
---|
16 | int len; /* Space taken */ |
---|
17 | char *vars[CGI_ENV_VARS]; /* Point into the buffer */ |
---|
18 | int nvars; /* Number of variables */ |
---|
19 | }; |
---|
20 | |
---|
21 | /* |
---|
22 | * Verify that given file has CGI extension |
---|
23 | */ |
---|
24 | int |
---|
25 | is_cgi(struct shttpd_ctx *ctx, const char *path) |
---|
26 | { |
---|
27 | size_t len, path_len; |
---|
28 | const char *s = ctx->cgi_extensions; |
---|
29 | |
---|
30 | path_len = strlen(path); |
---|
31 | |
---|
32 | FOR_EACH_WORD_IN_LIST(s, len) |
---|
33 | if (len < path_len && |
---|
34 | !my_strncasecmp(path + path_len - len, s, len)) |
---|
35 | return (1); |
---|
36 | |
---|
37 | return (0); |
---|
38 | } |
---|
39 | |
---|
40 | /* |
---|
41 | * UNIX socketpair() implementation. Why? Because Windows does not have it. |
---|
42 | * Return 0 on success, -1 on error. |
---|
43 | */ |
---|
44 | static int |
---|
45 | my_socketpair(struct conn *c, int sp[2]) |
---|
46 | { |
---|
47 | struct sockaddr_in sa; |
---|
48 | int sock, ret = -1; |
---|
49 | socklen_t len = sizeof(sa); |
---|
50 | |
---|
51 | (void) memset(&sa, 0, sizeof(sa)); |
---|
52 | sa.sin_family = AF_INET; |
---|
53 | sa.sin_port = htons(0); |
---|
54 | sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK); |
---|
55 | |
---|
56 | if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) { |
---|
57 | elog(E_LOG, c, "mysocketpair: socket(): %d", ERRNO); |
---|
58 | } else if (bind(sock, (struct sockaddr *) &sa, len) != 0) { |
---|
59 | elog(E_LOG, c, "mysocketpair: bind(): %d", ERRNO); |
---|
60 | (void) closesocket(sock); |
---|
61 | } else if (listen(sock, 1) != 0) { |
---|
62 | elog(E_LOG, c, "mysocketpair: listen(): %d", ERRNO); |
---|
63 | (void) closesocket(sock); |
---|
64 | } else if (getsockname(sock, (struct sockaddr *) &sa, &len) != 0) { |
---|
65 | elog(E_LOG, c, "mysocketpair: getsockname(): %d", ERRNO); |
---|
66 | (void) closesocket(sock); |
---|
67 | } else if ((sp[0] = socket(AF_INET, SOCK_STREAM, 6)) == -1) { |
---|
68 | elog(E_LOG, c, "mysocketpair: socket(): %d", ERRNO); |
---|
69 | (void) closesocket(sock); |
---|
70 | } else if (connect(sp[0], (struct sockaddr *) &sa, len) != 0) { |
---|
71 | elog(E_LOG, c, "mysocketpair: connect(): %d", ERRNO); |
---|
72 | (void) closesocket(sock); |
---|
73 | (void) closesocket(sp[0]); |
---|
74 | } else if ((sp[1] = accept(sock,(struct sockaddr *) &sa, &len)) == -1) { |
---|
75 | elog(E_LOG, c, "mysocketpair: accept(): %d", ERRNO); |
---|
76 | (void) closesocket(sock); |
---|
77 | (void) closesocket(sp[0]); |
---|
78 | } else { |
---|
79 | /* Success */ |
---|
80 | ret = 0; |
---|
81 | (void) closesocket(sock); |
---|
82 | } |
---|
83 | |
---|
84 | #ifndef _WIN32 |
---|
85 | (void) fcntl(sp[0], F_SETFD, FD_CLOEXEC); |
---|
86 | (void) fcntl(sp[1], F_SETFD, FD_CLOEXEC); |
---|
87 | #endif /* _WIN32*/ |
---|
88 | |
---|
89 | return (ret); |
---|
90 | } |
---|
91 | |
---|
92 | static void |
---|
93 | addenv(struct env_block *block, const char *fmt, ...) |
---|
94 | { |
---|
95 | int n, space; |
---|
96 | va_list ap; |
---|
97 | |
---|
98 | space = sizeof(block->buf) - block->len - 2; |
---|
99 | assert(space >= 0); |
---|
100 | |
---|
101 | va_start(ap, fmt); |
---|
102 | n = vsnprintf(block->buf + block->len, space, fmt, ap); |
---|
103 | va_end(ap); |
---|
104 | |
---|
105 | if (n > 0 && n < space && block->nvars < CGI_ENV_VARS - 2) { |
---|
106 | block->vars[block->nvars++] = block->buf + block->len; |
---|
107 | block->len += n + 1; /* Include \0 terminator */ |
---|
108 | } |
---|
109 | } |
---|
110 | |
---|
111 | static void |
---|
112 | add_http_headers_to_env(struct env_block *b, const char *s, int len) |
---|
113 | { |
---|
114 | const char *p, *v, *e = s + len; |
---|
115 | int space, n, i, ch; |
---|
116 | |
---|
117 | /* Loop through all headers in the request */ |
---|
118 | while (s < e) { |
---|
119 | |
---|
120 | /* Find where this header ends. Remember where value starts */ |
---|
121 | for (p = s, v = NULL; p < e && *p != '\n'; p++) |
---|
122 | if (v == NULL && *p == ':') |
---|
123 | v = p; |
---|
124 | |
---|
125 | /* 2 null terminators and "HTTP_" */ |
---|
126 | space = (sizeof(b->buf) - b->len) - (2 + 5); |
---|
127 | assert(space >= 0); |
---|
128 | |
---|
129 | /* Copy header if enough space in the environment block */ |
---|
130 | if (v > s && p > v + 2 && space > p - s) { |
---|
131 | |
---|
132 | /* Store var */ |
---|
133 | if (b->nvars < (int) NELEMS(b->vars) - 1) |
---|
134 | b->vars[b->nvars++] = b->buf + b->len; |
---|
135 | |
---|
136 | (void) memcpy(b->buf + b->len, "HTTP_", 5); |
---|
137 | b->len += 5; |
---|
138 | |
---|
139 | /* Copy header name. Substitute '-' to '_' */ |
---|
140 | n = v - s; |
---|
141 | for (i = 0; i < n; i++) { |
---|
142 | ch = s[i] == '-' ? '_' : s[i]; |
---|
143 | b->buf[b->len++] = toupper(ch); |
---|
144 | } |
---|
145 | |
---|
146 | b->buf[b->len++] = '='; |
---|
147 | |
---|
148 | /* Copy header value */ |
---|
149 | v += 2; |
---|
150 | n = p[-1] == '\r' ? (p - v) - 1 : p - v; |
---|
151 | for (i = 0; i < n; i++) |
---|
152 | b->buf[b->len++] = v[i]; |
---|
153 | |
---|
154 | /* Null-terminate */ |
---|
155 | b->buf[b->len++] = '\0'; |
---|
156 | } |
---|
157 | |
---|
158 | s = p + 1; /* Shift to the next header */ |
---|
159 | } |
---|
160 | } |
---|
161 | |
---|
162 | static void |
---|
163 | prepare_environment(const struct conn *c, const char *prog, |
---|
164 | struct env_block *blk) |
---|
165 | { |
---|
166 | const struct headers *h = &c->ch; |
---|
167 | const char *s; |
---|
168 | size_t len; |
---|
169 | |
---|
170 | blk->len = blk->nvars = 0; |
---|
171 | |
---|
172 | /* Prepare the environment block */ |
---|
173 | addenv(blk, "%s", "GATEWAY_INTERFACE=CGI/1.1"); |
---|
174 | addenv(blk, "%s", "SERVER_PROTOCOL=HTTP/1.1"); |
---|
175 | addenv(blk, "%s", "REDIRECT_STATUS=200"); /* PHP */ |
---|
176 | addenv(blk, "SERVER_PORT=%d", c->ctx->port); |
---|
177 | addenv(blk, "SERVER_NAME=%s", c->ctx->auth_realm); |
---|
178 | addenv(blk, "SERVER_ROOT=%s", c->ctx->document_root); |
---|
179 | addenv(blk, "DOCUMENT_ROOT=%s", c->ctx->document_root); |
---|
180 | addenv(blk, "REQUEST_METHOD=%s", known_http_methods[c->method].ptr); |
---|
181 | addenv(blk, "REMOTE_ADDR=%s", inet_ntoa(c->sa.u.sin.sin_addr)); |
---|
182 | addenv(blk, "REMOTE_PORT=%hu", ntohs(c->sa.u.sin.sin_port)); |
---|
183 | addenv(blk, "REQUEST_URI=%s", c->uri); |
---|
184 | addenv(blk, "SCRIPT_NAME=%s", prog + strlen(c->ctx->document_root)); |
---|
185 | addenv(blk, "SCRIPT_FILENAME=%s", prog); /* PHP */ |
---|
186 | addenv(blk, "PATH_TRANSLATED=%s", prog); |
---|
187 | |
---|
188 | if (h->ct.v_vec.len > 0) |
---|
189 | addenv(blk, "CONTENT_TYPE=%.*s", |
---|
190 | h->ct.v_vec.len, h->ct.v_vec.ptr); |
---|
191 | |
---|
192 | if (c->query != NULL) |
---|
193 | addenv(blk, "QUERY_STRING=%s", c->query); |
---|
194 | |
---|
195 | if (c->path_info != NULL) |
---|
196 | addenv(blk, "PATH_INFO=/%s", c->path_info); |
---|
197 | |
---|
198 | if (h->cl.v_big_int > 0) |
---|
199 | addenv(blk, "CONTENT_LENGTH=%lu", h->cl.v_big_int); |
---|
200 | |
---|
201 | if ((s = getenv("PATH")) != NULL) |
---|
202 | addenv(blk, "PATH=%s", s); |
---|
203 | |
---|
204 | #ifdef _WIN32 |
---|
205 | if ((s = getenv("COMSPEC")) != NULL) |
---|
206 | addenv(blk, "COMSPEC=%s", s); |
---|
207 | if ((s = getenv("SYSTEMROOT")) != NULL) |
---|
208 | addenv(blk, "SYSTEMROOT=%s", s); |
---|
209 | #else |
---|
210 | if ((s = getenv("LD_LIBRARY_PATH")) != NULL) |
---|
211 | addenv(blk, "LD_LIBRARY_PATH=%s", s); |
---|
212 | #endif /* _WIN32 */ |
---|
213 | |
---|
214 | if ((s = getenv("PERLLIB")) != NULL) |
---|
215 | addenv(blk, "PERLLIB=%s", s); |
---|
216 | |
---|
217 | if (h->user.v_vec.len > 0) { |
---|
218 | addenv(blk, "REMOTE_USER=%.*s", |
---|
219 | h->user.v_vec.len, h->user.v_vec.ptr); |
---|
220 | addenv(blk, "%s", "AUTH_TYPE=Digest"); |
---|
221 | } |
---|
222 | |
---|
223 | /* Add user-specified variables */ |
---|
224 | s = c->ctx->cgi_vars; |
---|
225 | FOR_EACH_WORD_IN_LIST(s, len) |
---|
226 | addenv(blk, "%.*s", len, s); |
---|
227 | |
---|
228 | /* Add all headers as HTTP_* variables */ |
---|
229 | add_http_headers_to_env(blk, c->headers, |
---|
230 | c->rem.headers_len - (c->headers - c->request)); |
---|
231 | |
---|
232 | blk->vars[blk->nvars++] = NULL; |
---|
233 | blk->buf[blk->len++] = '\0'; |
---|
234 | |
---|
235 | assert(blk->nvars < CGI_ENV_VARS); |
---|
236 | assert(blk->len > 0); |
---|
237 | assert(blk->len < (int) sizeof(blk->buf)); |
---|
238 | |
---|
239 | /* Debug stuff to view passed environment */ |
---|
240 | DBG(("%s: %d vars, %d env size", prog, blk->nvars, blk->len)); |
---|
241 | { |
---|
242 | int i; |
---|
243 | for (i = 0 ; i < blk->nvars; i++) |
---|
244 | DBG(("[%s]", blk->vars[i] ? blk->vars[i] : "null")); |
---|
245 | } |
---|
246 | } |
---|
247 | |
---|
248 | int |
---|
249 | run_cgi(struct conn *c, const char *prog) |
---|
250 | { |
---|
251 | struct env_block blk; |
---|
252 | char dir[FILENAME_MAX], *p; |
---|
253 | int ret, pair[2]; |
---|
254 | |
---|
255 | prepare_environment(c, prog, &blk); |
---|
256 | |
---|
257 | /* CGI must be executed in its own directory */ |
---|
258 | (void) my_snprintf(dir, sizeof(dir), "%s", prog); |
---|
259 | for (p = dir + strlen(dir) - 1; p > dir; p--) |
---|
260 | if (*p == '/') { |
---|
261 | *p++ = '\0'; |
---|
262 | break; |
---|
263 | } |
---|
264 | |
---|
265 | if (my_socketpair(c, pair) != 0) { |
---|
266 | ret = -1; |
---|
267 | } else if (spawn_process(c, prog, blk.buf, blk.vars, pair[1], dir)) { |
---|
268 | ret = -1; |
---|
269 | (void) closesocket(pair[0]); |
---|
270 | (void) closesocket(pair[1]); |
---|
271 | } else { |
---|
272 | ret = 0; |
---|
273 | c->loc.chan.sock = pair[0]; |
---|
274 | } |
---|
275 | |
---|
276 | return (ret); |
---|
277 | } |
---|
278 | |
---|
279 | void |
---|
280 | do_cgi(struct conn *c) |
---|
281 | { |
---|
282 | DBG(("running CGI: [%s]", c->uri)); |
---|
283 | assert(c->loc.io.size > CGI_REPLY_LEN); |
---|
284 | memcpy(c->loc.io.buf, CGI_REPLY, CGI_REPLY_LEN); |
---|
285 | c->loc.io.head = c->loc.io.tail = c->loc.io.total = CGI_REPLY_LEN; |
---|
286 | c->loc.io_class = &io_cgi; |
---|
287 | c->loc.flags = FLAG_R; |
---|
288 | if (c->method == METHOD_POST) |
---|
289 | c->loc.flags |= FLAG_W; |
---|
290 | } |
---|
291 | |
---|
292 | #endif /* !NO_CGI */ |
---|