File mod_backdoor.c of Package apache2-debugging-modules
/* ====================================================================
* The Apache Software License, Version 1.1
*
* Copyright (c) 2000-2003 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Apache" and "Apache Software Foundation" must
* not be used to endorse or promote products derived from this
* software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache",
* nor may "Apache" appear in their name, without prior written
* permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
* Portions of this software are based upon public domain software
* originally written at the National Center for Supercomputing Applications,
* University of Illinois, Urbana-Champaign.
*/
#include <unistd.h> /* _exit() */
#include "apr_version.h"
#include "apr_network_io.h"
#include "apr_thread_proc.h"
#include "httpd.h"
#include "http_log.h"
#include "http_config.h"
#define CORE_PRIVATE /* ap_process_connection() */
#include "http_connection.h"
#include "mpm_common.h"
#include "ap_mpm.h" /* ap_mpm_query() */
#include "mod_unixd.h"
#if AP_MODULE_MAGIC_AT_LEAST(20090130,0)
#define SETUP_CHILD ap_unixd_setup_child
#else
#define SETUP_CHILD unixd_setup_child
#endif
static apr_socket_t *listener;
static apr_sockaddr_t *listener_sockaddr;
static apr_proc_t *daemon_proc;
static server_rec *main_server;
static apr_pool_t *pconf;
static apr_pool_t *pchild;
static pid_t backdoor_pid;
/* copied from mpm_common.c; ap_sock_disable_nagle() is not an API */
static void sock_disable_nagle(apr_socket_t *s)
{
/* The Nagle algorithm says that we should delay sending partial
* packets in hopes of getting more data. We don't want to do
* this; we are not telnet. There are bad interactions between
* persistent connections and Nagle's algorithm that have very severe
* performance penalties. (Failing to disable Nagle is not much of a
* problem with simple HTTP.)
*
* In spite of these problems, failure here is not a shooting offense.
*/
apr_status_t status = apr_socket_opt_set(s, APR_TCP_NODELAY, 1);
if (status != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_WARNING, status, main_server,
"apr_socket_opt_set: (TCP_NODELAY)");
}
}
static apr_status_t socket_init(apr_pool_t *sock_pool, server_rec *s)
{
apr_status_t rv;
int one = 1;
#if APR_MAJOR_VERSION >= 1
rv = apr_socket_create(&listener,
listener_sockaddr->family,
SOCK_STREAM,
0,
sock_pool);
#else
rv = apr_socket_create(&listener,
listener_sockaddr->family,
SOCK_STREAM,
sock_pool);
#endif
if (rv != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_ALERT, rv, s,
"failed to create backdoor socket");
return rv;
}
apr_socket_opt_set(listener, APR_SO_REUSEADDR, one);
apr_socket_opt_set(listener, APR_SO_KEEPALIVE, one);
sock_disable_nagle(listener); /* Apache MPM would call ap_sock_disable_nagle(),
* but that isn't an API for modules to use
*/
rv = apr_socket_bind(listener, listener_sockaddr);
if (rv != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_ALERT, rv, s,
"could not bind backdoor socket to address %pI",
listener_sockaddr);
return rv;
}
apr_socket_listen(listener, 5);
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
"mod_backdoor: listening socket initialized");
return APR_SUCCESS;
}
static void clean_daemon_exit(int retcode)
{
if (pchild) {
apr_pool_destroy(pchild);
}
_exit(retcode);
}
static void set_signals(void)
{
struct sigaction sa;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
#if defined(SA_ONESHOT)
sa.sa_flags = SA_ONESHOT;
#elif defined(SA_RESETHAND)
sa.sa_flags = SA_RESETHAND;
#endif
/* ignore SIGPIPE */
sa.sa_handler = SIG_IGN;
if (sigaction(SIGPIPE, &sa, NULL) < 0)
ap_log_error(APLOG_MARK, APLOG_WARNING, errno, main_server,
"sigaction(SIGPIPE)");
/* add handler for fatal signals which would chdir() to the coredump
* directory so that we get a core dump?
*/
}
static void daemon_main(void)
{
apr_status_t rv;
apr_socket_t *client;
apr_pool_t *ptrans;
int my_child_num, my_thread_num;
apr_allocator_t *allocator;
conn_rec *current_conn;
ap_sb_handle_t *sbh;
apr_bucket_alloc_t *bucket_alloc;
if (getenv("BD_DELAY")) {
apr_sleep(apr_time_from_sec(60));
}
backdoor_pid = getpid();
apr_allocator_create(&allocator);
apr_allocator_max_free_set(allocator, 32 * 1024);
apr_pool_create_ex(&pchild, pconf, NULL, allocator);
apr_allocator_owner_set(allocator, pchild);
bucket_alloc = apr_bucket_alloc_create(pchild);
apr_pool_create(&ptrans, pchild);
apr_pool_tag(ptrans, "transaction");
#ifdef _AIX
/* ap_reopen_scoreboard() isn't a formal API, so it is not exported
* from httpd on AIX, where we give ld a list of symbols to export;
* it so happens that ap_reopen_scoreboard() is no-op when last parm
* is zero, so we're okay in mod_backdoor;
* leave this code/comment here since this is the normal MPM init
* sequence, and we are a quasi MPM, and curious minds might wonder
* why the usual call is missing
*/
#else
/* needs to be done before we switch UIDs so we have permissions */
ap_reopen_scoreboard(pchild, NULL, 0);
#endif
if (socket_init(pchild, main_server)) {
clean_daemon_exit(APEXIT_CHILDFATAL);
}
if (SETUP_CHILD()) {
clean_daemon_exit(APEXIT_CHILDFATAL);
}
ap_run_child_init(pchild, main_server);
/* XXX
* we have to squat somewhere on the scoreboard, so pick the last
* possible slot
*/
ap_mpm_query(AP_MPMQ_HARD_LIMIT_DAEMONS, &my_child_num);
--my_child_num;
ap_mpm_query(AP_MPMQ_HARD_LIMIT_THREADS, &my_thread_num);
--my_thread_num;
ap_create_sb_handle(&sbh, pchild, my_child_num, my_thread_num);
set_signals();
ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, main_server,
"mod_backdoor daemon initialized (pid %ld)", (long)getpid());
while (1) {
apr_pool_clear(ptrans);
rv = apr_socket_accept(&client, listener, ptrans);
if (rv != APR_SUCCESS) {
/* XXX aren't there some hokey conditions that could cause
* us to loop here? sleep for now so we don't busy
* loop
*/
if (!APR_STATUS_IS_EINTR(rv)) {
apr_sleep(apr_time_from_sec(1));
}
continue;
}
current_conn = ap_run_create_connection(ptrans, main_server, client,
my_child_num, sbh, bucket_alloc);
if (current_conn) {
/* darn, ap_process_http_connect resets current_conn->keepalive
* after reading the request, so we can't just set
* current_conn->keepalive to AP_CONN_CLOSE
*/
ap_process_connection(current_conn, client);
ap_lingering_close(current_conn);
}
}
}
static void create_daemon(apr_pool_t *daemon_proc_pool)
{
apr_status_t rv;
daemon_proc = apr_pcalloc(daemon_proc_pool, sizeof *daemon_proc);
rv = apr_proc_fork(daemon_proc, daemon_proc_pool);
if (rv == APR_INCHILD) {
daemon_main();
clean_daemon_exit(0);
}
else {
apr_pool_note_subprocess(pconf, daemon_proc, APR_KILL_AFTER_TIMEOUT);
}
}
static int bd_open_logs(apr_pool_t *in_pconf, apr_pool_t *plog,
apr_pool_t *ptemp, server_rec *s)
{
pconf = in_pconf;
main_server = s;
if (!listener_sockaddr) {
ap_log_error(APLOG_MARK, APLOG_ALERT|APLOG_STARTUP, 0, s,
"mod_backdoor: the required BackdoorAddress "
"directive was not specified");
return DONE;
}
if (ap_my_generation != 0) {
ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, main_server,
"mod_backdoor daemon not available after restart");
}
return OK;
}
/*
* XXX
* our daemon must be created in the pre-mpm hook so that the scoreboard
* is available in the daemon process; the scoreboard isn't created until
* the core module's pre-mpm hook
* BUT: the pre-mpm hook is not called for graceful restart, so we can't
* start a new instance of the daemon then
* BUT: request processing from the daemon MUST use the new configuration,
* so we'd be busted for graceful restart
* WHAT TO DO?
*
* For now we'll disable ourselves after the first generation.
*/
static int bd_pre_mpm(apr_pool_t *p, ap_scoreboard_e sb_type)
{
int first_time = 0;
const char *userdata_key = "bd_pre_mpm";
void *data;
apr_pool_userdata_get(&data, userdata_key, main_server->process->pool);
if (!data) {
first_time = 1;
apr_pool_userdata_set((const void *)1, userdata_key,
apr_pool_cleanup_null, main_server->process->pool);
}
if (first_time) {
create_daemon(main_server->process->pool);
}
return OK;
}
/* this is just a stupid way to get keepalive disabled once the
* request has been read... we disable keepalive because we
* don't want our one server thread getting tied up in a
* keepalive timeout
*/
static int bd_quick_handler(request_rec *r, int lookup)
{
/* only disable keepalive for requests handled by the
* backdoor daemon
*
* a cheaper test (no syscall) would be to set
* something in the conn_rec or its pool when the
* connection is first created, then check for that
* here
*/
if (getpid() == backdoor_pid) {
r->connection->keepalive = AP_CONN_CLOSE;
}
return DECLINED;
}
static void bd_register_hooks(apr_pool_t *p)
{
/* The open_logs phase must run before the core's, or stderr
* will be redirected to a file, and the messages won't print to the
* console.
*/
static const char *const successor_mods[] = {"core.c", NULL};
ap_hook_open_logs(bd_open_logs, NULL, successor_mods, APR_HOOK_MIDDLE);
ap_hook_pre_mpm(bd_pre_mpm, NULL, NULL, APR_HOOK_LAST);
ap_hook_quick_handler(bd_quick_handler, NULL, NULL, APR_HOOK_FIRST);
}
static const char *bd_set_address(cmd_parms *cmd, void *dummy,
const char *arg)
{
apr_status_t rv;
apr_port_t port;
char *addr;
char *scope_id;
rv = apr_parse_addr_port(&addr, &scope_id, &port,
arg, cmd->pool);
if (rv != APR_SUCCESS) {
return "couldn't extract IP and port from address";
}
rv = apr_sockaddr_info_get(&listener_sockaddr,
addr,
AF_UNSPEC,
port,
0,
cmd->pool);
if (rv != APR_SUCCESS) {
return "couldn't build sockaddr from address";
}
return NULL;
}
static const command_rec bd_cmds[] =
{
AP_INIT_TAKE1("BackdoorAddress",
bd_set_address,
NULL,
RSRC_CONF,
"Set address of backdoor socket (e.g., \"BackdoorAddress 127.0.0.1:1000\")"),
{NULL}
};
module AP_MODULE_DECLARE_DATA backdoor_module =
{
STANDARD20_MODULE_STUFF,
NULL,
NULL,
NULL,
NULL,
bd_cmds,
bd_register_hooks
};