1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3 /* Monkey HTTP Server
4 * ==================
5 * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io>
6 * Copyright (C) 2012-2013, Lauri Kasanen
7 *
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20
21 #include "cgi.h"
22
23 #include <sys/types.h>
24 #include <sys/wait.h>
25 #include <sys/time.h>
26 #include <sys/resource.h>
27 #include <sys/stat.h>
28
cgi_finish(struct cgi_request * r)29 void cgi_finish(struct cgi_request *r)
30 {
31 /*
32 * Unregister & close the CGI child process pipe reader fd from the
33 * thread event loop, otherwise we may get unexpected notifications.
34 */
35 mk_api->ev_del(mk_api->sched_loop(), (struct mk_event *) r);
36 close(r->fd);
37 if (r->chunked && r->active == MK_TRUE) {
38 PLUGIN_TRACE("CGI sending Chunked EOF");
39 channel_write(r, "0\r\n\r\n", 5);
40 }
41
42 /* Try to kill any child process */
43 if (r->child > 0) {
44 kill(r->child, SIGKILL);
45 r->child = 0;
46 }
47
48 /* Invalidte our socket handler */
49 requests_by_socket[r->socket] = NULL;
50 if (r->active == MK_TRUE) {
51 mk_api->http_request_end(r->plugin, r->cs, r->hangup);
52 }
53 cgi_req_del(r);
54 }
55
swrite(const int fd,const void * buf,const size_t count)56 int swrite(const int fd, const void *buf, const size_t count)
57 {
58 ssize_t pos = count, ret = 0;
59
60 while (pos > 0 && ret >= 0) {
61 ret = write(fd, buf, pos);
62 if (ret < 0) {
63 return ret;
64 }
65
66 pos -= ret;
67 buf += ret;
68 }
69 return count;
70 }
71
channel_write(struct cgi_request * r,void * buf,size_t count)72 int channel_write(struct cgi_request *r, void *buf, size_t count)
73 {
74 int ret;
75
76 if (r->active == MK_FALSE) {
77 return -1;
78 }
79
80 MK_TRACE("channel write: %d bytes", count);
81 mk_stream_in_cbuf(&r->sr->stream,
82 NULL,
83 buf, count,
84 NULL, NULL);
85
86 ret = mk_api->channel_flush(r->sr->session->channel);
87 if (ret & MK_CHANNEL_ERROR) {
88 r->active = MK_FALSE;
89 cgi_finish(r);
90 }
91 return 0;
92 }
93
cgi_write_post(void * p)94 static void cgi_write_post(void *p)
95 {
96 const struct post_t * const in = p;
97
98 swrite(in->fd, in->buf, in->len);
99 close(in->fd);
100 }
101
do_cgi(const char * const __restrict__ file,const char * const __restrict__ url,struct mk_http_request * sr,struct mk_http_session * cs,struct mk_plugin * plugin,char * interpreter,char * mimetype)102 static int do_cgi(const char *const __restrict__ file,
103 const char *const __restrict__ url,
104 struct mk_http_request *sr,
105 struct mk_http_session *cs,
106 struct mk_plugin *plugin,
107 char *interpreter,
108 char *mimetype)
109 {
110 int ret;
111 int devnull;
112 const int socket = cs->socket;
113 struct file_info finfo;
114 struct cgi_request *r = NULL;
115 struct mk_event *event;
116 char *env[30];
117 int writepipe[2], readpipe[2];
118 (void) plugin;
119
120 /* Unchanging env vars */
121 env[0] = "PATH_INFO=";
122 env[1] = "GATEWAY_INTERFACE=CGI/1.1";
123 env[2] = "REDIRECT_STATUS=200";
124 const int env_start = 3;
125 char *protocol;
126 unsigned long len;
127
128 /* Dynamic env vars */
129 unsigned short envpos = env_start;
130
131 char method[SHORTLEN];
132 char *query = NULL;
133 char request_uri[PATHLEN];
134 char script_filename[PATHLEN];
135 char script_name[PATHLEN];
136 char query_string[PATHLEN];
137 char remote_addr[INET6_ADDRSTRLEN+SHORTLEN];
138 char tmpaddr[INET6_ADDRSTRLEN], *ptr = tmpaddr;
139 char remote_port[SHORTLEN];
140 char content_length[SHORTLEN];
141 char content_type[SHORTLEN];
142 char server_software[SHORTLEN];
143 char server_protocol[SHORTLEN];
144 char http_host[SHORTLEN];
145
146 /* Check the interpreter exists */
147 if (interpreter) {
148 ret = mk_api->file_get_info(interpreter, &finfo, MK_FILE_EXEC);
149 if (ret == -1 ||
150 (finfo.is_file == MK_FALSE && finfo.is_link == MK_FALSE) ||
151 finfo.exec_access == MK_FALSE) {
152 return 500;
153 }
154 }
155
156 if (mimetype) {
157 sr->content_type.data = mimetype;
158 sr->content_type.len = strlen(mimetype);
159 }
160
161 snprintf(method, SHORTLEN, "REQUEST_METHOD=%.*s", (int) sr->method_p.len, sr->method_p.data);
162 env[envpos++] = method;
163
164 snprintf(server_software, SHORTLEN, "SERVER_SOFTWARE=%s",
165 mk_api->config->server_signature);
166 env[envpos++] = server_software;
167
168 snprintf(http_host, SHORTLEN, "HTTP_HOST=%.*s", (int) sr->host.len, sr->host.data);
169 env[envpos++] = http_host;
170
171 if (sr->protocol == MK_HTTP_PROTOCOL_11)
172 protocol = MK_HTTP_PROTOCOL_11_STR;
173 else
174 protocol = MK_HTTP_PROTOCOL_10_STR;
175
176 snprintf(server_protocol, SHORTLEN, "SERVER_PROTOCOL=%s", protocol);
177 env[envpos++] = server_protocol;
178
179 if (sr->query_string.len) {
180 query = mk_api->mem_alloc_z(sr->query_string.len + 1);
181 memcpy(query, sr->query_string.data, sr->query_string.len);
182 snprintf(request_uri, PATHLEN, "REQUEST_URI=%s?%s", url, query);
183 }
184 else {
185 snprintf(request_uri, PATHLEN, "REQUEST_URI=%s", url);
186 }
187 env[envpos++] = request_uri;
188
189 snprintf(script_filename, PATHLEN, "SCRIPT_FILENAME=%s", file);
190 env[envpos++] = script_filename;
191
192 snprintf(script_name, PATHLEN, "SCRIPT_NAME=%s", url);
193 env[envpos++] = script_name;
194
195 if (query) {
196 snprintf(query_string, PATHLEN, "QUERY_STRING=%s", query);
197 env[envpos++] = query_string;
198 mk_api->mem_free(query);
199 }
200
201 if (mk_api->socket_ip_str(socket, &ptr, INET6_ADDRSTRLEN, &len) < 0)
202 tmpaddr[0] = '\0';
203 snprintf(remote_addr, INET6_ADDRSTRLEN+SHORTLEN, "REMOTE_ADDR=%s", tmpaddr);
204 env[envpos++] = remote_addr;
205
206 snprintf(remote_port, SHORTLEN, "REMOTE_PORT=%ld", sr->port);
207 env[envpos++] = remote_port;
208
209 if (sr->data.len) {
210 snprintf(content_length, SHORTLEN, "CONTENT_LENGTH=%lu", sr->data.len);
211 env[envpos++] = content_length;
212 }
213
214 if (sr->content_type.len) {
215 snprintf(content_type, SHORTLEN, "CONTENT_TYPE=%.*s", (int)sr->content_type.len, sr->content_type.data);
216 env[envpos++] = content_type;
217 }
218
219
220 /* Must be NULL-terminated */
221 env[envpos] = NULL;
222
223 /* pipes, from monkey's POV */
224 if (pipe(writepipe) || pipe(readpipe)) {
225 mk_err("Failed to create pipe");
226 return 403;
227 }
228
229 pid_t pid = vfork();
230 if (pid < 0) {
231 mk_err("Failed to fork");
232 return 403;
233 }
234
235 /* Child */
236 if (pid == 0) {
237 close(writepipe[1]);
238 close(readpipe[0]);
239
240 /* Our stdin is the read end of monkey's writing */
241 if (dup2(writepipe[0], 0) < 0) {
242 mk_err("dup2 failed");
243 _exit(1);
244 }
245 close(writepipe[0]);
246
247 /* Our stdout is the write end of monkey's reading */
248 if (dup2(readpipe[1], 1) < 0) {
249 mk_err("dup2 failed");
250 _exit(1);
251 }
252 close(readpipe[1]);
253
254 /* Our stderr goes to /dev/null */
255 devnull = open("/dev/null", O_WRONLY);
256 if (devnull == -1) {
257 perror("open");
258 _exit(1);
259 }
260
261 if (dup2(devnull, 2) < 0) {
262 mk_err("dup2 failed");
263 _exit(1);
264 }
265 close(devnull);
266
267 char *argv[3] = { NULL };
268
269 char *tmp = mk_api->str_dup(file);
270 if (chdir(dirname(tmp)))
271 _exit(1);
272
273 char *tmp2 = mk_api->str_dup(file);
274 argv[0] = basename(tmp2);
275
276 /* Restore signals for the child */
277 signal(SIGPIPE, SIG_DFL);
278 signal(SIGCHLD, SIG_DFL);
279
280 if (!interpreter) {
281 execve(file, argv, env);
282 }
283 else {
284 argv[0] = basename(interpreter);
285 argv[1] = (char *) file;
286 execve(interpreter, argv, env);
287 }
288 /* Exec failed, return */
289 _exit(1);
290 }
291
292 /* Yay me */
293 close(writepipe[0]);
294 close(readpipe[1]);
295
296 /* If we have POST data to write, spawn a thread to do that */
297 if (sr->data.len) {
298 struct post_t p;
299 pthread_t tid;
300
301 p.fd = writepipe[1];
302 p.buf = sr->data.data;
303 p.len = sr->data.len;
304
305 ret = mk_api->worker_spawn(cgi_write_post, &p, &tid);
306 if (ret != 0) {
307 return 403;
308 }
309 }
310 else {
311 close(writepipe[1]);
312 }
313
314 r = cgi_req_create(readpipe[0], socket, plugin, sr, cs);
315 if (!r) {
316 return 403;
317 }
318 r->child = pid;
319
320 /*
321 * Hang up?: by default Monkey assumes the CGI scripts generate
322 * content dynamically (no Content-Length header), so for such HTTP/1.0
323 * clients we should close the connection as KeepAlive is not supported
324 * by specification, only on HTTP/1.1 where the Chunked Transfer encoding
325 * exists.
326 */
327 if (r->sr->protocol >= MK_HTTP_PROTOCOL_11) {
328 r->hangup = MK_FALSE;
329 }
330
331 /* Set transfer encoding */
332 if (r->sr->protocol >= MK_HTTP_PROTOCOL_11 &&
333 (r->sr->headers.status < MK_REDIR_MULTIPLE ||
334 r->sr->headers.status > MK_REDIR_USE_PROXY)) {
335 r->sr->headers.transfer_encoding = MK_HEADER_TE_TYPE_CHUNKED;
336 r->chunked = 1;
337 }
338
339 /* Register the 'request' context */
340 cgi_req_add(r);
341
342 /* Prepare the built-in event structure */
343 event = &r->event;
344 event->fd = readpipe[0];
345 event->type = MK_EVENT_CUSTOM;
346 event->mask = MK_EVENT_EMPTY;
347 event->data = r;
348 event->handler = cb_cgi_read;
349
350 /* Register the event into the worker event-loop */
351 ret = mk_api->ev_add(mk_api->sched_loop(),
352 readpipe[0],
353 MK_EVENT_CUSTOM, MK_EVENT_READ, r);
354 if (ret != 0) {
355 return 403;
356 }
357
358
359 /* XXX Fixme: this needs to be atomic */
360 requests_by_socket[socket] = r;
361 return 200;
362 }
363
mk_cgi_plugin_init(struct plugin_api ** api,char * confdir)364 int mk_cgi_plugin_init(struct plugin_api **api, char *confdir)
365 {
366 struct rlimit lim;
367 (void) confdir;
368
369 mk_api = *api;
370 mk_list_init(&cgi_global_matches);
371 pthread_key_create(&cgi_request_list, NULL);
372
373 /*
374 * We try to perform some quick lookup over the list of CGI
375 * instances. We do this with a fixed length array, if you use CGI
376 * you don't care too much about performance anyways.
377 */
378 getrlimit(RLIMIT_NOFILE, &lim);
379 requests_by_socket = mk_api->mem_alloc_z(sizeof(struct cgi_request *) * lim.rlim_cur);
380
381 /* Make sure we act good if the child dies */
382 signal(SIGPIPE, SIG_IGN);
383 signal(SIGCHLD, SIG_IGN);
384
385 return 0;
386 }
387
mk_cgi_plugin_exit()388 int mk_cgi_plugin_exit()
389 {
390 regfree(&match_regex);
391 mk_api->mem_free(requests_by_socket);
392
393 return 0;
394 }
395
mk_cgi_stage30(struct mk_plugin * plugin,struct mk_http_session * cs,struct mk_http_request * sr,int n_params,struct mk_list * params)396 int mk_cgi_stage30(struct mk_plugin *plugin,
397 struct mk_http_session *cs,
398 struct mk_http_request *sr,
399 int n_params,
400 struct mk_list *params)
401 {
402 char *interpreter = NULL;
403 char *mimetype = NULL;
404 struct mk_vhost_handler_param *param;
405 (void) plugin;
406
407 const char *const file = sr->real_path.data;
408
409 if (!sr->file_info.is_file) {
410 return MK_PLUGIN_RET_NOT_ME;
411 }
412
413 /* start running the CGI */
414 if (cgi_req_get(cs->socket)) {
415 PLUGIN_TRACE("Error, someone tried to retry\n");
416 return MK_PLUGIN_RET_CONTINUE;
417 }
418
419 if (n_params > 0) {
420 /* Interpreter */
421 param = mk_api->handler_param_get(0, params);
422 if (param) {
423 interpreter = param->p.data;
424 }
425
426 /* Mimetype */
427 param = mk_api->handler_param_get(0, params);
428 if (param) {
429 mimetype = param->p.data;
430 }
431 }
432
433 int status = do_cgi(file, sr->uri_processed.data,
434 sr, cs, plugin, interpreter, mimetype);
435
436 /* These are just for the other plugins, such as logger; bogus data */
437 mk_api->header_set_http_status(sr, status);
438 if (status != 200) {
439 return MK_PLUGIN_RET_CLOSE_CONX;
440 }
441
442 sr->headers.cgi = SH_CGI;
443 return MK_PLUGIN_RET_CONTINUE;
444 }
445
446 /*
447 * Invoked everytime a remote client drop the active connection, this
448 * callback is triggered by the Monkey Scheduler
449 */
mk_cgi_stage30_hangup(struct mk_plugin * plugin,struct mk_http_session * cs,struct mk_http_request * sr)450 int mk_cgi_stage30_hangup(struct mk_plugin *plugin,
451 struct mk_http_session *cs,
452 struct mk_http_request *sr)
453 {
454 struct cgi_request *r;
455 (void) sr;
456 (void) plugin;
457
458 PLUGIN_TRACE("CGI / Parent connection closed (hangup)");
459 r = requests_by_socket[cs->socket];
460 if (!r) {
461 return -1;
462 }
463
464 r->active = MK_FALSE;
465 cgi_finish(r);
466 return 0;
467 }
468
mk_cgi_worker_init()469 void mk_cgi_worker_init()
470 {
471 struct mk_list *list = mk_api->mem_alloc_z(sizeof(struct mk_list));
472
473 mk_list_init(list);
474 pthread_setspecific(cgi_request_list, (void *) list);
475 }
476
477
478 struct mk_plugin_stage mk_plugin_stage_cgi = {
479 .stage30 = &mk_cgi_stage30,
480 .stage30_hangup = &mk_cgi_stage30_hangup
481 };
482
483 struct mk_plugin mk_plugin_cgi = {
484 /* Identification */
485 .shortname = "cgi",
486 .name = "Common Gateway Interface",
487 .version = MK_VERSION_STR,
488 .hooks = MK_PLUGIN_STAGE,
489
490 /* Init / Exit */
491 .init_plugin = mk_cgi_plugin_init,
492 .exit_plugin = mk_cgi_plugin_exit,
493
494 /* Init Levels */
495 .master_init = NULL,
496 .worker_init = mk_cgi_worker_init,
497
498 /* Type */
499 .stage = &mk_plugin_stage_cgi
500 };
501