From 9ca05a3a514cf399d604a33b37dbc18e4fcd886a Mon Sep 17 00:00:00 2001 From: Ryan Bloom Date: Fri, 19 Nov 1999 20:22:51 +0000 Subject: [PATCH] Initial version of CGI Daemon module. This module needs to be tested more thoroughly and cleanup up a bit, but it does execute CGI's currently. git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@84137 13f79535-47bb-0310-9956-ffa450edef68 --- modules/generators/mod_cgid.c | 1006 +++++++++++++++++++++++++++++++++ 1 file changed, 1006 insertions(+) create mode 100644 modules/generators/mod_cgid.c diff --git a/modules/generators/mod_cgid.c b/modules/generators/mod_cgid.c new file mode 100644 index 0000000000..ae90d763d4 --- /dev/null +++ b/modules/generators/mod_cgid.c @@ -0,0 +1,1006 @@ +/* ==================================================================== + * Copyright (c) 1995-1999 The Apache Group. 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. + * + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the Apache Group + * for use in the Apache HTTP server project (http://www.apache.org/)." + * + * 4. The names "Apache Server" and "Apache Group" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * * apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the Apache Group + * for use in the Apache HTTP server project (http://www.apache.org/)." + * + * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY + * EXPRESSED 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 APACHE GROUP OR + * ITS 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Group and was originally based + * on public domain software written at the National Center for + * Supercomputing Applications, University of Illinois, Urbana-Champaign. + * For more information on the Apache Group and the Apache HTTP server + * project, please see . + * + */ + +/* + * http_script: keeps all script-related ramblings together. + * + * Compliant to cgi/1.1 spec + * + * Adapted by rst from original NCSA code by Rob McCool + * + * Apache adds some new env vars; REDIRECT_URL and REDIRECT_QUERY_STRING for + * custom error responses, and DOCUMENT_ROOT because we found it useful. + * It also adds SERVER_ADMIN - useful for scripts to know who to mail when + * they fail. + */ + + + +#define CORE_PRIVATE + +#include "apr_lib.h" +#include "apr_general.h" +#include "apr_file_io.h" +#include "apr_portable.h" +#include "httpd.h" +#include "http_config.h" +#include "http_request.h" +#include "http_core.h" +#include "http_protocol.h" +#include "http_main.h" +#include "http_log.h" +#include "util_script.h" +#include "http_conf_globals.h" +#include "buff.h" +#include "ap_mpm.h" +#include "iol_socket.h" + +#ifndef UNIX_PATH_MAX +#define UNIX_PATH_MAX 108 +#endif + +#ifndef sockaddr_un +struct sockaddr_un { + unsigned short sun_family; + char sun_path[UNIX_PATH_MAX]; +}; +#endif + + +module MODULE_VAR_EXPORT cgid_module; + +static void cgid_init(ap_context_t *p, ap_context_t *plog, ap_context_t *ptemp, server_rec *main_server); +static int once_through = 0; + +static ap_context_t *pcgi; + +/* KLUDGE --- for back-combatibility, we don't have to check Execcgid + * in ScriptAliased directories, which means we need to know if this + * request came through ScriptAlias or not... so the Alias module + * leaves a note for us. + */ + +static int is_scriptaliased(request_rec *r) +{ + const char *t = ap_table_get(r->notes, "alias-forced-type"); + return t && (!strcasecmp(t, "cgi-script")); +} + +/* Configuration stuff */ + +#define DEFAULT_LOGBYTES 10385760 +#define DEFAULT_BUFBYTES 1024 +#define DEFAULT_SOCKET "logs/cgisock" + +typedef struct { + const char *sockname; + char *logname; + long logbytes; + int bufbytes; + BUFF *bin; + BUFF *bout; + BUFF *berror; +} cgid_server_conf; + +/* If a request includes query info in the URL (stuff after "?"), and + * the query info does not contain "=" (indicative of a FORM submission), + * then this routine is called to create the argument list to be passed + * to the CGI script. When suexec is enabled, the suexec path, user, and + * group are the first three arguments to be passed; if not, all three + * must be NULL. The query info is split into separate arguments, where + * "+" is the separator between keyword arguments. + * + * XXXX: note that the WIN32 code uses one of the suexec strings + * to pass an interpreter name. Remember this if changing the way they + * are handled in create_argv. + * + */ +static char **create_argv(ap_context_t *p, char *path, char *user, char *group, + char *av0, const char *args) +{ + int x, numwords; + char **av; + char *w; + int idx = 0; + + /* count the number of keywords */ + + for (x = 0, numwords = 1; args[x]; x++) { + if (args[x] == '+') { + ++numwords; + } + } + + if (numwords > APACHE_ARG_MAX - 5) { + numwords = APACHE_ARG_MAX - 5; /* Truncate args to prevent overrun */ + } + av = (char **) ap_palloc(p, (numwords + 5) * sizeof(char *)); + + if (path) { + av[idx++] = path; + } + if (user) { + av[idx++] = user; + } + if (group) { + av[idx++] = group; + } + + av[idx++] = av0; + + for (x = 1; x <= numwords; x++) { + w = ap_getword_nulls(p, &args, '+'); + ap_unescape_url(w); + av[idx++] = ap_escape_shell_cmd(p, w); + } + av[idx] = NULL; + return av; +} + +static int call_exec(request_rec *r, char *argv0, char **env, int shellcmd) +{ + int pid = 0; + int errfileno = STDERR_FILENO; + + /* the fd on r->server->error_log is closed, but we need somewhere to * put the error messages from the log_* functions. So, we use stderr, + * since that is better than allowing errors to go unnoticed. + */ + ap_put_os_file(&r->server->error_log, &errfileno, r->pool); + /* TODO: reimplement suexec */ +#if 0 + if (ap_suexec_enabled + && ((r->server->server_uid != ap_user_id) + || (r->server->server_gid != ap_group_id) + || (!strncmp("/~", r->uri, 2)))) { + + char *execuser, *grpname; + struct passwd *pw; + struct group *gr; + + if (!strncmp("/~", r->uri, 2)) { + gid_t user_gid; + char *username = ap_pstrdup(r->pool, r->uri + 2); + char *pos = strchr(username, '/'); + + if (pos) { + *pos = '\0'; + } + + if ((pw = getpwnam(username)) == NULL) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "getpwnam: invalid username %s", username); + return (pid); + } + execuser = ap_pstrcat(r->pool, "~", pw->pw_name, NULL); + user_gid = pw->pw_gid; + + if ((gr = getgrgid(user_gid)) == NULL) { + if ((grpname = ap_palloc(r->pool, 16)) == NULL) { + return (pid); + } + else { + ap_snprintf(grpname, 16, "%ld", (long) user_gid); + } + } + else { + grpname = gr->gr_name; + } + } + else { + if ((pw = getpwuid(r->server->server_uid)) == NULL) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "getpwuid: invalid userid %ld", + (long) r->server->server_uid); + return (pid); + } + execuser = ap_pstrdup(r->pool, pw->pw_name); + + if ((gr = getgrgid(r->server->server_gid)) == NULL) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "getgrgid: invalid groupid %ld", + (long) r->server->server_gid); + return (pid); + } + grpname = gr->gr_name; + } + + if (shellcmd) { + execle(SUEXEC_BIN, SUEXEC_BIN, execuser, grpname, argv0, + NULL, env); + } + + else if ((!r->args) || (!r->args[0]) || strchr(r->args, '=')) { + execle(SUEXEC_BIN, SUEXEC_BIN, execuser, grpname, argv0, + NULL, env); + } + + else { + execve(SUEXEC_BIN, + create_argv(r->pool, SUEXEC_BIN, execuser, grpname, + argv0, r->args), + env); + } + } + else { +#endif + if (shellcmd) { + execle(SHELL_PATH, SHELL_PATH, "-c", argv0, NULL, env); + } + + else if ((!r->args) || (!r->args[0]) || strchr(r->args, '=')) { + execle(r->filename, argv0, NULL, env); + } + + else { + execve(r->filename, + create_argv(r->pool, NULL, NULL, NULL, argv0, r->args), + env); + } +#if 0 + } +#endif + return (pid); +} + +static void cgid_maint(int reason, void *data, ap_wait_t status) +{ +#ifdef HAS_OTHER_CHILD + int *sd = data; + switch (reason) { + case OC_REASON_DEATH: + case OC_REASON_LOST: + /* stop gap to make sure everything else works. In the end, + * we'll just restart the cgid server. */ + kill(getppid(), SIGWINCH); + break; + case OC_REASON_RESTART: + case OC_REASON_UNREGISTER: + kill(*sd, SIGHUP); + break; + } +#endif +} + +static void get_req(int fd, request_rec *r, char **filename, char **argv0, char ***env) +{ + int i, len, j; + unsigned char *data; + char **environ; + char temp[MAX_STRING_LEN]; + core_dir_config *temp_core; + void **dconf; + + r->server = ap_pcalloc(r->pool, sizeof(server_rec)); + + read(fd, &j, sizeof(int)); + read(fd, &len, sizeof(int)); + data = ap_pcalloc(r->pool, len); + i = read(fd, data, len); + + r->filename = ap_getword(r->pool, (const char **)&data, '\n'); + *argv0 = ap_getword(r->pool, (const char **)&data, '\n'); + + r->uri = ap_getword(r->pool, (const char **)&data, '\n'); + + environ = ap_pcalloc(r->pool, (j + 2) *sizeof(char *)); + i = 0; + for (i = 0; i < j; i++) { + environ[i] = ap_getword(r->pool, (const char **)&data, '\n'); + } + *env = environ; + r->args = ap_getword(r->pool, (const char **)&data, '\n'); + + read(fd, &r->server->server_uid, sizeof(uid_t)); + read(fd, &r->server->server_gid, sizeof(gid_t)); + + read(fd, &i, sizeof(int)); + + /* add 1, so that if i == 0, we still malloc something. */ + dconf = (void **)malloc(sizeof(void *) * i + 1); + + temp_core = (core_dir_config *)malloc(sizeof(core_module)); +#if 0 +#ifdef RLIMIT_CPU + read(fd, &j, sizeof(int)); + if (j) { + temp_core->limit_cpu = (struct rlimit *)malloc (sizeof(struct rlimit)); + read(fd, temp_core->limit_cpu, sizeof(struct rlimit)); + } + else { + temp_core->limit_cpu = NULL; + } +#endif + +#if defined (RLIMIT_DATA) || defined(RLIMIT_VMEM) || defined(RLIMIT_AS) + read(fd, &j, sizeof(int)); + if (j) { + temp_core->limit_mem = (struct rlimit *)malloc (sizeof(struct rlimit)); + read(fd, temp_core->limit_mem, sizeof(struct rlimit)); + } + else { + temp_core->limit_mem = NULL; + } +#endif + +#ifdef RLIMIT_NPROC + read(fd, &j, sizeof(int)); + if (j) { + temp_core->limit_nproc = (struct rlimit *)malloc (sizeof(struct rlimit)); + read(fd, temp_core->limit_nproc, sizeof(struct rlimit)); + } + else { + temp_core->limit_nproc = NULL; + } +#endif +#endif + dconf[i] = (void *)temp_core; + r->per_dir_config = dconf; +} + + + +static void send_req(int fd, request_rec *r, char *argv0, char **env) +{ + int len; + int rv; + int i = 0; + char *data; + core_dir_config *conf = ap_get_module_config(r->per_dir_config, + &core_module); + + data = ap_pstrcat(r->pool, r->filename, "\n", argv0, "\n", r->uri, "\n", + NULL); + + for (i =0; env[i]; i++) { + continue; + } + + if (write(fd, &i, sizeof(int)) < 0) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r, + "write to cgi daemon process"); + } + + for (i = 0; env[i]; i++) { + data = ap_pstrcat(r->pool, data, env[i], "\n", NULL); + } + data = ap_pstrcat(r->pool, data, r->args, NULL); + len = strlen(data); + if (write(fd, &len, sizeof(int)) < 0) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r, + "write to cgi daemon process"); + } + if (write(fd, data, len) < 0) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r, + "write to cgi daemon process"); + } + if (write(fd, &r->server->server_uid, sizeof(uid_t)) < 0) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r, + "write to cgi daemon process"); + } + if (write(fd, &r->server->server_gid, sizeof(gid_t)) < 0) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r, + "write to cgi daemon process"); + } + if (write(fd, &core_module.module_index, sizeof(int)) < 0) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r, + "write to cgi daemon process"); + } +#if 0 +#ifdef RLIMIT_CPU + if (conf->limit_cpu) { + len = 1; + write(fd, &len, sizeof(int)); + write(fd, conf->limit_cpu, sizeof(struct rlimit)); + } + else { + len = 0; + write(fd, &len, sizeof(int)); + } +#endif + +#if defined(RLIMIT_DATA) || defined(RLIMIT_VMEM) || defined(RLIMIT_AS) + if (conf->limit_mem) { + len = 1; + write(fd, &len, sizeof(int)); + write(fd, conf->limit_mem, sizeof(struct rlimit)); + } + else { + len = 0; + write(fd, &len, sizeof(int)); + } +#endif + +#ifdef RLIMIT_NPROC + if (conf->limit_nproc) { + len = 1; + write(fd, &len, sizeof(int)); + write(fd, conf->limit_nproc, sizeof(struct rlimit)); + } + else { + len = 0; + write(fd, &len, sizeof(int)); + } +#endif +#endif +} + +static int cgid_server_child(int sd) +{ + char *argv0; + char *filename; + char **env; + ap_context_t *p; + request_rec *r; + + ap_create_context(&p, pcgi); + r = ap_pcalloc(p, sizeof(request_rec)); + r->pool = p; + dup2(sd, STDIN_FILENO); + dup2(sd, STDOUT_FILENO); + get_req(sd, r, &filename, &argv0, &env); + call_exec(r, argv0, env, 0); + exit(-1); /* We should NEVER get here */ +} + +static int cgid_server(void *data) +{ + struct sockaddr_un unix_addr; + int pid; + int sd, sd2, len; + int errfile; + server_rec *main_server = data; + cgid_server_conf *sconf = (cgid_server_conf *)ap_get_module_config( + main_server->module_config, &cgid_module); + + signal(SIGCHLD, SIG_IGN); + unlink(sconf->sockname); + + if ((sd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, errno, main_server, + "Couldn't create unix domain socket"); + } + + memset(&unix_addr, 0, sizeof(unix_addr)); + unix_addr.sun_family = AF_UNIX; + strcpy(unix_addr.sun_path, sconf->sockname); + + if (bind(sd, (struct sockaddr *)&unix_addr, sizeof(unix_addr)) < 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, errno, main_server, + "Couldn't bind unix domain socket"); + } + /* Most implementations silently enforce a value of 5 anyway. + * This way, it'll work the same everywhere. */ + if (listen(sd, 5) < 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, errno, main_server, + "Couldn't listen on unix domain socket"); + } + + while (1) { + sd2 = accept(sd, (struct sockaddr *)&unix_addr, &len); + if (sd2 < 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, errno, (server_rec *)data, + "Error accepting on cgid socket."); + continue; + } + + if ((pid = fork()) > 0) { + close(sd2); + } + else if (pid == 0) { + /* setup the STDERR here, because I have all the info + * for it. I'll do the STDIN and STDOUT later, but I can't + * do STDERR as easily. + */ + if (sconf->logname) { + dup2(open(sconf->logname, O_WRONLY), STDERR_FILENO); + } + else { + ap_get_os_file(&errfile, main_server->error_log); + dup2(errfile, STDERR_FILENO); + } + cgid_server_child(sd2); + } + else { + ap_log_error(APLOG_MARK, APLOG_ERR, errno, (server_rec *)data, + "Couldn't fork cgi script"); + } + } + return -1; +} + +static void cgid_init(ap_context_t *p, ap_context_t *plog, ap_context_t *ptemp, server_rec *main_server) +{ + int pid; + int tempfd; + + cgid_server_conf *sconf = (cgid_server_conf *)ap_get_module_config( + main_server->module_config, &cgid_module); + + if (once_through > 0) { + ap_create_context(&pcgi, p); + tempfd = creat(sconf->sockname, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + close(tempfd); + + if ((pid = fork()) < 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, errno, main_server, + "Couldn't spawn cgid daemon process"); + } + else if (pid == 0) { + cgid_server(main_server); + exit(-1); + } +#ifdef HAS_OTHER_CHILD + ap_register_other_child(pid, cgid_maint, &pid, -1); +#endif + } + else once_through++; +} + +static void *create_cgid_config(ap_context_t *p, server_rec *s) +{ + cgid_server_conf *c = + (cgid_server_conf *) ap_pcalloc(p, sizeof(cgid_server_conf)); + + c->logname = NULL; + c->logbytes = DEFAULT_LOGBYTES; + c->bufbytes = DEFAULT_BUFBYTES; + c->sockname = ap_server_root_relative(p, DEFAULT_SOCKET); + c->bin = c->bout = c->berror = NULL; + return c; +} + +static void *merge_cgid_config(ap_context_t *p, void *basev, void *overridesv) +{ + cgid_server_conf *base = (cgid_server_conf *) basev, *overrides = (cgid_server_conf *) overridesv; + + return overrides->logname ? overrides : base; +} + +static const char *set_scriptlog(cmd_parms *cmd, void *dummy, char *arg) +{ + server_rec *s = cmd->server; + cgid_server_conf *conf = + (cgid_server_conf *) ap_get_module_config(s->module_config, &cgid_module); + + conf->logname = arg; + return NULL; +} + +static const char *set_scriptlog_length(cmd_parms *cmd, void *dummy, char *arg) +{ + server_rec *s = cmd->server; + cgid_server_conf *conf = + (cgid_server_conf *) ap_get_module_config(s->module_config, &cgid_module); + + conf->logbytes = atol(arg); + return NULL; +} + +static const char *set_scriptlog_buffer(cmd_parms *cmd, void *dummy, char *arg) +{ + server_rec *s = cmd->server; + cgid_server_conf *conf = + (cgid_server_conf *) ap_get_module_config(s->module_config, &cgid_module); + + conf->bufbytes = atoi(arg); + return NULL; +} + +static const char *set_script_socket(cmd_parms *cmd, void *dummy, char *arg) +{ + server_rec *s = cmd->server; + cgid_server_conf *conf = + (cgid_server_conf *) ap_get_module_config(s->module_config, &cgid_module); + + conf->sockname = ap_server_root_relative(cmd->pool, arg); + return NULL; +} + +static const command_rec cgid_cmds[] = +{ + {"ScriptLog", set_scriptlog, NULL, RSRC_CONF, TAKE1, + "the name of a log for script debugging info"}, + {"ScriptLogLength", set_scriptlog_length, NULL, RSRC_CONF, TAKE1, + "the maximum length (in bytes) of the script debug log"}, + {"ScriptLogBuffer", set_scriptlog_buffer, NULL, RSRC_CONF, TAKE1, + "the maximum size (in bytes) to record of a POST request"}, + {"Scriptsock", set_script_socket, NULL, RSRC_CONF, TAKE1, + "the name of the socket to use for communication with the cgi daemon."}, + {NULL} +}; + +static int log_scripterror(request_rec *r, cgid_server_conf * conf, int ret, + int show_errno, char *error) +{ + ap_file_t *f; + struct stat finfo; + + ap_log_rerror(APLOG_MARK, show_errno|APLOG_ERR, errno, r, + "%s: %s", error, r->filename); + + if (!conf->logname || + ((stat(ap_server_root_relative(r->pool, conf->logname), &finfo) == 0) + && (finfo.st_size > conf->logbytes)) || + (ap_open(&f, ap_server_root_relative(r->pool, conf->logname), + APR_APPEND, APR_OS_DEFAULT, r->pool) != APR_SUCCESS)) { + return ret; + } + + /* "%% [Wed Jun 19 10:53:21 1996] GET /cgid-bin/printenv HTTP/1.0" */ + ap_fprintf(f, "%%%% [%s] %s %s%s%s %s\n", ap_get_time(), r->method, r->uri, + r->args ? "?" : "", r->args ? r->args : "", r->protocol); + /* "%% 500 /usr/local/apache/cgid-bin */ + ap_fprintf(f, "%%%% %d %s\n", ret, r->filename); + + ap_fprintf(f, "%%error\n%s\n", error); + + ap_close(f); + return ret; +} + +static int log_script(request_rec *r, cgid_server_conf * conf, int ret, + char *dbuf, const char *sbuf, BUFF *script_in, BUFF *script_err) +{ + ap_array_header_t *hdrs_arr = ap_table_elts(r->headers_in); + ap_table_entry_t *hdrs = (ap_table_entry_t *) hdrs_arr->elts; + char argsbuffer[HUGE_STRING_LEN]; + ap_file_t *f; + int i; + struct stat finfo; + + if (!conf->logname || + ((stat(ap_server_root_relative(r->pool, conf->logname), &finfo) == 0) + && (finfo.st_size > conf->logbytes)) || + (ap_open(&f, ap_server_root_relative(r->pool, conf->logname), + APR_APPEND, APR_OS_DEFAULT, r->pool) != APR_SUCCESS)) { + /* Soak up script output */ + while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_in) > 0) + continue; + while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0) + continue; + return ret; + } + + /* "%% [Wed Jun 19 10:53:21 1996] GET /cgid-bin/printenv HTTP/1.0" */ + ap_fprintf(f, "%%%% [%s] %s %s%s%s %s\n", ap_get_time(), r->method, r->uri, + r->args ? "?" : "", r->args ? r->args : "", r->protocol); + /* "%% 500 /usr/local/apache/cgid-bin" */ + ap_fprintf(f, "%%%% %d %s\n", ret, r->filename); + + ap_puts("%request\n", f); + for (i = 0; i < hdrs_arr->nelts; ++i) { + if (!hdrs[i].key) + continue; + ap_fprintf(f, "%s: %s\n", hdrs[i].key, hdrs[i].val); + } + if ((r->method_number == M_POST || r->method_number == M_PUT) + && *dbuf) { + ap_fprintf(f, "\n%s\n", dbuf); + } + + ap_puts("%response\n", f); + hdrs_arr = ap_table_elts(r->err_headers_out); + hdrs = (ap_table_entry_t *) hdrs_arr->elts; + + for (i = 0; i < hdrs_arr->nelts; ++i) { + if (!hdrs[i].key) + continue; + ap_fprintf(f, "%s: %s\n", hdrs[i].key, hdrs[i].val); + } + + if (sbuf && *sbuf) + ap_fprintf(f, "%s\n", sbuf); + + if (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_in) > 0) { + ap_puts("%stdout\n", f); + ap_puts(argsbuffer, f); + while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_in) > 0) + ap_puts(argsbuffer, f); + ap_puts("\n", f); + } + + if (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0) { + ap_puts("%stderr\n", f); + ap_puts(argsbuffer, f); + while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0) + ap_puts(argsbuffer, f); + ap_puts("\n", f); + } + + ap_bclose(script_in); + ap_bclose(script_err); + + ap_close(f); + return ret; +} + + + +/**************************************************************** + * + * Actual cgid handling... + */ +static int cgid_handler(request_rec *r) +{ + int retval, nph, dbpos = 0; + char *argv0, *dbuf = NULL; + BUFF *script = NULL; + char argsbuffer[HUGE_STRING_LEN]; + void *sconf = r->server->module_config; + cgid_server_conf *conf = (cgid_server_conf *) ap_get_module_config(sconf, &cgid_module); + int is_included = !strcmp(r->protocol, "INCLUDED"); + int sd; + char **env; + struct sockaddr_un unix_addr; + ap_socket_t *tempsock = NULL; + int nbytes; + ap_iol *iol; + script = ap_bcreate(r->pool, B_RDWR); + + if (r->method_number == M_OPTIONS) { + /* 99 out of 100 cgid scripts, this is all they support */ + r->allowed |= (1 << M_GET); + r->allowed |= (1 << M_POST); + return DECLINED; + } + + if ((argv0 = strrchr(r->filename, '/')) != NULL) + argv0++; + else + argv0 = r->filename; + + nph = !(strncmp(argv0, "nph-", 4)); + + if ((argv0 = strrchr(r->filename, '/')) != NULL) + argv0++; + else + argv0 = r->filename; + + if (!(ap_allow_options(r) & OPT_EXECCGI) && !is_scriptaliased(r)) + return log_scripterror(r, conf, FORBIDDEN, APLOG_NOERRNO, + "Options ExecCGI is off in this directory"); + if (nph && is_included) + return log_scripterror(r, conf, FORBIDDEN, APLOG_NOERRNO, + "attempt to include NPH CGI script"); + +#if defined(OS2) || defined(WIN32) + /* Allow for cgid files without the .EXE extension on them under OS/2 */ + if (r->finfo.st_mode == 0) { + struct stat statbuf; + char *newfile; + + newfile = ap_pstrcat(r->pool, r->filename, ".EXE", NULL); + + if ((stat(newfile, &statbuf) != 0) || (!S_ISREG(statbuf.st_mode))) { + return log_scripterror(r, conf, NOT_FOUND, 0, + "script not found or unable to stat"); + } else { + r->filename = newfile; + } + } +#else + if (r->finfo.st_mode == 0) + return log_scripterror(r, conf, NOT_FOUND, APLOG_NOERRNO, + "script not found or unable to stat"); +#endif + if (S_ISDIR(r->finfo.st_mode)) + return log_scripterror(r, conf, FORBIDDEN, APLOG_NOERRNO, + "attempt to invoke directory as script"); +/* + if (!ap_suexec_enabled) { + if (!ap_can_exec(&r->finfo)) + return log_scripterror(r, conf, FORBIDDEN, APLOG_NOERRNO, + "file permissions deny server execution"); + } +*/ + ap_add_common_vars(r); + ap_add_cgi_vars(r); + env = ap_create_environment(r->pool, r->subprocess_env); + + if ((sd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { + return log_scripterror(r, conf, NOT_FOUND, 0, + "unable to create socket to cgi daemon"); + } + memset(&unix_addr, 0, sizeof(unix_addr)); + unix_addr.sun_family = AF_UNIX; + strcpy(unix_addr.sun_path, conf->sockname); + + if (connect(sd, (struct sockaddr *)&unix_addr, sizeof(unix_addr)) < 0) { + return log_scripterror(r, conf, NOT_FOUND, 0, + "unable to connect to cgi daemon"); + } + + send_req(sd, r, argv0, env); + + ap_put_os_sock(&tempsock, &sd, pcgi); + + iol = unix_attach_socket(tempsock); + + ap_bpush_iol(script, iol); + + if ((retval = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR))) + return retval; + + if ((argv0 = strrchr(r->filename, '/')) != NULL) + argv0++; + else + argv0 = r->filename; + + /* Transfer any put/post args, CERN style... + * Note that we already ignore SIGPIPE in the core server. + */ + + if (ap_should_client_block(r)) { + int dbsize, len_read; + + if (conf->logname) { + dbuf = ap_pcalloc(r->pool, conf->bufbytes + 1); + dbpos = 0; + } + + + + while ((len_read = + ap_get_client_block(r, argsbuffer, HUGE_STRING_LEN)) > 0) { + if (conf->logname) { + if ((dbpos + len_read) > conf->bufbytes) { + dbsize = conf->bufbytes - dbpos; + } + else { + dbsize = len_read; + } + memcpy(dbuf + dbpos, argsbuffer, dbsize); + dbpos += dbsize; + } + ap_bwrite(script, argsbuffer, len_read, &nbytes); + if (nbytes < len_read) { + /* silly script stopped reading, soak up remaining message */ + while (ap_get_client_block(r, argsbuffer, HUGE_STRING_LEN) > 0) { + /* dump it */ + } + break; + } + } + + ap_bflush(script); + + } + + /* Handle script return... */ + if (script && !nph) { + const char *location; + char sbuf[MAX_STRING_LEN]; + int ret; + + if ((ret = ap_scan_script_header_err_buff(r, script, sbuf))) { + return log_script(r, conf, ret, dbuf, sbuf, script, NULL); + } + +#ifdef CHARSET_EBCDIC + /* Now check the Content-Type to decide if conversion is needed */ + ap_checkconv(r); +#endif /*CHARSET_EBCDIC*/ + + location = ap_table_get(r->headers_out, "Location"); + + if (location && location[0] == '/' && r->status == 200) { + + /* Soak up all the script output */ + while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script) > 0) { + continue; + } + /* This redirect needs to be a GET no matter what the original + * method was. + */ + r->method = ap_pstrdup(r->pool, "GET"); + r->method_number = M_GET; + + /* We already read the message body (if any), so don't allow + * the redirected request to think it has one. We can ignore + * Transfer-Encoding, since we used REQUEST_CHUNKED_ERROR. + */ + ap_table_unset(r->headers_in, "Content-Length"); + + ap_internal_redirect_handler(location, r); + return OK; + } + else if (location && r->status == 200) { + /* XX Note that if a script wants to produce its own Redirect + * body, it now has to explicitly *say* "Status: 302" + */ + return REDIRECT; + } + + ap_send_http_header(r); + if (!r->header_only) { + ap_send_fb(script, r); + } + ap_bclose(script); + } + + if (script && nph) { + ap_send_fb(script, r); + } + +ap_destroy_pool(pcgi); + + return OK; /* NOT r->status, even if it has changed. */ +} + +static const handler_rec cgid_handlers[] = +{ + {CGI_MAGIC_TYPE, cgid_handler}, + {"cgi-script", cgid_handler}, + {NULL} +}; + +static void register_hook(void) +{ + ap_hook_post_config(cgid_init, NULL, NULL, HOOK_MIDDLE); +} + +module MODULE_VAR_EXPORT cgid_module = { + STANDARD20_MODULE_STUFF, + NULL, /* dir config creater */ + NULL, /* dir merger --- default is to override */ + create_cgid_config, /* server config */ + merge_cgid_config, /* merge server config */ + cgid_cmds, /* command table */ + cgid_handlers, /* handlers */ + register_hook /* register_handlers */ +}; + -- 2.40.0