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 */ };
いつか書く