/* ** mod_curltunnel.c -- Apache sample curltunnel module ** [Autogenerated via ``apxs -n curltunnel -g''] ** ** ------------------------------------------------------------ ** Version: 0.3 (2001.08.08) ** ------------------------------------------------------------ ** ** Apache Module: mod_curtunnel ** ** Module implements HTTP-Reverse-Proxy / HTTP-Tunnel ** Using curl library (curl.haxx.se) ** It can be used like mod_proxy's ProxyPass-directirve ** (like CurlTunnelPass /xxxx http://where.to.go/yyyy) ** ** Main feature is that it tries to keep-alive connections ** to both directions (but not very hard...) ** ** ** ------------------------------------------------------------ ** To play with this module, first compile it into a ** DSO file and install it into Apache's libexec directory ** by running: ** ** $ apxs -c -i mod_curltunnel.c -l curl ** ** Then activate it in Apache's httpd.conf file, for instance ** for the URL /curltunnel, as follows: ** ** # httpd.conf ** LoadModule curltunnel_module libexec/mod_curltunnel.so ** ------------------------------------------------------------ ** ** ------------------------------------------------------------ ** This has some copied code from apache modules... ** ------------------------------------------------------------ ** */ /* The section for the Configure script: * MODULE-DEFINITION-START * Name: curltunnel_module * ConfigStart if expr "$LIBS" : '.*\(-lcurl\)' then echo " add libcurl already in LIBS" else echo " add libcurl to LIBS" LIBS="$LIBS -lcurl" fi * ConfigEnd * MODULE-DEFINITION-END */ #include "httpd.h" #include "http_config.h" #include "http_protocol.h" #include "http_log.h" #include "http_core.h" #include "ap_config.h" #include "util_date.h" #include "util_uri.h" #include #include module curltunnel_module; /* -------------------------------------------------------------- */ /* -------------------------------------------------------------- */ #define CURLDATA_MAGIC 38476198 CURL *curl = NULL; /* -------------------------------------------------------------- */ typedef struct { char *real; char *fake; array_header *raliases; table *pars; } curltunnel_alias; /* -------------------------------------------------------------- */ typedef struct { array_header *aliases; array_header *raliases; } curltunnel_srv_conf; typedef struct { char *url; int type; regex_t *reg; regex_t *fake; } curltunnel_dir_conf; /* -------------------------------------------------------------- */ /* * this structure is used in handling requests and * it's given to curl as FILE * sometimes. * */ typedef struct { int magic; CURL *curl; request_rec *r; const curltunnel_alias *a; curltunnel_srv_conf *conf; int size; int header; int read_inited; int res; int has_code; int do_buffer; int buffer_bytes; int buffer_size; char *buffer_ptr; } curldata; /* -------------------------------------------------------------- */ static char *curltunnel_buffer = NULL; static int curltunnel_cache_size = -1; /* -------------------------------------------------------------- */ /* -------------------------------------------------------------- */ static void *create_curltunnel_server(pool *p, server_rec *s) { curltunnel_srv_conf *ps = ap_pcalloc(p, sizeof(curltunnel_srv_conf)); ps->aliases = ap_make_array(p, 10, sizeof(curltunnel_alias)); ps->raliases = ap_make_array(p, 10, sizeof(curltunnel_alias)); return (void *) ps; } static void * merge_curltunnel_server(pool *p, void *basev, void *overridesv) { curltunnel_srv_conf *ps = ap_pcalloc(p, sizeof(curltunnel_srv_conf)); curltunnel_srv_conf *base = (curltunnel_srv_conf *) basev; curltunnel_srv_conf *overrides = (curltunnel_srv_conf *) overridesv; ps->aliases = ap_append_arrays(p, base->aliases, overrides->aliases); ps->raliases = ap_append_arrays(p, base->raliases, overrides->raliases); return (void *) ps; } /* -------------------------------------------------------------- */ /* -------------------------------------------------------------- */ #define CT_UNDEF -1 #define CT_NO 0 #define CT_PREFIX 1 #define CT_REGEX 2 static void *create_curltunnel_dir(pool *p, char *dir) { curltunnel_dir_conf *ps = ap_pcalloc(p, sizeof(curltunnel_dir_conf)); ps->type = -1; ps->fake = NULL; ps->reg = NULL; ps->url = NULL; return (void *) ps; } static void *merge_curltunnel_dir(pool *p, void *basev, void *addv) { curltunnel_dir_conf *d; curltunnel_dir_conf *b = (curltunnel_dir_conf *) basev; curltunnel_dir_conf *n = (curltunnel_dir_conf *) addv; return (n->type >= 0) ? n : b; } /* -------------------------------------------------------------- */ /* -------------------------------------------------------------- */ static void curltunnel_child_init(server_rec *s, pool *p) { } static void curltunnel_child_exit(server_rec *s, pool *p) { } /* -------------------------------------------------------------- */ static char *curltunnel_location_reverse_map(request_rec *r, const char *url, array_header *raliases) { void *sconf; curltunnel_alias *ent; int i, l1, l2; char *u; l1 = strlen(url); ent = (curltunnel_alias *)raliases->elts; for (i = 0; i < raliases->nelts; i++) { l2 = strlen(ent[i].real); if (l1 >= l2 && strncmp(ent[i].real, url, l2) == 0) { u = ap_pstrcat(r->pool, ent[i].fake, &url[l2], NULL); return (char *) ap_construct_url(r->pool, u, r); } } return (char *) url; } /* -------------------------------------------------------------- */ /* Translate the URL into a 'filename' */ /* -------------------------------------------------------------- */ static int alias_match(const char *uri, const char *alias_fakename) { const char *end_fakename = alias_fakename + strlen(alias_fakename); const char *aliasp = alias_fakename, *urip = uri; while (aliasp < end_fakename) { if (*aliasp == '/') { /* any number of '/' in the alias matches any number in * the supplied URI, but there must be at least one... */ if (*urip != '/') return 0; while (*aliasp == '/') ++aliasp; while (*urip == '/') ++urip; } else { /* Other characters are compared literally */ if (*urip++ != *aliasp++) return 0; } } /* Check last alias path component matched all the way */ if (aliasp[-1] != '/' && *urip != '\0' && *urip != '/') return 0; /* Return number of characters from URI which matched (may be * greater than length of alias, since we may have matched * doubled slashes) */ return urip - uri; } /* -------------------------------------------------------------- */ /* -------------------------------------------------------------- */ static int curltunnel_translate(request_rec *r) { void *sconf = r->server->module_config; curltunnel_srv_conf *conf = (curltunnel_srv_conf *) ap_get_module_config(sconf, &curltunnel_module); curltunnel_dir_conf *df = (curltunnel_dir_conf *) ap_get_module_config(r->per_dir_config, &curltunnel_module); int i, len; int ll = r->server->loglevel | APLOG_LEVELMASK; curltunnel_alias *ent = (curltunnel_alias *) conf->aliases->elts; if (r->proxyreq != NOT_PROXY) { /* someone has already set up the proxy, it was possibly ourselves * in proxy_detect */ return OK; } /* XXX: since r->uri has been manipulated already we're not really * compliant with RFC1945 at this point. But this probably isn't * an issue because this is a hybrid proxy/origin server. */ // if(ll >= APLOG_DEBUG) ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, "(cultunnel) matching (%s) [%d]", // r->uri, sconf->aliases->nelts); for (i = 0; i < conf->aliases->nelts; i++) { len = alias_match(r->uri, ent[i].fake); if(ll >= APLOG_DEBUG) ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, r, "(cultunnel) match (%s) fake(%s) real(%s) [%d]", r->uri, ent[i].fake, ent[i].real, len); if (len > 0) { char *n; if((ent[i].real[0] == '!') && (ent[i].real[1] == 0)) return DECLINED; r->filename = ap_pstrcat(r->pool, "/curltunnel/", ent[i].real, r->uri + len, NULL); r->handler = "curltunnel-handler"; n = ap_pstrcat(r->pool, ent[i].real, r->uri + len, NULL); ap_table_setn(r->notes,"curltunnel", n); ap_table_setn(r->notes,"curltunnel-alias", (char *) &ent[i]); return OK; } } return DECLINED; } /* -------------------------------------------------------------- */ static void curltunnel_cleanup(void *datap) { if(curl == NULL) { ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, NULL, "curl-variable is NULL"); return; } curl_easy_cleanup(curl); curl = NULL; } static void slist_cleanup(void *datap) { struct curl_slist *s = datap; if(s != NULL) curl_slist_free_all(s); } /* -------------------------------------------------------------- */ /* -------------------------------------------------------------- */ /* -------------------------------------------------------------- */ /* -------------------------------------------------------------- */ static size_t curltunnel_read( void *ptr, size_t size, size_t nmemb, void *stream) { curldata *c = (curldata *) stream; request_rec *r = c->r; int res; int len; int siz = size * nmemb; if(!c->read_inited) { c->read_inited = 1; if ((res = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR))) { ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, r, "(curltunnel) cannot setup client-block"); c->res = res; return -1; } if (!ap_should_client_block(r)) { ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, "(curltunnel) should client block"); c->res = HTTP_FORBIDDEN; return -1; } } ap_hard_timeout("(curltunnel) get post data", r); len = ap_get_client_block(r, ptr, siz); ap_reset_timeout(r); return len; } /* -------------------------------------------------------------- */ static void send_header(curldata *c) { request_rec *r = c->r; array_header *arr; table_entry *ent; int i; int cnt; int (*func)(); if(c->header) return; // ----------------------------------------------- // check if any header filters // ----------------------------------------------- arr = ap_table_elts(r->notes); cnt = arr->nelts; ent = (table_entry *) arr->elts; for(i=0; i < cnt; i++) { char *k; if(!(k = ent[i].key)) continue; if(*k != 'h') continue; if(strcmp(k,"header-filter")) continue; func = (int (*)()) ent[i].val; if(func == NULL) continue; func(r,r->headers_out); } // ----------------------------------------------- c->header = 1; ap_send_http_header(r); } /* -------------------------------------------------------------- */ static size_t curltunnel_write( void *ptr, size_t psize, size_t nmemb, void *stream) { int siz = psize * nmemb; curldata *c = (curldata *) stream; request_rec *r = c->r; if(c->magic != CURLDATA_MAGIC) abort(); if(c->do_buffer) { if((c->buffer_bytes + siz) <= c->buffer_size) { memcpy(c->buffer_ptr, ptr, siz); c->buffer_bytes += siz; return siz; } c->do_buffer = 0; if(!c->header) { send_header(c); } ap_rwrite(c->buffer_ptr, c->buffer_bytes, r); ap_rwrite(ptr, siz, r); c->buffer_bytes = 0; } else { if(!c->header) { send_header(c); } ap_rwrite(ptr, siz, r); } return siz; } /* -------------------------------------------------------------- */ /* -------------------------------------------------------------- */ static size_t curltunnel_header( void *ptr, size_t psize, size_t nmemb, void *stream) { char *str = ptr; curldata *c = (curldata *) stream; request_rec *r = c->r; int siz = psize * nmemb; int ll = r->server->loglevel | APLOG_LEVELMASK; char *ep; char *tp; char *xp; char *name; char *value; int first; // ---------------------------------------------- // Check header // ---------------------------------------------- if(str == NULL) return siz; if(!c->has_code) { long code = -111; curl_easy_getinfo(curl, CURLINFO_HTTP_CODE,&code); if(code != -111) { r->status = code; c->has_code = 1; } else { ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, r, "(curltunnel) got not code"); } } tp = str + siz; *tp = 0; ep = strchr(str,':'); if(ep == 0) return siz; *ep++ = 0; while((*ep > 0) && (*ep <= 32) && (ep < tp)) ep++; // skip spaces. if((xp = strchr(ep,'\r')) != NULL) *xp = 0; if((xp = strchr(ep,'\n')) != NULL) *xp = 0; name = str; value = ep; // ---------------------------------------------- // Some headers to skip // ---------------------------------------------- first = toupper(*name); switch(first) { case 'C': if(!strcasecmp("Connection",name)) { return siz; } if(!strcasecmp("Content-type",name)) { r->content_type = ap_pstrdup(r->pool, value); return siz; } if(!strcasecmp("Content-length",name)) { c->size = atoi(ep); } break; case 'L': if(!strcasecmp("Location",name)) { value = curltunnel_location_reverse_map(r, value, c->a->raliases ? c->a->raliases : c->conf->raliases); } break; case 'S': // skip server if(!strcasecmp("Server",name)) { ap_table_set(r->notes,"Orginal-server", name); return siz; } break; case 'T': if(!strcasecmp("Transfer-Encoding",name)) // Transfer encoding. return siz; break; case 'U': if(!strcasecmp("URI",name)) { value = curltunnel_location_reverse_map(r, value, c->a->raliases ? c->a->raliases : c->conf->raliases); } break; default: break; } // ---------------------------------------------- // Add to out table. // ---------------------------------------------- if(ll >= APLOG_DEBUG) ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, r, "(curltunnel) header[%s]=[%s]", name, value); ap_table_add(r->headers_out, name, value); return siz; } /* -------------------------------------------------------------- */ /* -------------------------------------------------------------- */ static int curltunnel_mime(request_rec *r) { const char *url; if((url = ap_table_get(r->notes,"curltunnel")) != NULL) return OK; return DECLINED; } /* -------------------------------------------------------------- */ /* -------------------------------------------------------------- */ static int curltunnel_handler(request_rec *r) { const char *url; const char *nu; int i; int cnt; int ret; array_header *arr; table_entry *ent; char *p; int ll = r->server->loglevel | APLOG_LEVELMASK; struct curl_slist *hlist = NULL; curltunnel_srv_conf *conf; const curltunnel_alias *alias; curldata c; url = ap_table_get(r->notes,"curltunnel"); alias = (curltunnel_alias *) ap_table_get(r->notes,"curltunnel-alias"); if(ll >= APLOG_DEBUG) ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, r, "(curltunnel) handling url[%s]", url); // ---------------------------------------------- // Check if really using this // ---------------------------------------------- if(!url) return DECLINED; if(!alias) return DECLINED; conf = (curltunnel_srv_conf *) ap_get_module_config(r->server->module_config, &curltunnel_module); // ---------------------------------------------- // Check that we have curl for connection // ---------------------------------------------- if(curl == NULL) { curl = curl_easy_init(); if(curl == NULL) { ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, "curl-variable not inited"); return HTTP_INTERNAL_SERVER_ERROR; } ap_register_cleanup(r->connection->pool, (void *) curl, curltunnel_cleanup, curltunnel_cleanup); curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curltunnel_write); curl_easy_setopt(curl, CURLOPT_READFUNCTION, curltunnel_read); curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curltunnel_header); } ap_table_addn(r->headers_out, "CurlTunnel", ap_psprintf(r->pool, "keepalives(%d)", r->connection->keepalives)); // ---------------------------------------------- // Init some data. // ---------------------------------------------- memset(&c, 0, sizeof c); c.magic = CURLDATA_MAGIC; c.size = -1; c.r = r; c.conf = conf; c.a = alias; // ---------------------------------------------- // Buffering. // - if no cache...then no buffering // - if browser can do HTTP/1.1 Chunked mode.. no need. // - Other case try to buffer. // ---------------------------------------------- if(curltunnel_cache_size <= 0) { c.do_buffer = 0; // cannot do buffering } else if(r->proto_num >= HTTP_VERSION(1,1) && !ap_table_get(r->subprocess_env, "nokeepalive") && !ap_table_get(r->subprocess_env, "force-response-1.0")) { c.do_buffer = 0; // not need for it...can do chunked. } else { c.do_buffer = 1; c.buffer_ptr = curltunnel_buffer; c.buffer_size = curltunnel_cache_size; } // ---------------------------------------------- // ---------------------------------------------- if(alias->pars && (p = (char *) ap_table_get(alias->pars,"proxy")) != NULL) { curl_easy_setopt(curl, CURLOPT_PROXY, p); } curl_easy_setopt(curl, CURLOPT_INFILE, &c); curl_easy_setopt(curl, CURLOPT_FILE, &c); curl_easy_setopt(curl, CURLOPT_WRITEHEADER, &c); // ---------------------------------------------- // Make url // ---------------------------------------------- if(r->args || r->parsed_uri.fragment) { nu = ap_pstrcat(r->pool, url, "?", r->args, "#", r->parsed_uri.fragment, NULL); } else if(r->args) { nu = ap_pstrcat(r->pool, url, "?", r->args, NULL); } else if(r->parsed_uri.fragment) { nu = ap_pstrcat(r->pool, url, "#", r->parsed_uri.fragment, NULL); } else { nu = url; } if(ll >= APLOG_DEBUG) ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, r, "(curltunnel) changed url[%s]", nu); curl_easy_setopt(curl, CURLOPT_URL, nu); // ---------------------------------------------- // Make headers. // ---------------------------------------------- arr = ap_table_elts(r->headers_in); cnt = arr->nelts; ent = (table_entry *) arr->elts; hlist = NULL; hlist = curl_slist_append(hlist, "urgh: 12344444"); for(i=0; i < cnt; i++) { char *k; if(!(k = ent[i].key)) continue; if(!strcasecmp(k,"content-length")) continue; if(!strcasecmp(k,"connection")) continue; hlist = curl_slist_append(hlist, ap_pstrcat(r->pool, k, ": ", ent[i].val, NULL)); } ap_register_cleanup(r->pool, (void *) hlist, slist_cleanup, slist_cleanup); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, hlist); // ---------------------------------------------- // rest is depended on method. // ---------------------------------------------- switch(r->method_number) { case M_GET: { curl_easy_setopt(curl, CURLOPT_INFILESIZE, 0); if(r->header_only) { curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "HEAD"); } } break; case M_POST: { const char *hdr = ap_table_get(r->headers_in, "content-length"); int ss = 0; if(hdr != NULL) ss = atoi(hdr); curl_easy_setopt(curl, CURLOPT_PUT, 1); curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST"); curl_easy_setopt(curl, CURLOPT_INFILESIZE, ss); } break; default: ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, "(curltunnel) illegal method (%s)", r->method); return HTTP_METHOD_NOT_ALLOWED; } // ---------------------------------------------- // perform it. // ---------------------------------------------- ret = curl_easy_perform(curl); if(!c.header) { if(c.do_buffer) { ap_table_setn(r->headers_out,"Content-length", ap_psprintf(r->pool, "%d", c.buffer_bytes)); } send_header(&c); } if(c.buffer_bytes > 0) { ap_rwrite(c.buffer_ptr, c.buffer_bytes, r); } ap_rflush(r); return OK; } /* -------------------------------------------------------------- */ /* -------------------------------------------------------------- */ static const char *curltunnel_add_pass(cmd_parms *cmd, void *dummy, const char *args) { server_rec *s = cmd->server; curltunnel_srv_conf *conf; curltunnel_alias *nw; char *f; char *r; char *x; f = ap_getword_conf(cmd->pool, &args); r = ap_getword_conf(cmd->pool, &args); if(!f || !*f || !r || !*r) return "illegal curltunnel pass"; conf = (curltunnel_srv_conf *)ap_get_module_config(s->module_config, &curltunnel_module); nw = ap_push_array(conf->aliases); memset(nw, 0, sizeof(*nw)); nw->fake = f; nw->real = r; while(((x = ap_getword_conf(cmd->pool, &args)) != NULL) && (*x)) { if(!strcmp(x,"-reverse")) { curltunnel_alias *ra; char *r1 = ap_getword_conf(cmd->pool, &args); char *r2 = ap_getword_conf(cmd->pool, &args); if(!r1 || !*r1 || !r1 || !*r2) return "illegal curltunnel -reverse X Y"; if(nw->raliases == NULL) nw->raliases = ap_make_array(cmd->pool, 4, sizeof(curltunnel_alias)); ra = ap_push_array(nw->raliases); memset(ra, 0, sizeof(*ra)); ra->fake = r1; ra->real = r2; } else if(!strcmp(x,"-proxy")) { char *r1 = ap_getword_conf(cmd->pool, &args); if(nw->pars == NULL) nw->pars = ap_make_table(cmd->pool,4); if(!r1 || !*r1) return "illegal curltunnel -proxy host:port"; ap_table_setn(nw->pars,"proxy", r1); } else { return ap_psprintf(cmd->pool,"illegal pass parameter (%s)", x); } } return NULL; } static const char *curltunnel_add_rpass(cmd_parms *cmd, void *dummy, char *f, char *r) { server_rec *s = cmd->server; curltunnel_srv_conf *conf; curltunnel_alias *nw; conf = (curltunnel_srv_conf *)ap_get_module_config(s->module_config, &curltunnel_module); nw = ap_push_array(conf->raliases); nw->fake = f; nw->real = r; return NULL; } static const char *curltunnel_set_cache(cmd_parms *cmd, void *dummy, char *f) { int val; int len; int ker = 1; char buf[13]; // server_rec *s = cmd->server; // curltunnel_srv_conf *conf; // curltunnel_alias *nw; // conf = (curltunnel_srv_conf *)ap_get_module_config(s->module_config, &curltunnel_module); len = strlen(f); if(len <= 0) return "empty cache size"; if(len >= 12) return "urgh????"; if(f[len-1] == 'k') { strcpy(buf,f); buf[len-1] = 0; f = buf; ker = 1024; } if(!strcmp(f,"0")) { val = 0; } else if((val = atoi(f)) == 0) { return "not a number"; } val *= ker; if(curltunnel_cache_size > val) return NULL; if(val > curltunnel_cache_size) { curltunnel_cache_size = val; if(curltunnel_buffer) free(curltunnel_buffer); } if(val > 0) curltunnel_buffer = malloc(val); return NULL; } /* -------------------------------------------------------------- */ static const char *curltunnel_set_handle(cmd_parms *cmd, void *dummy, char *fake, char *real, char *ext) { curltunnel_dir_conf *df = (curltunnel_dir_conf *) dummy; server_rec *s = cmd->server; curltunnel_srv_conf *conf; curltunnel_alias *nw; conf = (curltunnel_srv_conf *)ap_get_module_config(s->module_config, &curltunnel_module); return NULL; } /* -------------------------------------------------------------- */ /* -------------------------------------------------------------- */ static command_rec curltunnel_cmds[] = { /* Server configuration */ { "CurlTunnelPass", curltunnel_add_pass, NULL, RSRC_CONF, RAW_ARGS, "Tunnel there" }, { "CurlTunnelPassReverse", curltunnel_add_rpass, NULL, RSRC_CONF, TAKE2, "Tunnel there" }, { "CurlTunnelCache", curltunnel_set_cache, NULL, RSRC_CONF, TAKE1, "Tunnel there" }, { "CurlTunnelHandle", curltunnel_set_handle, NULL, RSRC_CONF, TAKE2, "Tunnel there" }, #if EMULATE_PROXY_MODULE { "ProxyPass", curltunnel_add_pass, NULL, RSRC_CONF, RAW_ARGS, "Tunnel there" }, { "ProxyPassReverse", curltunnel_add_rpass, NULL, RSRC_CONF, TAKE2, "Tunnel there" }, #endif { NULL } }; /* -------------------------------------------------------------- */ /* -------------------------------------------------------------- */ /* Dispatch list of content handlers */ static const handler_rec curltunnel_handlers[] = { { "curltunnel-handler", curltunnel_handler }, { NULL, NULL } }; /* -------------------------------------------------------------- */ /* -------------------------------------------------------------- */ /* Dispatch list for API hooks */ module MODULE_VAR_EXPORT curltunnel_module = { STANDARD_MODULE_STUFF, NULL, /* module initializer */ NULL, /* create per-dir config structures */ NULL, /* merge per-dir config structures */ create_curltunnel_server,/* create per-server config structures */ merge_curltunnel_server, /* merge per-server config structures */ curltunnel_cmds, /* table of config file commands */ curltunnel_handlers, /* [#8] MIME-typed-dispatched handlers */ curltunnel_translate, /* [#1] URI to filename translation */ NULL, /* [#4] validate user id from request */ NULL, /* [#5] check if the user is ok _here_ */ NULL, /* [#3] check access by host address */ curltunnel_mime, /* [#6] determine MIME type */ NULL, /* [#7] pre-run fixups */ NULL, /* [#9] log a transaction */ NULL, /* [#2] header parser */ curltunnel_child_init, /* child_init */ curltunnel_child_exit, /* child_exit */ NULL /* [#0] post read-request */ }; /* -------------------------------------------------------------- */