/**************************************************************************** * apps/netutils/thttpd/thttpd_cgi.c * CGI support * * Copyright (C) 2009, 2011, 2016 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * Derived from the file libhttpd.c in the original THTTPD package: * * Copyright © 1995,1998,1999,2000,2001 by * Jef Poskanzer . All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "netutils/thttpd.h" #include "config.h" #include "timers.h" #include "libhttpd.h" #include "thttpd_alloc.h" #include "thttpd_strings.h" #include "fdwatch.h" #if defined(CONFIG_THTTPD) && defined(CONFIG_THTTPD_CGI_PATTERN) /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ /* CONFIG_THTTPD_CGIDUMP will dump the contents of each transfer to and * from the CGI task. */ #ifdef CONFIG_THTTPD_CGIDUMP # define cgi_dumpbuffer(m,a,n) lib_dumpbuffer(m,(FAR const uint8_t*)a,n) #else # define cgi_dumpbuffer(m,a,n) #endif /**************************************************************************** * Private Types ****************************************************************************/ enum cgi_outbuffer_e { CGI_OUTBUFFER_READHEADER = 0, /* Reading header from HTTP client */ CGI_OUTBUFFER_HEADERREAD, /* Header has been read */ CGI_OUTBUFFER_HEADERSENT, /* Header has been sent to the CGI program */ CGI_OUTBUFFER_READDATA, /* Transferring data from CGI to client */ CGI_OUTBUFFER_DONE, /* Finished */ }; struct cgi_outbuffer_s { enum cgi_outbuffer_e state; /* State of the transfer */ char *buffer; /* Allocated I/O buffer */ size_t size; /* Size of the allocation */ size_t len; /* Amount of valid data in the allocated buffer */ }; struct cgi_inbuffer_s { int contentlength; /* Size of content to send to CGI task */ int nbytes; /* Number of bytes sent */ char buffer[CONFIG_THTTPD_CGIINBUFFERSIZE]; /* Fixed size input buffer */ }; struct cgi_conn_s { /* Descriptors */ int connfd; /* Socket connect to CGI client */ int rdfd; /* Pipe read fd */ int wrfd; /* Pipe write fd */ /* Buffering */ struct cgi_outbuffer_s outbuf; /* Dynamically sized output buffer */ struct cgi_inbuffer_s inbuf; /* Fixed size input buffer */ }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ static void create_environment(httpd_conn *hc); static char **make_argp(httpd_conn *hc); static inline int cgi_interpose_input(struct cgi_conn_s *cc); static inline int cgi_interpose_output(struct cgi_conn_s *cc); static int cgi_child(int argc, char **argv); /**************************************************************************** * Private Data ****************************************************************************/ /* Used to hold off the main task until the CGI tasks have been configured */ static sem_t g_cgisem; /**************************************************************************** * Private Functions ****************************************************************************/ /* semaphore helpers */ static inline void cgi_semtake(void) { while (sem_wait(&g_cgisem) != 0) { /* The only case that an error should occr here is if the wait was\ * awakened by a signal. */ DEBUGASSERT(errno == EINTR || errno == ECANCELED); } } static inline void cgi_semgive(void) { sem_post(&g_cgisem); } /* Set up environment variables. Be real careful here to avoid * letting malicious clients overrun a buffer. We don't have * to worry about freeing stuff since we're a sub-task. */ static void create_environment(httpd_conn *hc) { char *cp; char buf[256]; setenv("PATH", CONFIG_THTTPD_CGI_PATH, TRUE); #ifdef CGI_LD_LIBRARY_PATH setenv("LD_LIBRARY_PATH", CGI_LD_LIBRARY_PATH, TRUE); #endif /* CGI_LD_LIBRARY_PATH */ setenv("SERVER_SOFTWARE", CONFIG_THTTPD_SERVER_SOFTWARE, TRUE); /* If vhosting, use that server-name here. */ #ifdef CONFIG_THTTPD_VHOST if (hc->vhostname) { cp = hc->vhostname; } else #endif { cp = hc->hs->hostname; } if (cp) { setenv("SERVER_NAME", cp, TRUE); } setenv("GATEWAY_INTERFACE", "CGI/1.1", TRUE); setenv("SERVER_PROTOCOL", hc->protocol, TRUE); snprintf(buf, sizeof(buf), "%d", (int)CONFIG_THTTPD_PORT); setenv("SERVER_PORT", buf, TRUE); setenv("REQUEST_METHOD", httpd_method_str(hc->method), TRUE); if (hc->pathinfo[0] != '\0') { char *cp2; size_t l; snprintf(buf, sizeof(buf), "/%s", hc->pathinfo); setenv("PATH_INFO", buf, TRUE); l = strlen(httpd_root) + strlen(hc->pathinfo) + 1; cp2 = NEW(char, l); if (cp2) { snprintf(cp2, l, "%s%s", httpd_root, hc->pathinfo); setenv("PATH_TRANSLATED", cp2, TRUE); } } snprintf(buf, sizeof(buf), "/%s", strcmp(hc->origfilename, ".") == 0 ? "" : hc->origfilename); setenv("SCRIPT_NAME", buf, TRUE); if (hc->query[0] != '\0') { setenv("QUERY_STRING", hc->query, TRUE); } setenv("REMOTE_ADDR", httpd_ntoa(&hc->client_addr), TRUE); if (hc->referer[0] != '\0') { setenv("HTTP_REFERER", hc->referer, TRUE); } if (hc->useragent[0] != '\0') { setenv("HTTP_USER_AGENT", hc->useragent, TRUE); } if (hc->accept[0] != '\0') { setenv("HTTP_ACCEPT", hc->accept, TRUE); } if (hc->accepte[0] != '\0') { setenv("HTTP_ACCEPT_ENCODING", hc->accepte, TRUE); } if (hc->acceptl[0] != '\0') { setenv("HTTP_ACCEPT_LANGUAGE", hc->acceptl, TRUE); } if (hc->cookie[0] != '\0') { setenv("HTTP_COOKIE", hc->cookie, TRUE); } if (hc->contenttype[0] != '\0') { setenv("CONTENT_TYPE", hc->contenttype, TRUE); } if (hc->hdrhost[0] != '\0') { setenv("HTTP_HOST", hc->hdrhost, TRUE); } if (hc->contentlength != -1) { snprintf(buf, sizeof(buf), "%lu", (unsigned long)hc->contentlength); setenv("CONTENT_LENGTH", buf, TRUE); } if (hc->remoteuser[0] != '\0') { setenv("REMOTE_USER", hc->remoteuser, TRUE); } if (hc->authorization[0] != '\0') { setenv("AUTH_TYPE", "Basic", TRUE); } /* We only support Basic auth at the moment. */ if (getenv("TZ") != NULL) { setenv("TZ", getenv("TZ"), TRUE); } setenv("CGI_PATTERN", CONFIG_THTTPD_CGI_PATTERN, TRUE); } /* Set up argument vector */ static FAR char **make_argp(httpd_conn *hc) { FAR char **argp; int argn; char *cp1; char *cp2; /* By allocating an arg slot for every character in the query, plus one * for the filename and one for the NULL, we are guaranteed to have * enough. We could actually use strlen/2. */ argp = NEW(char *, strlen(hc->query) + 2); if (!argp) { return NULL; } argp[0] = strrchr(hc->expnfilename, '/'); if (argp[0]) { ++argp[0]; } else { argp[0] = hc->expnfilename; } argn = 1; /* According to the CGI spec at http://hoohoo.ncsa.uiuc.edu/cgi/cl.html, * "The server should search the query information for a non-encoded = * character to determine if the command line is to be used, if it finds * one, the command line is not to be used." */ if (strchr(hc->query, '=') == NULL) { for (cp1 = cp2 = hc->query; *cp2 != '\0'; ++cp2) { if (*cp2 == '+') { *cp2 = '\0'; httpd_strdecode(cp1, cp1); argp[argn++] = cp1; cp1 = cp2 + 1; } } if (cp2 != cp1) { httpd_strdecode(cp1, cp1); argp[argn++] = cp1; } } argp[argn] = NULL; return argp; } /* Data is available from the client socket. This routine is * used only for POST requests. It reads the data from the * client and sends it to the child thread. */ static inline int cgi_interpose_input(struct cgi_conn_s *cc) { ssize_t nbytes_read; ssize_t nbytes_written; ninfo("nbytes: %d contentlength: %d\n", cc->inbuf.nbytes, cc->inbuf.contentlength); if (cc->inbuf.nbytes < cc->inbuf.contentlength) { do { nbytes_read = read(cc->connfd, cc->inbuf.buffer, MIN(CONFIG_THTTPD_CGIINBUFFERSIZE, cc->inbuf.contentlength - cc->inbuf.nbytes)); ninfo("nbytes_read: %d\n", nbytes_read); if (nbytes_read < 0) { if (errno != EINTR) { nerr("ERROR: read failed: %d\n", errno); return 1; } } } while (nbytes_read < 0); if (nbytes_read > 0) { nbytes_written = httpd_write(cc->wrfd, cc->inbuf.buffer, nbytes_read); ninfo("nbytes_written: %d\n", nbytes_written); if (nbytes_written != nbytes_read) { nerr("ERROR: httpd_write failed\n"); return 1; } cgi_dumpbuffer("Sent to CGI:", cc->inbuf.buffer, nbytes_written); } cc->inbuf.nbytes += nbytes_read; } if (cc->inbuf.nbytes >= cc->inbuf.contentlength) { /* Special hack to deal with broken browsers that send a LF or CRLF * after POST data, causing TCP resets - we just read and discard up * to 2 bytes. Unfortunately this doesn't fix the problem for CGIs * which avoid the interposer task due to their POST data being * short. Creating an interposer task for all POST CGIs is * unacceptably expensive. The eventual fix will come when interposing * gets integrated into the main loop as a tasklet instead of a task. */ /* Turn on no-delay mode in case we previously cleared it. */ httpd_set_ndelay(cc->connfd); /* And read up to 2 bytes. */ read(cc->connfd, cc->inbuf.buffer, CONFIG_THTTPD_CGIINBUFFERSIZE); return 1; } return 0; } /* This routine is used for parsed-header CGIs. The idea here is that the * CGI can return special headers such as "Status:" and "Location:" which * change the return status of the response. Since the return status has to * be the very first line written out, we have to accumulate all the headers * and check for the special ones before writing the status. Then we write * out the saved headers and proceed to echo the rest of the response. */ static inline int cgi_interpose_output(struct cgi_conn_s *cc) { ssize_t nbytes_read; char *br = NULL; int status; const char *title; char *cp; /* Make sure the connection is in blocking mode. It should already be * blocking, but we might as well be sure. */ httpd_clear_ndelay(cc->connfd); /* Loop while there are things we can do without waiting for more input */ ninfo("state: %d\n", cc->outbuf.state); switch (cc->outbuf.state) { case CGI_OUTBUFFER_READHEADER: { /* Slurp in all headers as they become available from the client. */ do { /* Read until we successfully read data or until an error * occurs. EAGAIN is not an error, but it is still cause to * return. */ nbytes_read = read(cc->rdfd, cc->inbuf.buffer, CONFIG_THTTPD_CGIINBUFFERSIZE); ninfo("Read %d bytes from fd %d\n", nbytes_read, cc->rdfd); if (nbytes_read < 0) { if (errno != EINTR) { if (errno != EAGAIN) { nerr("ERROR: read: %d\n", errno); } return 1; } } else { cgi_dumpbuffer("Received from CGI:", cc->inbuf.buffer, nbytes_read); } } while (nbytes_read < 0); /* Check for end-of-file */ if (nbytes_read <= 0) { ninfo("End-of-file\n"); br = &(cc->outbuf.buffer[cc->outbuf.len]); cc->outbuf.state = CGI_OUTBUFFER_HEADERREAD; } else { /* Accumulate more header data */ httpd_realloc_str(&cc->outbuf.buffer, &cc->outbuf.size, cc->outbuf.len + nbytes_read); memcpy(&(cc->outbuf.buffer[cc->outbuf.len]), cc->inbuf.buffer, nbytes_read); cc->outbuf.len += nbytes_read; cc->outbuf.buffer[cc->outbuf.len] = '\0'; ninfo("Header bytes accumulated: %d\n", cc->outbuf.len); /* Check for end of header */ if ((br = strstr(cc->outbuf.buffer, "\r\n\r\n")) != NULL || (br = strstr(cc->outbuf.buffer, "\012\012")) != NULL) { ninfo("End-of-header\n"); cc->outbuf.state = CGI_OUTBUFFER_HEADERREAD; } else { /* All of the headers have not yet been read ... Return. * We will be called again when more data is available * in the pipe connected to the CGI task. */ return 0; } } } /* Otherwise, fall through and parse status in the HTTP headers */ case CGI_OUTBUFFER_HEADERREAD: { /* If there were no headers, bail. */ if (cc->outbuf.buffer[0] == '\0') { cc->outbuf.state = CGI_OUTBUFFER_DONE; return 1; } /* Figure out the status. Look for a Status: or Location: header; * else if there's an HTTP header line, get it from there; else * default to 200. */ status = 200; if (strncmp(cc->outbuf.buffer, "HTTP/", 5) == 0) { cp = cc->outbuf.buffer; cp += strcspn(cp, " \t"); status = atoi(cp); } if ((cp = strstr(cc->outbuf.buffer, "Status:")) != NULL && cp < br && (cp == cc->outbuf.buffer || *(cp - 1) == '\012')) { cp += 7; cp += strspn(cp, " \t"); status = atoi(cp); } if ((cp = strstr(cc->outbuf.buffer, "Location:")) != NULL && cp < br && (cp == cc->outbuf.buffer || *(cp - 1) == '\012')) { status = 302; } /* Write the status line. */ ninfo("Status: %d\n", status); switch (status) { case 200: title = ok200title; break; case 302: title = err302title; break; case 304: title = err304title; break; case 400: BADREQUEST("status"); title = httpd_err400title; break; #ifdef CONFIG_THTTPD_AUTH_FILE case 401: title = err401title; break; #endif case 403: title = err403title; break; case 404: title = err404title; break; case 408: title = httpd_err408title; break; case 500: INTERNALERROR("status"); title = err500title; break; case 501: NOTIMPLEMENTED("status"); title = err501title; break; case 503: title = httpd_err503title; break; default: title = "Something"; break; } snprintf(cc->inbuf.buffer, CONFIG_THTTPD_CGIINBUFFERSIZE, "HTTP/1.0 %d %s\r\n", status, title); httpd_write(cc->connfd, cc->inbuf.buffer, strlen(cc->inbuf.buffer)); /* Write the saved cc->outbuf.buffer to the client. */ httpd_write(cc->connfd, cc->outbuf.buffer, cc->outbuf.len); } /* Then set up to read the data following the header from the CGI * program and pass it back to the client. We return now; we will * be called again when data is available on the pipe. */ cc->outbuf.state = CGI_OUTBUFFER_READDATA; return 0; case CGI_OUTBUFFER_READDATA: { /* Read data from the pipe. */ do { /* Read until we successfully read data or until an error * occurs. EAGAIN is not an error, but it is still cause * to return. */ nbytes_read = read(cc->rdfd, cc->inbuf.buffer, CONFIG_THTTPD_CGIINBUFFERSIZE); ninfo("Read %d bytes from fd %d\n", nbytes_read, cc->rdfd); if (nbytes_read < 0) { if (errno != EINTR) { if (errno != EAGAIN) { nerr("ERROR: read: %d\n", errno); } return 1; } } else { cgi_dumpbuffer("Received from CGI:", cc->inbuf.buffer, nbytes_read); } } while (nbytes_read < 0); /* Check for end of file */ if (nbytes_read == 0) { ninfo("End-of-file\n"); cc->outbuf.state = CGI_OUTBUFFER_DONE; return 1; } else { /* Forward the data from the CGI program to the client */ httpd_write(cc->connfd, cc->inbuf.buffer, nbytes_read); } } break; case CGI_OUTBUFFER_DONE: default: return 1; } return 0; } /* CGI child task. */ static int cgi_child(int argc, char **argv) { FAR httpd_conn *hc = (FAR httpd_conn *)strtoul(argv[1], NULL, 16); #if CONFIG_THTTPD_CGI_TIMELIMIT > 0 ClientData client_data; #endif FAR char **argp; FAR struct cgi_conn_s *cc; FAR struct fdwatch_s *fw; FAR char *directory; FAR char *dupname; bool indone; bool outdone; int child; int pipefd[2]; int nbytes; int fd; int ret; int errcode = 1; /* Use low-level debug out (because the low-level output may survive * closing all file descriptors */ ninfo("Started: %s\n", argv[1]); /* Allocate memory and initialize memory for interposing */ cc = (FAR struct cgi_conn_s *)httpd_malloc(sizeof(struct cgi_conn_s)); if (!cc) { nerr("ERROR: cgi_conn allocation failed\n"); close(hc->conn_fd); goto errout; } cc->connfd = hc->conn_fd; cc->wrfd = -1; cc->rdfd = -1; memset(&cc->outbuf, 0, sizeof(struct cgi_outbuffer_s)); /* Update all of the environment variable settings, these will be inherited * by the CGI task. */ create_environment(hc); /* Make the argument vector. */ argp = make_argp(hc); /* Close all file descriptors EXCEPT for stdin, stdout, stderr and * hc->conn_fd. We'll keep stderr open for error reporting; stdin and * stdout will be closed later by dup2(). Keeping stdin and stdout open * now prevents re-use of fd=0 and 1 by pipe(). */ ninfo("Closing descriptors\n"); for (fd = 3; fd < CONFIG_THTTPD_NFILE_DESCRIPTORS; fd++) { /* Keep hc->conn_fd open for obvious reasons */ if (fd != hc->conn_fd) { close(fd); } } /* Create pipes that will be interposed between the CGI task's stdin or * stdout and the socket. * * Setup up the STDIN pipe - a pipe to transfer data received on the * socket to the CGI program. */ ninfo("Create STDIN pipe\n"); ret = pipe(pipefd); if (ret < 0) { nerr("ERROR: STDIN pipe: %d\n", errno); goto errout_with_cgiconn; } else { /* Then map the receiving end the pipe to stdin, save the sending end, * and closing the original receiving end */ ret = dup2(pipefd[0], 0); cc->wrfd = pipefd[1]; close(pipefd[0]); if (ret < 0) { nerr("ERROR: STDIN dup2: %d\n", errno); goto errout_with_descriptors; } } /* Set up the STDOUT pipe - a pipe to transfer data received from the CGI * program to the client. */ if (ret == 0) { ninfo("Create STDOUT pipe\n"); ret = pipe(pipefd); if (ret < 0) { nerr("ERROR: STDOUT pipe: %d\n", errno); goto errout_with_descriptors; } else { /* Then map the sending end the pipe to stdout, save the * receiving end, and closing the original sending end */ ret = dup2(pipefd[1], 1); cc->rdfd = pipefd[0]; close(pipefd[1]); if (ret < 0) { nerr("ERROR: STDOUT dup2: %d\n", errno); goto errout_with_descriptors; } } } /* chdir to the directory containing the binary. This isn't in the CGI 1.1 * spec, but it's what other HTTP servers do. */ dupname = httpd_strdup(hc->expnfilename); if (dupname) { directory = dirname(dupname); if (directory) { chdir(directory); /* ignore errors */ } httpd_free(dupname); } /* Allocate memory for output buffering */ httpd_realloc_str(&cc->outbuf.buffer, &cc->outbuf.size, CONFIG_THTTPD_CGIOUTBUFFERSIZE); if (!cc->outbuf.buffer) { nerr("ERROR: hdr allocation failed\n"); goto errout_with_descriptors; } /* Create fdwatch structures */ fw = fdwatch_initialize(2); if (!fw) { nerr("ERROR: fdwatch allocation failed\n"); goto errout_with_outbuffer; } /* Run the CGI program. */ ninfo("Starting CGI: %s\n", hc->expnfilename); #ifdef CONFIG_THTTPD_NXFLAT child = exec(hc->expnfilename, argp, NULL, g_thttpdsymtab, g_thttpdnsymbols); #else child = exec(hc->expnfilename, argp, NULL, NULL, 0); #endif if (child < 0) { /* Something went wrong. */ nerr("ERROR: execve %s: %d\n", hc->expnfilename, errno); goto errout_with_watch; } /* Schedule a kill for the child task in case it runs too long. */ #if CONFIG_THTTPD_CGI_TIMELIMIT > 0 client_data.i = child; if (tmr_create(NULL, cgi_kill, client_data, CONFIG_THTTPD_CGI_TIMELIMIT * 1000L, 0) == NULL) { nerr("ERROR: tmr_create(cgi_kill child) failed\n"); goto errout_with_watch; } #endif /* Add the read descriptors to the watch */ fdwatch_add_fd(fw, cc->connfd, NULL); fdwatch_add_fd(fw, cc->rdfd, NULL); /* Send any data that is already buffer to the CGI task */ nbytes = hc->read_idx - hc->checked_idx; ninfo("nbytes: %d contentlength: %d\n", nbytes, hc->contentlength); if (nbytes > 0) { if (httpd_write(cc->wrfd, &(hc->read_buf[hc->checked_idx]), nbytes) != nbytes) { nerr("ERROR: httpd_write failed\n"); return 1; } } cc->inbuf.contentlength = hc->contentlength; cc->inbuf.nbytes = nbytes; /* Then perform the interposition */ indone = false; outdone = false; ninfo("Interposing\n"); cgi_semgive(); /* Not safe to reference hc after this point */ do { fdwatch(fw, 1000); /* Check for incoming data from the remote client to the CGI task */ if (!indone && fdwatch_check_fd(fw, cc->connfd)) { /* Transfer data from the client to the CGI program (POST) */ ninfo("Interpose input\n"); indone = cgi_interpose_input(cc); if (indone) { fdwatch_del_fd(fw, cc->connfd); } } /* Check for outgoing data from the CGI task to the remote client */ if (fdwatch_check_fd(fw, cc->rdfd)) { /* Handle receipt of headers and CGI program response (GET) */ ninfo("Interpose output\n"); outdone = cgi_interpose_output(cc); } /* No outgoing data... is the child task still running? Use kill() * kill() with signal number == 0 does not actually send a signal, but * can be used to check if the target task exists. If the task exists * but is hung, then you might enable CONFIG_THTTPD_CGI_TIMELIMIT to * kill the task. However, killing the task could cause other problems * (consider resetting the microprocessor instead). */ else if (kill(child, 0) != 0) { ninfo("CGI no longer running: %d\n", errno); outdone = true; } } while (!outdone); errcode = 0; /* Get rid of watch structures */ errout_with_watch: fdwatch_uninitialize(fw); /* Free output buffer memory */ errout_with_outbuffer: httpd_free(cc->outbuf.buffer); /* Close all descriptors */ errout_with_descriptors: close(cc->wrfd); close(cc->rdfd); errout_with_cgiconn: close(cc->connfd); httpd_free(cc); errout: ninfo("Return %d\n", errcode); if (errcode != 0) { INTERNALERROR("errout"); httpd_send_err(hc, 500, err500title, "", err500form, hc->encodedurl); httpd_write_response(hc); cgi_semgive(); } return errcode; } /**************************************************************************** * Private Function Prototypes ****************************************************************************/ int cgi(httpd_conn *hc) { char arg[16]; char *argv[2]; pid_t child; int retval = ERROR; /* Set up a semaphore to hold off the make THTTPD thread until the CGI * threads are configured (basically until the file descriptors are all * dup'ed and can be closed by the main thread). */ sem_init(&g_cgisem, 0, 0); if (hc->method == METHOD_GET || hc->method == METHOD_POST) { #ifdef CONFIG_THTTPD_CGILIMIT if (hc->hs->cgi_count >= CONFIG_THTTPD_CGILIMIT) { httpd_send_err(hc, 503, httpd_err503title, "", httpd_err503form, hc->encodedurl); goto errout_with_sem; } #endif ++hc->hs->cgi_count; httpd_clear_ndelay(hc->conn_fd); /* Start the child task. We use a trampoline task here so that we can * safely muck with the file descriptors before actually started the * CGI task. */ snprintf(arg, 16, "%p", hc); /* task_create doesn't handle binary arguments. */ argv[0] = arg; argv[1] = NULL; child = task_create("CGI child", CONFIG_THTTPD_CGI_PRIORITY, CONFIG_THTTPD_CGI_STACKSIZE, cgi_child, argv); if (child < 0) { nerr("ERROR: task_create: %d\n", errno); INTERNALERROR("task_create"); httpd_send_err(hc, 500, err500title, "", err500form, hc->encodedurl); goto errout_with_sem; } ninfo("Started CGI task %d for file '%s'\n", child, hc->expnfilename); /* Wait for the CGI threads to become initialized */ cgi_semtake(); hc->bytes_sent = CONFIG_THTTPD_CGI_BYTECOUNT; hc->should_linger = false; } else { NOTIMPLEMENTED("CGI"); httpd_send_err(hc, 501, err501title, "", err501form, httpd_method_str(hc->method)); goto errout_with_sem; } /* Successfully started */ retval = OK; errout_with_sem: sem_destroy(&g_cgisem); return retval; } #if CONFIG_THTTPD_CGI_TIMELIMIT > 0 static void cgi_kill(ClientData client_data, struct timeval *nowp) { pid_t pid = (pid_t)client_data.i; /* task_delete() is a very evil API. It can leave memory stranded! */ ninfo("Killing CGI child: %d\n", pid); if (task_delete(pid) != 0) { nerr("ERROR: task_delete() failed: %d\n", errno); } } #endif #endif /* CONFIG_THTTPD && CONFIG_THTTPD_CGI_PATTERN */