static void do_cgi(struct http_server_instance *ser, const char *uri, struct variable *vars, struct variable *headers) { int env_cnt, cgi_argc, i, res; char **cgi_env, **cgi_argv, *tmp, *path, *prog; struct variable *v; env_cnt = envcount(environ); cgi_env = (char **) alloca((CGI_ENV_LEN + env_cnt + 1) * sizeof(char *)); i = 0; cgi_env[i++] = "GATEWAY_INTERFACE=CGI/1.1"; cgi_env[i++] = "SERVER_PROTOCOL=HTTP/1.0"; cgi_env[i++] = "SERVER_SOFTWARE=OOGA"; /* FIXME */ asprintf(&cgi_env[i++], "SERVER_NAME=%s", "this.is.my.server.name.dom" /* host */); asprintf(&cgi_env[i++], "SERVER_PORT=%s", "80" /* port */); asprintf(&cgi_env[i++], "REQUEST_METHOD=%s", "POST" /* method_name[method] */); asprintf(&cgi_env[i++], "REQUEST_URI=%s", uri); asprintf(&cgi_env[i++], "SCRIPT_NAME=%s", "ooga" /* url */); if((v = find_var(headers, "Content-Length"))) asprintf(&cgi_env[i++], "CONTENT_LENGTH=%s", v->value); if((v = find_var(headers, "Content-Type"))) asprintf(&cgi_env[i++], "CONTENT_TYPE=%s", v->value); cgi_env[i] = 0; /* * FIXME: I don't like this, not one bit. * is there a clean way to count the number of variables without walking the list, or * alternatively, is there a way to cleanly keep reallocating larger and larger space for the argv list? * * Also, I don't like the if(!strlen_zero) statement there, I was going to do %s%c%s * and make %c a \0 and the second %s a zero-length string if there was no value, but I think that'd * end up stinkier. */ /* now set up argc/argv */ i = 1; if(vars) { v = vars; while(v) { v = v->next; i++; } } cgi_argv = (char **) alloca((i + 1) * sizeof(char *)); cgi_argc = 1; if(vars) { v = vars; while(v) { if(!strlen_zero(v->value)) asprintf(&cgi_argv[cgi_argc++], "%s=%s", v->name, v->value); else asprintf(&cgi_argv[cgi_argc++], "%s", v->name); v = v->next; } } cgi_argv[cgi_argc] = 0; /* copy uri to tmp for splitting into path and program */ path = tmp = alloca(strlen(uri) + 1); strcpy(tmp, uri); prog = strrchr(tmp, '/'); if(prog) { tmp[prog-tmp] = 0; prog++; /* prog now points to the program */ } else { /* no path?? */ path = "/bin"; prog = tmp; } chdir(path); cgi_argv[0] = prog; i = 3; while((res = access(cgi_argv[0], X_OK) != 0) || ((res == EAGAIN) && (i > 0))) { fprintf(stderr, "access returned %i, retries=%i\n", res, i); usleep(250); --i; } if(res == 0) { execve(cgi_argv[0], cgi_argv, cgi_env); /* doesn't return */ fprintf(stderr, "wtf, execve returned? errno=%i\n", errno); _exit(255); } else { /* can't execute */ fprintf(stderr, "can't execute %s: %i (%s)\n", cgi_argv[0], errno, strerror(errno)); _exit(255); } } static char *cgi_parent_process(FILE *http, pid_t child, int infd, int outfd, int *status, char **title, struct variable *headers) { int res, wres, first_read = 1, clen; struct pollfd pfd[2]; size_t size = 0; char inbuf[1024], outbuf[1024], *result = NULL; // TODO: add signal handlers // signal(SIGCHLD, cgi_child); // signal(SIGPIPE, SIG_IGN); /* NO! no signal! */ pfd[0].fd = infd; pfd[0].events = POLLIN; pfd[0].revents = 0; if(outfd > 0) { pfd[1].fd = outfd; pfd[1].events = POLLOUT; pfd[1].revents = 0; } /* now marshall data back and forth as needed */ while(poll(pfd, (outfd > 0) ? 2 : 1, -1) != -1) { if(pfd[0].revents & POLLIN) { /* CGI has data to read */ res = read(infd, inbuf, sizeof(inbuf)); if(res == 0) break; /* no data */ else if(res < 0) goto cgi_500; /* error */ if(first_read) { first_read = 0; *status = 200; fprintf(http, "HTTP/1.0 200 OK\r\nServer: OOGA\r\nPragma: no-cache\r\nConnection: close\r\n\r\n"); // signal(SIGCHLD, SIG_IGN); } else { wres = fwrite(inbuf, res, 1, http); } size += res; if(pfd[0].revents & POLLHUP) /* disconnect? */ break; } else if(outfd > 0 && pfd[1].revents & POLLOUT) { /* write POST data to cgi */ res = read(fileno(http), outbuf, sizeof(outbuf)); if(res < 1) { if(res == 0) { /* EOF? */ close(outfd); outfd = 0; } else /* error */ goto cgi_500; } wres = write(outfd, outbuf, res); if(wres != res) goto cgi_500; } else if(pfd[0].revents & POLLHUP) /* CGI disconnection */ break; else { /* other (unhandled) revent */ cgi_500: if(first_read) { result = http_error(500, "Internal Server Error", NULL, "Looks like the CGI crashed."); break; } else { fprintf(http, "\n\nLooks like the CGI crashed.\n\n"); break; } } } /* while(poll) */ /* buffer_flush(buffer_1); */ /* if we're using the TCP_CORK socket option, we've got to remove it to ensure the rest of the message is written out */ #ifdef TCP_CORK { int zero = 0; setsockopt(1, IPPROTO_TCP, TCP_CORK, &zero, sizeof(zero)); } #endif return result; } static char *cgi_callback(struct http_server_instance *ser, const char *uri, struct variable *vars, struct variable *headers, int *status, char **title, int *contentlength) { char *result = NULL; pid_t pid; /* child PID */ int fd[2]; /* http to CGI pipe pair, http writes to [1] */ int df[2]; /* CGI to http pipe pair, cgi writes to [1] */ if(pipe(fd) || pipe(df)) { return http_error(500, "Internal Server Error", NULL, "Cannot allocate pipe fds."); } pid = vfork(); if(pid > 0) { /* this is the parent (server) process */ close(fd[0]); close(df[1]); result = cgi_parent_process(ser->f, pid, df[0], fd[1], status, title, headers); } else if(pid == 0) { /* this is the child process that runs the CGI */ close(df[0]); close(fd[1]); // alarm(CGI_TIMEOUT); /* set up an alarm in case the CGI hangs */ do_cgi(ser, uri, vars, headers); /* and actually run the CGI program (doesn't return on success) */ } else { /* fork error */ result = http_error(500, "Internal Server Error", NULL, "Cannot vfork."); } return result; }