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