Main Page | Modules | Namespace List | Alphabetical List | Data Structures | Directories | File List | Data Fields | Globals | Related Pages | Examples

mod_cgi.c

Go to the documentation of this file.
00001 /* Copyright 1999-2005 The Apache Software Foundation or its licensors, as
00002  * applicable.
00003  *
00004  * Licensed under the Apache License, Version 2.0 (the "License");
00005  * you may not use this file except in compliance with the License.
00006  * You may obtain a copy of the License at
00007  *
00008  *     http://www.apache.org/licenses/LICENSE-2.0
00009  *
00010  * Unless required by applicable law or agreed to in writing, software
00011  * distributed under the License is distributed on an "AS IS" BASIS,
00012  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00013  * See the License for the specific language governing permissions and
00014  * limitations under the License.
00015  */
00016 
00017 /*
00018  * http_script: keeps all script-related ramblings together.
00019  * 
00020  * Compliant to CGI/1.1 spec
00021  * 
00022  * Adapted by rst from original NCSA code by Rob McCool
00023  *
00024  * Apache adds some new env vars; REDIRECT_URL and REDIRECT_QUERY_STRING for
00025  * custom error responses, and DOCUMENT_ROOT because we found it useful.
00026  * It also adds SERVER_ADMIN - useful for scripts to know who to mail when 
00027  * they fail.
00028  */
00029 
00030 #include "apr.h"
00031 #include "apr_strings.h"
00032 #include "apr_thread_proc.h"    /* for RLIMIT stuff */
00033 #include "apr_optional.h"
00034 #include "apr_buckets.h"
00035 #include "apr_lib.h"
00036 #include "apr_poll.h"
00037 
00038 #define APR_WANT_STRFUNC
00039 #include "apr_want.h"
00040 
00041 #define CORE_PRIVATE
00042 
00043 #include "util_filter.h"
00044 #include "ap_config.h"
00045 #include "httpd.h"
00046 #include "http_config.h"
00047 #include "http_request.h"
00048 #include "http_core.h"
00049 #include "http_protocol.h"
00050 #include "http_main.h"
00051 #include "http_log.h"
00052 #include "util_script.h"
00053 #include "ap_mpm.h"
00054 #include "mod_core.h"
00055 #include "mod_cgi.h"
00056 
00057 module AP_MODULE_DECLARE_DATA cgi_module;
00058 
00059 static APR_OPTIONAL_FN_TYPE(ap_register_include_handler) *cgi_pfn_reg_with_ssi;
00060 static APR_OPTIONAL_FN_TYPE(ap_ssi_get_tag_and_value) *cgi_pfn_gtv;
00061 static APR_OPTIONAL_FN_TYPE(ap_ssi_parse_string) *cgi_pfn_ps;
00062 static APR_OPTIONAL_FN_TYPE(ap_cgi_build_command) *cgi_build_command;
00063 
00064 /* Read and discard the data in the brigade produced by a CGI script */
00065 static void discard_script_output(apr_bucket_brigade *bb);
00066 
00067 /* KLUDGE --- for back-combatibility, we don't have to check ExecCGI
00068  * in ScriptAliased directories, which means we need to know if this
00069  * request came through ScriptAlias or not... so the Alias module
00070  * leaves a note for us.
00071  */
00072 
00073 static int is_scriptaliased(request_rec *r)
00074 {
00075     const char *t = apr_table_get(r->notes, "alias-forced-type");
00076     return t && (!strcasecmp(t, "cgi-script"));
00077 }
00078 
00079 /* Configuration stuff */
00080 
00081 #define DEFAULT_LOGBYTES 10385760
00082 #define DEFAULT_BUFBYTES 1024
00083 
00084 typedef struct {
00085     const char *logname;
00086     long        logbytes;
00087     apr_size_t  bufbytes;
00088 } cgi_server_conf;
00089 
00090 static void *create_cgi_config(apr_pool_t *p, server_rec *s)
00091 {
00092     cgi_server_conf *c =
00093     (cgi_server_conf *) apr_pcalloc(p, sizeof(cgi_server_conf));
00094 
00095     c->logname = NULL;
00096     c->logbytes = DEFAULT_LOGBYTES;
00097     c->bufbytes = DEFAULT_BUFBYTES;
00098 
00099     return c;
00100 }
00101 
00102 static void *merge_cgi_config(apr_pool_t *p, void *basev, void *overridesv)
00103 {
00104     cgi_server_conf *base = (cgi_server_conf *) basev,
00105                     *overrides = (cgi_server_conf *) overridesv;
00106 
00107     return overrides->logname ? overrides : base;
00108 }
00109 
00110 static const char *set_scriptlog(cmd_parms *cmd, void *dummy, const char *arg)
00111 {
00112     server_rec *s = cmd->server;
00113     cgi_server_conf *conf = ap_get_module_config(s->module_config,
00114                                                  &cgi_module);
00115 
00116     conf->logname = ap_server_root_relative(cmd->pool, arg);
00117 
00118     if (!conf->logname) {
00119         return apr_pstrcat(cmd->pool, "Invalid ScriptLog path ",
00120                            arg, NULL);
00121     }
00122 
00123     return NULL;
00124 }
00125 
00126 static const char *set_scriptlog_length(cmd_parms *cmd, void *dummy,
00127                                         const char *arg)
00128 {
00129     server_rec *s = cmd->server;
00130     cgi_server_conf *conf = ap_get_module_config(s->module_config,
00131                                                  &cgi_module);
00132 
00133     conf->logbytes = atol(arg);
00134     return NULL;
00135 }
00136 
00137 static const char *set_scriptlog_buffer(cmd_parms *cmd, void *dummy,
00138                                         const char *arg)
00139 {
00140     server_rec *s = cmd->server;
00141     cgi_server_conf *conf = ap_get_module_config(s->module_config,
00142                                                  &cgi_module);
00143 
00144     conf->bufbytes = atoi(arg);
00145     return NULL;
00146 }
00147 
00148 static const command_rec cgi_cmds[] =
00149 {
00150 AP_INIT_TAKE1("ScriptLog", set_scriptlog, NULL, RSRC_CONF,
00151      "the name of a log for script debugging info"),
00152 AP_INIT_TAKE1("ScriptLogLength", set_scriptlog_length, NULL, RSRC_CONF,
00153      "the maximum length (in bytes) of the script debug log"),
00154 AP_INIT_TAKE1("ScriptLogBuffer", set_scriptlog_buffer, NULL, RSRC_CONF,
00155      "the maximum size (in bytes) to record of a POST request"),
00156     {NULL}
00157 };
00158 
00159 static int log_scripterror(request_rec *r, cgi_server_conf * conf, int ret,
00160                            apr_status_t rv, char *error)
00161 {
00162     apr_file_t *f = NULL;
00163     apr_finfo_t finfo;
00164     char time_str[APR_CTIME_LEN];
00165     int log_flags = rv ? APLOG_ERR : APLOG_ERR;
00166 
00167     ap_log_rerror(APLOG_MARK, log_flags, rv, r, 
00168                   "%s: %s", error, r->filename);
00169 
00170     /* XXX Very expensive mainline case! Open, then getfileinfo! */
00171     if (!conf->logname ||
00172         ((apr_stat(&finfo, conf->logname,
00173                    APR_FINFO_SIZE, r->pool) == APR_SUCCESS) &&
00174          (finfo.size > conf->logbytes)) ||
00175         (apr_file_open(&f, conf->logname,
00176                        APR_APPEND|APR_WRITE|APR_CREATE, APR_OS_DEFAULT,
00177                        r->pool) != APR_SUCCESS)) {
00178         return ret;
00179     }
00180 
00181     /* "%% [Wed Jun 19 10:53:21 1996] GET /cgi-bin/printenv HTTP/1.0" */
00182     apr_ctime(time_str, apr_time_now());
00183     apr_file_printf(f, "%%%% [%s] %s %s%s%s %s\n", time_str, r->method, r->uri,
00184                     r->args ? "?" : "", r->args ? r->args : "", r->protocol);
00185     /* "%% 500 /usr/local/apache/cgi-bin */
00186     apr_file_printf(f, "%%%% %d %s\n", ret, r->filename);
00187 
00188     apr_file_printf(f, "%%error\n%s\n", error);
00189 
00190     apr_file_close(f);
00191     return ret;
00192 }
00193 
00194 /* Soak up stderr from a script and redirect it to the error log. 
00195  */
00196 static apr_status_t log_script_err(request_rec *r, apr_file_t *script_err)
00197 {
00198     char argsbuffer[HUGE_STRING_LEN];
00199     char *newline;
00200     apr_status_t rv;
00201 
00202     while ((rv = apr_file_gets(argsbuffer, HUGE_STRING_LEN,
00203                                script_err)) == APR_SUCCESS) {
00204         newline = strchr(argsbuffer, '\n');
00205         if (newline) {
00206             *newline = '\0';
00207         }
00208         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, 
00209                       "%s", argsbuffer);            
00210     }
00211 
00212     return rv;
00213 }
00214 
00215 static int log_script(request_rec *r, cgi_server_conf * conf, int ret,
00216                       char *dbuf, const char *sbuf, apr_bucket_brigade *bb, 
00217                       apr_file_t *script_err)
00218 {
00219     const apr_array_header_t *hdrs_arr = apr_table_elts(r->headers_in);
00220     const apr_table_entry_t *hdrs = (const apr_table_entry_t *) hdrs_arr->elts;
00221     char argsbuffer[HUGE_STRING_LEN];
00222     apr_file_t *f = NULL;
00223     apr_bucket *e;
00224     const char *buf;
00225     apr_size_t len;
00226     apr_status_t rv;
00227     int first;
00228     int i;
00229     apr_finfo_t finfo;
00230     char time_str[APR_CTIME_LEN];
00231 
00232     /* XXX Very expensive mainline case! Open, then getfileinfo! */
00233     if (!conf->logname ||
00234         ((apr_stat(&finfo, conf->logname,
00235                    APR_FINFO_SIZE, r->pool) == APR_SUCCESS) &&
00236          (finfo.size > conf->logbytes)) ||
00237         (apr_file_open(&f, conf->logname,
00238                        APR_APPEND|APR_WRITE|APR_CREATE, APR_OS_DEFAULT,
00239                        r->pool) != APR_SUCCESS)) {
00240         /* Soak up script output */
00241         discard_script_output(bb);
00242         log_script_err(r, script_err);
00243         return ret;
00244     }
00245 
00246     /* "%% [Wed Jun 19 10:53:21 1996] GET /cgi-bin/printenv HTTP/1.0" */
00247     apr_ctime(time_str, apr_time_now());
00248     apr_file_printf(f, "%%%% [%s] %s %s%s%s %s\n", time_str, r->method, r->uri,
00249                     r->args ? "?" : "", r->args ? r->args : "", r->protocol);
00250     /* "%% 500 /usr/local/apache/cgi-bin" */
00251     apr_file_printf(f, "%%%% %d %s\n", ret, r->filename);
00252 
00253     apr_file_puts("%request\n", f);
00254     for (i = 0; i < hdrs_arr->nelts; ++i) {
00255         if (!hdrs[i].key)
00256             continue;
00257         apr_file_printf(f, "%s: %s\n", hdrs[i].key, hdrs[i].val);
00258     }
00259     if ((r->method_number == M_POST || r->method_number == M_PUT) &&
00260         *dbuf) {
00261         apr_file_printf(f, "\n%s\n", dbuf);
00262     }
00263 
00264     apr_file_puts("%response\n", f);
00265     hdrs_arr = apr_table_elts(r->err_headers_out);
00266     hdrs = (const apr_table_entry_t *) hdrs_arr->elts;
00267 
00268     for (i = 0; i < hdrs_arr->nelts; ++i) {
00269         if (!hdrs[i].key)
00270             continue;
00271         apr_file_printf(f, "%s: %s\n", hdrs[i].key, hdrs[i].val);
00272     }
00273 
00274     if (sbuf && *sbuf)
00275         apr_file_printf(f, "%s\n", sbuf);
00276 
00277     first = 1;
00278     APR_BRIGADE_FOREACH(e, bb) {
00279         if (APR_BUCKET_IS_EOS(e)) {
00280             break;
00281         }
00282         rv = apr_bucket_read(e, &buf, &len, APR_BLOCK_READ);
00283         if (rv != APR_SUCCESS || (len == 0)) {
00284             break;
00285         }
00286         if (first) {
00287             apr_file_puts("%stdout\n", f);
00288             first = 0;
00289         }
00290         apr_file_write(f, buf, &len);
00291         apr_file_puts("\n", f);
00292     }
00293 
00294     if (apr_file_gets(argsbuffer, HUGE_STRING_LEN, script_err) == APR_SUCCESS) {
00295         apr_file_puts("%stderr\n", f);
00296         apr_file_puts(argsbuffer, f);
00297         while (apr_file_gets(argsbuffer, HUGE_STRING_LEN,
00298                              script_err) == APR_SUCCESS) {
00299             apr_file_puts(argsbuffer, f);
00300         }
00301         apr_file_puts("\n", f);
00302     }
00303 
00304     apr_brigade_destroy(bb);
00305     apr_file_close(script_err);
00306 
00307     apr_file_close(f);
00308     return ret;
00309 }
00310 
00311 
00312 /* This is the special environment used for running the "exec cmd="
00313  *   variety of SSI directives.
00314  */
00315 static void add_ssi_vars(request_rec *r)
00316 {
00317     apr_table_t *e = r->subprocess_env;
00318 
00319     if (r->path_info && r->path_info[0] != '\0') {
00320         request_rec *pa_req;
00321 
00322         apr_table_setn(e, "PATH_INFO", ap_escape_shell_cmd(r->pool,
00323                                                            r->path_info));
00324 
00325         pa_req = ap_sub_req_lookup_uri(ap_escape_uri(r->pool, r->path_info),
00326                                        r, NULL);
00327         if (pa_req->filename) {
00328             apr_table_setn(e, "PATH_TRANSLATED",
00329                            apr_pstrcat(r->pool, pa_req->filename,
00330                                        pa_req->path_info, NULL));
00331         }
00332         ap_destroy_sub_req(pa_req);
00333     }
00334 
00335     if (r->args) {
00336         char *arg_copy = apr_pstrdup(r->pool, r->args);
00337 
00338         apr_table_setn(e, "QUERY_STRING", r->args);
00339         ap_unescape_url(arg_copy);
00340         apr_table_setn(e, "QUERY_STRING_UNESCAPED",
00341                        ap_escape_shell_cmd(r->pool, arg_copy));
00342     }
00343 }
00344 
00345 static void cgi_child_errfn(apr_pool_t *pool, apr_status_t err,
00346                             const char *description)
00347 {
00348     apr_file_t *stderr_log;
00349     char errbuf[200];
00350 
00351     apr_file_open_stderr(&stderr_log, pool);
00352     /* Escape the logged string because it may be something that
00353      * came in over the network.
00354      */
00355     apr_file_printf(stderr_log,
00356                     "(%d)%s: %s\n",
00357                     err,
00358                     apr_strerror(err, errbuf, sizeof(errbuf)),
00359 #ifdef AP_UNSAFE_ERROR_LOG_UNESCAPED
00360                     description
00361 #else
00362                     ap_escape_logitem(pool, description)
00363 #endif
00364                     );
00365 }
00366 
00367 static apr_status_t run_cgi_child(apr_file_t **script_out,
00368                                   apr_file_t **script_in,
00369                                   apr_file_t **script_err, 
00370                                   const char *command,
00371                                   const char * const argv[],
00372                                   request_rec *r,
00373                                   apr_pool_t *p,
00374                                   cgi_exec_info_t *e_info)
00375 {
00376     const char * const *env;
00377     apr_procattr_t *procattr;
00378     apr_proc_t *procnew;
00379     apr_status_t rc = APR_SUCCESS;
00380 
00381 #if defined(RLIMIT_CPU)  || defined(RLIMIT_NPROC) || \
00382     defined(RLIMIT_DATA) || defined(RLIMIT_VMEM) || defined (RLIMIT_AS)
00383 
00384     core_dir_config *conf = ap_get_module_config(r->per_dir_config,
00385                                                  &core_module);
00386 #endif
00387 
00388 #ifdef DEBUG_CGI
00389 #ifdef OS2
00390     /* Under OS/2 need to use device con. */
00391     FILE *dbg = fopen("con", "w");
00392 #else
00393     FILE *dbg = fopen("/dev/tty", "w");
00394 #endif
00395     int i;
00396 #endif
00397 
00398     RAISE_SIGSTOP(CGI_CHILD);
00399 #ifdef DEBUG_CGI
00400     fprintf(dbg, "Attempting to exec %s as CGI child (argv0 = %s)\n",
00401             r->filename, argv[0]);
00402 #endif
00403 
00404     env = (const char * const *)ap_create_environment(p, r->subprocess_env);
00405 
00406 #ifdef DEBUG_CGI
00407     fprintf(dbg, "Environment: \n");
00408     for (i = 0; env[i]; ++i)
00409         fprintf(dbg, "'%s'\n", env[i]);
00410 #endif
00411 
00412     /* Transmute ourselves into the script.
00413      * NB only ISINDEX scripts get decoded arguments.
00414      */
00415     if (((rc = apr_procattr_create(&procattr, p)) != APR_SUCCESS) ||
00416         ((rc = apr_procattr_io_set(procattr,
00417                                    e_info->in_pipe,
00418                                    e_info->out_pipe,
00419                                    e_info->err_pipe)) != APR_SUCCESS) ||
00420         ((rc = apr_procattr_dir_set(procattr, 
00421                         ap_make_dirstr_parent(r->pool,
00422                                               r->filename))) != APR_SUCCESS) ||
00423 #ifdef RLIMIT_CPU
00424         ((rc = apr_procattr_limit_set(procattr, APR_LIMIT_CPU,
00425                                       conf->limit_cpu)) != APR_SUCCESS) ||
00426 #endif
00427 #if defined(RLIMIT_DATA) || defined(RLIMIT_VMEM) || defined(RLIMIT_AS)
00428         ((rc = apr_procattr_limit_set(procattr, APR_LIMIT_MEM,
00429                                       conf->limit_mem)) != APR_SUCCESS) ||
00430 #endif
00431 #ifdef RLIMIT_NPROC
00432         ((rc = apr_procattr_limit_set(procattr, APR_LIMIT_NPROC,
00433                                       conf->limit_nproc)) != APR_SUCCESS) ||
00434 #endif
00435         ((rc = apr_procattr_cmdtype_set(procattr,
00436                                         e_info->cmd_type)) != APR_SUCCESS) ||
00437 
00438         ((rc = apr_procattr_detach_set(procattr,
00439                                         e_info->detached)) != APR_SUCCESS) ||
00440         ((rc = apr_procattr_child_errfn_set(procattr, cgi_child_errfn)) != APR_SUCCESS)) {
00441         /* Something bad happened, tell the world. */
00442         ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r,
00443                       "couldn't set child process attributes: %s", r->filename);
00444     }
00445     else {
00446         procnew = apr_pcalloc(p, sizeof(*procnew));
00447         if (e_info->prog_type == RUN_AS_SSI) {
00448             SPLIT_AND_PASS_PRETAG_BUCKETS(*(e_info->bb), e_info->ctx,
00449                                           e_info->next, rc);
00450             if (rc != APR_SUCCESS) {
00451                 return rc;
00452             }
00453         }
00454 
00455         rc = ap_os_create_privileged_process(r, procnew, command, argv, env,
00456                                              procattr, p);
00457     
00458         if (rc != APR_SUCCESS) {
00459             /* Bad things happened. Everyone should have cleaned up. */
00460             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_TOCLIENT, rc, r,
00461                           "couldn't create child process: %d: %s", rc,
00462                           apr_filename_of_pathname(r->filename));
00463         }
00464         else {
00465             apr_pool_note_subprocess(p, procnew, APR_KILL_AFTER_TIMEOUT);
00466 
00467             *script_in = procnew->out;
00468             if (!*script_in)
00469                 return APR_EBADF;
00470             apr_file_pipe_timeout_set(*script_in, r->server->timeout);
00471 
00472             if (e_info->prog_type == RUN_AS_CGI) {
00473                 *script_out = procnew->in;
00474                 if (!*script_out)
00475                     return APR_EBADF;
00476                 apr_file_pipe_timeout_set(*script_out, r->server->timeout);
00477 
00478                 *script_err = procnew->err;
00479                 if (!*script_err)
00480                     return APR_EBADF;
00481                 apr_file_pipe_timeout_set(*script_err, r->server->timeout);
00482             }
00483         }
00484     }
00485 #ifdef DEBUG_CGI
00486     fclose(dbg);
00487 #endif
00488     return (rc);
00489 }
00490 
00491 
00492 static apr_status_t default_build_command(const char **cmd, const char ***argv,
00493                                           request_rec *r, apr_pool_t *p,
00494                                           cgi_exec_info_t *e_info)
00495 {
00496     int numwords, x, idx;
00497     char *w;
00498     const char *args = NULL;
00499 
00500     if (e_info->process_cgi) {
00501         *cmd = r->filename;
00502         /* Do not process r->args if they contain an '=' assignment 
00503          */
00504         if (r->args && r->args[0] && !ap_strchr_c(r->args, '=')) {
00505             args = r->args;
00506         }
00507     }
00508 
00509     if (!args) {
00510         numwords = 1;
00511     }
00512     else {
00513         /* count the number of keywords */
00514         for (x = 0, numwords = 2; args[x]; x++) {
00515             if (args[x] == '+') {
00516                 ++numwords;
00517             }
00518         }
00519     }
00520     /* Everything is - 1 to account for the first parameter 
00521      * which is the program name.
00522      */ 
00523     if (numwords > APACHE_ARG_MAX - 1) {
00524         numwords = APACHE_ARG_MAX - 1;    /* Truncate args to prevent overrun */
00525     }
00526     *argv = apr_palloc(p, (numwords + 2) * sizeof(char *));
00527     (*argv)[0] = *cmd;
00528     for (x = 1, idx = 1; x < numwords; x++) {
00529         w = ap_getword_nulls(p, &args, '+');
00530         ap_unescape_url(w);
00531         (*argv)[idx++] = ap_escape_shell_cmd(p, w);
00532     }
00533     (*argv)[idx] = NULL;
00534 
00535     return APR_SUCCESS;
00536 }
00537 
00538 static void discard_script_output(apr_bucket_brigade *bb)
00539 {
00540     apr_bucket *e;
00541     const char *buf;
00542     apr_size_t len;
00543     apr_status_t rv;
00544     APR_BRIGADE_FOREACH(e, bb) {
00545         if (APR_BUCKET_IS_EOS(e)) {
00546             break;
00547         }
00548         rv = apr_bucket_read(e, &buf, &len, APR_BLOCK_READ);
00549         if (rv != APR_SUCCESS) {
00550             break;
00551         }
00552     }
00553 }
00554 
00555 #if APR_FILES_AS_SOCKETS
00556 
00557 /* A CGI bucket type is needed to catch any output to stderr from the
00558  * script; see PR 22030. */
00559 static const apr_bucket_type_t bucket_type_cgi;
00560 
00561 struct cgi_bucket_data {
00562     apr_pollset_t *pollset;
00563     request_rec *r;
00564 };
00565 
00566 /* Create a CGI bucket using pipes from script stdout 'out'
00567  * and stderr 'err', for request 'r'. */
00568 static apr_bucket *cgi_bucket_create(request_rec *r,
00569                                      apr_file_t *out, apr_file_t *err,
00570                                      apr_bucket_alloc_t *list)
00571 {
00572     apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
00573     apr_status_t rv;
00574     apr_pollfd_t fd;
00575     struct cgi_bucket_data *data = apr_palloc(r->pool, sizeof *data);
00576     
00577     APR_BUCKET_INIT(b);
00578     b->free = apr_bucket_free;
00579     b->list = list;
00580     b->type = &bucket_type_cgi;
00581     b->length = (apr_size_t)(-1);
00582     b->start = -1;
00583 
00584     /* Create the pollset */
00585     rv = apr_pollset_create(&data->pollset, 2, r->pool, 0);
00586     AP_DEBUG_ASSERT(rv == APR_SUCCESS);
00587 
00588     fd.desc_type = APR_POLL_FILE;
00589     fd.reqevents = APR_POLLIN;
00590     fd.p = r->pool;
00591     fd.desc.f = out; /* script's stdout */
00592     fd.client_data = (void *)1;
00593     rv = apr_pollset_add(data->pollset, &fd);
00594     AP_DEBUG_ASSERT(rv == APR_SUCCESS);
00595     
00596     fd.desc.f = err; /* script's stderr */
00597     fd.client_data = (void *)2;
00598     rv = apr_pollset_add(data->pollset, &fd);
00599     AP_DEBUG_ASSERT(rv == APR_SUCCESS);
00600     
00601     data->r = r;
00602     b->data = data;
00603     return b;
00604 }
00605 
00606 /* Create a duplicate CGI bucket using given bucket data */
00607 static apr_bucket *cgi_bucket_dup(struct cgi_bucket_data *data,
00608                                   apr_bucket_alloc_t *list)
00609 {
00610     apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
00611     APR_BUCKET_INIT(b);
00612     b->free = apr_bucket_free;
00613     b->list = list;
00614     b->type = &bucket_type_cgi;
00615     b->length = (apr_size_t)(-1);
00616     b->start = -1;
00617     b->data = data;
00618     return b;
00619 }
00620 
00621 /* Handle stdout from CGI child.  Duplicate of logic from the _read
00622  * method of the real APR pipe bucket implementation. */
00623 static apr_status_t cgi_read_stdout(apr_bucket *a, apr_file_t *out,
00624                                     const char **str, apr_size_t *len)
00625 {
00626     char *buf;
00627     apr_status_t rv;
00628 
00629     *str = NULL;
00630     *len = APR_BUCKET_BUFF_SIZE;
00631     buf = apr_bucket_alloc(*len, a->list); /* XXX: check for failure? */
00632 
00633     rv = apr_file_read(out, buf, len);
00634 
00635     if (rv != APR_SUCCESS && rv != APR_EOF) {
00636         apr_bucket_free(buf);
00637         return rv;
00638     }
00639 
00640     if (*len > 0) {
00641         struct cgi_bucket_data *data = a->data;
00642         apr_bucket_heap *h;
00643 
00644         /* Change the current bucket to refer to what we read */
00645         a = apr_bucket_heap_make(a, buf, *len, apr_bucket_free);
00646         h = a->data;
00647         h->alloc_len = APR_BUCKET_BUFF_SIZE; /* note the real buffer size */
00648         *str = buf;
00649         APR_BUCKET_INSERT_AFTER(a, cgi_bucket_dup(data, a->list));
00650     }
00651     else {
00652         apr_bucket_free(buf);
00653         a = apr_bucket_immortal_make(a, "", 0);
00654         *str = a->data;
00655     }
00656     return rv;
00657 }
00658 
00659 /* Read method of CGI bucket: polls on stderr and stdout of the child,
00660  * sending any stderr output immediately away to the error log. */
00661 static apr_status_t cgi_bucket_read(apr_bucket *b, const char **str,
00662                                     apr_size_t *len, apr_read_type_e block)
00663 {
00664     struct cgi_bucket_data *data = b->data;
00665     apr_interval_time_t timeout;
00666     apr_status_t rv;
00667     int gotdata = 0;
00668 
00669     timeout = block == APR_NONBLOCK_READ ? 0 : data->r->server->timeout;
00670 
00671     do {
00672         const apr_pollfd_t *results;
00673         apr_int32_t num;
00674 
00675         rv = apr_pollset_poll(data->pollset, timeout, &num, &results);
00676         if (APR_STATUS_IS_TIMEUP(rv)) {
00677             return timeout == 0 ? APR_EAGAIN : rv;
00678         }
00679         else if (APR_STATUS_IS_EINTR(rv)) {
00680             continue;
00681         }
00682         else if (rv != APR_SUCCESS) {
00683             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, data->r,
00684                           "poll failed waiting for CGI child");
00685             return rv;
00686         }
00687         
00688         for (; num; num--, results++) {
00689             if (results[0].client_data == (void *)1) {
00690                 /* stdout */
00691                 rv = cgi_read_stdout(b, results[0].desc.f, str, len);
00692                 if (APR_STATUS_IS_EOF(rv)) {
00693                     rv = APR_SUCCESS;
00694                 }
00695                 gotdata = 1;
00696             } else {
00697                 /* stderr */
00698                 apr_status_t rv2 = log_script_err(data->r, results[0].desc.f);
00699                 if (APR_STATUS_IS_EOF(rv2)) {
00700                     apr_pollset_remove(data->pollset, &results[0]);
00701                 }
00702             }
00703         }
00704 
00705     } while (!gotdata);
00706 
00707     return rv;
00708 }
00709 
00710 static const apr_bucket_type_t bucket_type_cgi = {
00711     "CGI", 5, APR_BUCKET_DATA,
00712     apr_bucket_destroy_noop,
00713     cgi_bucket_read,
00714     apr_bucket_setaside_notimpl,
00715     apr_bucket_split_notimpl,
00716     apr_bucket_copy_notimpl
00717 };
00718 
00719 #endif
00720 
00721 static int cgi_handler(request_rec *r)
00722 {
00723     int nph;
00724     apr_size_t dbpos = 0;
00725     const char *argv0;
00726     const char *command;
00727     const char **argv;
00728     char *dbuf = NULL;
00729     apr_file_t *script_out = NULL, *script_in = NULL, *script_err = NULL;
00730     apr_bucket_brigade *bb;
00731     apr_bucket *b;
00732     int is_included;
00733     int seen_eos, child_stopped_reading;
00734     apr_pool_t *p;
00735     cgi_server_conf *conf;
00736     apr_status_t rv;
00737     cgi_exec_info_t e_info;
00738     conn_rec *c = r->connection;
00739 
00740     if(strcmp(r->handler, CGI_MAGIC_TYPE) && strcmp(r->handler, "cgi-script"))
00741         return DECLINED;
00742 
00743     is_included = !strcmp(r->protocol, "INCLUDED");
00744 
00745     p = r->main ? r->main->pool : r->pool;
00746 
00747     if (r->method_number == M_OPTIONS) {
00748         /* 99 out of 100 CGI scripts, this is all they support */
00749         r->allowed |= (AP_METHOD_BIT << M_GET);
00750         r->allowed |= (AP_METHOD_BIT << M_POST);
00751         return DECLINED;
00752     }
00753 
00754     argv0 = apr_filename_of_pathname(r->filename);
00755     nph = !(strncmp(argv0, "nph-", 4));
00756     conf = ap_get_module_config(r->server->module_config, &cgi_module);
00757 
00758     if (!(ap_allow_options(r) & OPT_EXECCGI) && !is_scriptaliased(r))
00759         return log_scripterror(r, conf, HTTP_FORBIDDEN, 0,
00760                                "Options ExecCGI is off in this directory");
00761     if (nph && is_included)
00762         return log_scripterror(r, conf, HTTP_FORBIDDEN, 0,
00763                                "attempt to include NPH CGI script");
00764 
00765     if (r->finfo.filetype == 0)
00766         return log_scripterror(r, conf, HTTP_NOT_FOUND, 0,
00767                                "script not found or unable to stat");
00768     if (r->finfo.filetype == APR_DIR)
00769         return log_scripterror(r, conf, HTTP_FORBIDDEN, 0,
00770                                "attempt to invoke directory as script");
00771 
00772     if ((r->used_path_info == AP_REQ_REJECT_PATH_INFO) &&
00773         r->path_info && *r->path_info)
00774     {
00775         /* default to accept */
00776         return log_scripterror(r, conf, HTTP_NOT_FOUND, 0,
00777                                "AcceptPathInfo off disallows user's path");
00778     }
00779 /*
00780     if (!ap_suexec_enabled) {
00781         if (!ap_can_exec(&r->finfo))
00782             return log_scripterror(r, conf, HTTP_FORBIDDEN, 0,
00783                                    "file permissions deny server execution");
00784     }
00785 
00786 */
00787     ap_add_common_vars(r);
00788     ap_add_cgi_vars(r);
00789 
00790     e_info.process_cgi = 1;
00791     e_info.cmd_type    = APR_PROGRAM;
00792     e_info.detached    = 0;
00793     e_info.in_pipe     = APR_CHILD_BLOCK;
00794     e_info.out_pipe    = APR_CHILD_BLOCK;
00795     e_info.err_pipe    = APR_CHILD_BLOCK;
00796     e_info.prog_type   = RUN_AS_CGI;
00797     e_info.bb          = NULL;
00798     e_info.ctx         = NULL;
00799     e_info.next        = NULL;
00800 
00801     /* build the command line */
00802     if ((rv = cgi_build_command(&command, &argv, r, p, &e_info)) != APR_SUCCESS) {
00803         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
00804                       "don't know how to spawn child process: %s", 
00805                       r->filename);
00806         return HTTP_INTERNAL_SERVER_ERROR;
00807     }
00808 
00809     /* run the script in its own process */
00810     if ((rv = run_cgi_child(&script_out, &script_in, &script_err,
00811                             command, argv, r, p, &e_info)) != APR_SUCCESS) {
00812         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
00813                       "couldn't spawn child process: %s", r->filename);
00814         return HTTP_INTERNAL_SERVER_ERROR;
00815     }
00816 
00817     /* Transfer any put/post args, CERN style...
00818      * Note that we already ignore SIGPIPE in the core server.
00819      */
00820     bb = apr_brigade_create(r->pool, c->bucket_alloc);
00821     seen_eos = 0;
00822     child_stopped_reading = 0;
00823     if (conf->logname) {
00824         dbuf = apr_palloc(r->pool, conf->bufbytes + 1);
00825         dbpos = 0;
00826     }
00827     do {
00828         apr_bucket *bucket;
00829 
00830         rv = ap_get_brigade(r->input_filters, bb, AP_MODE_READBYTES,
00831                             APR_BLOCK_READ, HUGE_STRING_LEN);
00832        
00833         if (rv != APR_SUCCESS) {
00834             return rv;
00835         }
00836 
00837         APR_BRIGADE_FOREACH(bucket, bb) {
00838             const char *data;
00839             apr_size_t len;
00840 
00841             if (APR_BUCKET_IS_EOS(bucket)) {
00842                 seen_eos = 1;
00843                 break;
00844             }
00845 
00846             /* We can't do much with this. */
00847             if (APR_BUCKET_IS_FLUSH(bucket)) {
00848                 continue;
00849             }
00850 
00851             /* If the child stopped, we still must read to EOS. */
00852             if (child_stopped_reading) {
00853                 continue;
00854             } 
00855 
00856             /* read */
00857             apr_bucket_read(bucket, &data, &len, APR_BLOCK_READ);
00858             
00859             if (conf->logname && dbpos < conf->bufbytes) {
00860                 int cursize;
00861 
00862                 if ((dbpos + len) > conf->bufbytes) {
00863                     cursize = conf->bufbytes - dbpos;
00864                 }
00865                 else {
00866                     cursize = len;
00867                 }
00868                 memcpy(dbuf + dbpos, data, cursize);
00869                 dbpos += cursize;
00870             }
00871 
00872             /* Keep writing data to the child until done or too much time
00873              * elapses with no progress or an error occurs.
00874              */
00875             rv = apr_file_write_full(script_out, data, len, NULL);
00876 
00877             if (rv != APR_SUCCESS) {
00878                 /* silly script stopped reading, soak up remaining message */
00879                 child_stopped_reading = 1;
00880             }
00881         }
00882         apr_brigade_cleanup(bb);
00883     }
00884     while (!seen_eos);
00885 
00886     if (conf->logname) {
00887         dbuf[dbpos] = '\0';
00888     }
00889     /* Is this flush really needed? */
00890     apr_file_flush(script_out);
00891     apr_file_close(script_out);
00892 
00893     AP_DEBUG_ASSERT(script_in != NULL);
00894 
00895     apr_brigade_cleanup(bb);
00896 
00897 #if APR_FILES_AS_SOCKETS
00898     apr_file_pipe_timeout_set(script_in, 0);
00899     apr_file_pipe_timeout_set(script_err, 0);
00900     
00901     b = cgi_bucket_create(r, script_in, script_err, c->bucket_alloc);
00902 #else
00903     b = apr_bucket_pipe_create(script_in, c->bucket_alloc);
00904 #endif
00905     APR_BRIGADE_INSERT_TAIL(bb, b);
00906     b = apr_bucket_eos_create(c->bucket_alloc);
00907     APR_BRIGADE_INSERT_TAIL(bb, b);
00908 
00909     /* Handle script return... */
00910     if (!nph) {
00911         const char *location;
00912         char sbuf[MAX_STRING_LEN];
00913         int ret;
00914 
00915         if ((ret = ap_scan_script_header_err_brigade(r, bb, sbuf))) {
00916             return log_script(r, conf, ret, dbuf, sbuf, bb, script_err);
00917         }
00918 
00919         location = apr_table_get(r->headers_out, "Location");
00920 
00921         if (location && location[0] == '/' && r->status == 200) {
00922             discard_script_output(bb);
00923             apr_brigade_destroy(bb);
00924             apr_file_pipe_timeout_set(script_err, r->server->timeout);
00925             log_script_err(r, script_err);
00926             /* This redirect needs to be a GET no matter what the original
00927              * method was.
00928              */
00929             r->method = apr_pstrdup(r->pool, "GET");
00930             r->method_number = M_GET;
00931 
00932             /* We already read the message body (if any), so don't allow
00933              * the redirected request to think it has one.  We can ignore 
00934              * Transfer-Encoding, since we used REQUEST_CHUNKED_ERROR.
00935              */
00936             apr_table_unset(r->headers_in, "Content-Length");
00937 
00938             ap_internal_redirect_handler(location, r);
00939             return OK;
00940         }
00941         else if (location && r->status == 200) {
00942             /* XX Note that if a script wants to produce its own Redirect
00943              * body, it now has to explicitly *say* "Status: 302"
00944              */
00945             discard_script_output(bb);
00946             apr_brigade_destroy(bb);
00947             return HTTP_MOVED_TEMPORARILY;
00948         }
00949 
00950         rv = ap_pass_brigade(r->output_filters, bb);
00951     }
00952     else /* nph */ {
00953         struct ap_filter_t *cur;
00954         
00955         /* get rid of all filters up through protocol...  since we
00956          * haven't parsed off the headers, there is no way they can
00957          * work
00958          */
00959 
00960         cur = r->proto_output_filters;
00961         while (cur && cur->frec->ftype < AP_FTYPE_CONNECTION) {
00962             cur = cur->next;
00963         }
00964         r->output_filters = r->proto_output_filters = cur;
00965 
00966         rv = ap_pass_brigade(r->output_filters, bb);
00967     }
00968 
00969     /* don't soak up script output if errors occurred writing it
00970      * out...  otherwise, we prolong the life of the script when the
00971      * connection drops or we stopped sending output for some other
00972      * reason */
00973     if (rv == APR_SUCCESS && !r->connection->aborted) {
00974         apr_file_pipe_timeout_set(script_err, r->server->timeout);
00975         log_script_err(r, script_err);
00976     }
00977     
00978     apr_file_close(script_err);
00979 
00980     return OK;                      /* NOT r->status, even if it has changed. */
00981 }
00982 
00983 /*============================================================================
00984  *============================================================================
00985  * This is the beginning of the cgi filter code moved from mod_include. This
00986  *   is the code required to handle the "exec" SSI directive.
00987  *============================================================================
00988  *============================================================================*/
00989 static int include_cgi(char *s, request_rec *r, ap_filter_t *next,
00990                        apr_bucket *head_ptr, apr_bucket **inserted_head)
00991 {
00992     request_rec *rr = ap_sub_req_lookup_uri(s, r, next);
00993     int rr_status;
00994     apr_bucket  *tmp_buck, *tmp2_buck;
00995 
00996     if (rr->status != HTTP_OK) {
00997         ap_destroy_sub_req(rr);
00998         return -1;
00999     }
01000 
01001     /* No hardwired path info or query allowed */
01002 
01003     if ((rr->path_info && rr->path_info[0]) || rr->args) {
01004         ap_destroy_sub_req(rr);
01005         return -1;
01006     }
01007     if (rr->finfo.filetype != APR_REG) {
01008         ap_destroy_sub_req(rr);
01009         return -1;
01010     }
01011 
01012     /* Script gets parameters of the *document*, for back compatibility */
01013 
01014     rr->path_info = r->path_info;       /* hard to get right; see mod_cgi.c */
01015     rr->args = r->args;
01016 
01017     /* Force sub_req to be treated as a CGI request, even if ordinary
01018      * typing rules would have called it something else.
01019      */
01020 
01021     ap_set_content_type(rr, CGI_MAGIC_TYPE);
01022 
01023     /* Run it. */
01024 
01025     rr_status = ap_run_sub_req(rr);
01026     if (ap_is_HTTP_REDIRECT(rr_status)) {
01027         apr_size_t len_loc;
01028         const char *location = apr_table_get(rr->headers_out, "Location");
01029         conn_rec *c = r->connection;
01030 
01031         location = ap_escape_html(rr->pool, location);
01032         len_loc = strlen(location);
01033 
01034         /* XXX: if most of this stuff is going to get copied anyway,
01035          * it'd be more efficient to pstrcat it into a single pool buffer
01036          * and a single pool bucket */
01037 
01038         tmp_buck = apr_bucket_immortal_create("<A HREF=\"",
01039                                               sizeof("<A HREF=\"") - 1,
01040                                               c->bucket_alloc);
01041         APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
01042         tmp2_buck = apr_bucket_heap_create(location, len_loc, NULL,
01043                                            c->bucket_alloc);
01044         APR_BUCKET_INSERT_BEFORE(head_ptr, tmp2_buck);
01045         tmp2_buck = apr_bucket_immortal_create("\">", sizeof("\">") - 1,
01046                                                c->bucket_alloc);
01047         APR_BUCKET_INSERT_BEFORE(head_ptr, tmp2_buck);
01048         tmp2_buck = apr_bucket_heap_create(location, len_loc, NULL,
01049                                            c->bucket_alloc);
01050         APR_BUCKET_INSERT_BEFORE(head_ptr, tmp2_buck);
01051         tmp2_buck = apr_bucket_immortal_create("</A>", sizeof("</A>") - 1,
01052                                                c->bucket_alloc);
01053         APR_BUCKET_INSERT_BEFORE(head_ptr, tmp2_buck);
01054 
01055         if (*inserted_head == NULL) {
01056             *inserted_head = tmp_buck;
01057         }
01058     }
01059 
01060     ap_destroy_sub_req(rr);
01061 
01062     return 0;
01063 }
01064 
01065 
01066 static int include_cmd(include_ctx_t *ctx, apr_bucket_brigade **bb,
01067                        const char *command, request_rec *r, ap_filter_t *f)
01068 {
01069     cgi_exec_info_t  e_info;
01070     const char   **argv;
01071     apr_file_t    *script_out = NULL, *script_in = NULL, *script_err = NULL;
01072     apr_bucket_brigade *bcgi;
01073     apr_bucket *b;
01074     apr_status_t rv;
01075 
01076     add_ssi_vars(r);
01077 
01078     e_info.process_cgi = 0;
01079     e_info.cmd_type    = APR_SHELLCMD;
01080     e_info.detached    = 0;
01081     e_info.in_pipe     = APR_NO_PIPE;
01082     e_info.out_pipe    = APR_FULL_BLOCK;
01083     e_info.err_pipe    = APR_NO_PIPE;
01084     e_info.prog_type   = RUN_AS_SSI;
01085     e_info.bb          = bb;
01086     e_info.ctx         = ctx;
01087     e_info.next        = f->next;
01088 
01089     if ((rv = cgi_build_command(&command, &argv, r, r->pool, &e_info)) != APR_SUCCESS) {
01090         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
01091                       "don't know how to spawn cmd child process: %s", 
01092                       r->filename);
01093         return HTTP_INTERNAL_SERVER_ERROR;
01094     }
01095 
01096     /* run the script in its own process */
01097     if ((rv = run_cgi_child(&script_out, &script_in, &script_err,
01098                             command, argv, r, r->pool,
01099                             &e_info)) != APR_SUCCESS) {
01100         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
01101                       "couldn't spawn child process: %s", r->filename);
01102         return HTTP_INTERNAL_SERVER_ERROR;
01103     }
01104 
01105     bcgi = apr_brigade_create(r->pool, f->c->bucket_alloc);
01106     b = apr_bucket_pipe_create(script_in, f->c->bucket_alloc);
01107     APR_BRIGADE_INSERT_TAIL(bcgi, b);
01108     ap_pass_brigade(f->next, bcgi);
01109 
01110     /* We can't close the pipe here, because we may return before the
01111      * full CGI has been sent to the network.  That's okay though,
01112      * because we can rely on the pool to close the pipe for us.
01113      */
01114 
01115     return 0;
01116 }
01117 
01118 static int handle_exec(include_ctx_t *ctx, apr_bucket_brigade **bb,
01119                        request_rec *r, ap_filter_t *f, apr_bucket *head_ptr,
01120                        apr_bucket **inserted_head)
01121 {
01122     char *tag     = NULL;
01123     char *tag_val = NULL;
01124     char *file = r->filename;
01125     apr_bucket  *tmp_buck;
01126     char parsed_string[MAX_STRING_LEN];
01127 
01128     *inserted_head = NULL;
01129     if (ctx->flags & FLAG_PRINTING) {
01130         if (ctx->flags & FLAG_NO_EXEC) {
01131             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
01132                           "exec used but not allowed in %s", r->filename);
01133             CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
01134         }
01135         else {
01136             while (1) {
01137                 cgi_pfn_gtv(ctx, &tag, &tag_val, 1);
01138                 if (tag_val == NULL) {
01139                     if (tag == NULL) {
01140                         return 0;
01141                     }
01142                     else {
01143                         return 1;
01144                     }
01145                 }
01146                 if (!strcmp(tag, "cmd")) {
01147                     cgi_pfn_ps(r, ctx, tag_val, parsed_string,
01148                                sizeof(parsed_string), 1);
01149                     if (include_cmd(ctx, bb, parsed_string, r, f) == -1) {
01150                         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
01151                                     "execution failure for parameter \"%s\" "
01152                                     "to tag exec in file %s", tag, r->filename);
01153                         CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr,
01154                                             *inserted_head);
01155                     }
01156                 }
01157                 else if (!strcmp(tag, "cgi")) {
01158                     apr_status_t retval = APR_SUCCESS;
01159 
01160                     cgi_pfn_ps(r, ctx, tag_val, parsed_string,
01161                                sizeof(parsed_string), 0);
01162 
01163                     SPLIT_AND_PASS_PRETAG_BUCKETS(*bb, ctx, f->next, retval);
01164                     if (retval != APR_SUCCESS) {
01165                         return retval;
01166                     }
01167 
01168                     if (include_cgi(parsed_string, r, f->next, head_ptr,
01169                                     inserted_head) == -1) {
01170                         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
01171                                       "invalid CGI ref \"%s\" in %s",
01172                                       tag_val, file);
01173                         CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr,
01174                                             *inserted_head);
01175                     }
01176                 }
01177                 else {
01178                     ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
01179                                   "unknown parameter \"%s\" to tag exec in %s",
01180                                   tag, file);
01181                     CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr,
01182                                         *inserted_head);
01183                 }
01184             }
01185         }
01186     }
01187     return 0;
01188 }
01189 
01190 
01191 /*============================================================================
01192  *============================================================================
01193  * This is the end of the cgi filter code moved from mod_include.
01194  *============================================================================
01195  *============================================================================*/
01196 
01197 
01198 static int cgi_post_config(apr_pool_t *p, apr_pool_t *plog,
01199                                 apr_pool_t *ptemp, server_rec *s)
01200 {
01201     cgi_pfn_reg_with_ssi = APR_RETRIEVE_OPTIONAL_FN(ap_register_include_handler);
01202     cgi_pfn_gtv          = APR_RETRIEVE_OPTIONAL_FN(ap_ssi_get_tag_and_value);
01203     cgi_pfn_ps           = APR_RETRIEVE_OPTIONAL_FN(ap_ssi_parse_string);
01204 
01205     if ((cgi_pfn_reg_with_ssi) && (cgi_pfn_gtv) && (cgi_pfn_ps)) {
01206         /* Required by mod_include filter. This is how mod_cgi registers
01207          *   with mod_include to provide processing of the exec directive.
01208          */
01209         cgi_pfn_reg_with_ssi("exec", handle_exec);
01210     }
01211 
01212     /* This is the means by which unusual (non-unix) os's may find alternate
01213      * means to run a given command (e.g. shebang/registry parsing on Win32)
01214      */
01215     cgi_build_command    = APR_RETRIEVE_OPTIONAL_FN(ap_cgi_build_command);
01216     if (!cgi_build_command) {
01217         cgi_build_command = default_build_command;
01218     }
01219     return OK;
01220 }
01221 
01222 static void register_hooks(apr_pool_t *p)
01223 {
01224     static const char * const aszPre[] = { "mod_include.c", NULL };
01225     ap_hook_handler(cgi_handler, NULL, NULL, APR_HOOK_MIDDLE);
01226     ap_hook_post_config(cgi_post_config, aszPre, NULL, APR_HOOK_REALLY_FIRST);
01227 }
01228 
01229 module AP_MODULE_DECLARE_DATA cgi_module =
01230 {
01231     STANDARD20_MODULE_STUFF,
01232     NULL,                        /* dir config creater */
01233     NULL,                        /* dir merger --- default is to override */
01234     create_cgi_config,           /* server config */
01235     merge_cgi_config,            /* merge server config */
01236     cgi_cmds,                    /* command apr_table_t */
01237     register_hooks               /* register hooks */
01238 };