mod_proxy_mapper.c

/* 
**  mod_proxy_mapper.c
*/ 

#include "httpd.h"
#include "http_config.h"
#include "http_log.h"
#include "http_protocol.h"
#include "http_request.h"
#include "http_core.h"
#include "ap_config.h"
#include "apr_strings.h"

#if 0 || defined(__GNUC__)      /* for syntax highlight */
#define strbegin(S, T)  \
    ({ size_t _l = strlen(T); \
       (strlen(S) >= _l && ! memcmp((S), (T), _l)); })
#else
#define strbegin(S, T)  \
    (strlen(S) >= strlen(T) && ! memcmp((S), (T), strlen(T)))
#endif

static int proxy_available = 0;

typedef enum mapper_type {
    MAPPER_UNDEFINED,
    MAPPER_NONE,
    MAPPER_FILE,
    MAPPER_URI,
} mapper_type;

typedef struct proxy_mapper_conf {
    mapper_type type;
    const char  *mapper;
} proxy_mapper_conf;

static void *
create_dir_config(apr_pool_t *p, char *dir)
{
    proxy_mapper_conf *conf
        = (proxy_mapper_conf *) apr_pcalloc(p, sizeof(proxy_mapper_conf));

    conf->type   = MAPPER_UNDEFINED;
    conf->mapper = NULL;

    return conf;
}

static const char *
set_proxy_mapper(cmd_parms *cmd, void *mconfig, const char *arg)
{
    proxy_mapper_conf *conf = (proxy_mapper_conf *) mconfig;
    int len;

    len = strlen(arg);
    if (! strcasecmp(arg, "none")) {
        conf->type   = MAPPER_NONE;
        conf->mapper = NULL;
    }
    else if (len >= 7 && ! memcmp(arg, "file://", 7)) {
        if (len == 7)
            return "file part of ProxyMapper is not specified.";
        /* @@TODO: check existence */

        conf->type   = MAPPER_FILE;
        conf->mapper = apr_pstrdup(cmd->pool, arg + 7);
    }
    else {
        conf->type   = MAPPER_URI;
        conf->mapper = apr_pstrdup(cmd->pool, arg);
    }

    return NULL;
}

static const command_rec proxy_mapper_cmds[] = {
    AP_INIT_TAKE1("ProxyMapper", set_proxy_mapper,
                  NULL, ACCESS_CONF | RSRC_CONF | OR_ALL,
                  "URI or path of mapper script"),
    { NULL }
};

static ap_filter_rec_t *null_input_filter_handle;
static ap_filter_rec_t *null_output_filter_handle;

static apr_status_t
null_input_filter(ap_filter_t *f, apr_bucket_brigade *bb,
                  ap_input_mode_t mode, apr_read_type_e block,
                  apr_off_t readbytes)
{
    apr_bucket *b;

    switch (mode) {

    case AP_MODE_INIT:
        return APR_SUCCESS;

    case AP_MODE_READBYTES:
    case AP_MODE_SPECULATIVE:
        b = apr_bucket_eos_create(f->c->bucket_alloc);
        APR_BRIGADE_INSERT_TAIL(bb, b);
        return APR_SUCCESS;

    default:
        return APR_EOF;

    }
}

static apr_status_t
null_output_filter(ap_filter_t *f, apr_bucket_brigade *bb)
{
    apr_brigade_destroy(bb);

    /* Yes, I know what I'm doing.
       This filter behaves like the bottom of output filters.
       So I do not call ap_pass_brigade() intentionally.  */
    return APR_SUCCESS;
}

static apr_status_t
set_null_input_filter(request_rec *r)
{
    ap_filter_t *f = apr_pcalloc(r->pool, sizeof(ap_filter_t));
    /* @@TODO: assert f */

    f->frec = null_input_filter_handle;
    f->r    = r;
    f->c    = r->connection;
    f->next = NULL;

    r->input_filters       = f;
    r->proto_input_filters = f;

    return APR_SUCCESS;
}

static apr_status_t
set_null_output_filter(request_rec *r)
{
    ap_filter_t *f = apr_pcalloc(r->pool, sizeof(ap_filter_t));
    /* @@TODO: assert f */

    f->frec = null_output_filter_handle;
    f->r    = r;
    f->c    = r->connection;
    f->next = NULL;

    r->output_filters       = f;
    r->proto_output_filters = f;

    return APR_SUCCESS;
}

module AP_MODULE_DECLARE_DATA proxy_mapper_module;

static int
hook_fixups(request_rec *r)
{
    proxy_mapper_conf   *conf;
    request_rec         *subreq = NULL;
    const char          *target = NULL;

    /*
     * no REQUEST_URI, it seems to be subreq-file; fast exit.
     */
    if (! r->uri || ! *(r->uri))
        return DECLINED;

    conf = (proxy_mapper_conf *) ap_get_module_config(r->per_dir_config,
                                                      &proxy_mapper_module);

    if (! conf)
        return DECLINED;

    if (conf->type == MAPPER_NONE || conf->type == MAPPER_UNDEFINED)
        return DECLINED;

    for (subreq = r->main; subreq; subreq = subreq->main) {
        if (! strcmp(subreq->uri, r->uri)) {
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                          "Proxy Mapper loop detected on URI %s.",
                              r->uri);
            return HTTP_FORBIDDEN;
        }
    }

    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
                  "Proxy Mapper start on URI %s.",
                      r->uri);

    switch (conf->type) {

    case MAPPER_FILE:
        if (! strcmp(r->canonical_filename, conf->mapper)) {
            ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r,
                          "Proxy Mapper itself detected on URI %s; DECLINED.",
                              r->uri);

            return DECLINED;
        }

        subreq = ap_sub_req_lookup_file(conf->mapper, r, NULL);
        break;

    case MAPPER_URI:
        if (! strcmp(r->uri, conf->mapper)) {
            ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r,
                          "Proxy Mapper itself detected on URI %s; DECLINED.",
                              r->uri);

            return DECLINED;
        }

        subreq = ap_sub_req_lookup_uri(conf->mapper, r, NULL);
        break;

    default:
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                      "Unsupported proxy mapper type (%d) for URI %s.",
                          conf->type, r->uri);
        return HTTP_FORBIDDEN;

    }

    if (subreq) {
        apr_status_t    status;
        apr_table_t     *outhdr;
        const char      *value;

        /* subreq might overwrite headers_in */
        subreq->headers_in = apr_table_copy(r->pool, r->headers_in);

        /* forbid use of request and response for subreq */
        set_null_input_filter(subreq);
        set_null_output_filter(subreq);

        /* set Content-Length to 0 */
        if (apr_table_get(subreq->headers_in, "Content-Length"))
            apr_table_set(subreq->headers_in, "Content-Length",
                                              apr_pstrdup(r->pool, "0"));

        status = ap_run_sub_req(subreq);
        if (status == 0 && subreq->status != 0)
            status = subreq->status;

        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
                      "subreq status = %d on URI %s.",
                          status, r->uri);

        if (ap_is_HTTP_REDIRECT(status)) {
            /* Redirect status is permitted. */
            status = OK;
        }
        else if (ap_is_HTTP_CLIENT_ERROR(status)) {
            /* Client Error (eg. NOT FOUND) => as is status */
            return status;
        }

        if (status != OK && status != HTTP_OK) {
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                          "Sub-request of proxy mapper failed: status = %d"
                          " on URI %s.",
                              status, r->uri);
            return HTTP_FORBIDDEN;      /* safety */
        }

        outhdr = apr_table_overlay(r->pool, subreq->err_headers_out,
                                            subreq->headers_out);

        do {
            value = apr_table_get(outhdr, "Location");
            if (value) {
                if (strbegin(value, "proxy:")) {
                    target = apr_pstrdup(r->pool, value);
                }
                else if (strbegin(value, "http://")
                 || strbegin(value, "https://")) {
                    target = apr_pstrcat(r->pool, "proxy:", value, NULL);
                }
                else {
                    /* UNSUPPORTED FEATURE */
                    ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
                                  "Unsupported redirection to '%s'"
                                  " on URI %s.",
                                      value, r->uri);
                }
            }
        } while (0);
    }
    else {
        if (conf->type == MAPPER_FILE || conf->type == MAPPER_URI) {
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                          "Lookup for proxy mapper sub-request failed"
                          " on URI %s.",
                              r->uri);
            return HTTP_FORBIDDEN;
        }
    }

    if (! target) {
        /* not for proxy remap */
        return DECLINED;
    }

    ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r,
                  "Proxy Mapper mapped URI %s to %s.",
                      r->uri, target);

    /* req->filename construction */
    r->filename = apr_pstrdup(r->pool, target);

    r->proxyreq = PROXYREQ_REVERSE;
    r->handler  = "proxy-server";

    return OK;
}

static int
hook_post_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp,
                 server_rec *s)
{
    proxy_available = (ap_find_linked_module("mod_proxy.c") != NULL);

    if (! proxy_available) {
        ap_log_error(APLOG_MARK, APLOG_CRIT, APR_EGENERAL, s,
                     "mod_proxy_mapper: you must enable mod_proxy.");
        return HTTP_INTERNAL_SERVER_ERROR;
    }

    return OK;
}

static void
register_hooks(apr_pool_t *p)
{
    /* fixup after mod_proxy, so that the proxied url will not
     * escape accidentally by mod_proxy's fixup.
     */
    static const char * const asz_pre[] = { "mod_proxy.c", NULL };

    null_input_filter_handle
        = ap_register_input_filter("NULL_INPUT_FILTER", null_input_filter,
                                   NULL, AP_FTYPE_CONTENT_SET);
    null_output_filter_handle
        = ap_register_output_filter("NULL_OUTPUT_FILTER", null_output_filter,
                                    NULL, AP_FTYPE_CONTENT_SET);

    ap_hook_post_config(hook_post_config, NULL, NULL, APR_HOOK_LAST);
    ap_hook_fixups(hook_fixups, asz_pre, NULL, APR_HOOK_FIRST);
}

/* Dispatch list for API hooks */
module AP_MODULE_DECLARE_DATA proxy_mapper_module = {
    STANDARD20_MODULE_STUFF, 
    create_dir_config,      /* create per-dir    config structures */
    NULL,                   /* merge  per-dir    config structures */
    NULL,                   /* create per-server config structures */
    NULL,                   /* merge  per-server config structures */
    proxy_mapper_cmds,      /* table of config file commands       */
    register_hooks          /* register hooks                      */
};

いつか書く