From 6beeb1b708550be0d4a53b272283e17e5e35fe17 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 17:01:30 +0200 Subject: Adding upstream version 2.4.57. Signed-off-by: Daniel Baumann --- modules/arch/netware/libprews.c | 79 ++ modules/arch/netware/mod_netware.c | 206 +++++ modules/arch/netware/mod_nw_ssl.c | 1285 +++++++++++++++++++++++++++ modules/arch/unix/Makefile.in | 3 + modules/arch/unix/config5.m4 | 33 + modules/arch/unix/mod_privileges.c | 588 ++++++++++++ modules/arch/unix/mod_systemd.c | 119 +++ modules/arch/unix/mod_unixd.c | 433 +++++++++ modules/arch/unix/mod_unixd.h | 41 + modules/arch/win32/Makefile.in | 3 + modules/arch/win32/config.m4 | 9 + modules/arch/win32/mod_isapi.c | 1728 ++++++++++++++++++++++++++++++++++++ modules/arch/win32/mod_isapi.dep | 61 ++ modules/arch/win32/mod_isapi.dsp | 115 +++ modules/arch/win32/mod_isapi.h | 271 ++++++ modules/arch/win32/mod_isapi.mak | 353 ++++++++ modules/arch/win32/mod_win32.c | 563 ++++++++++++ 17 files changed, 5890 insertions(+) create mode 100644 modules/arch/netware/libprews.c create mode 100644 modules/arch/netware/mod_netware.c create mode 100644 modules/arch/netware/mod_nw_ssl.c create mode 100644 modules/arch/unix/Makefile.in create mode 100644 modules/arch/unix/config5.m4 create mode 100644 modules/arch/unix/mod_privileges.c create mode 100644 modules/arch/unix/mod_systemd.c create mode 100644 modules/arch/unix/mod_unixd.c create mode 100644 modules/arch/unix/mod_unixd.h create mode 100644 modules/arch/win32/Makefile.in create mode 100644 modules/arch/win32/config.m4 create mode 100644 modules/arch/win32/mod_isapi.c create mode 100644 modules/arch/win32/mod_isapi.dep create mode 100644 modules/arch/win32/mod_isapi.dsp create mode 100644 modules/arch/win32/mod_isapi.h create mode 100644 modules/arch/win32/mod_isapi.mak create mode 100644 modules/arch/win32/mod_win32.c (limited to 'modules/arch') diff --git a/modules/arch/netware/libprews.c b/modules/arch/netware/libprews.c new file mode 100644 index 0000000..9a0b90e --- /dev/null +++ b/modules/arch/netware/libprews.c @@ -0,0 +1,79 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*------------------------------------------------------------------ + These functions are to be called when the shared NLM starts and + stops. By using these functions instead of defining a main() + and calling ExitThread(TSR_THREAD, 0), the load time of the + shared NLM is faster and memory size reduced. + + You may also want to override these in your own Apache module + to do any cleanup other than the mechanism Apache modules + provide. +------------------------------------------------------------------*/ +#include +#ifdef USE_WINSOCK +#include +#endif + +int _NonAppStart +( + void *NLMHandle, + void *errorScreen, + const char *cmdLine, + const char *loadDirPath, + size_t uninitializedDataLength, + void *NLMFileHandle, + int (*readRoutineP)( int conn, void *fileHandle, size_t offset, + size_t nbytes, size_t *bytesRead, void *buffer ), + size_t customDataOffset, + size_t customDataSize, + int messageCount, + const char **messages +) +{ +#pragma unused(cmdLine) +#pragma unused(loadDirPath) +#pragma unused(uninitializedDataLength) +#pragma unused(NLMFileHandle) +#pragma unused(readRoutineP) +#pragma unused(customDataOffset) +#pragma unused(customDataSize) +#pragma unused(messageCount) +#pragma unused(messages) + +#ifdef USE_WINSOCK + WSADATA wsaData; + + return WSAStartup((WORD) MAKEWORD(2, 0), &wsaData); +#else + return 0; +#endif +} + +void _NonAppStop( void ) +{ +#ifdef USE_WINSOCK + WSACleanup(); +#else + return; +#endif +} + +int _NonAppCheckUnload( void ) +{ + return 0; +} diff --git a/modules/arch/netware/mod_netware.c b/modules/arch/netware/mod_netware.c new file mode 100644 index 0000000..f873827 --- /dev/null +++ b/modules/arch/netware/mod_netware.c @@ -0,0 +1,206 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "apr_strings.h" +#include "apr_portable.h" +#include "apr_buckets.h" +#include "ap_config.h" +#include "httpd.h" +#include "http_config.h" +#include "http_core.h" +#include "http_protocol.h" +#include "http_request.h" +#include "http_log.h" +#include "util_script.h" +#include "mod_core.h" +#include "apr_optional.h" +#include "apr_lib.h" +#include "mod_cgi.h" +#include "mpm_common.h" + +#ifdef NETWARE + + +module AP_MODULE_DECLARE_DATA netware_module; + +typedef struct { + apr_table_t *file_type_handlers; /* CGI map from file types to CGI modules */ + apr_table_t *file_handler_mode; /* CGI module mode (spawn in same address space or not) */ + apr_table_t *extra_env_vars; /* Environment variables to be added to the CGI environment */ +} netware_dir_config; + + +static void *create_netware_dir_config(apr_pool_t *p, char *dir) +{ + netware_dir_config *new = (netware_dir_config*) apr_palloc(p, sizeof(netware_dir_config)); + + new->file_type_handlers = apr_table_make(p, 10); + new->file_handler_mode = apr_table_make(p, 10); + new->extra_env_vars = apr_table_make(p, 10); + + apr_table_setn(new->file_type_handlers, "NLM", "OS"); + + return new; +} + +static void *merge_netware_dir_configs(apr_pool_t *p, void *basev, void *addv) +{ + netware_dir_config *base = (netware_dir_config *) basev; + netware_dir_config *add = (netware_dir_config *) addv; + netware_dir_config *new = (netware_dir_config *) apr_palloc(p, sizeof(netware_dir_config)); + + new->file_type_handlers = apr_table_overlay(p, add->file_type_handlers, base->file_type_handlers); + new->file_handler_mode = apr_table_overlay(p, add->file_handler_mode, base->file_handler_mode); + new->extra_env_vars = apr_table_overlay(p, add->extra_env_vars, base->extra_env_vars); + + return new; +} + +static const char *set_extension_map(cmd_parms *cmd, netware_dir_config *m, + char *CGIhdlr, char *ext, char *detach) +{ + int i, len; + + if (*ext == '.') + ++ext; + + if (CGIhdlr != NULL) { + len = strlen(CGIhdlr); + for (i=0; ifile_type_handlers, ext, CGIhdlr); + if (detach) { + apr_table_set(m->file_handler_mode, ext, "y"); + } + + return NULL; +} + +static apr_status_t ap_cgi_build_command(const char **cmd, const char ***argv, + request_rec *r, apr_pool_t *p, + cgi_exec_info_t *e_info) +{ + char *ext = NULL; + char *cmd_only, *ptr; + const char *new_cmd; + netware_dir_config *d; + const char *args = ""; + + d = (netware_dir_config *)ap_get_module_config(r->per_dir_config, + &netware_module); + + if (e_info->process_cgi) { + /* Handle the complete file name, we DON'T want to follow suexec, since + * an unrooted command is as predictable as shooting craps in Win32. + * + * Notice that unlike most mime extension parsing, we have to use the + * win32 parsing here, therefore the final extension is the only one + * we will consider + */ + *cmd = r->filename; + if (r->args && r->args[0] && !ap_strchr_c(r->args, '=')) { + args = r->args; + } + } + + cmd_only = apr_pstrdup(p, *cmd); + e_info->cmd_type = APR_PROGRAM; + + /* truncate any arguments from the cmd */ + for (ptr = cmd_only; *ptr && (*ptr != ' '); ptr++); + *ptr = '\0'; + + /* Figure out what the extension is so that we can match it. */ + ext = strrchr(apr_filepath_name_get(cmd_only), '.'); + + /* If there isn't an extension then give it an empty string */ + if (!ext) { + ext = ""; + } + + /* eliminate the '.' if there is one */ + if (*ext == '.') { + ++ext; + } + + /* check if we have a registered command for the extension*/ + new_cmd = apr_table_get(d->file_type_handlers, ext); + e_info->detached = 1; + if (new_cmd == NULL) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02135) + "Could not find a command associated with the %s extension", ext); + return APR_EBADF; + } + if (stricmp(new_cmd, "OS")) { + /* If we have a registered command then add the file that was passed in as a + parameter to the registered command. */ + *cmd = apr_pstrcat (p, new_cmd, " ", cmd_only, NULL); + + /* Run in its own address space if specified */ + if (apr_table_get(d->file_handler_mode, ext)) { + e_info->addrspace = 1; + } + } + + /* Tokenize the full command string into its arguments */ + apr_tokenize_to_argv(*cmd, (char***)argv, p); + + /* The first argument should be the executible */ + *cmd = ap_server_root_relative(p, *argv[0]); + + return APR_SUCCESS; +} + +static int +netware_pre_config(apr_pool_t *pconf, apr_pool_t *plog, + apr_pool_t *ptemp) +{ + ap_sys_privileges_handlers(1); + return OK; +} + +static void register_hooks(apr_pool_t *p) +{ + APR_REGISTER_OPTIONAL_FN(ap_cgi_build_command); + ap_hook_pre_config(netware_pre_config, + NULL, NULL, APR_HOOK_FIRST); +} + +static const command_rec netware_cmds[] = { +AP_INIT_TAKE23("CGIMapExtension", set_extension_map, NULL, OR_FILEINFO, + "Full path to the CGI NLM module followed by a file extension. If the " + "first parameter is set to \"OS\" then the following file extension is " + "treated as NLM. The optional parameter \"detach\" can be specified if " + "the NLM should be launched in its own address space."), +{ NULL } +}; + +AP_DECLARE_MODULE(netware) = { + STANDARD20_MODULE_STUFF, + create_netware_dir_config, /* create per-dir config */ + merge_netware_dir_configs, /* merge per-dir config */ + NULL, /* server config */ + NULL, /* merge server config */ + netware_cmds, /* command apr_table_t */ + register_hooks /* register hooks */ +}; + +#endif diff --git a/modules/arch/netware/mod_nw_ssl.c b/modules/arch/netware/mod_nw_ssl.c new file mode 100644 index 0000000..298554a --- /dev/null +++ b/modules/arch/netware/mod_nw_ssl.c @@ -0,0 +1,1285 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * based on mod_tls.c - Apache SSL/TLS module for NetWare by Mike Gardiner. + * + * This module gives Apache the ability to do SSL/TLS with a minimum amount + * of effort. All of the SSL/TLS logic is already on NetWare versions 5 and + * above and is interfaced through WinSock on NetWare. As you can see in + * the code below SSL/TLS sockets can be created with three WinSock calls. + * + * To load, simply place the module in the modules directory under the main + * apache tree. Then add a "SecureListen" with two arguments. The first + * argument is an address and/or port. The second argument is the key pair + * name as created in ConsoleOne. + * + * Examples: + * + * SecureListen 443 "SSL CertificateIP" + * SecureListen 123.45.67.89:443 mycert + * + * The module also supports RFC 2817 / TLS Upgrade for HTTP 1.1. + * For this add a "NWSSLUpgradeable" with two arguments. The first + * argument is an address and/or port. The second argument is the key pair + * name as created in ConsoleOne. + * + * Examples: + * + * NWSSLUpgradeable 8080 "SSL CertificateIP" + * NWSSLUpgradeable 123.45.67.89:8080 mycert + * + */ + +#define WS_SSL + +#define MAX_ADDRESS 512 +#define MAX_KEY 80 + + +#include "httpd.h" +#include "http_config.h" +#include "http_connection.h" +#include "http_core.h" +#include "http_log.h" +#include "http_protocol.h" +#include "http_request.h" +#include "ap_listen.h" +#include "apr_strings.h" +#include "apr_portable.h" +#include "apr_optional.h" + +#include + +#ifndef SO_TLS_UNCLEAN_SHUTDOWN +#define SO_TLS_UNCLEAN_SHUTDOWN 0 +#endif + +/* The ssl_var_lookup() optional function retrieves SSL environment + * variables. */ +APR_DECLARE_OPTIONAL_FN(char *, ssl_var_lookup, + (apr_pool_t *, server_rec *, + conn_rec *, request_rec *, + char *)); + +/* An optional function which returns non-zero if the given connection + * is using SSL/TLS. */ +APR_DECLARE_OPTIONAL_FN(int, ssl_is_https, (conn_rec *)); + +/* The ssl_proxy_enable() and ssl_engine_disable() optional functions + * are used by mod_proxy to enable use of SSL for outgoing + * connections. */ +APR_DECLARE_OPTIONAL_FN(int, ssl_proxy_enable, (conn_rec *)); +APR_DECLARE_OPTIONAL_FN(int, ssl_engine_disable, (conn_rec *)); + +#define strEQ(s1,s2) (strcmp(s1,s2) == 0) +#define strNE(s1,s2) (strcmp(s1,s2) != 0) +#define strEQn(s1,s2,n) (strncmp(s1,s2,n) == 0) +#define strNEn(s1,s2,n) (strncmp(s1,s2,n) != 0) + +#define strcEQ(s1,s2) (strcasecmp(s1,s2) == 0) +#define strcNE(s1,s2) (strcasecmp(s1,s2) != 0) +#define strcEQn(s1,s2,n) (strncasecmp(s1,s2,n) == 0) +#define strcNEn(s1,s2,n) (strncasecmp(s1,s2,n) != 0) + +#define strIsEmpty(s) (s == NULL || s[0] == NUL) + + +module AP_MODULE_DECLARE_DATA nwssl_module; + +typedef struct NWSSLSrvConfigRec NWSSLSrvConfigRec; +typedef struct seclisten_rec seclisten_rec; +typedef struct seclistenup_rec seclistenup_rec; +typedef struct secsocket_data secsocket_data; + +struct seclisten_rec { + seclisten_rec *next; + struct sockaddr_in local_addr; /* local IP address and port */ + int fd; + int used; /* Only used during restart */ + char key[MAX_KEY]; + int mutual; + char *addr; + apr_port_t port; +}; + +struct seclistenup_rec { + seclistenup_rec *next; + char key[MAX_KEY]; + char *addr; + apr_port_t port; +}; + +struct NWSSLSrvConfigRec { + apr_table_t *sltable; + apr_table_t *slutable; + apr_pool_t *pPool; +}; + +struct secsocket_data { + apr_socket_t* csd; + int is_secure; +}; + +static apr_array_header_t *certlist = NULL; +static unicode_t** certarray = NULL; +static int numcerts = 0; +static seclisten_rec* ap_seclisteners = NULL; +static seclistenup_rec* ap_seclistenersup = NULL; + +static ap_listen_rec *nw_old_listeners; + +#define get_nwssl_cfg(srv) (NWSSLSrvConfigRec *) ap_get_module_config(srv->module_config, &nwssl_module) + + +static void build_cert_list(apr_pool_t *p) +{ + int i; + char **rootcerts = (char **)certlist->elts; + + numcerts = certlist->nelts; + certarray = apr_palloc(p, sizeof(unicode_t*)*numcerts); + + for (i = 0; i < numcerts; ++i) { + unicode_t *unistr; + unistr = (unicode_t*)apr_palloc(p, strlen(rootcerts[i])*4); + loc2uni (UNI_LOCAL_DEFAULT, unistr, rootcerts[i], 0, 2); + certarray[i] = unistr; + } +} + +/* + * Parses a host of the form
[:port] + * :port is permitted if 'port' is not NULL + */ +static unsigned long parse_addr(const char *w, unsigned short *ports) +{ + struct hostent *hep; + unsigned long my_addr; + char *p; + + p = strchr(w, ':'); + if (ports != NULL) { + *ports = 0; + if (p != NULL && strcmp(p + 1, "*") != 0) + *ports = atoi(p + 1); + } + + if (p != NULL) + *p = '\0'; + if (strcmp(w, "*") == 0) { + if (p != NULL) + *p = ':'; + return htonl(INADDR_ANY); + } + + my_addr = apr_inet_addr((char *)w); + if (my_addr != INADDR_NONE) { + if (p != NULL) + *p = ':'; + return my_addr; + } + + hep = gethostbyname(w); + + if ((!hep) || (hep->h_addrtype != AF_INET || !hep->h_addr_list[0])) { + /* XXX Should be echoing by h_errno the actual failure, no? + * ap_log_error would be good here. Better yet - APRize. + */ + fprintf(stderr, "Cannot resolve host name %s --- exiting!\n", w); + exit(1); + } + + if (hep->h_addr_list[1]) { + fprintf(stderr, "Host %s has multiple addresses ---\n", w); + fprintf(stderr, "you must choose one explicitly for use as\n"); + fprintf(stderr, "a secure port. Exiting!!!\n"); + exit(1); + } + + if (p != NULL) + *p = ':'; + + return ((struct in_addr *) (hep->h_addr))->s_addr; +} + +static int find_secure_listener(seclisten_rec *lr) +{ + seclisten_rec *sl; + + for (sl = ap_seclisteners; sl; sl = sl->next) { + if (!memcmp(&sl->local_addr, &lr->local_addr, sizeof(sl->local_addr))) { + sl->used = 1; + return sl->fd; + } + } + return -1; +} + +static char *get_port_key(conn_rec *c) +{ + seclistenup_rec *sl; + + for (sl = ap_seclistenersup; sl; sl = sl->next) { + if ((sl->port == (c->local_addr)->port) && + ((strcmp(sl->addr, "0.0.0.0") == 0) || + (strcmp(sl->addr, c->local_ip) == 0))) { + return sl->key; + } + } + return NULL; +} + +static int make_secure_socket(apr_pool_t *pconf, + const struct sockaddr_in *server, + char* key, int mutual, server_rec *sconf) +{ + int s; + char addr[MAX_ADDRESS]; + struct sslserveropts opts; + unsigned int optParam; + WSAPROTOCOL_INFO SecureProtoInfo; + + if (server->sin_addr.s_addr != htonl(INADDR_ANY)) + apr_snprintf(addr, sizeof(addr), "address %s port %d", + inet_ntoa(server->sin_addr), ntohs(server->sin_port)); + else + apr_snprintf(addr, sizeof(addr), "port %d", ntohs(server->sin_port)); + + /* note that because we're about to slack we don't use psocket */ + memset(&SecureProtoInfo, 0, sizeof(WSAPROTOCOL_INFO)); + + SecureProtoInfo.iAddressFamily = AF_INET; + SecureProtoInfo.iSocketType = SOCK_STREAM; + SecureProtoInfo.iProtocol = IPPROTO_TCP; + SecureProtoInfo.iSecurityScheme = SECURITY_PROTOCOL_SSL; + + s = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, + (LPWSAPROTOCOL_INFO)&SecureProtoInfo, 0, 0); + + if (s == INVALID_SOCKET) { + ap_log_error(APLOG_MARK, APLOG_CRIT, WSAGetLastError(), sconf, + APLOGNO(02120) + "make_secure_socket: failed to get a socket for %s", + addr); + return -1; + } + + if (!mutual) { + optParam = SO_SSL_ENABLE | SO_SSL_SERVER; + + if (WSAIoctl(s, SO_SSL_SET_FLAGS, (char *)&optParam, + sizeof(optParam), NULL, 0, NULL, NULL, NULL)) { + ap_log_error(APLOG_MARK, APLOG_CRIT, WSAGetLastError(), sconf, + APLOGNO(02121) + "make_secure_socket: for %s, WSAIoctl: " + "(SO_SSL_SET_FLAGS)", addr); + return -1; + } + } + + opts.cert = key; + opts.certlen = strlen(key); + opts.sidtimeout = 0; + opts.sidentries = 0; + opts.siddir = NULL; + + if (WSAIoctl(s, SO_SSL_SET_SERVER, (char *)&opts, sizeof(opts), + NULL, 0, NULL, NULL, NULL) != 0) { + ap_log_error(APLOG_MARK, APLOG_CRIT, WSAGetLastError(), sconf, + APLOGNO(02122) + "make_secure_socket: for %s, WSAIoctl: " + "(SO_SSL_SET_SERVER)", addr); + return -1; + } + + if (mutual) { + optParam = 0x07; /* SO_SSL_AUTH_CLIENT */ + + if (WSAIoctl(s, SO_SSL_SET_FLAGS, (char*)&optParam, sizeof(optParam), + NULL, 0, NULL, NULL, NULL)) { + ap_log_error(APLOG_MARK, APLOG_CRIT, WSAGetLastError(), sconf, + APLOGNO(02123) + "make_secure_socket: for %s, WSAIoctl: " + "(SO_SSL_SET_FLAGS)", addr); + return -1; + } + } + + optParam = SO_TLS_UNCLEAN_SHUTDOWN; + WSAIoctl(s, SO_SSL_SET_FLAGS, (char *)&optParam, sizeof(optParam), + NULL, 0, NULL, NULL, NULL); + + return s; +} + +static int convert_secure_socket(conn_rec *c, apr_socket_t *csd) +{ + int rcode; + struct tlsclientopts sWS2Opts; + struct nwtlsopts sNWTLSOpts; + struct sslserveropts opts; + unsigned long ulFlags; + SOCKET sock; + unicode_t keyFileName[60]; + + apr_os_sock_get(&sock, csd); + + /* zero out buffers */ + memset((char *)&sWS2Opts, 0, sizeof(struct tlsclientopts)); + memset((char *)&sNWTLSOpts, 0, sizeof(struct nwtlsopts)); + + /* turn on ssl for the socket */ + ulFlags = (numcerts ? SO_TLS_ENABLE : SO_TLS_ENABLE | SO_TLS_BLIND_ACCEPT); + rcode = WSAIoctl(sock, SO_TLS_SET_FLAGS, &ulFlags, sizeof(unsigned long), + NULL, 0, NULL, NULL, NULL); + if (SOCKET_ERROR == rcode) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, c->base_server, APLOGNO(02124) + "Error: %d with WSAIoctl(flag SO_TLS_ENABLE)", + WSAGetLastError()); + return rcode; + } + + ulFlags = SO_TLS_UNCLEAN_SHUTDOWN; + WSAIoctl(sock, SO_TLS_SET_FLAGS, &ulFlags, sizeof(unsigned long), + NULL, 0, NULL, NULL, NULL); + + /* setup the socket for SSL */ + memset (&sWS2Opts, 0, sizeof(sWS2Opts)); + memset (&sNWTLSOpts, 0, sizeof(sNWTLSOpts)); + sWS2Opts.options = &sNWTLSOpts; + + if (numcerts) { + sNWTLSOpts.walletProvider = WAL_PROV_DER; /* the wallet provider defined in wdefs.h */ + sNWTLSOpts.TrustedRootList = certarray; /* array of certs in UNICODE format */ + sNWTLSOpts.numElementsInTRList = numcerts; /* number of certs in TRList */ + } + else { + /* setup the socket for SSL */ + unicpy(keyFileName, L"SSL CertificateIP"); + sWS2Opts.wallet = keyFileName; /* no client certificate */ + sWS2Opts.walletlen = unilen(keyFileName); + + sNWTLSOpts.walletProvider = WAL_PROV_KMO; /* the wallet provider defined in wdefs.h */ + } + + /* make the IOCTL call */ + rcode = WSAIoctl(sock, SO_TLS_SET_CLIENT, &sWS2Opts, + sizeof(struct tlsclientopts), NULL, 0, NULL, + NULL, NULL); + + /* make sure that it was successful */ + if (SOCKET_ERROR == rcode ) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, c->base_server, APLOGNO(02125) + "Error: %d with WSAIoctl(SO_TLS_SET_CLIENT)", + WSAGetLastError()); + } + return rcode; +} + +static int SSLize_Socket(SOCKET socketHnd, char *key, request_rec *r) +{ + int rcode; + struct tlsserveropts sWS2Opts; + struct nwtlsopts sNWTLSOpts; + unicode_t SASKey[512]; + unsigned long ulFlag; + + memset((char *)&sWS2Opts, 0, sizeof(struct tlsserveropts)); + memset((char *)&sNWTLSOpts, 0, sizeof(struct nwtlsopts)); + + ulFlag = SO_TLS_ENABLE; + rcode = WSAIoctl(socketHnd, SO_TLS_SET_FLAGS, &ulFlag, + sizeof(unsigned long), NULL, 0, NULL, NULL, NULL); + if (rcode) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, APLOGNO(02126) + "Error: %d with WSAIoctl(SO_TLS_SET_FLAGS, SO_TLS_ENABLE)", + WSAGetLastError()); + goto ERR; + } + + + ulFlag = SO_TLS_SERVER; + rcode = WSAIoctl(socketHnd, SO_TLS_SET_FLAGS, &ulFlag, + sizeof(unsigned long),NULL, 0, NULL, NULL, NULL); + + if (rcode) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, APLOGNO(02127) + "Error: %d with WSAIoctl(SO_TLS_SET_FLAGS, SO_TLS_SERVER)", + WSAGetLastError()); + goto ERR; + } + + loc2uni(UNI_LOCAL_DEFAULT, SASKey, key, 0, 0); + + /* setup the tlsserveropts struct */ + sWS2Opts.wallet = SASKey; + sWS2Opts.walletlen = unilen(SASKey); + sWS2Opts.sidtimeout = 0; + sWS2Opts.sidentries = 0; + sWS2Opts.siddir = NULL; + sWS2Opts.options = &sNWTLSOpts; + + /* setup the nwtlsopts structure */ + + sNWTLSOpts.walletProvider = WAL_PROV_KMO; + sNWTLSOpts.keysList = NULL; + sNWTLSOpts.numElementsInKeyList = 0; + sNWTLSOpts.reservedforfutureuse = NULL; + sNWTLSOpts.reservedforfutureCRL = NULL; + sNWTLSOpts.reservedforfutureCRLLen = 0; + sNWTLSOpts.reserved1 = NULL; + sNWTLSOpts.reserved2 = NULL; + sNWTLSOpts.reserved3 = NULL; + + rcode = WSAIoctl(socketHnd, + SO_TLS_SET_SERVER, + &sWS2Opts, + sizeof(struct tlsserveropts), + NULL, + 0, + NULL, + NULL, + NULL); + if (SOCKET_ERROR == rcode) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, APLOGNO(02128) + "Error: %d with WSAIoctl(SO_TLS_SET_SERVER)", WSAGetLastError()); + goto ERR; + } + +ERR: + return rcode; +} + +static const char *set_secure_listener(cmd_parms *cmd, void *dummy, + const char *ips, const char* key, + const char* mutual) +{ + NWSSLSrvConfigRec* sc = get_nwssl_cfg(cmd->server); + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + char *ports, *addr; + unsigned short port; + seclisten_rec *new; + ap_listen_rec **walk; + apr_sockaddr_t *sa; + int found_listener = 0; + + + if (err != NULL) + return err; + + ports = strchr(ips, ':'); + + if (ports != NULL) { + if (ports == ips) + return "Missing IP address"; + else if (ports[1] == '\0') + return "Address must end in :"; + + *(ports++) = '\0'; + } + else { + ports = (char*)ips; + } + + new = apr_pcalloc(cmd->server->process->pool, sizeof(seclisten_rec)); + new->local_addr.sin_family = AF_INET; + + if (ports == ips) { + new->local_addr.sin_addr.s_addr = htonl(INADDR_ANY); + addr = apr_pstrdup(cmd->server->process->pool, "0.0.0.0"); + } + else { + new->local_addr.sin_addr.s_addr = parse_addr(ips, NULL); + addr = apr_pstrdup(cmd->server->process->pool, ips); + } + + port = atoi(ports); + + if (!port) + return "Port must be numeric"; + + /* If the specified addr:port was created previously, put the listen + socket record back on the ap_listeners list so that the socket + will be reused rather than recreated */ + for (walk = &nw_old_listeners; *walk;) { + sa = (*walk)->bind_addr; + if (sa) { + ap_listen_rec *new; + apr_port_t oldport; + + oldport = sa->port; + /* If both ports are equivalent, then if their names are equivalent, + * then we will re-use the existing record. + */ + if (port == oldport && + ((!addr && !sa->hostname) || + ((addr && sa->hostname) && !strcmp(sa->hostname, addr)))) { + new = *walk; + *walk = new->next; + new->next = ap_listeners; + ap_listeners = new; + found_listener = 1; + continue; + } + } + + walk = &(*walk)->next; + } + + apr_table_add(sc->sltable, ports, addr); + + /* If we found a pre-existing listen socket record, then there + is no need to create a new secure listen socket record. */ + if (found_listener) { + return NULL; + } + + new->local_addr.sin_port = htons(port); + new->fd = -1; + new->used = 0; + new->next = ap_seclisteners; + strcpy(new->key, key); + new->mutual = (mutual) ? 1 : 0; + new->addr = addr; + new->port = port; + ap_seclisteners = new; + return NULL; +} + +static const char *set_secure_upgradeable_listener(cmd_parms *cmd, void *dummy, + const char *ips, const char* key) +{ + NWSSLSrvConfigRec* sc = get_nwssl_cfg(cmd->server); + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + char *ports, *addr; + unsigned short port; + seclistenup_rec *new; + + if (err != NULL) + return err; + + ports = strchr(ips, ':'); + + if (ports != NULL) { + if (ports == ips) + return "Missing IP address"; + else if (ports[1] == '\0') + return "Address must end in :"; + + *(ports++) = '\0'; + } + else { + ports = (char*)ips; + } + + if (ports == ips) { + addr = apr_pstrdup(cmd->pool, "0.0.0.0"); + } + else { + addr = apr_pstrdup(cmd->pool, ips); + } + + port = atoi(ports); + + if (!port) + return "Port must be numeric"; + + apr_table_set(sc->slutable, ports, addr); + + new = apr_pcalloc(cmd->pool, sizeof(seclistenup_rec)); + new->next = ap_seclistenersup; + strcpy(new->key, key); + new->addr = addr; + new->port = port; + ap_seclistenersup = new; + + return err; +} + +static apr_status_t nwssl_socket_cleanup(void *data) +{ + ap_listen_rec* slr = (ap_listen_rec*)data; + ap_listen_rec* lr; + + /* Remove our secure listener from the listener list */ + for (lr = ap_listeners; lr; lr = lr->next) { + /* slr is at the head of the list */ + if (lr == slr) { + ap_listeners = slr->next; + break; + } + /* slr is somewhere in between or at the end*/ + if (lr->next == slr) { + lr->next = slr->next; + break; + } + } + return APR_SUCCESS; +} + +static const char *set_trusted_certs(cmd_parms *cmd, void *dummy, char *arg) +{ + char **ptr = (char **)apr_array_push(certlist); + + *ptr = arg; + return NULL; +} + +static int nwssl_pre_config(apr_pool_t *pconf, apr_pool_t *plog, + apr_pool_t *ptemp) +{ + seclisten_rec* ap_old_seclisteners; + ap_listen_rec **walk; + seclisten_rec **secwalk; + apr_sockaddr_t *sa; + int found; + + /* Pull all of the listeners that were created by mod_nw_ssl out of the + ap_listeners list so that the normal listen socket processing does + automatically close them */ + nw_old_listeners = NULL; + ap_old_seclisteners = NULL; + + for (secwalk = &ap_seclisteners; *secwalk;) { + found = 0; + for (walk = &ap_listeners; *walk;) { + sa = (*walk)->bind_addr; + if (sa) { + ap_listen_rec *new; + seclisten_rec *secnew; + apr_port_t oldport; + + oldport = sa->port; + /* If both ports are equivalent, then if their names are equivalent, + * then we will re-use the existing record. + */ + if ((*secwalk)->port == oldport && + ((!(*secwalk)->addr && !sa->hostname) || + (((*secwalk)->addr && sa->hostname) && !strcmp(sa->hostname, (*secwalk)->addr)))) { + /* Move the listen socket from ap_listeners to nw_old_listeners */ + new = *walk; + *walk = new->next; + new->next = nw_old_listeners; + nw_old_listeners = new; + + /* Move the secure socket record to ap_old_seclisterners */ + secnew = *secwalk; + *secwalk = secnew->next; + secnew->next = ap_old_seclisteners; + ap_old_seclisteners = secnew; + found = 1; + break; + } + } + + walk = &(*walk)->next; + } + if (!found && &(*secwalk)->next) { + secwalk = &(*secwalk)->next; + } + } + + /* Restore the secure socket records list so that the post config can + process all of the sockets normally */ + ap_seclisteners = ap_old_seclisteners; + ap_seclistenersup = NULL; + certlist = apr_array_make(pconf, 1, sizeof(char *)); + + /* Now that we have removed all of the mod_nw_ssl created socket records, + allow the normal listen socket handling to occur. + NOTE: If for any reason mod_nw_ssl is removed as a built-in module, + the following call must be put back into the pre-config handler of the + MPM. It is only here to ensure that mod_nw_ssl fixes up the listen + socket list before anything else looks at it. */ + ap_listen_pre_config(); + + return OK; +} + +static int nwssl_pre_connection(conn_rec *c, void *csd) +{ + + if (apr_table_get(c->notes, "nwconv-ssl")) { + convert_secure_socket(c, (apr_socket_t*)csd); + } + else { + secsocket_data *csd_data = apr_palloc(c->pool, sizeof(secsocket_data)); + + csd_data->csd = (apr_socket_t*)csd; + csd_data->is_secure = 0; + ap_set_module_config(c->conn_config, &nwssl_module, (void*)csd_data); + } + + return OK; +} + +static int nwssl_post_config(apr_pool_t *pconf, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s) +{ + seclisten_rec* sl; + ap_listen_rec* lr; + apr_socket_t* sd; + apr_status_t status; + seclistenup_rec *slu; + int found; + ap_listen_rec *walk; + seclisten_rec *secwalk, *lastsecwalk; + apr_sockaddr_t *sa; + + /* Walk the old listeners list and compare it to the secure + listeners list and remove any secure listener records that + are not being reused */ + for (walk = nw_old_listeners; walk; walk = walk->next) { + sa = walk->bind_addr; + if (sa) { + ap_listen_rec *new; + apr_port_t oldport; + + oldport = sa->port; + for (secwalk = ap_seclisteners, lastsecwalk = ap_seclisteners; secwalk; secwalk = lastsecwalk->next) { + unsigned short port = secwalk->port; + char *addr = secwalk->addr; + /* If both ports are equivalent, then if their names are equivalent, + * then we will re-use the existing record. + */ + if (port == oldport && + ((!addr && !sa->hostname) || + ((addr && sa->hostname) && !strcmp(sa->hostname, addr)))) { + if (secwalk == ap_seclisteners) { + ap_seclisteners = secwalk->next; + } + else { + lastsecwalk->next = secwalk->next; + } + apr_socket_close(walk->sd); + walk->active = 0; + break; + } + else { + lastsecwalk = secwalk; + } + } + } + } + + for (sl = ap_seclisteners; sl != NULL; sl = sl->next) { + /* If we find a pre-existing listen socket and it has already been + created, then no need to go any further, just reuse it. */ + if (((sl->fd = find_secure_listener(sl)) >= 0) && (sl->used)) { + continue; + } + + if (sl->fd < 0) + sl->fd = make_secure_socket(s->process->pool, &sl->local_addr, sl->key, sl->mutual, s); + + if (sl->fd >= 0) { + apr_os_sock_info_t sock_info; + + sock_info.os_sock = &(sl->fd); + sock_info.local = (struct sockaddr*)&(sl->local_addr); + sock_info.remote = NULL; + sock_info.family = APR_INET; + sock_info.type = SOCK_STREAM; + + apr_os_sock_make(&sd, &sock_info, s->process->pool); + + lr = apr_pcalloc(s->process->pool, sizeof(ap_listen_rec)); + + if (lr) { + lr->sd = sd; + if ((status = apr_sockaddr_info_get(&lr->bind_addr, sl->addr, APR_UNSPEC, sl->port, 0, + s->process->pool)) != APR_SUCCESS) { + ap_log_perror(APLOG_MARK, APLOG_CRIT, status, pconf, APLOGNO(02129) + "alloc_listener: failed to set up sockaddr for %s:%d", sl->addr, sl->port); + return HTTP_INTERNAL_SERVER_ERROR; + } + lr->next = ap_listeners; + ap_listeners = lr; + apr_pool_cleanup_register(s->process->pool, lr, nwssl_socket_cleanup, apr_pool_cleanup_null); + } + } else { + return HTTP_INTERNAL_SERVER_ERROR; + } + } + + for (slu = ap_seclistenersup; slu; slu = slu->next) { + /* Check the listener list for a matching upgradeable listener */ + found = 0; + for (lr = ap_listeners; lr; lr = lr->next) { + if (slu->port == lr->bind_addr->port) { + found = 1; + break; + } + } + if (!found) { + ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, plog, APLOGNO(02130) + "No Listen directive found for upgradeable listener %s:%d", slu->addr, slu->port); + } + } + + build_cert_list(s->process->pool); + + return OK; +} + +static void *nwssl_config_server_create(apr_pool_t *p, server_rec *s) +{ + NWSSLSrvConfigRec *new = apr_palloc(p, sizeof(NWSSLSrvConfigRec)); + new->sltable = apr_table_make(p, 5); + new->slutable = apr_table_make(p, 5); + return new; +} + +static void *nwssl_config_server_merge(apr_pool_t *p, void *basev, void *addv) +{ + NWSSLSrvConfigRec *base = (NWSSLSrvConfigRec *)basev; + NWSSLSrvConfigRec *add = (NWSSLSrvConfigRec *)addv; + NWSSLSrvConfigRec *merged = (NWSSLSrvConfigRec *)apr_palloc(p, sizeof(NWSSLSrvConfigRec)); + return merged; +} + +static int compare_ipports(void *rec, const char *key, const char *value) +{ + conn_rec *c = (conn_rec*)rec; + + if (value && + ((strcmp(value, "0.0.0.0") == 0) || (strcmp(value, c->local_ip) == 0))) + { + return 0; + } + return 1; +} + +static int isSecureConnEx (const server_rec *s, const conn_rec *c, const apr_table_t *t) +{ + char port[8]; + + itoa((c->local_addr)->port, port, 10); + if (!apr_table_do(compare_ipports, (void*)c, t, port, NULL)) { + return 1; + } + + return 0; +} + +static int isSecureConn (const server_rec *s, const conn_rec *c) +{ + NWSSLSrvConfigRec *sc = get_nwssl_cfg(s); + + return isSecureConnEx (s, c, sc->sltable); +} + +static int isSecureConnUpgradeable (const server_rec *s, const conn_rec *c) +{ + NWSSLSrvConfigRec *sc = get_nwssl_cfg(s); + + return isSecureConnEx (s, c, sc->slutable); +} + +static int isSecure (const request_rec *r) +{ + return isSecureConn (r->server, r->connection); +} + +static int isSecureUpgradeable (const request_rec *r) +{ + return isSecureConnUpgradeable (r->server, r->connection); +} + +static int isSecureUpgraded (const request_rec *r) +{ + secsocket_data *csd_data = (secsocket_data*)ap_get_module_config(r->connection->conn_config, &nwssl_module); + + return csd_data->is_secure; +} + +static int nwssl_hook_Fixup(request_rec *r) +{ + if (!isSecure(r) && !isSecureUpgraded(r)) + return DECLINED; + + apr_table_setn(r->subprocess_env, "HTTPS", "on"); + + return DECLINED; +} + +static const char *nwssl_hook_http_scheme(const request_rec *r) +{ + if (isSecure(r) && !isSecureUpgraded(r)) + return "https"; + + return NULL; +} + +static apr_port_t nwssl_hook_default_port(const request_rec *r) +{ + if (isSecure(r)) + return DEFAULT_HTTPS_PORT; + + return 0; +} + +int ssl_proxy_enable(conn_rec *c) +{ + apr_table_setn(c->notes, "nwconv-ssl", "Y"); + + return 1; +} + +int ssl_engine_disable(conn_rec *c) +{ + return 1; +} + +static int ssl_is_https(conn_rec *c) +{ + secsocket_data *csd_data = (secsocket_data*)ap_get_module_config(c->conn_config, &nwssl_module); + + return isSecureConn (c->base_server, c) || (csd_data && csd_data->is_secure); +} + +/* This function must remain safe to use for a non-SSL connection. */ +char *ssl_var_lookup(apr_pool_t *p, server_rec *s, conn_rec *c, request_rec *r, char *var) +{ + NWSSLSrvConfigRec *mc = get_nwssl_cfg(s); + const char *result; + BOOL resdup; + apr_time_exp_t tm; + + result = NULL; + resdup = TRUE; + + /* + * When no pool is given try to find one + */ + if (p == NULL) { + if (r != NULL) + p = r->pool; + else if (c != NULL) + p = c->pool; + else + p = mc->pPool; + } + + /* + * Request dependent stuff + */ + if (r != NULL) { + switch (var[0]) { + case 'H': + case 'h': + if (strcEQ(var, "HTTP_USER_AGENT")) + result = apr_table_get(r->headers_in, "User-Agent"); + else if (strcEQ(var, "HTTP_REFERER")) + result = apr_table_get(r->headers_in, "Referer"); + else if (strcEQ(var, "HTTP_COOKIE")) + result = apr_table_get(r->headers_in, "Cookie"); + else if (strcEQ(var, "HTTP_FORWARDED")) + result = apr_table_get(r->headers_in, "Forwarded"); + else if (strcEQ(var, "HTTP_HOST")) + result = apr_table_get(r->headers_in, "Host"); + else if (strcEQ(var, "HTTP_PROXY_CONNECTION")) + result = apr_table_get(r->headers_in, "Proxy-Connection"); + else if (strcEQ(var, "HTTP_ACCEPT")) + result = apr_table_get(r->headers_in, "Accept"); + else if (strcEQ(var, "HTTPS")) { + if (isSecure(r) || isSecureUpgraded(r)) + result = "on"; + else + result = "off"; + } + else if (strlen(var) > 5 && strcEQn(var, "HTTP:", 5)) + /* all other headers from which we are still not know about */ + result = apr_table_get(r->headers_in, var+5); + break; + + case 'R': + case 'r': + if (strcEQ(var, "REQUEST_METHOD")) + result = r->method; + else if (strcEQ(var, "REQUEST_SCHEME")) + result = ap_http_scheme(r); + else if (strcEQ(var, "REQUEST_URI")) + result = r->uri; + else if (strcEQ(var, "REQUEST_FILENAME")) + result = r->filename; + else if (strcEQ(var, "REMOTE_ADDR")) + result = r->useragent_ip; + else if (strcEQ(var, "REMOTE_HOST")) + result = ap_get_useragent_host(r, REMOTE_NAME, NULL); + else if (strcEQ(var, "REMOTE_IDENT")) + result = ap_get_remote_logname(r); + else if (strcEQ(var, "REMOTE_USER")) + result = r->user; + break; + + case 'S': + case 's': + if (strcEQn(var, "SSL", 3)) break; /* shortcut common case */ + + if (strcEQ(var, "SERVER_ADMIN")) + result = r->server->server_admin; + else if (strcEQ(var, "SERVER_NAME")) + result = ap_get_server_name_for_url(r); + else if (strcEQ(var, "SERVER_PORT")) + result = apr_psprintf(p, "%u", ap_get_server_port(r)); + else if (strcEQ(var, "SERVER_PROTOCOL")) + result = r->protocol; + else if (strcEQ(var, "SCRIPT_FILENAME")) + result = r->filename; + break; + + default: + if (strcEQ(var, "PATH_INFO")) + result = r->path_info; + else if (strcEQ(var, "QUERY_STRING")) + result = r->args; + else if (strcEQ(var, "IS_SUBREQ")) + result = (r->main != NULL ? "true" : "false"); + else if (strcEQ(var, "DOCUMENT_ROOT")) + result = ap_document_root(r); + else if (strcEQ(var, "AUTH_TYPE")) + result = r->ap_auth_type; + else if (strcEQ(var, "THE_REQUEST")) + result = r->the_request; + break; + } + } + + /* + * Connection stuff + */ + if (result == NULL && c != NULL) { + /* XXX-Can't get specific SSL info from NetWare */ + /* SSLConnRec *sslconn = myConnConfig(c); + if (strlen(var) > 4 && strcEQn(var, "SSL_", 4) + && sslconn && sslconn->ssl) + result = ssl_var_lookup_ssl(p, c, var+4);*/ + + if (strlen(var) > 4 && strcEQn(var, "SSL_", 4)) + result = NULL; + } + + /* + * Totally independent stuff + */ + if (result == NULL) { + if (strlen(var) > 12 && strcEQn(var, "SSL_VERSION_", 12)) + result = NULL; + /* XXX-Can't get specific SSL info from NetWare */ + /*result = ssl_var_lookup_ssl_version(p, var+12);*/ + else if (strcEQ(var, "SERVER_SOFTWARE")) + result = ap_get_server_banner(); + else if (strcEQ(var, "API_VERSION")) { + result = apr_itoa(p, MODULE_MAGIC_NUMBER_MAJOR); + resdup = FALSE; + } + else if (strcEQ(var, "TIME_YEAR")) { + apr_time_exp_lt(&tm, apr_time_now()); + result = apr_psprintf(p, "%02d%02d", + (tm.tm_year / 100) + 19, tm.tm_year % 100); + resdup = FALSE; + } +#define MKTIMESTR(format, tmfield) \ + apr_time_exp_lt(&tm, apr_time_now()); \ + result = apr_psprintf(p, format, tm.tmfield); \ + resdup = FALSE; + else if (strcEQ(var, "TIME_MON")) { + MKTIMESTR("%02d", tm_mon+1) + } + else if (strcEQ(var, "TIME_DAY")) { + MKTIMESTR("%02d", tm_mday) + } + else if (strcEQ(var, "TIME_HOUR")) { + MKTIMESTR("%02d", tm_hour) + } + else if (strcEQ(var, "TIME_MIN")) { + MKTIMESTR("%02d", tm_min) + } + else if (strcEQ(var, "TIME_SEC")) { + MKTIMESTR("%02d", tm_sec) + } + else if (strcEQ(var, "TIME_WDAY")) { + MKTIMESTR("%d", tm_wday) + } + else if (strcEQ(var, "TIME")) { + apr_time_exp_lt(&tm, apr_time_now()); + result = apr_psprintf(p, + "%02d%02d%02d%02d%02d%02d%02d", (tm.tm_year / 100) + 19, + (tm.tm_year % 100), tm.tm_mon+1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec); + resdup = FALSE; + } + /* all other env-variables from the parent Apache process */ + else if (strlen(var) > 4 && strcEQn(var, "ENV:", 4)) { + result = apr_table_get(r->notes, var+4); + if (result == NULL) + result = apr_table_get(r->subprocess_env, var+4); + if (result == NULL) + result = getenv(var+4); + } + } + + if (result != NULL && resdup) + result = apr_pstrdup(p, result); + if (result == NULL) + result = ""; + return (char *)result; +} + +#define SWITCH_STATUS_LINE "HTTP/1.1 101 Switching Protocols" +#define UPGRADE_HEADER "Upgrade: TLS/1.0, HTTP/1.1" +#define CONNECTION_HEADER "Connection: Upgrade" + +static apr_status_t ssl_io_filter_Upgrade(ap_filter_t *f, + apr_bucket_brigade *bb) + +{ + const char *upgrade; + apr_bucket_brigade *upgradebb; + request_rec *r = f->r; + apr_socket_t *csd = NULL; + char *key; + int ret; + secsocket_data *csd_data; + apr_bucket *b; + apr_status_t rv; + + /* Just remove the filter, if it doesn't work the first time, it won't + * work at all for this request. + */ + ap_remove_output_filter(f); + + if (!r) { + /* + ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, APLOGNO(02131) + "Unable to get upgradeable socket handle"); + */ + return ap_pass_brigade(f->next, bb); + } + + /* No need to ensure that this is a server with optional SSL, the filter + * is only inserted if that is true. + */ + + upgrade = apr_table_get(r->headers_in, "Upgrade"); + if (upgrade == NULL + || strcmp(ap_getword(r->pool, &upgrade, ','), "TLS/1.0")) { + /* "Upgrade: TLS/1.0, ..." header not found, don't do Upgrade */ + return ap_pass_brigade(f->next, bb); + } + + apr_table_unset(r->headers_out, "Upgrade"); + + csd_data = (secsocket_data*)ap_get_module_config(r->connection->conn_config, &nwssl_module); + csd = csd_data->csd; + + /* Send the interim 101 response. */ + upgradebb = apr_brigade_create(r->pool, f->c->bucket_alloc); + + ap_fputs(f->next, upgradebb, SWITCH_STATUS_LINE CRLF + UPGRADE_HEADER CRLF CONNECTION_HEADER CRLF CRLF); + + b = apr_bucket_flush_create(f->c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(upgradebb, b); + + rv = ap_pass_brigade(f->next, upgradebb); + if (rv) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02132) + "could not send interim 101 Upgrade response"); + return AP_FILTER_ERROR; + } + + key = get_port_key(r->connection); + + if (csd && key) { + int sockdes; + apr_os_sock_get(&sockdes, csd); + + + ret = SSLize_Socket(sockdes, key, r); + if (!ret) { + csd_data->is_secure = 1; + } + } + else { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, APLOGNO(02133) + "Upgradeable socket handle not found"); + return AP_FILTER_ERROR; + } + + ap_log_error(APLOG_MARK, APLOG_INFO, 0, r->server, APLOGNO(02134) + "Awaiting re-negotiation handshake"); + + /* Now that we have initialized the ssl connection which added the ssl_io_filter, + pass the brigade off to the connection based output filters so that the + request can complete encrypted */ + return ap_pass_brigade(f->c->output_filters, bb); +} + +static void ssl_hook_Insert_Filter(request_rec *r) +{ + NWSSLSrvConfigRec *sc = get_nwssl_cfg(r->server); + + if (isSecureUpgradeable (r)) { + ap_add_output_filter("UPGRADE_FILTER", NULL, r, r->connection); + } +} + +static const command_rec nwssl_module_cmds[] = +{ + AP_INIT_TAKE23("SecureListen", set_secure_listener, NULL, RSRC_CONF, + "specify an address and/or port with a key pair name.\n" + "Optional third parameter of MUTUAL configures the port for mutual authentication."), + AP_INIT_TAKE2("NWSSLUpgradeable", set_secure_upgradeable_listener, NULL, RSRC_CONF, + "specify an address and/or port with a key pair name, that can be upgraded to an SSL connection.\n" + "The address and/or port must have already be defined using a Listen directive."), + AP_INIT_ITERATE("NWSSLTrustedCerts", set_trusted_certs, NULL, RSRC_CONF, + "Adds trusted certificates that are used to create secure connections to proxied servers"), + {NULL} +}; + +static void register_hooks(apr_pool_t *p) +{ + ap_register_output_filter ("UPGRADE_FILTER", ssl_io_filter_Upgrade, NULL, AP_FTYPE_PROTOCOL + 5); + + ap_hook_pre_config(nwssl_pre_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_pre_connection(nwssl_pre_connection, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_post_config(nwssl_post_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_fixups(nwssl_hook_Fixup, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_http_scheme(nwssl_hook_http_scheme, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_default_port(nwssl_hook_default_port, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_insert_filter(ssl_hook_Insert_Filter, NULL, NULL, APR_HOOK_MIDDLE); + + APR_REGISTER_OPTIONAL_FN(ssl_is_https); + APR_REGISTER_OPTIONAL_FN(ssl_var_lookup); + + APR_REGISTER_OPTIONAL_FN(ssl_proxy_enable); + APR_REGISTER_OPTIONAL_FN(ssl_engine_disable); +} + +AP_DECLARE_MODULE(nwssl) = +{ + STANDARD20_MODULE_STUFF, + NULL, /* dir config creater */ + NULL, /* dir merger --- default is to override */ + nwssl_config_server_create, /* server config */ + nwssl_config_server_merge, /* merge server config */ + nwssl_module_cmds, /* command apr_table_t */ + register_hooks +}; + diff --git a/modules/arch/unix/Makefile.in b/modules/arch/unix/Makefile.in new file mode 100644 index 0000000..167b343 --- /dev/null +++ b/modules/arch/unix/Makefile.in @@ -0,0 +1,3 @@ + +include $(top_srcdir)/build/special.mk + diff --git a/modules/arch/unix/config5.m4 b/modules/arch/unix/config5.m4 new file mode 100644 index 0000000..3d099f8 --- /dev/null +++ b/modules/arch/unix/config5.m4 @@ -0,0 +1,33 @@ + +APACHE_MODPATH_INIT(arch/unix) + +if ap_mpm_is_enabled "worker" \ + || ap_mpm_is_enabled "event" \ + || ap_mpm_is_enabled "prefork"; then + unixd_mods_enable=yes +else + unixd_mods_enable=no +fi + +APACHE_MODULE(unixd, unix specific support, , , $unixd_mods_enable) +APACHE_MODULE(privileges, Per-virtualhost Unix UserIDs and enhanced security for Solaris, , , no, [ + AC_CHECK_HEADERS(priv.h, [ap_HAVE_PRIV_H="yes"], [ap_HAVE_PRIV_H="no"]) + if test $ap_HAVE_PRIV_H = "no"; then + AC_MSG_WARN([Your system does not support privileges.]) + enable_privileges="no" + fi +]) + +APACHE_MODULE(systemd, Systemd support, , , no, [ + if test "${ac_cv_header_systemd_sd_daemon_h}" = "no" || test -z "${SYSTEMD_LIBS}"; then + AC_MSG_WARN([Your system does not support systemd.]) + enable_systemd="no" + else + APR_ADDTO(MOD_SYSTEMD_LDADD, [$SYSTEMD_LIBS]) + fi +]) + +APR_ADDTO(INCLUDES, [-I\$(top_srcdir)/$modpath_current]) + +APACHE_MODPATH_FINISH + diff --git a/modules/arch/unix/mod_privileges.c b/modules/arch/unix/mod_privileges.c new file mode 100644 index 0000000..fede3d8 --- /dev/null +++ b/modules/arch/unix/mod_privileges.c @@ -0,0 +1,588 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include "httpd.h" +#include "http_config.h" +#include "http_protocol.h" +#include "http_log.h" +#include "mpm_common.h" +#include "ap_mpm.h" +#include "apr_strings.h" + +/* TODO - get rid of unixd dependency */ +#include "unixd.h" + +#define CFG_CHECK(x) if ((x) == -1) { \ + char msgbuf[128]; \ + apr_strerror(errno, msgbuf, sizeof(msgbuf)); \ + return apr_pstrdup(cmd->pool, msgbuf); \ +} +#define CR_CHECK(x, y) if (x == -1) \ + ap_log_error(APLOG_MARK, APLOG_CRIT, errno, 0, y \ + "Failed to initialise privileges") + +module AP_MODULE_DECLARE_DATA privileges_module; + +/* #define BIG_SECURITY_HOLE 1 */ + +typedef enum { PRIV_UNSET, PRIV_FAST, PRIV_SECURE, PRIV_SELECTIVE } priv_mode; + +typedef struct { + priv_set_t *priv; + priv_set_t *child_priv; + uid_t uid; + gid_t gid; + priv_mode mode; +} priv_cfg; + +typedef struct { + priv_mode mode; +} priv_dir_cfg; + +static priv_set_t *priv_setid; +static priv_set_t *priv_default = NULL; +static int dtrace_enabled = 0; + +static apr_status_t priv_cfg_cleanup(void *CFG) +{ + priv_cfg *cfg = CFG; + priv_freeset(cfg->priv); + priv_freeset(cfg->child_priv); + return APR_SUCCESS; +} +static void *privileges_merge_cfg(apr_pool_t *pool, void *BASE, void *ADD) +{ + /* inherit the mode if it's not set; the rest won't be inherited */ + priv_cfg *base = BASE; + priv_cfg *add = ADD; + priv_cfg *ret = apr_pmemdup(pool, add, sizeof(priv_cfg)); + ret->mode = (add->mode == PRIV_UNSET) ? base->mode : add->mode; + return ret; +} +static void *privileges_create_cfg(apr_pool_t *pool, server_rec *s) +{ + priv_cfg *cfg = apr_palloc(pool, sizeof(priv_cfg)); + + /* Start at basic privileges all round. */ + cfg->priv = priv_str_to_set("basic", ",", NULL); + cfg->child_priv = priv_str_to_set("basic", ",", NULL); + + /* By default, run in secure vhost mode. + * That means dropping basic privileges we don't usually need. + */ + CR_CHECK(priv_delset(cfg->priv, PRIV_FILE_LINK_ANY), APLOGNO(03160)); + CR_CHECK(priv_delset(cfg->priv, PRIV_PROC_INFO), APLOGNO(03161)); + CR_CHECK(priv_delset(cfg->priv, PRIV_PROC_SESSION), APLOGNO(03162)); + +/* Hmmm, should CGI default to secure too ? */ +/* + CR_CHECK(priv_delset(cfg->child_priv, PRIV_FILE_LINK_ANY), APLOGNO(03163)); + CR_CHECK(priv_delset(cfg->child_priv, PRIV_PROC_INFO), APLOGNO(03164)); + CR_CHECK(priv_delset(cfg->child_priv, PRIV_PROC_SESSION), APLOGNO(03165)); + CR_CHECK(priv_delset(cfg->child_priv, PRIV_PROC_FORK), APLOGNO(03166)); + CR_CHECK(priv_delset(cfg->child_priv, PRIV_PROC_EXEC), APLOGNO(03167)); +*/ + + /* we´ll use 0 for unset */ + cfg->uid = 0; + cfg->gid = 0; + cfg->mode = PRIV_UNSET; + apr_pool_cleanup_register(pool, cfg, priv_cfg_cleanup, + apr_pool_cleanup_null); + + /* top-level default_priv wants the top-level cfg */ + if (priv_default == NULL) { + priv_default = cfg->priv; + } + return cfg; +} +static void *privileges_create_dir_cfg(apr_pool_t *pool, char *dummy) +{ + priv_dir_cfg *cfg = apr_palloc(pool, sizeof(priv_dir_cfg)); + cfg->mode = PRIV_UNSET; + return cfg; +} +static void *privileges_merge_dir_cfg(apr_pool_t *pool, void *BASE, void *ADD) +{ + priv_dir_cfg *base = BASE; + priv_dir_cfg *add = ADD; + priv_dir_cfg *ret = apr_palloc(pool, sizeof(priv_dir_cfg)); + ret->mode = (add->mode == PRIV_UNSET) ? base->mode : add->mode; + return ret; +} + +static apr_status_t privileges_end_req(void *data) +{ + request_rec *r = data; + priv_cfg *cfg = ap_get_module_config(r->server->module_config, + &privileges_module); + priv_dir_cfg *dcfg = ap_get_module_config(r->per_dir_config, + &privileges_module); + + /* ugly hack: grab default uid and gid from unixd */ + extern unixd_config_rec ap_unixd_config; + + /* If we forked a child, we dropped privilege to revert, so + * all we can do now is exit + */ + if ((cfg->mode == PRIV_SECURE) || + ((cfg->mode == PRIV_SELECTIVE) && (dcfg->mode == PRIV_SECURE))) { + exit(0); + } + + /* if either user or group are not the default, restore them */ + if (cfg->uid || cfg->gid) { + if (setppriv(PRIV_ON, PRIV_EFFECTIVE, priv_setid) == -1) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02136) + "PRIV_ON failed restoring default user/group"); + } + if (cfg->uid && (setuid(ap_unixd_config.user_id) == -1)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02137) + "Error restoring default userid"); + } + if (cfg->gid && (setgid(ap_unixd_config.group_id) == -1)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02138) + "Error restoring default group"); + } + } + + /* restore default privileges */ + if (setppriv(PRIV_SET, PRIV_EFFECTIVE, priv_default) == -1) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r, APLOGNO(02139) + "Error restoring default privileges"); + } + return APR_SUCCESS; +} +static int privileges_req(request_rec *r) +{ + /* secure mode: fork a process to handle the request */ + apr_proc_t proc; + apr_status_t rv; + int exitcode; + apr_exit_why_e exitwhy; + int fork_req; + priv_cfg *cfg = ap_get_module_config(r->server->module_config, + &privileges_module); + + void *breadcrumb = ap_get_module_config(r->request_config, + &privileges_module); + + if (!breadcrumb) { + /* first call: this is the vhost */ + fork_req = (cfg->mode == PRIV_SECURE); + + /* set breadcrumb */ + ap_set_module_config(r->request_config, &privileges_module, &cfg->mode); + + /* If we have per-dir config, defer doing anything */ + if ((cfg->mode == PRIV_SELECTIVE)) { + /* Defer dropping privileges 'til we have a directory + * context that'll tell us whether to fork. + */ + return DECLINED; + } + } + else { + /* second call is for per-directory. */ + priv_dir_cfg *dcfg; + if ((cfg->mode != PRIV_SELECTIVE)) { + /* Our fate was already determined for the vhost - + * nothing to do per-directory + */ + return DECLINED; + } + dcfg = ap_get_module_config(r->per_dir_config, &privileges_module); + fork_req = (dcfg->mode == PRIV_SECURE); + } + + if (fork_req) { + rv = apr_proc_fork(&proc, r->pool); + switch (rv) { + case APR_INPARENT: + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02140) + "parent waiting for child"); + /* FIXME - does the child need to run synchronously? + * esp. if we enable mod_privileges with threaded MPMs? + * We do need at least to ensure r outlives the child. + */ + rv = apr_proc_wait(&proc, &exitcode, &exitwhy, APR_WAIT); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02141) "parent: child %s", + (rv == APR_CHILD_DONE) ? "done" : "notdone"); + + /* The child has taken responsibility for reading all input + * and sending all output. So we need to bow right out, + * and even abandon "normal" housekeeping. + */ + r->eos_sent = 1; + apr_table_unset(r->headers_in, "Content-Type"); + apr_table_unset(r->headers_in, "Content-Length"); + /* Testing with ab and 100k requests reveals no nasties + * so I infer we're not leaking anything like memory + * or file descriptors. That's nice! + */ + return DONE; + case APR_INCHILD: + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02142) "In child!"); + break; /* now we'll drop privileges in the child */ + default: + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02143) + "Failed to fork secure child process!"); + return HTTP_INTERNAL_SERVER_ERROR; + } + } + + /* OK, now drop privileges. */ + + /* cleanup should happen even if something fails part-way through here */ + apr_pool_cleanup_register(r->pool, r, privileges_end_req, + apr_pool_cleanup_null); + /* set user and group if configured */ + if (cfg->uid || cfg->gid) { + if (setppriv(PRIV_ON, PRIV_EFFECTIVE, priv_setid) == -1) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02144) + "No privilege to set user/group"); + } + /* if we should be able to set these but can't, it could be + * a serious security issue. Bail out rather than risk it! + */ + if (cfg->uid && (setuid(cfg->uid) == -1)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02145) + "Error setting userid"); + return HTTP_INTERNAL_SERVER_ERROR; + } + if (cfg->gid && (setgid(cfg->gid) == -1)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02146) + "Error setting group"); + return HTTP_INTERNAL_SERVER_ERROR; + } + } + /* set vhost's privileges */ + if (setppriv(PRIV_SET, PRIV_EFFECTIVE, cfg->priv) == -1) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r, APLOGNO(02147) + "Error setting effective privileges"); + return HTTP_INTERNAL_SERVER_ERROR; + } + + /* ... including those of any subprocesses */ + if (setppriv(PRIV_SET, PRIV_INHERITABLE, cfg->child_priv) == -1) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r, APLOGNO(02148) + "Error setting inheritable privileges"); + return HTTP_INTERNAL_SERVER_ERROR; + } + if (setppriv(PRIV_SET, PRIV_LIMIT, cfg->child_priv) == -1) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r, APLOGNO(02149) + "Error setting limit privileges"); + return HTTP_INTERNAL_SERVER_ERROR; + } + + /* If we're in a child process, drop down PPERM too */ + if (fork_req) { + if (setppriv(PRIV_SET, PRIV_PERMITTED, cfg->priv) == -1) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r, APLOGNO(02150) + "Error setting permitted privileges"); + return HTTP_INTERNAL_SERVER_ERROR; + } + } + + return OK; +} +#define PDROP_CHECK(x) if (x == -1) { \ + ap_log_error(APLOG_MARK, APLOG_CRIT, errno, s, APLOGNO(02151) \ + "Error dropping privileges"); \ + return !OK; \ + } + +static int privileges_drop_first(apr_pool_t *pool, server_rec *s) +{ + /* We need to set privileges before mod_unixd, + * 'cos otherwise setuid will wipe our privilege to do so + */ + priv_cfg *spcfg; + server_rec *sp; + priv_set_t *ppriv = priv_allocset(); + + /* compute ppriv from the union of all the vhosts plus setid */ + priv_copyset(priv_setid, ppriv); + for (sp = s; sp != NULL; sp=sp->next) { + spcfg = ap_get_module_config(sp->module_config, &privileges_module); + priv_union(spcfg->priv, ppriv); + } + PDROP_CHECK(setppriv(PRIV_SET, PRIV_PERMITTED, ppriv)) + PDROP_CHECK(setppriv(PRIV_SET, PRIV_EFFECTIVE, ppriv)) + priv_freeset(ppriv); + + return OK; +} +static int privileges_drop_last(apr_pool_t *pool, server_rec *s) +{ + /* Our config stuff has set the privileges we need, so now + * we just set them to those of the parent server_rec + * + * This has to happen after mod_unixd, 'cos mod_unixd needs + * privileges we drop here. + */ + priv_cfg *cfg = ap_get_module_config(s->module_config, &privileges_module); + + /* defaults - the default vhost */ + PDROP_CHECK(setppriv(PRIV_SET, PRIV_LIMIT, cfg->child_priv)) + PDROP_CHECK(setppriv(PRIV_SET, PRIV_INHERITABLE, cfg->child_priv)) + PDROP_CHECK(setppriv(PRIV_SET, PRIV_EFFECTIVE, cfg->priv)) + + return OK; +} +static apr_status_t privileges_term(void *rec) +{ + priv_freeset(priv_setid); + return APR_SUCCESS; +} +static int privileges_postconf(apr_pool_t *pconf, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s) +{ + priv_cfg *cfg; + server_rec *sp; + + /* if we have dtrace enabled, merge it into everything */ + if (dtrace_enabled) { + for (sp = s; sp != NULL; sp = sp->next) { + cfg = ap_get_module_config(sp->module_config, &privileges_module); + CR_CHECK(priv_addset(cfg->priv, PRIV_DTRACE_KERNEL), APLOGNO(03168)); + CR_CHECK(priv_addset(cfg->priv, PRIV_DTRACE_PROC), APLOGNO(03169)); + CR_CHECK(priv_addset(cfg->priv, PRIV_DTRACE_USER), APLOGNO(03170)); + CR_CHECK(priv_addset(cfg->child_priv, PRIV_DTRACE_KERNEL), APLOGNO(03171)); + CR_CHECK(priv_addset(cfg->child_priv, PRIV_DTRACE_PROC), APLOGNO(03172)); + CR_CHECK(priv_addset(cfg->child_priv, PRIV_DTRACE_USER), APLOGNO(03173)); + } + CR_CHECK(priv_addset(priv_default, PRIV_DTRACE_KERNEL), APLOGNO(03174)); + CR_CHECK(priv_addset(priv_default, PRIV_DTRACE_PROC), APLOGNO(03175)); + CR_CHECK(priv_addset(priv_default, PRIV_DTRACE_USER), APLOGNO(03176)); + } + + /* set up priv_setid for per-request use */ + priv_setid = priv_allocset(); + apr_pool_cleanup_register(pconf, NULL, privileges_term, + apr_pool_cleanup_null); + priv_emptyset(priv_setid); + if (priv_addset(priv_setid, PRIV_PROC_SETID) == -1) { + ap_log_perror(APLOG_MARK, APLOG_CRIT, errno, ptemp, APLOGNO(02152) + "priv_addset"); + return !OK; + } + return OK; +} +static int privileges_init(apr_pool_t *pconf, apr_pool_t *plog, + apr_pool_t *ptemp) +{ + /* refuse to work if the MPM is threaded */ + int threaded; + int rv = ap_mpm_query(AP_MPMQ_IS_THREADED, &threaded); + if (rv != APR_SUCCESS) { + ap_log_perror(APLOG_MARK, APLOG_NOTICE, rv, ptemp, APLOGNO(02153) + "mod_privileges: unable to determine MPM characteristics." + " Please ensure you are using a non-threaded MPM " + "with this module."); + } + if (threaded) { + ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, ptemp, APLOGNO(02154) + "mod_privileges is not compatible with a threaded MPM."); + return !OK; + } + return OK; +} +static void privileges_hooks(apr_pool_t *pool) +{ + ap_hook_post_read_request(privileges_req, NULL, NULL, + APR_HOOK_REALLY_FIRST); + ap_hook_header_parser(privileges_req, NULL, NULL, APR_HOOK_REALLY_FIRST); + ap_hook_drop_privileges(privileges_drop_first, NULL, NULL, APR_HOOK_FIRST); + ap_hook_drop_privileges(privileges_drop_last, NULL, NULL, APR_HOOK_LAST); + ap_hook_post_config(privileges_postconf, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_pre_config(privileges_init, NULL, NULL, APR_HOOK_FIRST); +} + +static const char *vhost_user(cmd_parms *cmd, void *dir, const char *arg) +{ + priv_cfg *cfg = ap_get_module_config(cmd->server->module_config, + &privileges_module); + cfg->uid = ap_uname2id(arg); + if (cfg->uid == 0) { + return apr_pstrcat(cmd->pool, "Invalid userid for VHostUser: ", + arg, NULL); + } + return NULL; +} +static const char *vhost_group(cmd_parms *cmd, void *dir, const char *arg) +{ + priv_cfg *cfg = ap_get_module_config(cmd->server->module_config, + &privileges_module); + cfg->gid = ap_gname2id(arg); + if (cfg->uid == 0) { + return apr_pstrcat(cmd->pool, "Invalid groupid for VHostGroup: ", + arg, NULL); + } + return NULL; +} +static const char *vhost_secure(cmd_parms *cmd, void *dir, int arg) +{ + priv_cfg *cfg = ap_get_module_config(cmd->server->module_config, + &privileges_module); + if (!arg) { + /* add basic privileges, excluding those covered by cgimode */ + CFG_CHECK(priv_addset(cfg->priv, PRIV_FILE_LINK_ANY)); + CFG_CHECK(priv_addset(cfg->priv, PRIV_PROC_INFO)); + CFG_CHECK(priv_addset(cfg->priv, PRIV_PROC_SESSION)); + } + return NULL; +} +static const char *vhost_cgimode(cmd_parms *cmd, void *dir, const char *arg) +{ + priv_cfg *cfg = ap_get_module_config(cmd->server->module_config, + &privileges_module); + if (!strcasecmp(arg, "on")) { + /* default - nothing to do */ + } + else if (!strcasecmp(arg, "off")) { + /* drop fork+exec privs */ + CFG_CHECK(priv_delset(cfg->priv, PRIV_PROC_FORK)); + CFG_CHECK(priv_delset(cfg->priv, PRIV_PROC_EXEC)); + } + else if (!strcasecmp(arg, "secure")) { + /* deny privileges to CGI procs */ + CFG_CHECK(priv_delset(cfg->child_priv, PRIV_PROC_FORK)); + CFG_CHECK(priv_delset(cfg->child_priv, PRIV_PROC_EXEC)); + CFG_CHECK(priv_delset(cfg->child_priv, PRIV_FILE_LINK_ANY)); + CFG_CHECK(priv_delset(cfg->child_priv, PRIV_PROC_INFO)); + CFG_CHECK(priv_delset(cfg->child_priv, PRIV_PROC_SESSION)); + } + else { + return "VHostCGIMode must be On, Off or Secure"; + } + + return NULL; +} +static const char *dtraceenable(cmd_parms *cmd, void *dir, int arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + dtrace_enabled = arg; + return NULL; +} + +static const char *privs_mode(cmd_parms *cmd, void *dir, const char *arg) +{ + priv_mode mode = PRIV_UNSET; + if (!strcasecmp(arg, "FAST")) { + mode = PRIV_FAST; + } + else if (!strcasecmp(arg, "SECURE")) { + mode = PRIV_SECURE; + } + else if (!strcasecmp(arg, "SELECTIVE")) { + mode = PRIV_SELECTIVE; + } + + if (cmd->path) { + /* In a directory context, set the per_dir_config */ + priv_dir_cfg *cfg = dir; + cfg->mode = mode; + if ((mode == PRIV_UNSET) || (mode == PRIV_SELECTIVE)) { + return "PrivilegesMode in a Directory context must be FAST or SECURE"; + } + } + else { + /* In a global or vhost context, set the server config */ + priv_cfg *cfg = ap_get_module_config(cmd->server->module_config, + &privileges_module); + cfg->mode = mode; + if (mode == PRIV_UNSET) { + return "PrivilegesMode must be FAST, SECURE or SELECTIVE"; + } + } + return NULL; +} + +#ifdef BIG_SECURITY_HOLE +static const char *vhost_privs(cmd_parms *cmd, void *dir, const char *arg) +{ + priv_cfg *cfg = ap_get_module_config(cmd->server->module_config, + &privileges_module); + const char *priv = arg; + + if (*priv == '-') { + CFG_CHECK(priv_delset(cfg->priv, priv+1)); + } + else if (*priv == '+') { + CFG_CHECK(priv_addset(cfg->priv, priv+1)); + } + else { + priv_emptyset(cfg->priv); + CFG_CHECK(priv_addset(cfg->priv, priv)); + } + return NULL; +} +static const char *vhost_cgiprivs(cmd_parms *cmd, void *dir, const char *arg) +{ + priv_cfg *cfg = ap_get_module_config(cmd->server->module_config, + &privileges_module); + const char *priv = arg; + if (*priv == '-') { + CFG_CHECK(priv_delset(cfg->child_priv, priv+1)); + } + else if (*priv == '+') { + CFG_CHECK(priv_addset(cfg->child_priv, priv+1)); + } + else { + priv_emptyset(cfg->child_priv); + CFG_CHECK(priv_addset(cfg->child_priv, priv)); + } + return NULL; +} +#endif +static const command_rec privileges_cmds[] = { + AP_INIT_TAKE1("VHostUser", vhost_user, NULL, RSRC_CONF, + "Userid under which the virtualhost will run"), + AP_INIT_TAKE1("VHostGroup", vhost_group, NULL, RSRC_CONF, + "Group under which the virtualhost will run"), + AP_INIT_FLAG("VHostSecure", vhost_secure, NULL, RSRC_CONF, + "Run in enhanced security mode (default ON)"), + AP_INIT_TAKE1("VHostCGIMode", vhost_cgimode, NULL, RSRC_CONF, + "Enable fork+exec for this virtualhost (Off|Secure|On)"), + AP_INIT_FLAG("DTracePrivileges", dtraceenable, NULL, RSRC_CONF, + "Enable DTrace"), + AP_INIT_TAKE1("PrivilegesMode", privs_mode, NULL, RSRC_CONF|ACCESS_CONF, + "tradeoff performance vs security (fast or secure)"), +#ifdef BIG_SECURITY_HOLE + AP_INIT_ITERATE("VHostPrivs", vhost_privs, NULL, RSRC_CONF, + "Privileges available in the (virtual) server"), + AP_INIT_ITERATE("VHostCGIPrivs", vhost_cgiprivs, NULL, RSRC_CONF, + "Privileges available to external programs"), +#endif + {NULL} +}; +AP_DECLARE_MODULE(privileges) = { + STANDARD20_MODULE_STUFF, + privileges_create_dir_cfg, + privileges_merge_dir_cfg, + privileges_create_cfg, + privileges_merge_cfg, + privileges_cmds, + privileges_hooks +}; diff --git a/modules/arch/unix/mod_systemd.c b/modules/arch/unix/mod_systemd.c new file mode 100644 index 0000000..c3e7082 --- /dev/null +++ b/modules/arch/unix/mod_systemd.c @@ -0,0 +1,119 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include "ap_mpm.h" +#include +#include +#include +#include +#include +#include +#include "unixd.h" +#include "scoreboard.h" +#include "mpm_common.h" + +#include "systemd/sd-daemon.h" + +#if APR_HAVE_UNISTD_H +#include +#endif + +static int systemd_pre_config(apr_pool_t *pconf, apr_pool_t *plog, + apr_pool_t *ptemp) +{ + sd_notify(0, + "RELOADING=1\n" + "STATUS=Reading configuration...\n"); + ap_extended_status = 1; + return OK; +} + +/* Report the service is ready in post_config, which could be during + * startup or after a reload. The server could still hit a fatal + * startup error after this point during ap_run_mpm(), so this is + * perhaps too early, but by post_config listen() has been called on + * the TCP ports so new connections will not be rejected. There will + * always be a possible async failure event simultaneous to the + * service reporting "ready", so this should be good enough. */ +static int systemd_post_config(apr_pool_t *p, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *main_server) +{ + sd_notify(0, "READY=1\n" + "STATUS=Configuration loaded.\n"); + return OK; +} + +static int systemd_pre_mpm(apr_pool_t *p, ap_scoreboard_e sb_type) +{ + sd_notifyf(0, "READY=1\n" + "STATUS=Processing requests...\n" + "MAINPID=%" APR_PID_T_FMT, getpid()); + + return OK; +} + +static int systemd_monitor(apr_pool_t *p, server_rec *s) +{ + ap_sload_t sload; + apr_interval_time_t up_time; + char bps[5]; + + if (!ap_extended_status) { + /* Nothing useful to report with ExtendedStatus disabled. */ + return DECLINED; + } + + ap_get_sload(&sload); + /* up_time in seconds */ + up_time = (apr_uint32_t) apr_time_sec(apr_time_now() - + ap_scoreboard_image->global->restart_time); + + apr_strfsize((unsigned long)((float) (sload.bytes_served) + / (float) up_time), bps); + + sd_notifyf(0, "READY=1\n" + "STATUS=Total requests: %lu; Idle/Busy workers %d/%d;" + "Requests/sec: %.3g; Bytes served/sec: %sB/sec\n", + sload.access_count, sload.idle, sload.busy, + ((float) sload.access_count) / (float) up_time, bps); + + return DECLINED; +} + +static void systemd_register_hooks(apr_pool_t *p) +{ + /* Enable ap_extended_status. */ + ap_hook_pre_config(systemd_pre_config, NULL, NULL, APR_HOOK_LAST); + /* Signal service is ready. */ + ap_hook_post_config(systemd_post_config, NULL, NULL, APR_HOOK_REALLY_LAST); + /* We know the PID in this hook ... */ + ap_hook_pre_mpm(systemd_pre_mpm, NULL, NULL, APR_HOOK_LAST); + /* Used to update httpd's status line using sd_notifyf */ + ap_hook_monitor(systemd_monitor, NULL, NULL, APR_HOOK_MIDDLE); +} + +AP_DECLARE_MODULE(systemd) = { + STANDARD20_MODULE_STUFF, + NULL, + NULL, + NULL, + NULL, + NULL, + systemd_register_hooks, +}; diff --git a/modules/arch/unix/mod_unixd.c b/modules/arch/unix/mod_unixd.c new file mode 100644 index 0000000..1baa278 --- /dev/null +++ b/modules/arch/unix/mod_unixd.c @@ -0,0 +1,433 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ap_config.h" +#include "httpd.h" +#include "http_config.h" +#include "http_main.h" +#include "http_log.h" +#include "http_core.h" +#include "mpm_common.h" +#include "os.h" +#include "ap_mpm.h" +#include "mod_unixd.h" +#include "apr_thread_proc.h" +#include "apr_strings.h" +#include "apr_portable.h" +#ifdef HAVE_PWD_H +#include +#endif +#ifdef HAVE_SYS_RESOURCE_H +#include +#endif +/* XXX */ +#include +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_GRP_H +#include +#endif +#ifdef HAVE_STRINGS_H +#include +#endif +#ifdef HAVE_SYS_SEM_H +#include +#endif +#ifdef HAVE_SYS_PRCTL_H +#include +#endif + +#ifndef DEFAULT_USER +#define DEFAULT_USER "#-1" +#endif +#ifndef DEFAULT_GROUP +#define DEFAULT_GROUP "#-1" +#endif + +#if 0 +typedef struct { + const char *user_name; + uid_t user_id; + gid_t group_id; + const char *chroot_dir; +} unixd_config_t; +#else +/* + * TODO: clean up the separation between this code + * and its data structures and unixd.c, as shown + * by the fact that we include unixd.h. Create + * mod_unixd.h which does what we need and + * clean up unixd.h for what it no longer needs + */ +#include "unixd.h" +#endif + + +/* Set group privileges. + * + * Note that we use the username as set in the config files, rather than + * the lookup of to uid --- the same uid may have multiple passwd entries, + * with different sets of groups for each. + */ + +static int set_group_privs(void) +{ + if (!geteuid()) { + const char *name; + + /* Get username if passed as a uid */ + + if (ap_unixd_config.user_name[0] == '#') { + struct passwd *ent; + uid_t uid = atol(&ap_unixd_config.user_name[1]); + + if ((ent = getpwuid(uid)) == NULL) { + ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02155) + "getpwuid: couldn't determine user name from uid %ld, " + "you probably need to modify the User directive", + (long)uid); + return -1; + } + + name = ent->pw_name; + } + else + name = ap_unixd_config.user_name; + +#if !defined(OS2) + /* OS/2 doesn't support groups. */ + /* + * Set the GID before initgroups(), since on some platforms + * setgid() is known to zap the group list. + */ + if (setgid(ap_unixd_config.group_id) == -1) { + ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02156) + "setgid: unable to set group id to Group %ld", + (long)ap_unixd_config.group_id); + return -1; + } + + /* Reset `groups' attributes. */ + + if (initgroups(name, ap_unixd_config.group_id) == -1) { + ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02157) + "initgroups: unable to set groups for User %s " + "and Group %ld", name, (long)ap_unixd_config.group_id); + return -1; + } +#endif /* !defined(OS2) */ + } + return 0; +} + + +static int +unixd_drop_privileges(apr_pool_t *pool, server_rec *s) +{ + int rv = set_group_privs(); + + if (rv) { + return rv; + } + + if (NULL != ap_unixd_config.chroot_dir) { + if (geteuid()) { + rv = errno; + ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02158) + "Cannot chroot when not started as root"); + return rv; + } + + if (chdir(ap_unixd_config.chroot_dir) != 0) { + rv = errno; + ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02159) + "Can't chdir to %s", ap_unixd_config.chroot_dir); + return rv; + } + + if (chroot(ap_unixd_config.chroot_dir) != 0) { + rv = errno; + ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02160) + "Can't chroot to %s", ap_unixd_config.chroot_dir); + return rv; + } + + if (chdir("/") != 0) { + rv = errno; + ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02161) + "Can't chdir to new root"); + return rv; + } + } + + /* Only try to switch if we're running as root */ + if (!geteuid() && ( +#ifdef _OSD_POSIX + os_init_job_environment(NULL, ap_unixd_config.user_name, ap_exists_config_define("DEBUG")) != 0 || +#endif + setuid(ap_unixd_config.user_id) == -1)) { + rv = errno; + ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02162) + "setuid: unable to change to uid: %ld", + (long) ap_unixd_config.user_id); + return rv; + } +#if defined(HAVE_PRCTL) && defined(PR_SET_DUMPABLE) + /* this applies to Linux 2.4+ */ + if (ap_coredumpdir_configured) { + if (prctl(PR_SET_DUMPABLE, 1)) { + rv = errno; + ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02163) + "set dumpable failed - this child will not coredump" + " after software errors"); + return rv; + } + } +#endif + + return OK; +} + + +static const char * +unixd_set_user(cmd_parms *cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + ap_unixd_config.user_name = arg; + ap_unixd_config.user_id = ap_uname2id(arg); +#if !defined (BIG_SECURITY_HOLE) && !defined (OS2) + if (ap_unixd_config.user_id == 0) { + return "Error:\tApache has not been designed to serve pages while\n" + "\trunning as root. There are known race conditions that\n" + "\twill allow any local user to read any file on the system.\n" + "\tIf you still desire to serve pages as root then\n" + "\tadd -DBIG_SECURITY_HOLE to the CFLAGS env variable\n" + "\tand then rebuild the server.\n" + "\tIt is strongly suggested that you instead modify the User\n" + "\tdirective in your httpd.conf file to list a non-root\n" + "\tuser.\n"; + } +#endif + + return NULL; +} + +static const char* +unixd_set_group(cmd_parms *cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + ap_unixd_config.group_name = arg; + ap_unixd_config.group_id = ap_gname2id(arg); + + return NULL; +} + +static const char* +unixd_set_chroot_dir(cmd_parms *cmd, void *dummy, + const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + if (!ap_is_directory(cmd->pool, arg)) { + return "ChrootDir must be a valid directory"; + } + + ap_unixd_config.chroot_dir = arg; + return NULL; +} + +static const char * +unixd_set_suexec(cmd_parms *cmd, void *dummy, int arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + + if (err != NULL) { + return err; + } + + if (!ap_unixd_config.suexec_enabled && arg) { + return apr_pstrcat(cmd->pool, "suEXEC isn't supported: ", + ap_unixd_config.suexec_disabled_reason, NULL); + } + + if (!arg) { + ap_unixd_config.suexec_disabled_reason = "Suexec directive is Off"; + } + + ap_unixd_config.suexec_enabled = arg; + return NULL; +} + +#ifdef AP_SUEXEC_CAPABILITIES +/* If suexec is using capabilities, don't test for the setuid bit. */ +#define SETUID_TEST(finfo) (1) +#else +#define SETUID_TEST(finfo) (finfo.protection & APR_USETID) +#endif + +static int +unixd_pre_config(apr_pool_t *pconf, apr_pool_t *plog, + apr_pool_t *ptemp) +{ + apr_finfo_t wrapper; + ap_unixd_config.user_name = DEFAULT_USER; + ap_unixd_config.user_id = ap_uname2id(DEFAULT_USER); + ap_unixd_config.group_name = DEFAULT_GROUP; + ap_unixd_config.group_id = ap_gname2id(DEFAULT_GROUP); + + ap_unixd_config.chroot_dir = NULL; /* none */ + + /* Check for suexec */ + ap_unixd_config.suexec_enabled = 0; + if ((apr_stat(&wrapper, SUEXEC_BIN, APR_FINFO_NORM, ptemp)) + == APR_SUCCESS) { + if (SETUID_TEST(wrapper) && wrapper.user == 0 + && (access(SUEXEC_BIN, R_OK|X_OK) == 0)) { + ap_unixd_config.suexec_enabled = 1; + ap_unixd_config.suexec_disabled_reason = ""; + } + else { + ap_unixd_config.suexec_disabled_reason = + "Invalid owner or file mode for " SUEXEC_BIN; + } + } + else { + ap_unixd_config.suexec_disabled_reason = + "Missing suexec binary " SUEXEC_BIN; + } + + ap_sys_privileges_handlers(1); + return OK; +} + +AP_DECLARE(int) ap_unixd_setup_child(void) +{ + if (set_group_privs()) { + return -1; + } + + if (NULL != ap_unixd_config.chroot_dir) { + if (geteuid()) { + ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02164) + "Cannot chroot when not started as root"); + return -1; + } + if (chdir(ap_unixd_config.chroot_dir) != 0) { + ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02165) + "Can't chdir to %s", ap_unixd_config.chroot_dir); + return -1; + } + if (chroot(ap_unixd_config.chroot_dir) != 0) { + ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02166) + "Can't chroot to %s", ap_unixd_config.chroot_dir); + return -1; + } + if (chdir("/") != 0) { + ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02167) + "Can't chdir to new root"); + return -1; + } + } + + /* Only try to switch if we're running as root */ + if (!geteuid() && ( +#ifdef _OSD_POSIX + os_init_job_environment(NULL, ap_unixd_config.user_name, ap_exists_config_define("DEBUG")) != 0 || +#endif + setuid(ap_unixd_config.user_id) == -1)) { + ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02168) + "setuid: unable to change to uid: %ld", + (long) ap_unixd_config.user_id); + return -1; + } +#if defined(HAVE_PRCTL) && defined(PR_SET_DUMPABLE) + /* this applies to Linux 2.4+ */ + if (ap_coredumpdir_configured) { + if (prctl(PR_SET_DUMPABLE, 1)) { + ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02169) + "set dumpable failed - this child will not coredump" + " after software errors"); + } + } +#endif + return 0; +} + +static void unixd_dump_config(apr_pool_t *p, server_rec *s) +{ + apr_file_t *out = NULL; + apr_uid_t uid = ap_unixd_config.user_id; + apr_gid_t gid = ap_unixd_config.group_id; + char *no_root = ""; + if (!ap_exists_config_define("DUMP_RUN_CFG")) + return; + if (geteuid() != 0) + no_root = " not_used"; + apr_file_open_stdout(&out, p); + apr_file_printf(out, "User: name=\"%s\" id=%lu%s\n", + ap_unixd_config.user_name, (unsigned long)uid, no_root); + apr_file_printf(out, "Group: name=\"%s\" id=%lu%s\n", + ap_unixd_config.group_name, (unsigned long)gid, no_root); + if (ap_unixd_config.chroot_dir) + apr_file_printf(out, "ChrootDir: \"%s\"%s\n", + ap_unixd_config.chroot_dir, no_root); +} + +static void unixd_hooks(apr_pool_t *pool) +{ + ap_hook_pre_config(unixd_pre_config, + NULL, NULL, APR_HOOK_FIRST); + ap_hook_test_config(unixd_dump_config, + NULL, NULL, APR_HOOK_FIRST); + ap_hook_drop_privileges(unixd_drop_privileges, + NULL, NULL, APR_HOOK_MIDDLE); +} + +static const command_rec unixd_cmds[] = { + AP_INIT_TAKE1("User", unixd_set_user, NULL, RSRC_CONF, + "Effective user id for this server"), + AP_INIT_TAKE1("Group", unixd_set_group, NULL, RSRC_CONF, + "Effective group id for this server"), + AP_INIT_TAKE1("ChrootDir", unixd_set_chroot_dir, NULL, RSRC_CONF, + "The directory to chroot(2) into"), + AP_INIT_FLAG("Suexec", unixd_set_suexec, NULL, RSRC_CONF, + "Enable or disable suEXEC support"), + {NULL} +}; + +AP_DECLARE_MODULE(unixd) = { + STANDARD20_MODULE_STUFF, + NULL, + NULL, + NULL, + NULL, + unixd_cmds, + unixd_hooks +}; + diff --git a/modules/arch/unix/mod_unixd.h b/modules/arch/unix/mod_unixd.h new file mode 100644 index 0000000..a7f439e --- /dev/null +++ b/modules/arch/unix/mod_unixd.h @@ -0,0 +1,41 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file mod_unixd.h + * @brief common stuff that unix MPMs will want + * + * @addtogroup APACHE_OS_UNIX + * @{ + */ + +#ifndef MOD_UNIXD_H +#define MOD_UNIXD_H + +#include "ap_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +AP_DECLARE(int) ap_unixd_setup_child(void); + +#ifdef __cplusplus +} +#endif + +#endif +/** @} */ diff --git a/modules/arch/win32/Makefile.in b/modules/arch/win32/Makefile.in new file mode 100644 index 0000000..7c5c149 --- /dev/null +++ b/modules/arch/win32/Makefile.in @@ -0,0 +1,3 @@ +# a modules Makefile has no explicit targets -- they will be defined by +# whatever modules are enabled. just grab special.mk to deal with this. +include $(top_srcdir)/build/special.mk diff --git a/modules/arch/win32/config.m4 b/modules/arch/win32/config.m4 new file mode 100644 index 0000000..584e76d --- /dev/null +++ b/modules/arch/win32/config.m4 @@ -0,0 +1,9 @@ +dnl modules enabled in this directory by default + +dnl APACHE_MODULE(name, helptext[, objects[, structname[, default[, config]]]]) + +APACHE_MODPATH_INIT(arch/win32) + +APACHE_MODULE(isapi, isapi extension support, , , no) + +APACHE_MODPATH_FINISH diff --git a/modules/arch/win32/mod_isapi.c b/modules/arch/win32/mod_isapi.c new file mode 100644 index 0000000..a9816e5 --- /dev/null +++ b/modules/arch/win32/mod_isapi.c @@ -0,0 +1,1728 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * mod_isapi.c - Internet Server Application (ISA) module for Apache + * by Alexei Kosut , significant overhauls and + * redesign by William Rowe , and hints from many + * other developer/users who have hit on specific flaws. + * + * This module implements the ISAPI Handler architecture, allowing + * Apache to load Internet Server Applications (ISAPI extensions), + * similar to the support in IIS, Zope, O'Reilly's WebSite and others. + * + * It is a complete implementation of the ISAPI 2.0 specification, + * except for "Microsoft extensions" to the API which provide + * asynchronous I/O. It is further extended to include additional + * "Microsoft extensions" through IIS 5.0, with some deficiencies + * where one-to-one mappings don't exist. + * + * Refer to /manual/mod/mod_isapi.html for additional details on + * configuration and use, but check this source for specific support + * of the API, + */ + +#include "ap_config.h" +#include "httpd.h" +#include "http_config.h" +#include "http_core.h" +#include "http_protocol.h" +#include "http_request.h" +#include "http_log.h" +#include "util_script.h" +#include "mod_core.h" +#include "apr_lib.h" +#include "apr_strings.h" +#include "apr_portable.h" +#include "apr_buckets.h" +#include "apr_thread_mutex.h" +#include "apr_thread_rwlock.h" +#include "apr_hash.h" +#include "mod_isapi.h" + +/* Retry frequency for a failed-to-load isapi .dll */ +#define ISAPI_RETRY apr_time_from_sec(30) + +/********************************************************** + * + * ISAPI Module Configuration + * + **********************************************************/ + +module AP_MODULE_DECLARE_DATA isapi_module; + +#define ISAPI_UNDEF -1 + +/* Our isapi per-dir config structure */ +typedef struct isapi_dir_conf { + int read_ahead_buflen; + int log_unsupported; + int log_to_errlog; + int log_to_query; + int fake_async; +} isapi_dir_conf; + +typedef struct isapi_loaded isapi_loaded; + +apr_status_t isapi_lookup(apr_pool_t *p, server_rec *s, request_rec *r, + const char *fpath, isapi_loaded** isa); + +static void *create_isapi_dir_config(apr_pool_t *p, char *dummy) +{ + isapi_dir_conf *dir = apr_palloc(p, sizeof(isapi_dir_conf)); + + dir->read_ahead_buflen = ISAPI_UNDEF; + dir->log_unsupported = ISAPI_UNDEF; + dir->log_to_errlog = ISAPI_UNDEF; + dir->log_to_query = ISAPI_UNDEF; + dir->fake_async = ISAPI_UNDEF; + + return dir; +} + +static void *merge_isapi_dir_configs(apr_pool_t *p, void *base_, void *add_) +{ + isapi_dir_conf *base = (isapi_dir_conf *) base_; + isapi_dir_conf *add = (isapi_dir_conf *) add_; + isapi_dir_conf *dir = apr_palloc(p, sizeof(isapi_dir_conf)); + + dir->read_ahead_buflen = (add->read_ahead_buflen == ISAPI_UNDEF) + ? base->read_ahead_buflen + : add->read_ahead_buflen; + dir->log_unsupported = (add->log_unsupported == ISAPI_UNDEF) + ? base->log_unsupported + : add->log_unsupported; + dir->log_to_errlog = (add->log_to_errlog == ISAPI_UNDEF) + ? base->log_to_errlog + : add->log_to_errlog; + dir->log_to_query = (add->log_to_query == ISAPI_UNDEF) + ? base->log_to_query + : add->log_to_query; + dir->fake_async = (add->fake_async == ISAPI_UNDEF) + ? base->fake_async + : add->fake_async; + + return dir; +} + +static const char *isapi_cmd_cachefile(cmd_parms *cmd, void *dummy, + const char *filename) +{ + isapi_loaded *isa; + apr_finfo_t tmp; + apr_status_t rv; + char *fspec; + + /* ### Just an observation ... it would be terribly cool to be + * able to use this per-dir, relative to the directory block being + * defined. The hash result remains global, but shorthand of + * + * ISAPICacheFile myapp.dll anotherapp.dll thirdapp.dll + * + * would be very convienent. + */ + fspec = ap_server_root_relative(cmd->pool, filename); + if (!fspec) { + ap_log_error(APLOG_MARK, APLOG_WARNING, APR_EBADPATH, cmd->server, APLOGNO(02103) + "invalid module path, skipping %s", filename); + return NULL; + } + if ((rv = apr_stat(&tmp, fspec, APR_FINFO_TYPE, + cmd->temp_pool)) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_WARNING, rv, cmd->server, APLOGNO(02104) + "unable to stat, skipping %s", fspec); + return NULL; + } + if (tmp.filetype != APR_REG) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(02105) + "not a regular file, skipping %s", fspec); + return NULL; + } + + /* Load the extension as cached (with null request_rec) */ + rv = isapi_lookup(cmd->pool, cmd->server, NULL, fspec, &isa); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_WARNING, rv, cmd->server, APLOGNO(02106) + "unable to cache, skipping %s", fspec); + return NULL; + } + + return NULL; +} + +static const command_rec isapi_cmds[] = { + AP_INIT_TAKE1("ISAPIReadAheadBuffer", ap_set_int_slot, + (void *)APR_OFFSETOF(isapi_dir_conf, read_ahead_buflen), + OR_FILEINFO, "Maximum client request body to initially pass to the" + " ISAPI handler (default: 49152)"), + AP_INIT_FLAG("ISAPILogNotSupported", ap_set_flag_slot, + (void *)APR_OFFSETOF(isapi_dir_conf, log_unsupported), + OR_FILEINFO, "Log requests not supported by the ISAPI server" + " on or off (default: off)"), + AP_INIT_FLAG("ISAPIAppendLogToErrors", ap_set_flag_slot, + (void *)APR_OFFSETOF(isapi_dir_conf, log_to_errlog), + OR_FILEINFO, "Send all Append Log requests to the error log" + " on or off (default: off)"), + AP_INIT_FLAG("ISAPIAppendLogToQuery", ap_set_flag_slot, + (void *)APR_OFFSETOF(isapi_dir_conf, log_to_query), + OR_FILEINFO, "Append Log requests are concatenated to the query args" + " on or off (default: on)"), + AP_INIT_FLAG("ISAPIFakeAsync", ap_set_flag_slot, + (void *)APR_OFFSETOF(isapi_dir_conf, fake_async), + OR_FILEINFO, "Fake Asynchronous support for isapi callbacks" + " on or off [Experimental] (default: off)"), + AP_INIT_ITERATE("ISAPICacheFile", isapi_cmd_cachefile, NULL, + RSRC_CONF, "Cache the specified ISAPI extension in-process"), + {NULL} +}; + +/********************************************************** + * + * ISAPI Module Cache handling section + * + **********************************************************/ + +/* Our isapi global config values */ +static struct isapi_global_conf { + apr_pool_t *pool; + apr_thread_mutex_t *lock; + apr_hash_t *hash; +} loaded; + +/* Our loaded isapi module description structure */ +struct isapi_loaded { + const char *filename; + apr_thread_rwlock_t *in_progress; + apr_status_t last_load_rv; + apr_time_t last_load_time; + apr_dso_handle_t *handle; + HSE_VERSION_INFO *isapi_version; + apr_uint32_t report_version; + apr_uint32_t timeout; + PFN_GETEXTENSIONVERSION GetExtensionVersion; + PFN_HTTPEXTENSIONPROC HttpExtensionProc; + PFN_TERMINATEEXTENSION TerminateExtension; +}; + +static apr_status_t isapi_unload(isapi_loaded *isa, int force) +{ + /* All done with the DLL... get rid of it... + * + * If optionally cached, and we weren't asked to force the unload, + * pass HSE_TERM_ADVISORY_UNLOAD, and if it returns 1, unload, + * otherwise, leave it alone (it didn't choose to cooperate.) + */ + if (!isa->handle) { + return APR_SUCCESS; + } + if (isa->TerminateExtension) { + if (force) { + (*isa->TerminateExtension)(HSE_TERM_MUST_UNLOAD); + } + else if (!(*isa->TerminateExtension)(HSE_TERM_ADVISORY_UNLOAD)) { + return APR_EGENERAL; + } + } + apr_dso_unload(isa->handle); + isa->handle = NULL; + return APR_SUCCESS; +} + +static apr_status_t cleanup_isapi(void *isa_) +{ + isapi_loaded* isa = (isapi_loaded*) isa_; + + /* We must force the module to unload, we are about + * to lose the isapi structure's allocation entirely. + */ + return isapi_unload(isa, 1); +} + +static apr_status_t isapi_load(apr_pool_t *p, server_rec *s, isapi_loaded *isa) +{ + apr_status_t rv; + + isa->isapi_version = apr_pcalloc(p, sizeof(HSE_VERSION_INFO)); + + /* TODO: These aught to become overridable, so that we + * assure a given isapi can be fooled into behaving well. + * + * The tricky bit, they aren't really a per-dir sort of + * config, they will always be constant across every + * reference to the .dll no matter what context (vhost, + * location, etc) they apply to. + */ + isa->report_version = 0x500; /* Revision 5.0 */ + isa->timeout = 300 * 1000000; /* microsecs, not used */ + + rv = apr_dso_load(&isa->handle, isa->filename, p); + if (rv) + { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(02107) + "failed to load %s", isa->filename); + isa->handle = NULL; + return rv; + } + + rv = apr_dso_sym((void**)&isa->GetExtensionVersion, isa->handle, + "GetExtensionVersion"); + if (rv) + { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(02108) + "missing GetExtensionVersion() in %s", + isa->filename); + apr_dso_unload(isa->handle); + isa->handle = NULL; + return rv; + } + + rv = apr_dso_sym((void**)&isa->HttpExtensionProc, isa->handle, + "HttpExtensionProc"); + if (rv) + { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(02109) + "missing HttpExtensionProc() in %s", + isa->filename); + apr_dso_unload(isa->handle); + isa->handle = NULL; + return rv; + } + + /* TerminateExtension() is an optional interface */ + rv = apr_dso_sym((void**)&isa->TerminateExtension, isa->handle, + "TerminateExtension"); + apr_set_os_error(0); + + /* Run GetExtensionVersion() */ + if (!(isa->GetExtensionVersion)(isa->isapi_version)) { + apr_status_t rv = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(02110) + "failed call to GetExtensionVersion() in %s", + isa->filename); + apr_dso_unload(isa->handle); + isa->handle = NULL; + return rv; + } + + apr_pool_cleanup_register(p, isa, cleanup_isapi, + apr_pool_cleanup_null); + + return APR_SUCCESS; +} + +apr_status_t isapi_lookup(apr_pool_t *p, server_rec *s, request_rec *r, + const char *fpath, isapi_loaded** isa) +{ + apr_status_t rv; + const char *key; + + if ((rv = apr_thread_mutex_lock(loaded.lock)) != APR_SUCCESS) { + return rv; + } + + *isa = apr_hash_get(loaded.hash, fpath, APR_HASH_KEY_STRING); + + if (*isa) { + + /* If we find this lock exists, use a set-aside copy of gainlock + * to avoid race conditions on NULLing the in_progress variable + * when the load has completed. Release the global isapi hash + * lock so other requests can proceed, then rdlock for completion + * of loading our desired dll or wrlock if we would like to retry + * loading the dll (because last_load_rv failed and retry is up.) + */ + apr_thread_rwlock_t *gainlock = (*isa)->in_progress; + + /* gainlock is NULLed after the module loads successfully. + * This free-threaded module can be used without any locking. + */ + if (!gainlock) { + rv = (*isa)->last_load_rv; + apr_thread_mutex_unlock(loaded.lock); + return rv; + } + + + if ((*isa)->last_load_rv == APR_SUCCESS) { + apr_thread_mutex_unlock(loaded.lock); + if ((rv = apr_thread_rwlock_rdlock(gainlock)) + != APR_SUCCESS) { + return rv; + } + rv = (*isa)->last_load_rv; + apr_thread_rwlock_unlock(gainlock); + return rv; + } + + if (apr_time_now() > (*isa)->last_load_time + ISAPI_RETRY) { + + /* Remember last_load_time before releasing the global + * hash lock to avoid colliding with another thread + * that hit this exception at the same time as our + * retry attempt, since we unlock the global mutex + * before attempting a write lock for this module. + */ + apr_time_t check_time = (*isa)->last_load_time; + apr_thread_mutex_unlock(loaded.lock); + + if ((rv = apr_thread_rwlock_wrlock(gainlock)) + != APR_SUCCESS) { + return rv; + } + + /* If last_load_time is unchanged, we still own this + * retry, otherwise presume another thread provided + * our retry (for good or ill). Relock the global + * hash for updating last_load_ vars, so their update + * is always atomic to the global lock. + */ + if (check_time == (*isa)->last_load_time) { + + rv = isapi_load(loaded.pool, s, *isa); + + apr_thread_mutex_lock(loaded.lock); + (*isa)->last_load_rv = rv; + (*isa)->last_load_time = apr_time_now(); + apr_thread_mutex_unlock(loaded.lock); + } + else { + rv = (*isa)->last_load_rv; + } + apr_thread_rwlock_unlock(gainlock); + + return rv; + } + + /* We haven't hit timeup on retry, let's grab the last_rv + * within the hash mutex before unlocking. + */ + rv = (*isa)->last_load_rv; + apr_thread_mutex_unlock(loaded.lock); + + return rv; + } + + /* If the module was not found, it's time to create a hash key entry + * before releasing the hash lock to avoid multiple threads from + * loading the same module. + */ + key = apr_pstrdup(loaded.pool, fpath); + *isa = apr_pcalloc(loaded.pool, sizeof(isapi_loaded)); + (*isa)->filename = key; + if (r) { + /* A mutex that exists only long enough to attempt to + * load this isapi dll, the release this module to all + * other takers that came along during the one-time + * load process. Short lifetime for this lock would + * be great, however, using r->pool is nasty if those + * blocked on the lock haven't all unlocked before we + * attempt to destroy. A nastier race condition than + * I want to deal with at this moment... + */ + apr_thread_rwlock_create(&(*isa)->in_progress, loaded.pool); + apr_thread_rwlock_wrlock((*isa)->in_progress); + } + + apr_hash_set(loaded.hash, key, APR_HASH_KEY_STRING, *isa); + + /* Now attempt to load the isapi on our own time, + * allow other isapi processing to resume. + */ + apr_thread_mutex_unlock(loaded.lock); + + rv = isapi_load(loaded.pool, s, *isa); + (*isa)->last_load_time = apr_time_now(); + (*isa)->last_load_rv = rv; + + if (r && (rv == APR_SUCCESS)) { + /* Let others who are blocked on this particular + * module resume their requests, for better or worse. + */ + apr_thread_rwlock_t *unlock = (*isa)->in_progress; + (*isa)->in_progress = NULL; + apr_thread_rwlock_unlock(unlock); + } + else if (!r && (rv != APR_SUCCESS)) { + /* We must leave a rwlock around for requests to retry + * loading this dll after timeup... since we were in + * the setup code we had avoided creating this lock. + */ + apr_thread_rwlock_create(&(*isa)->in_progress, loaded.pool); + } + + return (*isa)->last_load_rv; +} + +/********************************************************** + * + * ISAPI Module request callbacks section + * + **********************************************************/ + +/* Our "Connection ID" structure */ +struct isapi_cid { + EXTENSION_CONTROL_BLOCK *ecb; + isapi_dir_conf dconf; + isapi_loaded *isa; + request_rec *r; + int headers_set; + int response_sent; + PFN_HSE_IO_COMPLETION completion; + void *completion_arg; + apr_thread_mutex_t *completed; +}; + +static int APR_THREAD_FUNC regfnGetServerVariable(isapi_cid *cid, + char *variable_name, + void *buf_ptr, + apr_uint32_t *buf_size) +{ + request_rec *r = cid->r; + const char *result; + char *buf_data = (char*)buf_ptr; + apr_uint32_t len; + + if (!strcmp(variable_name, "ALL_HTTP")) + { + /* crlf delimited, colon split, comma separated and + * null terminated list of HTTP_ vars + */ + const apr_array_header_t *arr = apr_table_elts(r->subprocess_env); + const apr_table_entry_t *elts = (const apr_table_entry_t *)arr->elts; + int i; + + for (len = 0, i = 0; i < arr->nelts; i++) { + if (!strncmp(elts[i].key, "HTTP_", 5)) { + len += strlen(elts[i].key) + strlen(elts[i].val) + 3; + } + } + + if (*buf_size < len + 1) { + *buf_size = len + 1; + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INSUFFICIENT_BUFFER)); + return 0; + } + + for (i = 0; i < arr->nelts; i++) { + if (!strncmp(elts[i].key, "HTTP_", 5)) { + strcpy(buf_data, elts[i].key); + buf_data += strlen(elts[i].key); + *(buf_data++) = ':'; + strcpy(buf_data, elts[i].val); + buf_data += strlen(elts[i].val); + *(buf_data++) = '\r'; + *(buf_data++) = '\n'; + } + } + + *(buf_data++) = '\0'; + *buf_size = len + 1; + return 1; + } + + if (!strcmp(variable_name, "ALL_RAW")) + { + /* crlf delimited, colon split, comma separated and + * null terminated list of the raw request header + */ + const apr_array_header_t *arr = apr_table_elts(r->headers_in); + const apr_table_entry_t *elts = (const apr_table_entry_t *)arr->elts; + int i; + + for (len = 0, i = 0; i < arr->nelts; i++) { + len += strlen(elts[i].key) + strlen(elts[i].val) + 4; + } + + if (*buf_size < len + 1) { + *buf_size = len + 1; + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INSUFFICIENT_BUFFER)); + return 0; + } + + for (i = 0; i < arr->nelts; i++) { + strcpy(buf_data, elts[i].key); + buf_data += strlen(elts[i].key); + *(buf_data++) = ':'; + *(buf_data++) = ' '; + strcpy(buf_data, elts[i].val); + buf_data += strlen(elts[i].val); + *(buf_data++) = '\r'; + *(buf_data++) = '\n'; + } + *(buf_data++) = '\0'; + *buf_size = len + 1; + return 1; + } + + /* Not a special case */ + result = apr_table_get(r->subprocess_env, variable_name); + + if (result) { + len = strlen(result); + if (*buf_size < len + 1) { + *buf_size = len + 1; + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INSUFFICIENT_BUFFER)); + return 0; + } + strcpy(buf_data, result); + *buf_size = len + 1; + return 1; + } + + /* Not Found */ + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_INDEX)); + return 0; +} + +static int APR_THREAD_FUNC regfnReadClient(isapi_cid *cid, + void *buf_data, + apr_uint32_t *buf_size) +{ + request_rec *r = cid->r; + apr_uint32_t read = 0; + int res = 0; + + if (r->remaining < *buf_size) { + *buf_size = (apr_size_t)r->remaining; + } + + while (read < *buf_size && + ((res = ap_get_client_block(r, (char*)buf_data + read, + *buf_size - read)) > 0)) { + read += res; + } + + *buf_size = read; + if (res < 0) { + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_READ_FAULT)); + } + return (res >= 0); +} + +/* Common code invoked for both HSE_REQ_SEND_RESPONSE_HEADER and + * the newer HSE_REQ_SEND_RESPONSE_HEADER_EX ServerSupportFunction(s) + * as well as other functions that write responses and presume that + * the support functions above are optional. + * + * Other callers trying to split headers and body bytes should pass + * head/headlen alone (leaving stat/statlen NULL/0), so that they + * get a proper count of bytes consumed. The argument passed to stat + * isn't counted as the head bytes are. + */ +static apr_ssize_t send_response_header(isapi_cid *cid, + const char *stat, + const char *head, + apr_size_t statlen, + apr_size_t headlen) +{ + int head_present = 1; + int termarg; + int res; + int old_status; + const char *termch; + apr_size_t ate = 0; + + if (!head || headlen == 0 || !*head) { + head = stat; + stat = NULL; + headlen = statlen; + statlen = 0; + head_present = 0; /* Don't eat the header */ + } + + if (!stat || statlen == 0 || !*stat) { + if (head && headlen && *head && ((stat = memchr(head, '\r', headlen)) + || (stat = memchr(head, '\n', headlen)) + || (stat = memchr(head, '\0', headlen)) + || (stat = head + headlen))) { + statlen = stat - head; + if (memchr(head, ':', statlen)) { + stat = "Status: 200 OK"; + statlen = strlen(stat); + } + else { + const char *flip = head; + head = stat; + stat = flip; + headlen -= statlen; + ate += statlen; + if (*head == '\r' && headlen) + ++head, --headlen, ++ate; + if (*head == '\n' && headlen) + ++head, --headlen, ++ate; + } + } + } + + if (stat && (statlen > 0) && *stat) { + char *newstat; + if (!apr_isdigit(*stat)) { + const char *stattok = stat; + int toklen = statlen; + while (toklen && *stattok && !apr_isspace(*stattok)) { + ++stattok; --toklen; + } + while (toklen && apr_isspace(*stattok)) { + ++stattok; --toklen; + } + /* Now decide if we follow the xxx message + * or the http/x.x xxx message format + */ + if (toklen && apr_isdigit(*stattok)) { + statlen = toklen; + stat = stattok; + } + } + newstat = apr_palloc(cid->r->pool, statlen + 9); + strcpy(newstat, "Status: "); + apr_cpystrn(newstat + 8, stat, statlen + 1); + stat = newstat; + statlen += 8; + } + + if (!head || headlen == 0 || !*head) { + head = "\r\n"; + headlen = 2; + } + else + { + if (head[headlen - 1] && head[headlen]) { + /* Whoops... not NULL terminated */ + head = apr_pstrndup(cid->r->pool, head, headlen); + } + } + + /* Seems IIS does not enforce the requirement for \r\n termination + * on HSE_REQ_SEND_RESPONSE_HEADER, but we won't panic... + * ap_scan_script_header_err_strs handles this aspect for us. + * + * Parse them out, or die trying + */ + old_status = cid->r->status; + + if (stat) { + res = ap_scan_script_header_err_strs_ex(cid->r, NULL, + APLOG_MODULE_INDEX, &termch, &termarg, stat, head, NULL); + } + else { + res = ap_scan_script_header_err_strs_ex(cid->r, NULL, + APLOG_MODULE_INDEX, &termch, &termarg, head, NULL); + } + + /* Set our status. */ + if (res) { + /* This is an immediate error result from the parser + */ + cid->r->status = res; + cid->r->status_line = ap_get_status_line(cid->r->status); + cid->ecb->dwHttpStatusCode = cid->r->status; + } + else if (cid->r->status) { + /* We have a status in r->status, so let's just use it. + * This is likely to be the Status: parsed above, and + * may also be a delayed error result from the parser. + * If it was filled in, status_line should also have + * been filled in. + */ + cid->ecb->dwHttpStatusCode = cid->r->status; + } + else if (cid->ecb->dwHttpStatusCode + && cid->ecb->dwHttpStatusCode != HTTP_OK) { + /* Now we fall back on dwHttpStatusCode if it appears + * ap_scan_script_header fell back on the default code. + * Any other results set dwHttpStatusCode to the decoded + * status value. + */ + cid->r->status = cid->ecb->dwHttpStatusCode; + cid->r->status_line = ap_get_status_line(cid->r->status); + } + else if (old_status) { + /* Well... either there is no dwHttpStatusCode or it's HTTP_OK. + * In any case, we don't have a good status to return yet... + * Perhaps the one we came in with will be better. Let's use it, + * if we were given one (note this is a pedantic case, it would + * normally be covered above unless the scan script code unset + * the r->status). Should there be a check here as to whether + * we are setting a valid response code? + */ + cid->r->status = old_status; + cid->r->status_line = ap_get_status_line(cid->r->status); + cid->ecb->dwHttpStatusCode = cid->r->status; + } + else { + /* None of dwHttpStatusCode, the parser's r->status nor the + * old value of r->status were helpful, and nothing was decoded + * from Status: string passed to us. Let's just say HTTP_OK + * and get the data out, this was the isapi dev's oversight. + */ + cid->r->status = HTTP_OK; + cid->r->status_line = ap_get_status_line(cid->r->status); + cid->ecb->dwHttpStatusCode = cid->r->status; + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, cid->r, APLOGNO(02111) + "Could not determine HTTP response code; using %d", + cid->r->status); + } + + if (cid->r->status == HTTP_INTERNAL_SERVER_ERROR) { + return -1; + } + + /* If only Status was passed, we consumed nothing + */ + if (!head_present) + return 0; + + cid->headers_set = 1; + + /* If all went well, tell the caller we consumed the headers complete + */ + if (!termch) + return(ate + headlen); + + /* Any data left must be sent directly by the caller, all we + * give back is the size of the headers we consumed (which only + * happens if the parser got to the head arg, which varies based + * on whether we passed stat+head to scan, or only head. + */ + if (termch && (termarg == (stat ? 1 : 0)) + && head_present && head + headlen > termch) { + return ate + termch - head; + } + return ate; +} + +static int APR_THREAD_FUNC regfnWriteClient(isapi_cid *cid, + void *buf_ptr, + apr_uint32_t *size_arg, + apr_uint32_t flags) +{ + request_rec *r = cid->r; + conn_rec *c = r->connection; + apr_uint32_t buf_size = *size_arg; + char *buf_data = (char*)buf_ptr; + apr_bucket_brigade *bb; + apr_bucket *b; + apr_status_t rv = APR_SUCCESS; + + if (!cid->headers_set) { + /* It appears that the foxisapi module and other clients + * presume that WriteClient("headers\n\nbody") will work. + * Parse them out, or die trying. + */ + apr_ssize_t ate; + ate = send_response_header(cid, NULL, buf_data, 0, buf_size); + if (ate < 0) { + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER)); + return 0; + } + + buf_data += ate; + buf_size -= ate; + } + + if (buf_size) { + bb = apr_brigade_create(r->pool, c->bucket_alloc); + b = apr_bucket_transient_create(buf_data, buf_size, c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, b); + b = apr_bucket_flush_create(c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, b); + rv = ap_pass_brigade(r->output_filters, bb); + cid->response_sent = 1; + if (rv != APR_SUCCESS) + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(02984) + "WriteClient ap_pass_brigade failed: %s", + r->filename); + } + + if ((flags & HSE_IO_ASYNC) && cid->completion) { + if (rv == APR_SUCCESS) { + cid->completion(cid->ecb, cid->completion_arg, + *size_arg, ERROR_SUCCESS); + } + else { + cid->completion(cid->ecb, cid->completion_arg, + *size_arg, ERROR_WRITE_FAULT); + } + } + return (rv == APR_SUCCESS); +} + +static int APR_THREAD_FUNC regfnServerSupportFunction(isapi_cid *cid, + apr_uint32_t HSE_code, + void *buf_ptr, + apr_uint32_t *buf_size, + apr_uint32_t *data_type) +{ + request_rec *r = cid->r; + conn_rec *c = r->connection; + char *buf_data = (char*)buf_ptr; + request_rec *subreq; + apr_status_t rv; + + switch (HSE_code) { + case HSE_REQ_SEND_URL_REDIRECT_RESP: + /* Set the status to be returned when the HttpExtensionProc() + * is done. + * WARNING: Microsoft now advertises HSE_REQ_SEND_URL_REDIRECT_RESP + * and HSE_REQ_SEND_URL as equivalent per the Jan 2000 SDK. + * They most definitely are not, even in their own samples. + */ + apr_table_set (r->headers_out, "Location", buf_data); + cid->r->status = cid->ecb->dwHttpStatusCode = HTTP_MOVED_TEMPORARILY; + cid->r->status_line = ap_get_status_line(cid->r->status); + cid->headers_set = 1; + return 1; + + case HSE_REQ_SEND_URL: + /* Soak up remaining input */ + if (r->remaining > 0) { + char argsbuffer[HUGE_STRING_LEN]; + while (ap_get_client_block(r, argsbuffer, HUGE_STRING_LEN)); + } + + /* Reset the method to GET */ + r->method = "GET"; + r->method_number = M_GET; + + /* Don't let anyone think there's still data */ + apr_table_unset(r->headers_in, "Content-Length"); + + /* AV fault per PR3598 - redirected path is lost! */ + buf_data = apr_pstrdup(r->pool, (char*)buf_data); + ap_internal_redirect(buf_data, r); + return 1; + + case HSE_REQ_SEND_RESPONSE_HEADER: + { + /* Parse them out, or die trying */ + apr_size_t statlen = 0, headlen = 0; + apr_ssize_t ate; + if (buf_data) + statlen = strlen((char*) buf_data); + if (data_type) + headlen = strlen((char*) data_type); + ate = send_response_header(cid, (char*) buf_data, + (char*) data_type, + statlen, headlen); + if (ate < 0) { + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER)); + return 0; + } + else if ((apr_size_t)ate < headlen) { + apr_bucket_brigade *bb; + apr_bucket *b; + bb = apr_brigade_create(cid->r->pool, c->bucket_alloc); + b = apr_bucket_transient_create((char*) data_type + ate, + headlen - ate, c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, b); + b = apr_bucket_flush_create(c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, b); + rv = ap_pass_brigade(cid->r->output_filters, bb); + cid->response_sent = 1; + if (rv != APR_SUCCESS) + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(03177) + "ServerSupportFunction " + "HSE_REQ_SEND_RESPONSE_HEADER " + "ap_pass_brigade failed: %s", r->filename); + return (rv == APR_SUCCESS); + } + /* Deliberately hold off sending 'just the headers' to begin to + * accumulate the body and speed up the overall response, or at + * least wait for the end the session. + */ + return 1; + } + + case HSE_REQ_DONE_WITH_SESSION: + /* Signal to resume the thread completing this request, + * leave it to the pool cleanup to dispose of our mutex. + */ + if (cid->completed) { + (void)apr_thread_mutex_unlock(cid->completed); + return 1; + } + else if (cid->dconf.log_unsupported) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02671) + "ServerSupportFunction " + "HSE_REQ_DONE_WITH_SESSION is not supported: %s", + r->filename); + } + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER)); + return 0; + + case HSE_REQ_MAP_URL_TO_PATH: + { + /* Map a URL to a filename */ + char *file = (char *)buf_data; + apr_uint32_t len; + subreq = ap_sub_req_lookup_uri( + apr_pstrndup(cid->r->pool, file, *buf_size), r, NULL); + + if (!subreq->filename) { + ap_destroy_sub_req(subreq); + return 0; + } + + len = (apr_uint32_t)strlen(subreq->filename); + + if ((subreq->finfo.filetype == APR_DIR) + && (!subreq->path_info) + && (subreq->filename[len - 1] != '/')) + file = apr_pstrcat(cid->r->pool, subreq->filename, "/", NULL); + else + file = apr_pstrcat(cid->r->pool, subreq->filename, + subreq->path_info, NULL); + + ap_destroy_sub_req(subreq); + +#ifdef WIN32 + /* We need to make this a real Windows path name */ + apr_filepath_merge(&file, "", file, APR_FILEPATH_NATIVE, r->pool); +#endif + + *buf_size = apr_cpystrn(buf_data, file, *buf_size) - buf_data; + + return 1; + } + + case HSE_REQ_GET_SSPI_INFO: + if (cid->dconf.log_unsupported) + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02672) + "ServerSupportFunction HSE_REQ_GET_SSPI_INFO " + "is not supported: %s", r->filename); + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER)); + return 0; + + case HSE_APPEND_LOG_PARAMETER: + /* Log buf_data, of buf_size bytes, in the URI Query (cs-uri-query) field + */ + apr_table_set(r->notes, "isapi-parameter", (char*) buf_data); + if (cid->dconf.log_to_query) { + if (r->args) + r->args = apr_pstrcat(r->pool, r->args, (char*) buf_data, NULL); + else + r->args = apr_pstrdup(r->pool, (char*) buf_data); + } + if (cid->dconf.log_to_errlog) + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02985) + "%s: %s", cid->r->filename, + (char*) buf_data); + return 1; + + case HSE_REQ_IO_COMPLETION: + /* Emulates a completion port... Record callback address and + * user defined arg, we will call this after any async request + * (e.g. transmitfile) as if the request executed async. + * Per MS docs... HSE_REQ_IO_COMPLETION replaces any prior call + * to HSE_REQ_IO_COMPLETION, and buf_data may be set to NULL. + */ + if (cid->dconf.fake_async) { + cid->completion = (PFN_HSE_IO_COMPLETION) buf_data; + cid->completion_arg = (void *) data_type; + return 1; + } + if (cid->dconf.log_unsupported) + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02673) + "ServerSupportFunction HSE_REQ_IO_COMPLETION " + "is not supported: %s", r->filename); + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER)); + return 0; + + case HSE_REQ_TRANSMIT_FILE: + { + /* we do nothing with (tf->dwFlags & HSE_DISCONNECT_AFTER_SEND) + */ + HSE_TF_INFO *tf = (HSE_TF_INFO*)buf_data; + apr_uint32_t sent = 0; + apr_ssize_t ate = 0; + apr_bucket_brigade *bb; + apr_bucket *b; + apr_file_t *fd; + apr_off_t fsize; + + if (!cid->dconf.fake_async && (tf->dwFlags & HSE_IO_ASYNC)) { + if (cid->dconf.log_unsupported) + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02674) + "ServerSupportFunction HSE_REQ_TRANSMIT_FILE " + "as HSE_IO_ASYNC is not supported: %s", r->filename); + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER)); + return 0; + } + + /* Presume the handle was opened with the CORRECT semantics + * for TransmitFile + */ + if ((rv = apr_os_file_put(&fd, &tf->hFile, + APR_READ | APR_XTHREAD, r->pool)) + != APR_SUCCESS) { + return 0; + } + if (tf->BytesToWrite) { + fsize = tf->BytesToWrite; + } + else { + apr_finfo_t fi; + if (apr_file_info_get(&fi, APR_FINFO_SIZE, fd) != APR_SUCCESS) { + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER)); + return 0; + } + fsize = fi.size - tf->Offset; + } + + /* apr_dupfile_oshandle (&fd, tf->hFile, r->pool); */ + bb = apr_brigade_create(r->pool, c->bucket_alloc); + + /* According to MS: if calling HSE_REQ_TRANSMIT_FILE with the + * HSE_IO_SEND_HEADERS flag, then you can't otherwise call any + * HSE_SEND_RESPONSE_HEADERS* fn, but if you don't use the flag, + * you must have done so. They document that the pHead headers + * option is valid only for HSE_IO_SEND_HEADERS - we are a bit + * more flexible and assume with the flag, pHead are the + * response headers, and without, pHead simply contains text + * (handled after this case). + */ + if ((tf->dwFlags & HSE_IO_SEND_HEADERS) && tf->pszStatusCode) { + ate = send_response_header(cid, tf->pszStatusCode, + (char*)tf->pHead, + strlen(tf->pszStatusCode), + tf->HeadLength); + } + else if (!cid->headers_set && tf->pHead && tf->HeadLength + && *(char*)tf->pHead) { + ate = send_response_header(cid, NULL, (char*)tf->pHead, + 0, tf->HeadLength); + if (ate < 0) + { + apr_brigade_destroy(bb); + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER)); + return 0; + } + } + + if (tf->pHead && (apr_size_t)ate < tf->HeadLength) { + b = apr_bucket_transient_create((char*)tf->pHead + ate, + tf->HeadLength - ate, + c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, b); + sent = tf->HeadLength; + } + + sent += (apr_uint32_t)fsize; + apr_brigade_insert_file(bb, fd, tf->Offset, fsize, r->pool); + + if (tf->pTail && tf->TailLength) { + sent += tf->TailLength; + b = apr_bucket_transient_create((char*)tf->pTail, + tf->TailLength, c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, b); + } + + b = apr_bucket_flush_create(c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, b); + rv = ap_pass_brigade(r->output_filters, bb); + cid->response_sent = 1; + if (rv != APR_SUCCESS) + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(03178) + "ServerSupportFunction " + "HSE_REQ_TRANSMIT_FILE " + "ap_pass_brigade failed: %s", r->filename); + + /* Use tf->pfnHseIO + tf->pContext, or if NULL, then use cid->fnIOComplete + * pass pContect to the HseIO callback. + */ + if (tf->dwFlags & HSE_IO_ASYNC) { + if (tf->pfnHseIO) { + if (rv == APR_SUCCESS) { + tf->pfnHseIO(cid->ecb, tf->pContext, + ERROR_SUCCESS, sent); + } + else { + tf->pfnHseIO(cid->ecb, tf->pContext, + ERROR_WRITE_FAULT, sent); + } + } + else if (cid->completion) { + if (rv == APR_SUCCESS) { + cid->completion(cid->ecb, cid->completion_arg, + sent, ERROR_SUCCESS); + } + else { + cid->completion(cid->ecb, cid->completion_arg, + sent, ERROR_WRITE_FAULT); + } + } + } + return (rv == APR_SUCCESS); + } + + case HSE_REQ_REFRESH_ISAPI_ACL: + if (cid->dconf.log_unsupported) + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02675) + "ServerSupportFunction " + "HSE_REQ_REFRESH_ISAPI_ACL " + "is not supported: %s", r->filename); + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER)); + return 0; + + case HSE_REQ_IS_KEEP_CONN: + *((int *)buf_data) = (r->connection->keepalive == AP_CONN_KEEPALIVE); + return 1; + + case HSE_REQ_ASYNC_READ_CLIENT: + { + apr_uint32_t read = 0; + int res = 0; + if (!cid->dconf.fake_async) { + if (cid->dconf.log_unsupported) + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02986) + "asynchronous I/O not supported: %s", + r->filename); + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER)); + return 0; + } + + if (r->remaining < *buf_size) { + *buf_size = (apr_size_t)r->remaining; + } + + while (read < *buf_size && + ((res = ap_get_client_block(r, (char*)buf_data + read, + *buf_size - read)) > 0)) { + read += res; + } + + if ((*data_type & HSE_IO_ASYNC) && cid->completion) { + /* XXX: Many authors issue their next HSE_REQ_ASYNC_READ_CLIENT + * within the completion logic. An example is MS's own PSDK + * sample web/iis/extensions/io/ASyncRead. This potentially + * leads to stack exhaustion. To refactor, the notification + * logic needs to move to isapi_handler() - differentiating + * the cid->completed event with a new flag to indicate + * an async-notice versus the async request completed. + */ + if (res >= 0) { + cid->completion(cid->ecb, cid->completion_arg, + read, ERROR_SUCCESS); + } + else { + cid->completion(cid->ecb, cid->completion_arg, + read, ERROR_READ_FAULT); + } + } + return (res >= 0); + } + + case HSE_REQ_GET_IMPERSONATION_TOKEN: /* Added in ISAPI 4.0 */ + if (cid->dconf.log_unsupported) + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02676) + "ServerSupportFunction " + "HSE_REQ_GET_IMPERSONATION_TOKEN " + "is not supported: %s", r->filename); + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER)); + return 0; + + case HSE_REQ_MAP_URL_TO_PATH_EX: + { + /* Map a URL to a filename */ + HSE_URL_MAPEX_INFO *info = (HSE_URL_MAPEX_INFO*)data_type; + char* test_uri = apr_pstrndup(r->pool, (char *)buf_data, *buf_size); + + subreq = ap_sub_req_lookup_uri(test_uri, r, NULL); + info->cchMatchingURL = strlen(test_uri); + info->cchMatchingPath = apr_cpystrn(info->lpszPath, subreq->filename, + sizeof(info->lpszPath)) - info->lpszPath; + + /* Mapping started with assuming both strings matched. + * Now roll on the path_info as a mismatch and handle + * terminating slashes for directory matches. + */ + if (subreq->path_info && *subreq->path_info) { + apr_cpystrn(info->lpszPath + info->cchMatchingPath, + subreq->path_info, + sizeof(info->lpszPath) - info->cchMatchingPath); + info->cchMatchingURL -= strlen(subreq->path_info); + if (subreq->finfo.filetype == APR_DIR + && info->cchMatchingPath < sizeof(info->lpszPath) - 1) { + /* roll forward over path_info's first slash */ + ++info->cchMatchingPath; + ++info->cchMatchingURL; + } + } + else if (subreq->finfo.filetype == APR_DIR + && info->cchMatchingPath < sizeof(info->lpszPath) - 1) { + /* Add a trailing slash for directory */ + info->lpszPath[info->cchMatchingPath++] = '/'; + info->lpszPath[info->cchMatchingPath] = '\0'; + } + + /* If the matched isn't a file, roll match back to the prior slash */ + if (subreq->finfo.filetype == APR_NOFILE) { + while (info->cchMatchingPath && info->cchMatchingURL) { + if (info->lpszPath[info->cchMatchingPath - 1] == '/') + break; + --info->cchMatchingPath; + --info->cchMatchingURL; + } + } + + /* Paths returned with back slashes */ + for (test_uri = info->lpszPath; *test_uri; ++test_uri) + if (*test_uri == '/') + *test_uri = '\\'; + + /* is a combination of: + * HSE_URL_FLAGS_READ 0x001 Allow read + * HSE_URL_FLAGS_WRITE 0x002 Allow write + * HSE_URL_FLAGS_EXECUTE 0x004 Allow execute + * HSE_URL_FLAGS_SSL 0x008 Require SSL + * HSE_URL_FLAGS_DONT_CACHE 0x010 Don't cache (VRoot only) + * HSE_URL_FLAGS_NEGO_CERT 0x020 Allow client SSL cert + * HSE_URL_FLAGS_REQUIRE_CERT 0x040 Require client SSL cert + * HSE_URL_FLAGS_MAP_CERT 0x080 Map client SSL cert to account + * HSE_URL_FLAGS_SSL128 0x100 Require 128-bit SSL cert + * HSE_URL_FLAGS_SCRIPT 0x200 Allow script execution + * + * XxX: As everywhere, EXEC flags could use some work... + * and this could go further with more flags, as desired. + */ + info->dwFlags = (subreq->finfo.protection & APR_UREAD ? 0x001 : 0) + | (subreq->finfo.protection & APR_UWRITE ? 0x002 : 0) + | (subreq->finfo.protection & APR_UEXECUTE ? 0x204 : 0); + return 1; + } + + case HSE_REQ_ABORTIVE_CLOSE: + if (cid->dconf.log_unsupported) + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02677) + "ServerSupportFunction HSE_REQ_ABORTIVE_CLOSE" + " is not supported: %s", r->filename); + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER)); + return 0; + + case HSE_REQ_GET_CERT_INFO_EX: /* Added in ISAPI 4.0 */ + if (cid->dconf.log_unsupported) + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02678) + "ServerSupportFunction " + "HSE_REQ_GET_CERT_INFO_EX " + "is not supported: %s", r->filename); + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER)); + return 0; + + case HSE_REQ_SEND_RESPONSE_HEADER_EX: /* Added in ISAPI 4.0 */ + { + HSE_SEND_HEADER_EX_INFO *shi = (HSE_SEND_HEADER_EX_INFO*)buf_data; + + /* Ignore shi->fKeepConn - we don't want the advise + */ + apr_ssize_t ate = send_response_header(cid, shi->pszStatus, + shi->pszHeader, + shi->cchStatus, + shi->cchHeader); + if (ate < 0) { + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER)); + return 0; + } + else if ((apr_size_t)ate < shi->cchHeader) { + apr_bucket_brigade *bb; + apr_bucket *b; + bb = apr_brigade_create(cid->r->pool, c->bucket_alloc); + b = apr_bucket_transient_create(shi->pszHeader + ate, + shi->cchHeader - ate, + c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, b); + b = apr_bucket_flush_create(c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, b); + rv = ap_pass_brigade(cid->r->output_filters, bb); + cid->response_sent = 1; + if (rv != APR_SUCCESS) + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(03179) + "ServerSupportFunction " + "HSE_REQ_SEND_RESPONSE_HEADER_EX " + "ap_pass_brigade failed: %s", r->filename); + return (rv == APR_SUCCESS); + } + /* Deliberately hold off sending 'just the headers' to begin to + * accumulate the body and speed up the overall response, or at + * least wait for the end the session. + */ + return 1; + } + + case HSE_REQ_CLOSE_CONNECTION: /* Added after ISAPI 4.0 */ + if (cid->dconf.log_unsupported) + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02679) + "ServerSupportFunction " + "HSE_REQ_CLOSE_CONNECTION " + "is not supported: %s", r->filename); + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER)); + return 0; + + case HSE_REQ_IS_CONNECTED: /* Added after ISAPI 4.0 */ + /* Returns True if client is connected c.f. MSKB Q188346 + * assuming the identical return mechanism as HSE_REQ_IS_KEEP_CONN + */ + *((int *)buf_data) = (r->connection->aborted == 0); + return 1; + + case HSE_REQ_EXTENSION_TRIGGER: /* Added after ISAPI 4.0 */ + /* Undocumented - defined by the Microsoft Jan '00 Platform SDK + */ + if (cid->dconf.log_unsupported) + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02680) + "ServerSupportFunction " + "HSE_REQ_EXTENSION_TRIGGER " + "is not supported: %s", r->filename); + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER)); + return 0; + + default: + if (cid->dconf.log_unsupported) + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02681) + "ServerSupportFunction (%d) not supported: " + "%s", HSE_code, r->filename); + apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER)); + return 0; + } +} + +/********************************************************** + * + * ISAPI Module request invocation section + * + **********************************************************/ + +static apr_status_t isapi_handler (request_rec *r) +{ + isapi_dir_conf *dconf; + apr_table_t *e; + apr_status_t rv; + isapi_loaded *isa; + isapi_cid *cid; + const char *val; + apr_uint32_t read; + int res; + + if (strcmp(r->handler, "isapi-isa") + && strcmp(r->handler, "isapi-handler")) { + /* Hang on to the isapi-isa for compatibility with older docs + * (wtf did '-isa' mean in the first place?) but introduce + * a newer and clearer "isapi-handler" name. + */ + return DECLINED; + } + dconf = ap_get_module_config(r->per_dir_config, &isapi_module); + e = r->subprocess_env; + + /* Use similar restrictions as CGIs + * + * If this fails, it's pointless to load the isapi dll. + */ + if (!(ap_allow_options(r) & OPT_EXECCGI)) { + return HTTP_FORBIDDEN; + } + if (r->finfo.filetype == APR_NOFILE) { + return HTTP_NOT_FOUND; + } + if (r->finfo.filetype != APR_REG) { + return HTTP_FORBIDDEN; + } + if ((r->used_path_info == AP_REQ_REJECT_PATH_INFO) && + r->path_info && *r->path_info) { + /* default to accept */ + return HTTP_NOT_FOUND; + } + + if (isapi_lookup(r->pool, r->server, r, r->filename, &isa) + != APR_SUCCESS) { + return HTTP_INTERNAL_SERVER_ERROR; + } + /* Set up variables */ + ap_add_common_vars(r); + ap_add_cgi_vars(r); + apr_table_setn(e, "UNMAPPED_REMOTE_USER", "REMOTE_USER"); + if ((val = apr_table_get(e, "HTTPS")) && (strcmp(val, "on") == 0)) + apr_table_setn(e, "SERVER_PORT_SECURE", "1"); + else + apr_table_setn(e, "SERVER_PORT_SECURE", "0"); + apr_table_setn(e, "URL", r->uri); + + /* Set up connection structure and ecb, + * NULL or zero out most fields. + */ + cid = apr_pcalloc(r->pool, sizeof(isapi_cid)); + + /* Fixup defaults for dconf */ + cid->dconf.read_ahead_buflen = (dconf->read_ahead_buflen == ISAPI_UNDEF) + ? 49152 : dconf->read_ahead_buflen; + cid->dconf.log_unsupported = (dconf->log_unsupported == ISAPI_UNDEF) + ? 0 : dconf->log_unsupported; + cid->dconf.log_to_errlog = (dconf->log_to_errlog == ISAPI_UNDEF) + ? 0 : dconf->log_to_errlog; + cid->dconf.log_to_query = (dconf->log_to_query == ISAPI_UNDEF) + ? 1 : dconf->log_to_query; + cid->dconf.fake_async = (dconf->fake_async == ISAPI_UNDEF) + ? 0 : dconf->fake_async; + + cid->ecb = apr_pcalloc(r->pool, sizeof(EXTENSION_CONTROL_BLOCK)); + cid->ecb->ConnID = cid; + cid->isa = isa; + cid->r = r; + r->status = 0; + + cid->ecb->cbSize = sizeof(EXTENSION_CONTROL_BLOCK); + cid->ecb->dwVersion = isa->report_version; + cid->ecb->dwHttpStatusCode = 0; + strcpy(cid->ecb->lpszLogData, ""); + /* TODO: are copies really needed here? + */ + cid->ecb->lpszMethod = (char*) r->method; + cid->ecb->lpszQueryString = (char*) apr_table_get(e, "QUERY_STRING"); + cid->ecb->lpszPathInfo = (char*) apr_table_get(e, "PATH_INFO"); + cid->ecb->lpszPathTranslated = (char*) apr_table_get(e, "PATH_TRANSLATED"); + cid->ecb->lpszContentType = (char*) apr_table_get(e, "CONTENT_TYPE"); + + /* Set up the callbacks */ + cid->ecb->GetServerVariable = regfnGetServerVariable; + cid->ecb->WriteClient = regfnWriteClient; + cid->ecb->ReadClient = regfnReadClient; + cid->ecb->ServerSupportFunction = regfnServerSupportFunction; + + /* Set up client input */ + res = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR); + if (res) { + return res; + } + + if (ap_should_client_block(r)) { + /* Time to start reading the appropriate amount of data, + * and allow the administrator to tweak the number + */ + if (r->remaining) { + cid->ecb->cbTotalBytes = (apr_size_t)r->remaining; + if (cid->ecb->cbTotalBytes > (apr_uint32_t)cid->dconf.read_ahead_buflen) + cid->ecb->cbAvailable = cid->dconf.read_ahead_buflen; + else + cid->ecb->cbAvailable = cid->ecb->cbTotalBytes; + } + else + { + cid->ecb->cbTotalBytes = 0xffffffff; + cid->ecb->cbAvailable = cid->dconf.read_ahead_buflen; + } + + cid->ecb->lpbData = apr_pcalloc(r->pool, cid->ecb->cbAvailable + 1); + + read = 0; + while (read < cid->ecb->cbAvailable && + ((res = ap_get_client_block(r, (char*)cid->ecb->lpbData + read, + cid->ecb->cbAvailable - read)) > 0)) { + read += res; + } + + if (res < 0) { + return HTTP_INTERNAL_SERVER_ERROR; + } + + /* Although it's not to spec, IIS seems to null-terminate + * its lpdData string. So we will too. + */ + if (res == 0) + cid->ecb->cbAvailable = cid->ecb->cbTotalBytes = read; + else + cid->ecb->cbAvailable = read; + cid->ecb->lpbData[read] = '\0'; + } + else { + cid->ecb->cbTotalBytes = 0; + cid->ecb->cbAvailable = 0; + cid->ecb->lpbData = NULL; + } + + /* To emulate async behavior... + * + * We create a cid->completed mutex and lock on it so that the + * app can believe is it running async. + * + * This request completes upon a notification through + * ServerSupportFunction(HSE_REQ_DONE_WITH_SESSION), which + * unlocks this mutex. If the HttpExtensionProc() returns + * HSE_STATUS_PENDING, we will attempt to gain this lock again + * which may *only* happen once HSE_REQ_DONE_WITH_SESSION has + * unlocked the mutex. + */ + if (cid->dconf.fake_async) { + rv = apr_thread_mutex_create(&cid->completed, + APR_THREAD_MUTEX_UNNESTED, + r->pool); + if (cid->completed && (rv == APR_SUCCESS)) { + rv = apr_thread_mutex_lock(cid->completed); + } + + if (!cid->completed || (rv != APR_SUCCESS)) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02112) + "Failed to create completion mutex"); + return HTTP_INTERNAL_SERVER_ERROR; + } + } + + /* All right... try and run the sucker */ + rv = (*isa->HttpExtensionProc)(cid->ecb); + + /* Check for a log message - and log it */ + if (*cid->ecb->lpszLogData) { + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02113) + "%s: %s", r->filename, cid->ecb->lpszLogData); + } + + switch(rv) { + case 0: /* Strange, but MS isapi accepts this as success */ + case HSE_STATUS_SUCCESS: + case HSE_STATUS_SUCCESS_AND_KEEP_CONN: + /* Ignore the keepalive stuff; Apache handles it just fine without + * the ISAPI Handler's "advice". + * Per Microsoft: "In IIS versions 4.0 and later, the return + * values HSE_STATUS_SUCCESS and HSE_STATUS_SUCCESS_AND_KEEP_CONN + * are functionally identical: Keep-Alive connections are + * maintained, if supported by the client." + * ... so we were pat all this time + */ + break; + + case HSE_STATUS_PENDING: + /* emulating async behavior... + */ + if (cid->completed) { + /* The completion port was locked prior to invoking + * HttpExtensionProc(). Once we can regain the lock, + * when ServerSupportFunction(HSE_REQ_DONE_WITH_SESSION) + * is called by the extension to release the lock, + * we may finally destroy the request. + */ + (void)apr_thread_mutex_lock(cid->completed); + break; + } + else if (cid->dconf.log_unsupported) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02114) + "asynch I/O result HSE_STATUS_PENDING " + "from HttpExtensionProc() is not supported: %s", + r->filename); + r->status = HTTP_INTERNAL_SERVER_ERROR; + } + break; + + case HSE_STATUS_ERROR: + /* end response if we have yet to do so. + */ + ap_log_rerror(APLOG_MARK, APLOG_WARNING, apr_get_os_error(), r, APLOGNO(02115) + "HSE_STATUS_ERROR result from " + "HttpExtensionProc(): %s", r->filename); + r->status = HTTP_INTERNAL_SERVER_ERROR; + break; + + default: + ap_log_rerror(APLOG_MARK, APLOG_WARNING, apr_get_os_error(), r, APLOGNO(02116) + "unrecognized result code %d " + "from HttpExtensionProc(): %s ", + rv, r->filename); + r->status = HTTP_INTERNAL_SERVER_ERROR; + break; + } + + /* Flush the response now, including headers-only responses */ + if (cid->headers_set || cid->response_sent) { + conn_rec *c = r->connection; + apr_bucket_brigade *bb; + apr_bucket *b; + apr_status_t rv; + + bb = apr_brigade_create(r->pool, c->bucket_alloc); + b = apr_bucket_eos_create(c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, b); + rv = ap_pass_brigade(r->output_filters, bb); + cid->response_sent = 1; + + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(02117) + "ap_pass_brigade failed to " + "complete the response: %s ", r->filename); + } + + return OK; /* NOT r->status, even if it has changed. */ + } + + /* As the client returned no error, and if we did not error out + * ourselves, trust dwHttpStatusCode to say something relevant. + */ + if (!ap_is_HTTP_SERVER_ERROR(r->status) && cid->ecb->dwHttpStatusCode) { + r->status = cid->ecb->dwHttpStatusCode; + } + + /* For all missing-response situations simply return the status, + * and let the core respond to the client. + */ + return r->status; +} + +/********************************************************** + * + * ISAPI Module Setup Hooks + * + **********************************************************/ + +static int isapi_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp) +{ + apr_status_t rv; + + apr_pool_create_ex(&loaded.pool, pconf, NULL, NULL); + if (!loaded.pool) { + ap_log_error(APLOG_MARK, APLOG_ERR, APR_EGENERAL, NULL, APLOGNO(02118) + "could not create the isapi cache pool"); + return APR_EGENERAL; + } + apr_pool_tag(loaded.pool, "mod_isapi_load"); + + loaded.hash = apr_hash_make(loaded.pool); + if (!loaded.hash) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, APLOGNO(02119) + "Failed to create module cache"); + return APR_EGENERAL; + } + + rv = apr_thread_mutex_create(&loaded.lock, APR_THREAD_MUTEX_DEFAULT, + loaded.pool); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, NULL, APLOGNO(02682) + "Failed to create module cache lock"); + return rv; + } + return OK; +} + +static void isapi_hooks(apr_pool_t *cont) +{ + ap_hook_pre_config(isapi_pre_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_handler(isapi_handler, NULL, NULL, APR_HOOK_MIDDLE); +} + +AP_DECLARE_MODULE(isapi) = { + STANDARD20_MODULE_STUFF, + create_isapi_dir_config, /* create per-dir config */ + merge_isapi_dir_configs, /* merge per-dir config */ + NULL, /* server config */ + NULL, /* merge server config */ + isapi_cmds, /* command apr_table_t */ + isapi_hooks /* register hooks */ +}; diff --git a/modules/arch/win32/mod_isapi.dep b/modules/arch/win32/mod_isapi.dep new file mode 100644 index 0000000..b36dbcf --- /dev/null +++ b/modules/arch/win32/mod_isapi.dep @@ -0,0 +1,61 @@ +# Microsoft Developer Studio Generated Dependency File, included by mod_isapi.mak + +..\..\..\build\win32\httpd.rc : \ + "..\..\..\include\ap_release.h"\ + + +.\mod_isapi.c : \ + "..\..\..\include\ap_config.h"\ + "..\..\..\include\ap_config_layout.h"\ + "..\..\..\include\ap_expr.h"\ + "..\..\..\include\ap_hooks.h"\ + "..\..\..\include\ap_mmn.h"\ + "..\..\..\include\ap_regex.h"\ + "..\..\..\include\ap_release.h"\ + "..\..\..\include\apache_noprobes.h"\ + "..\..\..\include\http_config.h"\ + "..\..\..\include\http_core.h"\ + "..\..\..\include\http_log.h"\ + "..\..\..\include\http_protocol.h"\ + "..\..\..\include\http_request.h"\ + "..\..\..\include\httpd.h"\ + "..\..\..\include\mod_core.h"\ + "..\..\..\include\os.h"\ + "..\..\..\include\util_cfgtree.h"\ + "..\..\..\include\util_filter.h"\ + "..\..\..\include\util_script.h"\ + "..\..\..\srclib\apr-util\include\apr_buckets.h"\ + "..\..\..\srclib\apr-util\include\apr_hooks.h"\ + "..\..\..\srclib\apr-util\include\apr_optional.h"\ + "..\..\..\srclib\apr-util\include\apr_optional_hooks.h"\ + "..\..\..\srclib\apr-util\include\apr_uri.h"\ + "..\..\..\srclib\apr-util\include\apu.h"\ + "..\..\..\srclib\apr\include\apr.h"\ + "..\..\..\srclib\apr\include\apr_allocator.h"\ + "..\..\..\srclib\apr\include\apr_dso.h"\ + "..\..\..\srclib\apr\include\apr_errno.h"\ + "..\..\..\srclib\apr\include\apr_file_info.h"\ + "..\..\..\srclib\apr\include\apr_file_io.h"\ + "..\..\..\srclib\apr\include\apr_general.h"\ + "..\..\..\srclib\apr\include\apr_global_mutex.h"\ + "..\..\..\srclib\apr\include\apr_hash.h"\ + "..\..\..\srclib\apr\include\apr_inherit.h"\ + "..\..\..\srclib\apr\include\apr_lib.h"\ + "..\..\..\srclib\apr\include\apr_mmap.h"\ + "..\..\..\srclib\apr\include\apr_network_io.h"\ + "..\..\..\srclib\apr\include\apr_poll.h"\ + "..\..\..\srclib\apr\include\apr_pools.h"\ + "..\..\..\srclib\apr\include\apr_portable.h"\ + "..\..\..\srclib\apr\include\apr_proc_mutex.h"\ + "..\..\..\srclib\apr\include\apr_ring.h"\ + "..\..\..\srclib\apr\include\apr_shm.h"\ + "..\..\..\srclib\apr\include\apr_strings.h"\ + "..\..\..\srclib\apr\include\apr_tables.h"\ + "..\..\..\srclib\apr\include\apr_thread_mutex.h"\ + "..\..\..\srclib\apr\include\apr_thread_proc.h"\ + "..\..\..\srclib\apr\include\apr_thread_rwlock.h"\ + "..\..\..\srclib\apr\include\apr_time.h"\ + "..\..\..\srclib\apr\include\apr_user.h"\ + "..\..\..\srclib\apr\include\apr_want.h"\ + ".\mod_isapi.h"\ + diff --git a/modules/arch/win32/mod_isapi.dsp b/modules/arch/win32/mod_isapi.dsp new file mode 100644 index 0000000..39df3e4 --- /dev/null +++ b/modules/arch/win32/mod_isapi.dsp @@ -0,0 +1,115 @@ +# Microsoft Developer Studio Project File - Name="mod_isapi" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=mod_isapi - Win32 Release +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "mod_isapi.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "mod_isapi.mak" CFG="mod_isapi - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "mod_isapi - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "mod_isapi - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "mod_isapi - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c +# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_isapi_src" /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /fo"Release/mod_isapi.res" /i "../../../include" /i "../../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_isapi.so" /d LONG_NAME="isapi_module for Apache" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /out:".\Release\mod_isapi.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_isapi.so +# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_isapi.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_isapi.so /opt:ref +# Begin Special Build Tool +TargetPath=.\Release\mod_isapi.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2 +# End Special Build Tool + +!ELSEIF "$(CFG)" == "mod_isapi - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c +# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_isapi_src" /FD /c +# ADD BASE MTL /nologo /D "_DEBUG" /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /fo"Debug/mod_isapi.res" /i "../../../include" /i "../../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_isapi.so" /d LONG_NAME="isapi_module for Apache" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_isapi.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_isapi.so +# ADD LINK32 kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_isapi.so" /base:@..\..\..\os\win32\BaseAddr.ref,mod_isapi.so +# Begin Special Build Tool +TargetPath=.\Debug\mod_isapi.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2 +# End Special Build Tool + +!ENDIF + +# Begin Target + +# Name "mod_isapi - Win32 Release" +# Name "mod_isapi - Win32 Debug" +# Begin Source File + +SOURCE=.\mod_isapi.c +# End Source File +# Begin Source File + +SOURCE=.\mod_isapi.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\build\win32\httpd.rc +# End Source File +# End Target +# End Project diff --git a/modules/arch/win32/mod_isapi.h b/modules/arch/win32/mod_isapi.h new file mode 100644 index 0000000..5284386 --- /dev/null +++ b/modules/arch/win32/mod_isapi.h @@ -0,0 +1,271 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file mod_isapi.h + * @brief ISAPI module extension to Apache + * + * @defgroup MOD_ISAPI mod_isapi + * @ingroup APACHE_MODS + * @{ + */ + +#ifndef MOD_ISAPI_H +#define MOD_ISAPI_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* The Version Information storage passed to a module on startup + * via the GetExtensionVersion() entry point. + */ +typedef struct HSE_VERSION_INFO { + apr_uint32_t dwExtensionVersion; + char lpszExtensionDesc[256]; +} HSE_VERSION_INFO; + +/* The startup entry point that must be exported by every ISAPI handler + */ +int APR_THREAD_FUNC GetExtensionVersion(HSE_VERSION_INFO *ver_info); +typedef int (APR_THREAD_FUNC *PFN_GETEXTENSIONVERSION)(HSE_VERSION_INFO *ver_info); + +/* Our internal 'HCONN' representation, always opaque to the user. + */ +typedef struct isapi_cid isapi_cid; +typedef struct isapi_cid *HCONN; + +/* Prototypes of the essential functions exposed by mod_isapi + * for the module to communicate with Apache. + */ +typedef int (APR_THREAD_FUNC + *PFN_GETSERVERVARIABLE)(HCONN cid, + char *variable_name, + void *buf_data, + apr_uint32_t *buf_size); +typedef int (APR_THREAD_FUNC + *PFN_WRITECLIENT)(HCONN cid, + void *buf_data, + apr_uint32_t *buf_size, + apr_uint32_t flags); +typedef int (APR_THREAD_FUNC + *PFN_READCLIENT)(HCONN cid, + void *buf_data, + apr_uint32_t *buf_size); +typedef int (APR_THREAD_FUNC + *PFN_SERVERSUPPORTFUNCTION)(HCONN cid, + apr_uint32_t HSE_code, + void *buf_data, + apr_uint32_t *buf_size, + apr_uint32_t *flags); + +/* The ecb structure is passed on each invocation of the module + */ +typedef struct EXTENSION_CONTROL_BLOCK { + apr_uint32_t cbSize; + apr_uint32_t dwVersion; + HCONN ConnID; + apr_uint32_t dwHttpStatusCode; + char lpszLogData[80]; + char *lpszMethod; + char *lpszQueryString; + char *lpszPathInfo; + char *lpszPathTranslated; + apr_uint32_t cbTotalBytes; + apr_uint32_t cbAvailable; + unsigned char *lpbData; + char *lpszContentType; + + PFN_GETSERVERVARIABLE GetServerVariable; + PFN_WRITECLIENT WriteClient; + PFN_READCLIENT ReadClient; + PFN_SERVERSUPPORTFUNCTION ServerSupportFunction; +} EXTENSION_CONTROL_BLOCK; + +/* Status/Headers structure to pass to HSE_SEND_HEADER_EX, + * an MS extension to ServerSupportFunction + */ +typedef struct HSE_SEND_HEADER_EX_INFO { + const char * pszStatus; /* HTTP status text, such as "200 OK" */ + const char * pszHeader; /* HTTP header lines text, such as + * "Content-type: text/plain\r\n" + * "Content-Language: en\r\n" + * Note that (in spite of cchFoo lengths below) + * NULL characters will interfere in headers. + */ + apr_uint32_t cchStatus; /* length of pszStatus text */ + apr_uint32_t cchHeader; /* length of pszHeader text */ + int fKeepConn; /* Ignored: used to set keep-alive status, + * but Apache follows the client's negotiated + * HTTP contract to decide. + */ +} HSE_SEND_HEADER_EX_INFO; + +/* Our only 'supported' MS extended flag bit for TransmitFile, + * HSE_IO_SEND_HEADERS indicates that Status+Headers are present + * in the pszStatusCode member of the HSE_TF_INFO structure. + */ +#define HSE_IO_SEND_HEADERS 8 + +/* The remaining flags are MS extended flag bits that bear little + * relation to Apache; the rules that the Apache server obeys follow + * its own design and HTTP protocol filter rules. + * + * We do not support async, however, we fake it. If HSE_IO_SYNC is + * not passed, and a completion context was defined, we will invoke the + * completion function immediately following the transfer, and then + * return to the caller. If HSE_IO_SYNC is passed, there is no call + * necessary to the completion context. + */ +#define HSE_IO_SYNC 1 +#define HSE_IO_ASYNC 2 +#define HSE_IO_DISCONNECT_AFTER_SEND 4 +#define HSE_IO_NODELAY 4096 + +/* The Completion function prototype. This callback may be fixed with + * the HSE_REQ_IO_COMPLETION ServerSupportFunction call, or overridden + * for the HSE_REQ_TRANSMIT_FILE call. + */ +typedef void (APR_THREAD_FUNC *PFN_HSE_IO_COMPLETION) + (EXTENSION_CONTROL_BLOCK *ecb, + void *ctxt, + apr_uint32_t cbIO, + apr_uint32_t dwError); + +/* TransmitFile structure to pass to HSE_REQ_TRANSMIT_FILE, an MS extension + */ +typedef struct HSE_TF_INFO { + PFN_HSE_IO_COMPLETION pfnHseIO; /* Overrides the default setting of + * HSE_REQ_IO_COMPLETION if not NULL + */ + void *pContext; + apr_os_file_t hFile; /* HANDLE/fd to transmit */ + const char *pszStatusCode; /* Ignored if HSE_IO_SEND_HEADERS is + * not set. Includes HTTP status text + * plus header text lines, such as + * "200 OK\r\n" + * "Content-type: text/plain\r\n" + */ + apr_uint32_t BytesToWrite; /* 0 is write-all */ + apr_uint32_t Offset; /* File Offset */ + void *pHead; /* Prefix with *pHead body text */ + apr_uint32_t HeadLength; /* Length of *pHead body text */ + void *pTail; /* Prefix with *pTail body text */ + apr_uint32_t TailLength; /* Length of *pTail body text */ + apr_uint32_t dwFlags; /* bit flags described above */ +} HSE_TF_INFO; + +typedef struct HSE_URL_MAPEX_INFO { + char lpszPath[260]; + apr_uint32_t dwFlags; + apr_uint32_t cchMatchingPath; + apr_uint32_t cchMatchingURL; + apr_uint32_t dwReserved1; + apr_uint32_t dwReserved2; +} HSE_URL_MAPEX_INFO; + +/* Original ISAPI ServerSupportFunction() HSE_code methods */ +#define HSE_REQ_SEND_URL_REDIRECT_RESP 1 +#define HSE_REQ_SEND_URL 2 +#define HSE_REQ_SEND_RESPONSE_HEADER 3 +#define HSE_REQ_DONE_WITH_SESSION 4 + +/* MS Extended methods to ISAPI ServerSupportFunction() HSE_code */ +#define HSE_REQ_MAP_URL_TO_PATH 1001 /* Emulated */ +#define HSE_REQ_GET_SSPI_INFO 1002 /* Not Supported */ +#define HSE_APPEND_LOG_PARAMETER 1003 /* Supported */ +#define HSE_REQ_IO_COMPLETION 1005 /* Emulated */ +#define HSE_REQ_TRANSMIT_FILE 1006 /* Async Emulated */ +#define HSE_REQ_REFRESH_ISAPI_ACL 1007 /* Not Supported */ +#define HSE_REQ_IS_KEEP_CONN 1008 /* Supported */ +#define HSE_REQ_ASYNC_READ_CLIENT 1010 /* Emulated */ +/* Added with ISAPI 4.0 */ +#define HSE_REQ_GET_IMPERSONATION_TOKEN 1011 /* Not Supported */ +#define HSE_REQ_MAP_URL_TO_PATH_EX 1012 /* Emulated */ +#define HSE_REQ_ABORTIVE_CLOSE 1014 /* Ignored */ +/* Added after ISAPI 4.0 in IIS 5.0 */ +#define HSE_REQ_GET_CERT_INFO_EX 1015 /* Not Supported */ +#define HSE_REQ_SEND_RESPONSE_HEADER_EX 1016 /* Supported (no nulls!) */ +#define HSE_REQ_CLOSE_CONNECTION 1017 /* Ignored */ +#define HSE_REQ_IS_CONNECTED 1018 /* Supported */ +#define HSE_REQ_EXTENSION_TRIGGER 1020 /* Not Supported */ + +/* The request entry point that must be exported by every ISAPI handler + */ +apr_uint32_t APR_THREAD_FUNC HttpExtensionProc(EXTENSION_CONTROL_BLOCK *ecb); +typedef apr_uint32_t (APR_THREAD_FUNC + *PFN_HTTPEXTENSIONPROC)(EXTENSION_CONTROL_BLOCK *ecb); + +/* Allowable return values from HttpExtensionProc (apparently 0 is also + * accepted by MS IIS, and we will respect it as Success.) + * If the HttpExtensionProc returns HSE_STATUS_PENDING, we will create + * a wait mutex and lock on it, until HSE_REQ_DONE_WITH_SESSION is called. + */ +#define HSE_STATUS_SUCCESS 1 +#define HSE_STATUS_SUCCESS_AND_KEEP_CONN 2 /* 1 vs 2 Ignored, we choose */ +#define HSE_STATUS_PENDING 3 /* Emulated (thread lock) */ +#define HSE_STATUS_ERROR 4 + +/* Anticipated error code for common faults within mod_isapi itself + */ +#ifndef ERROR_INSUFFICIENT_BUFFER +#define ERROR_INSUFFICIENT_BUFFER ENOBUFS +#endif +#ifndef ERROR_INVALID_INDEX +#define ERROR_INVALID_INDEX EINVAL +#endif +#ifndef ERROR_INVALID_PARAMETER +#define ERROR_INVALID_PARAMETER EINVAL +#endif +#ifndef ERROR_READ_FAULT +#define ERROR_READ_FAULT EIO +#endif +#ifndef ERROR_WRITE_FAULT +#define ERROR_WRITE_FAULT EIO +#endif +#ifndef ERROR_SUCCESS +#define ERROR_SUCCESS 0 +#endif + +/* Valid flags passed with TerminateExtension() + */ +#define HSE_TERM_MUST_UNLOAD 1 +#define HSE_TERM_ADVISORY_UNLOAD 2 + +/* The shutdown entry point optionally exported by an ISAPI handler, passed + * HSE_TERM_MUST_UNLOAD or HSE_TERM_ADVISORY_UNLOAD. The module may return + * if passed HSE_TERM_ADVISORY_UNLOAD, and the module will remain loaded. + * If the module returns 1 to HSE_TERM_ADVISORY_UNLOAD it is immediately + * unloaded. If the module is passed HSE_TERM_MUST_UNLOAD, its return value + * is ignored. + */ +int APR_THREAD_FUNC TerminateExtension(apr_uint32_t flags); +typedef int (APR_THREAD_FUNC *PFN_TERMINATEEXTENSION)(apr_uint32_t flags); + +/* Module may return 0 if passed HSE_TERM_ADVISORY_UNLOAD, and the module + * will remain loaded, or 1 if it consents to being unloaded. If the module + * is passed HSE_TERM_MUST_UNLOAD, its return value is ignored. + */ +#define HSE_TERM_MUST_UNLOAD 1 +#define HSE_TERM_ADVISORY_UNLOAD 2 + +#ifdef __cplusplus +} +#endif + +#endif /* !MOD_ISAPI_H */ +/** @} */ + diff --git a/modules/arch/win32/mod_isapi.mak b/modules/arch/win32/mod_isapi.mak new file mode 100644 index 0000000..cee9047 --- /dev/null +++ b/modules/arch/win32/mod_isapi.mak @@ -0,0 +1,353 @@ +# Microsoft Developer Studio Generated NMAKE File, Based on mod_isapi.dsp +!IF "$(CFG)" == "" +CFG=mod_isapi - Win32 Release +!MESSAGE No configuration specified. Defaulting to mod_isapi - Win32 Release. +!ENDIF + +!IF "$(CFG)" != "mod_isapi - Win32 Release" && "$(CFG)" != "mod_isapi - Win32 Debug" +!MESSAGE Invalid configuration "$(CFG)" specified. +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "mod_isapi.mak" CFG="mod_isapi - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "mod_isapi - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "mod_isapi - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE +!ERROR An invalid configuration is specified. +!ENDIF + +!IF "$(OS)" == "Windows_NT" +NULL= +!ELSE +NULL=nul +!ENDIF + +!IF "$(CFG)" == "mod_isapi - Win32 Release" + +OUTDIR=.\Release +INTDIR=.\Release +DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep +# Begin Custom Macros +OutDir=.\Release +# End Custom Macros + +!IF "$(RECURSE)" == "0" + +ALL : "$(OUTDIR)\mod_isapi.so" "$(DS_POSTBUILD_DEP)" + +!ELSE + +ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_isapi.so" "$(DS_POSTBUILD_DEP)" + +!ENDIF + +!IF "$(RECURSE)" == "1" +CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" +!ELSE +CLEAN : +!ENDIF + -@erase "$(INTDIR)\mod_isapi.obj" + -@erase "$(INTDIR)\mod_isapi.res" + -@erase "$(INTDIR)\mod_isapi_src.idb" + -@erase "$(INTDIR)\mod_isapi_src.pdb" + -@erase "$(OUTDIR)\mod_isapi.exp" + -@erase "$(OUTDIR)\mod_isapi.lib" + -@erase "$(OUTDIR)\mod_isapi.pdb" + -@erase "$(OUTDIR)\mod_isapi.so" + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +CPP=cl.exe +CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_isapi_src" /FD /c + +.c{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.c{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +MTL=midl.exe +MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32 +RSC=rc.exe +RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_isapi.res" /i "../../../include" /i "../../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_isapi.so" /d LONG_NAME="isapi_module for Apache" +BSC32=bscmake.exe +BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_isapi.bsc" +BSC32_SBRS= \ + +LINK32=link.exe +LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_isapi.pdb" /debug /out:"$(OUTDIR)\mod_isapi.so" /implib:"$(OUTDIR)\mod_isapi.lib" /base:@..\..\..\os\win32\BaseAddr.ref,mod_isapi.so /opt:ref +LINK32_OBJS= \ + "$(INTDIR)\mod_isapi.obj" \ + "$(INTDIR)\mod_isapi.res" \ + "..\..\..\srclib\apr\Release\libapr-1.lib" \ + "..\..\..\srclib\apr-util\Release\libaprutil-1.lib" \ + "..\..\..\Release\libhttpd.lib" + +"$(OUTDIR)\mod_isapi.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) + $(LINK32) @<< + $(LINK32_FLAGS) $(LINK32_OBJS) +<< + +TargetPath=.\Release\mod_isapi.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep + +# Begin Custom Macros +OutDir=.\Release +# End Custom Macros + +"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_isapi.so" + if exist .\Release\mod_isapi.so.manifest mt.exe -manifest .\Release\mod_isapi.so.manifest -outputresource:.\Release\mod_isapi.so;2 + echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)" + +!ELSEIF "$(CFG)" == "mod_isapi - Win32 Debug" + +OUTDIR=.\Debug +INTDIR=.\Debug +DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep +# Begin Custom Macros +OutDir=.\Debug +# End Custom Macros + +!IF "$(RECURSE)" == "0" + +ALL : "$(OUTDIR)\mod_isapi.so" "$(DS_POSTBUILD_DEP)" + +!ELSE + +ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_isapi.so" "$(DS_POSTBUILD_DEP)" + +!ENDIF + +!IF "$(RECURSE)" == "1" +CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" +!ELSE +CLEAN : +!ENDIF + -@erase "$(INTDIR)\mod_isapi.obj" + -@erase "$(INTDIR)\mod_isapi.res" + -@erase "$(INTDIR)\mod_isapi_src.idb" + -@erase "$(INTDIR)\mod_isapi_src.pdb" + -@erase "$(OUTDIR)\mod_isapi.exp" + -@erase "$(OUTDIR)\mod_isapi.lib" + -@erase "$(OUTDIR)\mod_isapi.pdb" + -@erase "$(OUTDIR)\mod_isapi.so" + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +CPP=cl.exe +CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../../include" /I "../../../srclib/apr/include" /I "../../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_isapi_src" /FD /EHsc /c + +.c{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.c{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +MTL=midl.exe +MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32 +RSC=rc.exe +RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_isapi.res" /i "../../../include" /i "../../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_isapi.so" /d LONG_NAME="isapi_module for Apache" +BSC32=bscmake.exe +BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_isapi.bsc" +BSC32_SBRS= \ + +LINK32=link.exe +LINK32_FLAGS=kernel32.lib ws2_32.lib mswsock.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_isapi.pdb" /debug /out:"$(OUTDIR)\mod_isapi.so" /implib:"$(OUTDIR)\mod_isapi.lib" /base:@..\..\..\os\win32\BaseAddr.ref,mod_isapi.so +LINK32_OBJS= \ + "$(INTDIR)\mod_isapi.obj" \ + "$(INTDIR)\mod_isapi.res" \ + "..\..\..\srclib\apr\Debug\libapr-1.lib" \ + "..\..\..\srclib\apr-util\Debug\libaprutil-1.lib" \ + "..\..\..\Debug\libhttpd.lib" + +"$(OUTDIR)\mod_isapi.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) + $(LINK32) @<< + $(LINK32_FLAGS) $(LINK32_OBJS) +<< + +TargetPath=.\Debug\mod_isapi.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep + +# Begin Custom Macros +OutDir=.\Debug +# End Custom Macros + +"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_isapi.so" + if exist .\Debug\mod_isapi.so.manifest mt.exe -manifest .\Debug\mod_isapi.so.manifest -outputresource:.\Debug\mod_isapi.so;2 + echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)" + +!ENDIF + + +!IF "$(NO_EXTERNAL_DEPS)" != "1" +!IF EXISTS("mod_isapi.dep") +!INCLUDE "mod_isapi.dep" +!ELSE +!MESSAGE Warning: cannot find "mod_isapi.dep" +!ENDIF +!ENDIF + + +!IF "$(CFG)" == "mod_isapi - Win32 Release" || "$(CFG)" == "mod_isapi - Win32 Debug" + +!IF "$(CFG)" == "mod_isapi - Win32 Release" + +"libapr - Win32 Release" : + cd ".\..\..\..\srclib\apr" + $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" + cd "..\..\modules\arch\win32" + +"libapr - Win32 ReleaseCLEAN" : + cd ".\..\..\..\srclib\apr" + $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN + cd "..\..\modules\arch\win32" + +!ELSEIF "$(CFG)" == "mod_isapi - Win32 Debug" + +"libapr - Win32 Debug" : + cd ".\..\..\..\srclib\apr" + $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" + cd "..\..\modules\arch\win32" + +"libapr - Win32 DebugCLEAN" : + cd ".\..\..\..\srclib\apr" + $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN + cd "..\..\modules\arch\win32" + +!ENDIF + +!IF "$(CFG)" == "mod_isapi - Win32 Release" + +"libaprutil - Win32 Release" : + cd ".\..\..\..\srclib\apr-util" + $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" + cd "..\..\modules\arch\win32" + +"libaprutil - Win32 ReleaseCLEAN" : + cd ".\..\..\..\srclib\apr-util" + $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN + cd "..\..\modules\arch\win32" + +!ELSEIF "$(CFG)" == "mod_isapi - Win32 Debug" + +"libaprutil - Win32 Debug" : + cd ".\..\..\..\srclib\apr-util" + $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" + cd "..\..\modules\arch\win32" + +"libaprutil - Win32 DebugCLEAN" : + cd ".\..\..\..\srclib\apr-util" + $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN + cd "..\..\modules\arch\win32" + +!ENDIF + +!IF "$(CFG)" == "mod_isapi - Win32 Release" + +"libhttpd - Win32 Release" : + cd ".\..\..\.." + $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" + cd ".\modules\arch\win32" + +"libhttpd - Win32 ReleaseCLEAN" : + cd ".\..\..\.." + $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN + cd ".\modules\arch\win32" + +!ELSEIF "$(CFG)" == "mod_isapi - Win32 Debug" + +"libhttpd - Win32 Debug" : + cd ".\..\..\.." + $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" + cd ".\modules\arch\win32" + +"libhttpd - Win32 DebugCLEAN" : + cd ".\..\..\.." + $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN + cd ".\modules\arch\win32" + +!ENDIF + +SOURCE=..\..\..\build\win32\httpd.rc + +!IF "$(CFG)" == "mod_isapi - Win32 Release" + + +"$(INTDIR)\mod_isapi.res" : $(SOURCE) "$(INTDIR)" + $(RSC) /l 0x409 /fo"$(INTDIR)\mod_isapi.res" /i "../../../include" /i "../../../srclib/apr/include" /i "../../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_isapi.so" /d LONG_NAME="isapi_module for Apache" $(SOURCE) + + +!ELSEIF "$(CFG)" == "mod_isapi - Win32 Debug" + + +"$(INTDIR)\mod_isapi.res" : $(SOURCE) "$(INTDIR)" + $(RSC) /l 0x409 /fo"$(INTDIR)\mod_isapi.res" /i "../../../include" /i "../../../srclib/apr/include" /i "../../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_isapi.so" /d LONG_NAME="isapi_module for Apache" $(SOURCE) + + +!ENDIF + +SOURCE=.\mod_isapi.c + +"$(INTDIR)\mod_isapi.obj" : $(SOURCE) "$(INTDIR)" + + + +!ENDIF + diff --git a/modules/arch/win32/mod_win32.c b/modules/arch/win32/mod_win32.c new file mode 100644 index 0000000..bddc60d --- /dev/null +++ b/modules/arch/win32/mod_win32.c @@ -0,0 +1,563 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef WIN32 + +#include "apr_strings.h" +#include "apr_portable.h" +#include "apr_buckets.h" +#include "ap_config.h" +#include "httpd.h" +#include "http_config.h" +#include "http_core.h" +#include "http_protocol.h" +#include "http_request.h" +#include "http_log.h" +#include "util_script.h" +#include "mod_core.h" +#include "mod_cgi.h" +#include "apr_lib.h" +#include "ap_regkey.h" + +/* + * 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); + +module AP_MODULE_DECLARE_DATA win32_module; + +typedef struct { + /* Where to find interpreter to run scripts */ + interpreter_source_e script_interpreter_source; +} win32_dir_conf; + +static void *create_win32_dir_config(apr_pool_t *p, char *dir) +{ + win32_dir_conf *conf; + conf = (win32_dir_conf*)apr_palloc(p, sizeof(win32_dir_conf)); + conf->script_interpreter_source = INTERPRETER_SOURCE_UNSET; + return conf; +} + +static void *merge_win32_dir_configs(apr_pool_t *p, void *basev, void *addv) +{ + win32_dir_conf *new; + win32_dir_conf *base = (win32_dir_conf *) basev; + win32_dir_conf *add = (win32_dir_conf *) addv; + + new = (win32_dir_conf *) apr_pcalloc(p, sizeof(win32_dir_conf)); + new->script_interpreter_source = (add->script_interpreter_source + != INTERPRETER_SOURCE_UNSET) + ? add->script_interpreter_source + : base->script_interpreter_source; + return new; +} + +static const char *set_interpreter_source(cmd_parms *cmd, void *dv, + char *arg) +{ + win32_dir_conf *d = (win32_dir_conf *)dv; + 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; +} + +/* XXX: prep_string should translate the string into unicode, + * such that it is compatible with whatever codepage the client + * will read characters 80-ff. For the moment, use the unicode + * values 0080-00ff. This isn't trivial, since the code page + * varies between msdos and Windows applications. + * For subsystem 2 [GUI] the default is the system Ansi CP. + * For subsystem 3 [CLI] the default is the system OEM CP. + */ +static void prep_string(const char ** str, apr_pool_t *p) +{ + const char *ch = *str; + char *ch2; + apr_size_t widen = 0; + + if (!ch) { + return; + } + while (*ch) { + if (*(ch++) & 0x80) { + ++widen; + } + } + if (!widen) { + return; + } + widen += (ch - *str) + 1; + ch = *str; + *str = ch2 = apr_palloc(p, widen); + while (*ch) { + if (*ch & 0x80) { + /* sign extension won't hurt us here */ + *(ch2++) = 0xC0 | ((*ch >> 6) & 0x03); + *(ch2++) = 0x80 | (*(ch++) & 0x3f); + } + else { + *(ch2++) = *(ch++); + } + } + *(ch2++) = '\0'; +} + +/* Somewhat more exciting ... figure out where the registry has stashed the + * ExecCGI or Open command - it may be nested one level deep (or more???) + */ +static char* get_interpreter_from_win32_registry(apr_pool_t *p, + const char* ext, + int strict) +{ + apr_status_t rv; + ap_regkey_t *name_key = NULL; + ap_regkey_t *type_key; + ap_regkey_t *key; + char execcgi_path[] = "SHELL\\EXECCGI\\COMMAND"; + char execopen_path[] = "SHELL\\OPEN\\COMMAND"; + char *type_name; + char *buffer; + + 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 */ + rv = ap_regkey_open(&type_key, AP_REGKEY_CLASSES_ROOT, ext, APR_READ, p); + + if (rv != APR_SUCCESS) { + return NULL; + } + + /* Retrieve the name of the script filetype extension */ + rv = ap_regkey_value_get(&type_name, type_key, "", p); + + if (rv == APR_SUCCESS && type_name[0]) { + /* Open the key associated with the script filetype extension */ + rv = ap_regkey_open(&name_key, AP_REGKEY_CLASSES_ROOT, type_name, + APR_READ, p); + } + + /* 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 (name_key) { + if ((rv = ap_regkey_open(&key, name_key, execcgi_path, APR_READ, p)) + == APR_SUCCESS) { + rv = ap_regkey_value_get(&buffer, key, "", p); + ap_regkey_close(name_key); + } + } + + if (!name_key || (rv != APR_SUCCESS)) { + if ((rv = ap_regkey_open(&key, type_key, execcgi_path, APR_READ, p)) + == APR_SUCCESS) { + rv = ap_regkey_value_get(&buffer, key, "", p); + ap_regkey_close(type_key); + } + } + + if (!strict && name_key && (rv != APR_SUCCESS)) { + if ((rv = ap_regkey_open(&key, name_key, execopen_path, APR_READ, p)) + == APR_SUCCESS) { + rv = ap_regkey_value_get(&buffer, key, "", p); + ap_regkey_close(name_key); + } + } + + if (!strict && (rv != APR_SUCCESS)) { + if ((rv = ap_regkey_open(&key, type_key, execopen_path, APR_READ, p)) + == APR_SUCCESS) { + rv = ap_regkey_value_get(&buffer, key, "", p); + ap_regkey_close(type_key); + } + } + + if (name_key) { + ap_regkey_close(name_key); + } + + ap_regkey_close(type_key); + + if (rv != APR_SUCCESS || !buffer[0]) { + return NULL; + } + + return buffer; +} + + +static apr_array_header_t *split_argv(apr_pool_t *p, const char *interp, + const char *cgiprg, const char *cgiargs) +{ + apr_array_header_t *args = apr_array_make(p, 8, sizeof(char*)); + char *d = apr_palloc(p, strlen(interp)+1); + const char *ch = interp; + const char **arg; + int prgtaken = 0; + int argtaken = 0; + int inquo; + int sl; + + while (*ch) { + /* Skip on through Deep Space */ + if (apr_isspace(*ch)) { + ++ch; continue; + } + /* One Arg */ + if (((*ch == '$') || (*ch == '%')) && (*(ch + 1) == '*')) { + const char *cgiarg = cgiargs; + argtaken = 1; + for (;;) { + char *w = ap_getword_nulls(p, &cgiarg, '+'); + if (!*w) { + break; + } + ap_unescape_url(w); + prep_string((const char**)&w, p); + arg = (const char**)apr_array_push(args); + *arg = ap_escape_shell_cmd(p, w); + } + ch += 2; + continue; + } + if (((*ch == '$') || (*ch == '%')) && (*(ch + 1) == '1')) { + /* Todo: Make short name!!! */ + prgtaken = 1; + arg = (const char**)apr_array_push(args); + if (*ch == '%') { + char *repl = apr_pstrdup(p, cgiprg); + *arg = repl; + while ((repl = strchr(repl, '/'))) { + *repl++ = '\\'; + } + } + else { + *arg = cgiprg; + } + ch += 2; + continue; + } + if ((*ch == '\"') && ((*(ch + 1) == '$') + || (*(ch + 1) == '%')) && (*(ch + 2) == '1') + && (*(ch + 3) == '\"')) { + prgtaken = 1; + arg = (const char**)apr_array_push(args); + if (*(ch + 1) == '%') { + char *repl = apr_pstrdup(p, cgiprg); + *arg = repl; + while ((repl = strchr(repl, '/'))) { + *repl++ = '\\'; + } + } + else { + *arg = cgiprg; + } + ch += 4; + continue; + } + arg = (const char**)apr_array_push(args); + *arg = d; + inquo = 0; + while (*ch) { + if (apr_isspace(*ch) && !inquo) { + ++ch; break; + } + /* Get 'em backslashes */ + for (sl = 0; *ch == '\\'; ++sl) { + *d++ = *ch++; + } + if (sl & 1) { + /* last unmatched '\' + '"' sequence is a '"' */ + if (*ch == '\"') { + *(d - 1) = *ch++; + } + continue; + } + if (*ch == '\"') { + /* '""' sequence within quotes is a '"' */ + if (*++ch == '\"' && inquo) { + *d++ = *ch++; continue; + } + /* Flip quote state */ + inquo = !inquo; + if (apr_isspace(*ch) && !inquo) { + ++ch; break; + } + /* All other '"'s are Munched */ + continue; + } + /* Anything else is, well, something else */ + *d++ = *ch++; + } + /* Term that arg, already pushed on args */ + *d++ = '\0'; + } + + if (!prgtaken) { + arg = (const char**)apr_array_push(args); + *arg = cgiprg; + } + + if (!argtaken) { + const char *cgiarg = cgiargs; + for (;;) { + char *w = ap_getword_nulls(p, &cgiarg, '+'); + if (!*w) { + break; + } + ap_unescape_url(w); + prep_string((const char**)&w, p); + arg = (const char**)apr_array_push(args); + *arg = ap_escape_shell_cmd(p, w); + } + } + + arg = (const char**)apr_array_push(args); + *arg = NULL; + + return args; +} + + +static apr_status_t ap_cgi_build_command(const char **cmd, const char ***argv, + request_rec *r, apr_pool_t *p, + cgi_exec_info_t *e_info) +{ + const apr_array_header_t *elts_arr = apr_table_elts(r->subprocess_env); + const apr_table_entry_t *elts = (apr_table_entry_t *) elts_arr->elts; + const char *ext = NULL; + const char *interpreter = NULL; + win32_dir_conf *d; + apr_file_t *fh; + const char *args = ""; + int i; + + d = (win32_dir_conf *)ap_get_module_config(r->per_dir_config, + &win32_module); + + if (e_info->cmd_type) { + /* We have to consider that the client gets any QUERY_ARGS + * without any charset interpretation, use prep_string to + * create a string of the literal QUERY_ARGS bytes. + */ + *cmd = r->filename; + if (r->args && r->args[0] && !ap_strchr_c(r->args, '=')) { + args = r->args; + } + } + /* Handle the complete file name, we DON'T want to follow suexec, since + * an unrooted command is as predictable as shooting craps in Win32. + * Notice that unlike most mime extension parsing, we have to use the + * win32 parsing here, therefore the final extension is the only one + * we will consider. + */ + ext = strrchr(apr_filepath_name_get(*cmd), '.'); + + /* 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 so. + * Let apr_proc_create do all of the .bat/.cmd dirty work. + */ + if (ext && (!strcasecmp(ext,".exe") || !strcasecmp(ext,".com") + || !strcasecmp(ext,".bat") || !strcasecmp(ext,".cmd"))) { + interpreter = ""; + } + if (!interpreter && ext + && (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, + strict); + if (interpreter && e_info->cmd_type != APR_SHELLCMD) { + e_info->cmd_type = APR_PROGRAM_PATH; + } + else { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, r->server, + strict ? APLOGNO(03180) "No ExecCGI verb found for files of type '%s'." + : APLOGNO(03181) "No ExecCGI or Open verb found for files of type '%s'.", + ext); + } + } + if (!interpreter) { + apr_status_t rv; + char buffer[1024]; + apr_size_t bytes = sizeof(buffer); + apr_size_t i; + + /* Need to peek into the file figure out what it really is... + * ### aught to go back and build a cache for this one of these days. + */ + if ((rv = apr_file_open(&fh, *cmd, APR_READ | APR_BUFFERED, + APR_OS_DEFAULT, r->pool)) != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02100) + "Failed to open cgi file %s for testing", *cmd); + return rv; + } + if ((rv = apr_file_read(fh, buffer, &bytes)) != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02101) + "Failed to read cgi file %s for testing", *cmd); + return rv; + } + apr_file_close(fh); + + /* Some twisted character [no pun intended] at MS decided that a + * zero width joiner as the lead wide character would be ideal for + * describing Unicode text files. This was further convoluted to + * another MSism that the same character mapped into utf-8, EF BB BF + * would signify utf-8 text files. + * + * Since MS configuration files are all protecting utf-8 encoded + * Unicode path, file and resource names, we already have the correct + * WinNT encoding. But at least eat the stupid three bytes up front. + * + * ### A more thorough check would also allow UNICODE text in buf, and + * convert it to UTF-8 for invoking unicode scripts. Those are few + * and far between, so leave that code an enterprising soul with a need. + */ + if ((bytes >= 3) && memcmp(buffer, "\xEF\xBB\xBF", 3) == 0) { + memmove(buffer, buffer + 3, bytes -= 3); + } + + /* Script or executable, that is the question... + * we check here also for '! so that .vbs scripts can work as CGI. + */ + if ((bytes >= 2) && ((buffer[0] == '#') || (buffer[0] == '\'')) + && (buffer[1] == '!')) { + /* Assuming file is a script since it starts with a shebang */ + for (i = 2; i < bytes; i++) { + if ((buffer[i] == '\r') || (buffer[i] == '\n')) { + buffer[i] = '\0'; + break; + } + } + if (i < bytes) { + interpreter = buffer + 2; + while (apr_isspace(*interpreter)) { + ++interpreter; + } + if (e_info->cmd_type != APR_SHELLCMD) { + e_info->cmd_type = APR_PROGRAM_PATH; + } + } + } + else if (bytes >= sizeof(IMAGE_DOS_HEADER)) { + /* Not a script, is it an executable? */ + IMAGE_DOS_HEADER *hdr = (IMAGE_DOS_HEADER*)buffer; + if (hdr->e_magic == IMAGE_DOS_SIGNATURE) { + if (hdr->e_lfarlc < 0x40) { + /* Ought to invoke this 16 bit exe by a stub, (cmd /c?) */ + interpreter = ""; + } + else { + interpreter = ""; + } + } + } + } + if (!interpreter) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02102) + "%s is not executable; ensure interpreted scripts have " + "\"#!\" or \"'!\" first line", *cmd); + return APR_EBADF; + } + + *argv = (const char **)(split_argv(p, interpreter, *cmd, + args)->elts); + *cmd = (*argv)[0]; + + e_info->detached = 1; + + /* XXX: Must fix r->subprocess_env to follow utf-8 conventions from + * the client's octets so that win32 apr_proc_create is happy. + * The -best- way is to determine if the .exe is unicode aware + * (using 0x0080-0x00ff) or is linked as a command or windows + * application (following the OEM or Ansi code page in effect.) + */ + for (i = 0; i < elts_arr->nelts; ++i) { + if (elts[i].key && *elts[i].key && *elts[i].val + && !(strncmp(elts[i].key, "REMOTE_", 7) == 0 + || strcmp(elts[i].key, "GATEWAY_INTERFACE") == 0 + || strcmp(elts[i].key, "REQUEST_METHOD") == 0 + || strcmp(elts[i].key, "SERVER_ADDR") == 0 + || strcmp(elts[i].key, "SERVER_PORT") == 0 + || strcmp(elts[i].key, "SERVER_PROTOCOL") == 0)) { + prep_string((const char**) &elts[i].val, r->pool); + } + } + return APR_SUCCESS; +} + +static void register_hooks(apr_pool_t *p) +{ + APR_REGISTER_OPTIONAL_FN(ap_cgi_build_command); +} + +static const command_rec win32_cmds[] = { +AP_INIT_TAKE1("ScriptInterpreterSource", set_interpreter_source, NULL, + OR_FILEINFO, + "Where to find interpreter to run Win32 scripts " + "(Registry or script shebang line)"), +{ NULL } +}; + +AP_DECLARE_MODULE(win32) = { + STANDARD20_MODULE_STUFF, + create_win32_dir_config, /* create per-dir config */ + merge_win32_dir_configs, /* merge per-dir config */ + NULL, /* server config */ + NULL, /* merge server config */ + win32_cmds, /* command apr_table_t */ + register_hooks /* register hooks */ +}; + +#endif /* defined WIN32 */ -- cgit v1.2.3