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
};
openSUSE Build Service is sponsored by