Commit ee42a492 authored by William A. Rowe Jr's avatar William A. Rowe Jr
Browse files

   Remove the Win32 script-processing exception from mod_cgi, and
   roll build_command_line/build_argv_list into a unified, overrideable
   ap_cgi_build_command optional function.

   Eliminates a ton of Win32 cruft from core.c for registry parsing.
   Win32 (through the default handler, and newest changes to the
   apr_proc_create fn) continues to serve .bat/.exe files.  This is in
   preparation for adding modules/arch/win32/mod_win32 for scripts.

   Please review the mod_cgi.c behavior very carefully.


git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@91058 13f79535-47bb-0310-9956-ffa450edef68
parent 315ea755
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
Changes with Apache 2.0.26-dev

  *) Remove the Win32 script-processing exception from mod_cgi, and
     roll build_command_line/build_argv_list into a unified, overrideable
     ap_cgi_build_command optional function.  [William Rowe]

  *) Rewrite find_start_sequence to use a better search algorithm
     to find the start tag.  [Justin Erenkrantz]

+0 −19
Original line number Diff line number Diff line
@@ -319,20 +319,6 @@ AP_DECLARE(int) ap_satisfies(request_rec *r);
 */
AP_DECLARE(const apr_array_header_t *) ap_requires(request_rec *r);    

#if defined(WIN32)
/* 
 * CGI Script stuff for Win32...
 */
typedef enum { eFileTypeUNKNOWN, eFileTypeBIN, eFileTypeEXE16, eFileTypeEXE32, 
               eFileTypeSCRIPT } file_type_e;
typedef enum { INTERPRETER_SOURCE_UNSET, INTERPRETER_SOURCE_REGISTRY_STRICT, 
               INTERPRETER_SOURCE_REGISTRY, INTERPRETER_SOURCE_SHEBANG 
             } interpreter_source_e;
AP_DECLARE(file_type_e) ap_get_win32_interpreter(const request_rec *, 
                                                 char **interpreter,
                                                 char **arguments);
#endif

#ifdef CORE_PRIVATE

/*
@@ -458,11 +444,6 @@ typedef struct {
    apr_array_header_t *sec_file;
    regex_t *r;

#ifdef WIN32
    /* Where to find interpreter to run scripts */
    interpreter_source_e script_interpreter_source;
#endif    

    const char *mime_type;       /* forced with ForceType  */
    const char *handler;         /* forced with SetHandler */
    const char *output_filters;  /* forced with SetOutputFilters */
+52 −77
Original line number Diff line number Diff line
@@ -74,6 +74,7 @@
#include "apr_thread_proc.h"    /* for RLIMIT stuff */
#include "apr_optional.h"
#include "apr_buckets.h"
#include "apr_lib.h"

#define APR_WANT_STRFUNC
#include "apr_want.h"
@@ -97,9 +98,26 @@

module AP_MODULE_DECLARE_DATA cgi_module;

/* There has to be a better place to put this - uhm... where exactly? */
/**
 * Reprocess the command and arguments to execute the given CGI script.
 * @param cmd Pointer to the command to execute (may be overridden)
 * @param argv Pointer to the arguments to pass (may be overridden)
 * @param r The current request
 * @param p The pool to allocate correct cmd/argv elements within.
 * @deffunc apr_status_t ap_cgi_build_command(const char **cmd, const char ***argv, request_rec *r, apr_pool_t *p)
 * @tip This callback may be registered by the os-specific module 
 * to correct the command and arguments for apr_proc_create invocation
 * on a given os.  mod_cgi will call the function if registered.
 */
APR_DECLARE_OPTIONAL_FN(apr_status_t, ap_cgi_build_command, 
                        (const char **cmd, const char ***argv,
                         request_rec *r, apr_pool_t *p));

static APR_OPTIONAL_FN_TYPE(ap_register_include_handler) *cgi_pfn_reg_with_ssi;
static APR_OPTIONAL_FN_TYPE(ap_ssi_get_tag_and_value) *cgi_pfn_gtv;
static APR_OPTIONAL_FN_TYPE(ap_ssi_parse_string) *cgi_pfn_ps;
static APR_OPTIONAL_FN_TYPE(ap_cgi_build_command) *cgi_build_command;

typedef enum {RUN_AS_SSI, RUN_AS_CGI} prog_types;

@@ -479,12 +497,21 @@ static apr_status_t run_cgi_child(apr_file_t **script_out,
    return (rc);
}

static apr_status_t build_argv_list(const char ***argv, request_rec *r,
                                    apr_pool_t *p)

static apr_status_t default_build_command(const char **cmd, const char ***argv,
                                          request_rec *r, apr_pool_t *p)
{
    int numwords, x, idx;
    char *w;
    const char *args = r->args;
    const char *argv0;

    /* Allow suexec's "/" check to succeed */
    if ((argv0 = strrchr(r->filename, '/')) != NULL)
        argv0++;
    else
        argv0 = r->filename;
    *cmd = argv0;

    if (!args || !args[0] || ap_strchr_c(args, '=')) {
        numwords = 1;
@@ -497,14 +524,14 @@ static apr_status_t build_argv_list(const char ***argv, request_rec *r,
            }
        }
    }
    /* Everything is - 1 to account for the first parameter which is the
     * program name.  We didn't used to have to do this, but APR wants it.
    /* Everything is - 1 to account for the first parameter 
     * which is the program name.
     */ 
    if (numwords > APACHE_ARG_MAX - 1) {
        numwords = APACHE_ARG_MAX - 1;	/* Truncate args to prevent overrun */
    }
    *argv = apr_palloc(p, (numwords + 2) * sizeof(char *));
 
    (*argv)[0] = argv0;
    for (x = 1, idx = 1; x < numwords; x++) {
        w = ap_getword_nulls(p, &args, '+');
        ap_unescape_url(w);
@@ -515,57 +542,6 @@ static apr_status_t build_argv_list(const char ***argv, request_rec *r,
    return APR_SUCCESS;
}

static apr_status_t build_command_line(const char **cmd, request_rec *r,
                                       apr_pool_t *p)
{
#ifdef WIN32
    char *quoted_filename = NULL;
    char *interpreter = NULL;
    char *arguments = NULL;
    file_type_e fileType;
#endif
    const char *argv0;

    /* Allow suexec's "/" check to succeed */
    if ((argv0 = strrchr(r->filename, '/')) != NULL)
        argv0++;
    else
        argv0 = r->filename;

#ifdef WIN32 
    *cmd = NULL;
    fileType = ap_get_win32_interpreter(r, &interpreter, &arguments);

    if (fileType == eFileTypeUNKNOWN) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r,
                      "%s is not executable; ensure interpreted scripts have "
                      "\"#!\" first line", 
                      r->filename);
        return APR_EBADF;
    }

    /*
     * Build the command string to pass to ap_os_create_privileged_process()
     */
    quoted_filename = apr_pstrcat(p, "\"", r->filename, "\"", NULL);
    if (interpreter && *interpreter) {
        if (arguments && *arguments)
            *cmd = apr_pstrcat(p, interpreter, " ", quoted_filename, " ", 
                              arguments, NULL);
        else
            *cmd = apr_pstrcat(p, interpreter, " ", quoted_filename, " ", NULL);
    }
    else if (arguments && *arguments) {
        *cmd = apr_pstrcat(p, quoted_filename, " ", arguments, NULL);
    }
    else {
        *cmd = apr_pstrcat(p, quoted_filename, NULL);
    }
#else
    *cmd = argv0;
#endif
    return APR_SUCCESS;
}

static int cgi_handler(request_rec *r)
{
@@ -596,11 +572,7 @@ static int cgi_handler(request_rec *r)
	return DECLINED;
    }

    if ((argv0 = strrchr(r->filename, '/')) != NULL)
	argv0++;
    else
	argv0 = r->filename;

    argv0 = apr_filename_of_pathname(r->filename);
    nph = !(strncmp(argv0, "nph-", 4));
    conf = ap_get_module_config(r->server->module_config, &cgi_module);

@@ -632,18 +604,12 @@ static int cgi_handler(request_rec *r)
    ap_add_common_vars(r);

    /* build the command line */
    if ((rv = build_command_line(&command, r, p)) != APR_SUCCESS) {
    if ((rv = cgi_build_command(&command, &argv, r, p)) != APR_SUCCESS) {
	ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
	      "couldn't spawn child process: %s", r->filename);
	return HTTP_INTERNAL_SERVER_ERROR;
    }
    /* build the argument list */
    else if ((rv = build_argv_list(&argv, r, p)) != APR_SUCCESS) {
	ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
		      "couldn't spawn child process: %s", r->filename);
		      "don't know how to spawn child process: %s", 
                      r->filename);
	return HTTP_INTERNAL_SERVER_ERROR;
    }
    argv[0] = apr_pstrdup(p, command);

    e_info.cmd_type  = APR_PROGRAM;
    e_info.in_pipe   = APR_CHILD_BLOCK;
@@ -857,13 +823,14 @@ static int include_cmd(include_ctx_t *ctx, apr_bucket_brigade **bb, char *comman
    apr_file_t    *script_out = NULL, *script_in = NULL, *script_err = NULL;
    apr_bucket_brigade *bcgi;
    apr_bucket *b;
    apr_status_t rv;

    if (build_argv_list(&argv, r, r->pool) != APR_SUCCESS) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r,
                      "couldn't spawn cmd child process: %s", r->filename);
    if ((rv = cgi_build_command(&command, &argv, r, r->pool)) != APR_SUCCESS) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
                      "don't know how to spawn cmd child process: %s", 
                      r->filename);
        return HTTP_INTERNAL_SERVER_ERROR;
    }
    argv[0] = apr_pstrdup(r->pool, command);

    e_info.cmd_type  = APR_SHELLCMD;
    e_info.in_pipe   = APR_NO_PIPE;
@@ -875,9 +842,9 @@ static int include_cmd(include_ctx_t *ctx, apr_bucket_brigade **bb, char *comman
    e_info.next      = f->next;

    /* run the script in its own process */
    if (run_cgi_child(&script_out, &script_in, &script_err,
                      command, argv, r, r->pool, &e_info) != APR_SUCCESS) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r,
    if ((rv = run_cgi_child(&script_out, &script_in, &script_err,
                      command, argv, r, r->pool, &e_info)) != APR_SUCCESS) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
                      "couldn't spawn child process: %s", r->filename);
        return HTTP_INTERNAL_SERVER_ERROR;
    }
@@ -978,6 +945,14 @@ static void cgi_post_config(apr_pool_t *p, apr_pool_t *plog,
         */
        cgi_pfn_reg_with_ssi("exec", handle_exec);
    }

    /* This is the means by which unusual (non-unix) os's may find alternate
     * means to run a given command (e.g. shebang/registry parsing on Win32)
     */
    cgi_build_command    = APR_RETRIEVE_OPTIONAL_FN(ap_cgi_build_command);
    if (!cgi_build_command) {
        cgi_build_command = default_build_command;
    }
}

static void register_hooks(apr_pool_t *p)
+3 −318
Original line number Diff line number Diff line
@@ -151,9 +151,6 @@ static void *create_core_dir_config(apr_pool_t *a, char *dir)
    conf->limit_req_body = 0;
    conf->limit_xml_body = AP_LIMIT_UNSET;
    conf->sec_file = apr_array_make(a, 2, sizeof(ap_conf_vector_t *));
#ifdef WIN32
    conf->script_interpreter_source = INTERPRETER_SOURCE_UNSET;
#endif

    conf->server_signature = srv_sig_unset;

@@ -289,12 +286,6 @@ static void *merge_core_dir_configs(apr_pool_t *a, void *basev, void *newv)
        conf->satisfy = new->satisfy;
    }

#ifdef WIN32
    if (new->script_interpreter_source != INTERPRETER_SOURCE_UNSET) {
        conf->script_interpreter_source = new->script_interpreter_source;
    }
#endif

    if (new->server_signature != srv_sig_unset) {
	conf->server_signature = new->server_signature;
    }
@@ -782,290 +773,6 @@ AP_DECLARE(unsigned long) ap_get_limit_req_body(const request_rec *r)
    return d->limit_req_body;
}

#ifdef WIN32
static apr_status_t get_win32_registry_default_value(apr_pool_t *p, HKEY hkey,
                                                     char* relativepath, 
                                                     char **value)
{
    HKEY hkeyOpen;
    DWORD type;
    DWORD size = 0;
    DWORD result = RegOpenKeyEx(hkey, relativepath, 0, 
                                KEY_QUERY_VALUE, &hkeyOpen);
    
    if (result != ERROR_SUCCESS) 
        return APR_FROM_OS_ERROR(result);

    /* Read to NULL buffer to determine value size */
    result = RegQueryValueEx(hkeyOpen, "", 0, &type, NULL, &size);
    
   if (result == ERROR_SUCCESS) {
        if ((size < 2) || (type != REG_SZ && type != REG_EXPAND_SZ)) {
            result = ERROR_INVALID_PARAMETER;
        }
        else {
            *value = apr_palloc(p, size);
            /* Read value based on size query above */
            result = RegQueryValueEx(hkeyOpen, "", 0, &type, *value, &size);
        }
    }

    /* TODO: This might look fine, but we need to provide some warning
     * somewhere that some environment variables may -not- be translated,
     * seeing as we may have chopped the environment table down somewhat.
     */
    if ((result == ERROR_SUCCESS) && (type == REG_EXPAND_SZ)) 
    {
        char *tmp = *value;
        size = ExpandEnvironmentStrings(tmp, *value, 0);
        if (size) {
            *value = apr_palloc(p, size);
            size = ExpandEnvironmentStrings(tmp, *value, size);
        }
    }

    RegCloseKey(hkeyOpen);
    return APR_FROM_OS_ERROR(result);
}

static char* get_interpreter_from_win32_registry(apr_pool_t *p, const char* ext,
                                                 char** arguments, int strict)
{
    char execcgi_path[] = "SHELL\\EXECCGI\\COMMAND";
    char execopen_path[] = "SHELL\\OPEN\\COMMAND";
    char typeName[MAX_PATH];
    int cmdOfName = FALSE;
    HKEY hkeyName;
    HKEY hkeyType;
    DWORD type;
    int size;
    int result;
    char *buffer;
    char *s;
    
    if (!ext)
        return NULL;
    /* 
     * Future optimization:
     * When the registry is successfully searched, store the strings for
     * interpreter and arguments in an ext hash to speed up subsequent look-ups
     */

    /* Open the key associated with the script filetype extension */
    result = RegOpenKeyEx(HKEY_CLASSES_ROOT, ext, 0, KEY_QUERY_VALUE, 
                          &hkeyType);

    if (result != ERROR_SUCCESS) 
        return NULL;

    /* Retrieve the name of the script filetype extension */
    size = sizeof(typeName);
    result = RegQueryValueEx(hkeyType, "", NULL, &type, typeName, &size);
    
    if (result == ERROR_SUCCESS && type == REG_SZ && typeName[0]) {
        /* Open the key associated with the script filetype extension */
        result = RegOpenKeyEx(HKEY_CLASSES_ROOT, typeName, 0, 
                              KEY_QUERY_VALUE, &hkeyName);

        if (result == ERROR_SUCCESS)
            cmdOfName = TRUE;
    }

    /* Open the key for the script command path by:
     * 
     *   1) the 'named' filetype key for ExecCGI/Command
     *   2) the extension's type key for ExecCGI/Command
     *
     * and if the strict arg is false, then continue trying:
     *
     *   3) the 'named' filetype key for Open/Command
     *   4) the extension's type key for Open/Command
     */

    if (cmdOfName) {
        result = get_win32_registry_default_value(p, hkeyName, 
                                                  execcgi_path, &buffer);
    }

    if (!cmdOfName || (result != ERROR_SUCCESS)) {
        result = get_win32_registry_default_value(p, hkeyType, 
                                                  execcgi_path, &buffer);
    }

    if (!strict && cmdOfName && (result != ERROR_SUCCESS)) {
        result = get_win32_registry_default_value(p, hkeyName, 
                                                  execopen_path, &buffer);
    }

    if (!strict && (result != ERROR_SUCCESS)) {
        result = get_win32_registry_default_value(p, hkeyType, 
                                                  execopen_path, &buffer);
    }

    if (cmdOfName)
        RegCloseKey(hkeyName);

    RegCloseKey(hkeyType);

    if (result != ERROR_SUCCESS)
        return NULL;

    /*
     * The canonical way shell command entries are entered in the Win32 
     * registry is as follows:
     *   shell [options] "%1" [args]
     * where
     *   shell - full path name to interpreter or shell to run.
     *           E.g., c:\usr\local\ntreskit\perl\bin\perl.exe
     *   options - optional switches
     *              E.g., \C
     *   "%1" - Place holder for file to run the shell against. 
     *          Typically quoted.
     *   options - additional arguments
     *              E.g., /silent
     *
     * If we find a %1 or a quoted %1, lop off the remainder to arguments. 
     */
    if (buffer && *buffer) {
        if ((s = strstr(buffer, "\"%1")))
        {
            *s = '\0';
            *arguments = s + 4;
        }
        else if ((s = strstr(buffer, "%1"))) 
        {
            *s = '\0';
            *arguments = buffer + 2;
        }
        else
            *arguments = strchr(buffer, '\0');
        while (**arguments && isspace(**arguments))
            ++*arguments;
    }

    return buffer;
}

AP_DECLARE (file_type_e) ap_get_win32_interpreter(const  request_rec *r, 
                                                  char** interpreter,
                                                  char** arguments)
{
    HANDLE hFile;
    DWORD nBytesRead;
    BOOLEAN bResult;
    char buffer[1024];
    core_dir_config *d;
    int i;
    file_type_e fileType = eFileTypeUNKNOWN;
    char *ext = NULL;
    char *exename = NULL;

    d = (core_dir_config *)ap_get_module_config(r->per_dir_config, 
                                                &core_module);

    /* Find the file extension */
    exename = strrchr(r->filename, '/');
    if (!exename) {
        exename = strrchr(r->filename, '\\');
    }
    if (!exename) {
        exename = r->filename;
    }
    else {
        exename++;
    }
    ext = strrchr(exename, '.');

    if (ext && (!strcasecmp(ext,".bat") || !strcasecmp(ext,".cmd"))) 
    {
        char *comspec = getenv("COMSPEC");
        if (comspec) {
            *interpreter = apr_pstrcat(r->pool, "\"", comspec, "\" /c ", NULL);
            return eFileTypeSCRIPT;
        }
        ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, 0, r->server,
         "Failed to start a '%s' file as a script." APR_EOL_STR
         "\tCOMSPEC variable is missing from the environment.", ext);
        return eFileTypeUNKNOWN;
    }

    /* If the file has an extension and it is not .com and not .exe and
     * we've been instructed to search the registry, then do it!
     */
    if (ext && strcasecmp(ext,".exe") && strcasecmp(ext,".com") &&
        (d->script_interpreter_source == INTERPRETER_SOURCE_REGISTRY ||
         d->script_interpreter_source == INTERPRETER_SOURCE_REGISTRY_STRICT)) {
         /* Check the registry */
        int strict = (d->script_interpreter_source 
                            == INTERPRETER_SOURCE_REGISTRY_STRICT);
        *interpreter = get_interpreter_from_win32_registry(r->pool, ext, 
                                                           arguments, strict);
        if (*interpreter)
            return eFileTypeSCRIPT;
        else if (d->script_interpreter_source == INTERPRETER_SOURCE_REGISTRY_STRICT) {
            ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, 0, r->server,
             "ScriptInterpreterSource config directive set to \"registry-strict\"." APR_EOL_STR
             "\tInterpreter not found for files of type '%s'.", ext);
             return eFileTypeUNKNOWN;
        }
        else
        {
            ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, 0, r->server,
             "ScriptInterpreterSource config directive set to \"registry\"." APR_EOL_STR
             "\tInterpreter not found for files of type '%s', "
             "trying \"script\" method...", ext);
        }
    }        

    /* Need to peek into the file figure out what it really is... */
    /* This is wrong for Unicode FS ... should move to APR */
    hFile = CreateFile(r->filename, GENERIC_READ, FILE_SHARE_READ, NULL,
                       OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
        return eFileTypeUNKNOWN;
    }
    bResult = ReadFile(hFile, (void*) &buffer, sizeof(buffer) - 1, 
                       &nBytesRead, NULL);
    if (!bResult || (nBytesRead == 0)) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, GetLastError(), r,
                      "ReadFile(%s) failed", r->filename);
        CloseHandle(hFile);
        return eFileTypeUNKNOWN;
    }
    CloseHandle(hFile);
    buffer[nBytesRead] = '\0';

    /* Script or executable, that is the question... */
    if ((buffer[0] == '#') && (buffer[1] == '!')) {
        /* Assuming file is a script since it starts with a shebang */
        fileType = eFileTypeSCRIPT;
        for (i = 2; i < sizeof(buffer); i++) {
            if ((buffer[i] == '\r')
                || (buffer[i] == '\n')) {
                break;
            }
        }
        buffer[i] = '\0';
        for (i = 2; buffer[i] == ' ' ; ++i)
            ;
        *interpreter = apr_pstrdup(r->pool, buffer + i ); 
    }
    else {
        /* Not a script, is it an executable? */
        IMAGE_DOS_HEADER *hdr = (IMAGE_DOS_HEADER*)buffer;    
        if ((nBytesRead >= sizeof(IMAGE_DOS_HEADER)) && (hdr->e_magic == IMAGE_DOS_SIGNATURE)) {
            if (hdr->e_lfarlc < 0x40)
                fileType = eFileTypeEXE16;
            else
                fileType = eFileTypeEXE32;
        }
        else
            fileType = eFileTypeUNKNOWN;
    }

    return fileType;
}
#endif

/*****************************************************************
 *
@@ -1484,7 +1191,9 @@ AP_CORE_DECLARE_NONSTD(const char *) ap_limit_section(cmd_parms *cmd, void *dumm
    return errmsg;
}

/* We use this in <DirectoryMatch> and <FilesMatch>, to ensure that 
/* XXX: Bogus - need to do this differently (at least OS2/Netware suffer
 * the same problem!!!
 * We use this in <DirectoryMatch> and <FilesMatch>, to ensure that 
 * people don't get bitten by wrong-cased regex matches
 */

@@ -2377,25 +2086,6 @@ AP_DECLARE(size_t) ap_get_limit_xml_body(const request_rec *r)
    return (size_t)conf->limit_xml_body;
}

#ifdef WIN32
static const char *set_interpreter_source(cmd_parms *cmd, core_dir_config *d,
                                                char *arg)
{
    if (!strcasecmp(arg, "registry")) {
        d->script_interpreter_source = INTERPRETER_SOURCE_REGISTRY;
    } else if (!strcasecmp(arg, "registry-strict")) {
        d->script_interpreter_source = INTERPRETER_SOURCE_REGISTRY_STRICT;
    } else if (!strcasecmp(arg, "script")) {
        d->script_interpreter_source = INTERPRETER_SOURCE_SHEBANG;
    } else {
        return apr_pstrcat(cmd->temp_pool, "ScriptInterpreterSource \"", arg, 
                          "\" must be \"registry\", \"registry-strict\" or "
                          "\"script\"", NULL);
    }
    return NULL;
}
#endif

#if !defined (RLIMIT_CPU) || !(defined (RLIMIT_DATA) || defined (RLIMIT_VMEM) || defined(RLIMIT_AS)) || !defined (RLIMIT_NPROC)
static const char *no_set_limit(cmd_parms *cmd, void *conf_,
                                const char *arg, const char *arg2)
@@ -2765,11 +2455,6 @@ AP_INIT_TAKE1("NameVirtualHost", ap_set_name_virtual_host, NULL, RSRC_CONF,
AP_INIT_TAKE1("BS2000Account", set_bs2000_account, NULL, RSRC_CONF,
  "Name of server User's bs2000 logon account name"),
#endif
#ifdef WIN32
AP_INIT_TAKE1("ScriptInterpreterSource", set_interpreter_source, NULL,
  OR_FILEINFO,
  "Where to find interpreter to run Win32 scripts (Registry or script shebang line)"),
#endif
AP_INIT_TAKE1("ServerTokens", set_serv_tokens, NULL, RSRC_CONF,
  "Determine tokens displayed in the Server: header - Min(imal), OS or Full"),
AP_INIT_TAKE1("LimitRequestLine", set_limit_req_line, NULL, RSRC_CONF,