X-Git-Url: http://git.home-dn.net/?p=manu%2Fsuphp.git;a=blobdiff_plain;f=src%2Fapache2%2Fmod_suphp.c;h=4b26a7a8a1f8b0864d947c1b35a1d023e9cfc4a6;hp=c0f724080d3662ef75a720fcfbc9063f434f9d56;hb=e522833716b528c5c704dd8a858b6ebbb299f106;hpb=cbb396a040b4d24c85748829bfaff18b468bf5a7 diff --git a/src/apache2/mod_suphp.c b/src/apache2/mod_suphp.c index c0f7240..4b26a7a 100644 --- a/src/apache2/mod_suphp.c +++ b/src/apache2/mod_suphp.c @@ -1,5 +1,5 @@ /* - suPHP - (c)2002-2004 Sebastian Marsching + suPHP - (c)2002-2005 Sebastian Marsching This file is part of suPHP. @@ -22,6 +22,7 @@ #include "apr_strings.h" #include "apr_thread_proc.h" #include "apr_buckets.h" +#include "apr_poll.h" #define CORE_PRIVATE @@ -33,6 +34,8 @@ #include "util_script.h" #include "util_filter.h" +/* needed for get_suexec_identity hook */ +#include "unixd.h" module AP_MODULE_DECLARE_DATA suphp_module; @@ -41,46 +44,14 @@ module AP_MODULE_DECLARE_DATA suphp_module; Auxiliary functions *********************/ -static int suphp_bucket_read(apr_bucket *b, char *buf, int len) -{ - const char *dst_end = buf + len; - char * dst = buf; - apr_status_t rv; - const char *bucket_data; - apr_size_t bucket_data_len; - const char *src; - const char *src_end; - int count = 0; - - if (APR_BUCKET_IS_EOS(b)) - return -1; - - rv = apr_bucket_read(b, &bucket_data, &bucket_data_len, APR_BLOCK_READ); - if (!APR_STATUS_IS_SUCCESS(rv) || (bucket_data_len == 0)) - { - return 0; - } - src = bucket_data; - src_end = bucket_data + bucket_data_len; - while ((src < src_end) && (dst < dst_end)) - { - *dst = *src; - dst++; - src++; - count++; - } - *dst = 0; - return count; -} - - -static void suphp_log_script_err(request_rec *r, apr_file_t *script_err) +static apr_status_t suphp_log_script_err(request_rec *r, apr_file_t *script_err) { char argsbuffer[HUGE_STRING_LEN]; char *newline; - - while (apr_file_gets(argsbuffer, HUGE_STRING_LEN, - script_err) == APR_SUCCESS) { + apr_status_t rv; + + while ((rv = apr_file_gets(argsbuffer, HUGE_STRING_LEN, + script_err)) == APR_SUCCESS) { newline = strchr(argsbuffer, '\n'); if (newline) { *newline = '\0'; @@ -88,6 +59,43 @@ static void suphp_log_script_err(request_rec *r, apr_file_t *script_err) ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "%s", argsbuffer); } + + return rv; +} + +char *suphp_brigade_read(apr_pool_t *p, apr_bucket_brigade *bb, int bytes) +{ + char *target_buf; + char *next_byte; + char *last_byte; + apr_bucket *b; + + if (bytes == 0) { + return NULL; + } + + target_buf = (char *) apr_palloc(p, bytes + 1); + next_byte = target_buf; + last_byte = target_buf + bytes; + + for (b = APR_BRIGADE_FIRST(bb); b != APR_BRIGADE_SENTINEL(bb); b = APR_BUCKET_NEXT(b)) { + char *buf; + apr_size_t size; + apr_size_t i; + if (apr_bucket_read(b, &buf, &size, APR_BLOCK_READ) == APR_SUCCESS) { + for (i = 0; i < size; i++) { + *next_byte = *buf; + next_byte++; + buf++; + if (next_byte == last_byte) { + *next_byte = 0; + return target_buf; + } + } + } + } + next_byte = 0; + return target_buf; } @@ -114,6 +122,7 @@ typedef struct { char *target_user; char *target_group; #endif + apr_table_t *handlers; } suphp_conf; @@ -130,6 +139,10 @@ static void *suphp_create_dir_config(apr_pool_t *p, char *dir) cfg->target_group = NULL; #endif + /* Create table with 0 initial elements */ + /* This size may be increased for performance reasons */ + cfg->handlers = apr_table_make(p, 0); + return (void *) cfg; } @@ -171,6 +184,8 @@ static void *suphp_merge_dir_config(apr_pool_t *p, void *base, merged->target_group = NULL; #endif + merged->handlers = apr_table_overlay(p, child->handlers, parent->handlers); + return (void *) merged; } @@ -273,6 +288,29 @@ static const char *suphp_handle_cmd_user_group(cmd_parms *cmd, void *mconfig, #endif +static const char *suphp_handle_cmd_add_handler(cmd_parms *cmd, void *mconfig, + const char *arg) +{ + suphp_conf *cfg = (suphp_conf *) mconfig; + // Mark active handler with '1' + apr_table_set(cfg->handlers, arg, "1"); + + return NULL; +} + + +static const char *suphp_handle_cmd_remove_handler(cmd_parms *cmd, + void *mconfig, + const char *arg) +{ + suphp_conf *cfg = (suphp_conf *) mconfig; + // Mark deactivated handler with '0' + apr_table_set(cfg->handlers, arg, "0"); + + return NULL; +} + + static const command_rec suphp_cmds[] = { AP_INIT_FLAG("suPHP_Engine", suphp_handle_cmd_engine, NULL, RSRC_CONF | ACCESS_CONF, @@ -283,9 +321,184 @@ static const command_rec suphp_cmds[] = AP_INIT_TAKE2("suPHP_UserGroup", suphp_handle_cmd_user_group, NULL, RSRC_CONF | ACCESS_CONF, "User and group scripts shall be run as"), #endif + AP_INIT_ITERATE("suPHP_AddHandler", suphp_handle_cmd_add_handler, NULL, ACCESS_CONF, "Tells mod_suphp to handle these MIME-types"), + AP_INIT_ITERATE("suPHP_RemoveHandler", suphp_handle_cmd_remove_handler, NULL, ACCESS_CONF, "Tells mod_suphp not to handle these MIME-types"), {NULL} }; +/***************************************** + Code for reading script's stdout/stderr + based on mod_cgi's code + *****************************************/ + +#if APR_FILES_AS_SOCKETS + +static const apr_bucket_type_t bucket_type_suphp; + +struct suphp_bucket_data { + apr_pollset_t *pollset; + request_rec *r; +}; + +static apr_bucket *suphp_bucket_create(request_rec *r, apr_file_t *out, apr_file_t *err, apr_bucket_alloc_t *list) +{ + apr_bucket *b = apr_bucket_alloc(sizeof(*b), list); + apr_status_t rv; + apr_pollfd_t fd; + struct suphp_bucket_data *data = apr_palloc(r->pool, sizeof(*data)); + + APR_BUCKET_INIT(b); + b->free = apr_bucket_free; + b->list = list; + b->type = &bucket_type_suphp; + b->length = (apr_size_t) (-1); + b->start = (-1); + + /* Create the pollset */ + rv = apr_pollset_create(&data->pollset, 2, r->pool, 0); + AP_DEBUG_ASSERT(rv == APR_SUCCESS); + + fd.desc_type = APR_POLL_FILE; + fd.reqevents = APR_POLLIN; + fd.p = r->pool; + fd.desc.f = out; /* script's stdout */ + fd.client_data = (void *) 1; + rv = apr_pollset_add(data->pollset, &fd); + AP_DEBUG_ASSERT(rv == APR_SUCCESS); + + fd.desc.f = err; /* script's stderr */ + fd.client_data = (void *) 2; + rv = apr_pollset_add(data->pollset, &fd); + AP_DEBUG_ASSERT(rv == APR_SUCCESS); + + data->r = r; + b->data = data; + return b; +} + +static apr_bucket *suphp_bucket_dup(struct suphp_bucket_data *data, apr_bucket_alloc_t *list) +{ + apr_bucket *b = apr_bucket_alloc(sizeof(*b), list); + APR_BUCKET_INIT(b); + b->free = apr_bucket_free; + b->list = list; + b->type = &bucket_type_suphp; + b->length = (apr_size_t) (-1); + b->start = (-1); + b->data = data; + return b; +} + +/* This utility method is needed, because APR's implementation for the + pipe bucket cannot handle or special bucket type */ +static apr_status_t suphp_read_fd(apr_bucket *b, apr_file_t *fd, const char **str, apr_size_t *len) +{ + char *buf; + apr_status_t rv; + + *str = NULL; + *len = APR_BUCKET_BUFF_SIZE; + buf = apr_bucket_alloc(*len, b->list); + + rv = apr_file_read(fd, buf, len); + + if (*len > 0) { + /* Got data */ + struct suphp_bucket_data *data = b->data; + apr_bucket_heap *h; + + /* Change the current bucket to refer to what we read + and append the pipe bucket after it */ + b = apr_bucket_heap_make(b, buf, *len, apr_bucket_free); + /* Here, b->data is the new heap bucket data */ + h = b->data; + h->alloc_len = APR_BUCKET_BUFF_SIZE; /* note the real buffer size */ + *str = buf; + APR_BUCKET_INSERT_AFTER(b, suphp_bucket_dup(data, b->list)); + } else { + /* Got no data */ + apr_bucket_free(buf); + b = apr_bucket_immortal_make(b, "", 0); + /* Here, b->data is the reference to the empty string */ + *str = b->data; + } + return rv; +} + +/* Poll on stdout and stderr to make sure the process does not block + because of a full system (stderr) buffer */ +static apr_status_t suphp_bucket_read(apr_bucket *b, const char **str, apr_size_t *len, apr_read_type_e block) { + struct suphp_bucket_data *data = b->data; + apr_interval_time_t timeout; + apr_status_t rv; + int gotdata = 0; + + timeout = (block == APR_NONBLOCK_READ) ? 0 : data->r->server->timeout; + + do { + const apr_pollfd_t *results; + apr_int32_t num; + + rv = apr_pollset_poll(data->pollset, timeout, &num, &results); + if (APR_STATUS_IS_TIMEUP(rv)) { + return (timeout == 0) ? APR_EAGAIN : rv; + } else if (APR_STATUS_IS_EINTR(rv)) { + continue; + } else if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, data->r, "Poll failed waiting for suPHP child process"); + return rv; + } + + while (num > 0) { + if (results[0].client_data == (void *) 1) { + /* handle stdout */ + rv = suphp_read_fd(b, results[0].desc.f, str, len); + if (APR_STATUS_IS_EOF(rv)) { + rv = APR_SUCCESS; + } + gotdata = 1; + } else { + /* handle stderr */ + apr_status_t rv2 = suphp_log_script_err(data->r, results[0].desc.f); + if (APR_STATUS_IS_EOF(rv2)) { + apr_pollset_remove(data->pollset, &results[0]); + } + } + num--; + results++; + } + } while (!gotdata); + + return rv; +} + +static const apr_bucket_type_t bucket_type_suphp = { + "SUPHP", 5, APR_BUCKET_DATA, + apr_bucket_destroy_noop, + suphp_bucket_read, + apr_bucket_setaside_notimpl, + apr_bucket_split_notimpl, + apr_bucket_copy_notimpl +}; + +#endif + +static void suphp_discard_output(apr_bucket_brigade *bb) { + apr_bucket *b; + const char *buf; + apr_size_t len; + apr_status_t rv; + for (b = APR_BRIGADE_FIRST(bb); b != APR_BRIGADE_SENTINEL(bb); b = APR_BUCKET_NEXT(b)) { + if (APR_BUCKET_IS_EOS(b)) { + break; + } + rv = apr_bucket_read(b, &buf, &len, APR_BLOCK_READ); + if (rv != APR_SUCCESS) { + break; + } + } +} + /****************** Hooks / handlers @@ -313,10 +526,16 @@ static int suphp_handler(request_rec *r) #else char strbuf[MAX_STRING_LEN]; #endif + char *tmpbuf; int nph = 0; int eos_reached = 0; char *auth_user = NULL; char *auth_pass = NULL; + +#ifdef SUPHP_USE_USERGROUP + char *ud_user = NULL; + char *ud_group = NULL; +#endif apr_bucket_brigade *bb; apr_bucket *b; @@ -328,12 +547,12 @@ static int suphp_handler(request_rec *r) dconf = ap_get_module_config(r->per_dir_config, &suphp_module); core_conf = (core_dir_config *) ap_get_module_config(r->per_dir_config, &core_module); - /* only handle request if x-httpd-php handler is asigned */ + /* only handle request if mod_suphp is active for this handler */ + /* check only first byte of value (second has to be \0) */ + if ((apr_table_get(dconf->handlers, r->handler) == NULL) + || (*(apr_table_get(dconf->handlers, r->handler)) == '0')) + return DECLINED; - if (strcmp(r->handler, "x-httpd-php") - && strcmp(r->handler, "application/x-httpd-php")) - return DECLINED; - /* check if suPHP is enabled for this request */ if (((sconf->engine != SUPHP_ENGINE_ON) @@ -374,9 +593,17 @@ static int suphp_handler(request_rec *r) if ((sconf->target_user == NULL || sconf->target_group == NULL) && (dconf->target_user == NULL || dconf->target_group == NULL)) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "No user or group set - set suPHP_UserGroup"); - return HTTP_INTERNAL_SERVER_ERROR; + /* Check for userdir request */ + ap_unix_identity_t *userdir_id = NULL; + userdir_id = ap_run_get_suexec_identity(r); + if (userdir_id != NULL && userdir_id->userdir) { + ud_user = apr_psprintf(r->pool, "#%ld", (long) userdir_id->uid); + ud_group = apr_psprintf(r->pool, "#%ld", (long) userdir_id->gid); + } else { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "No user or group set - set suPHP_UserGroup"); + return HTTP_INTERNAL_SERVER_ERROR; + } } #endif @@ -391,20 +618,22 @@ static int suphp_handler(request_rec *r) ap_add_common_vars(r); ap_add_cgi_vars(r); - apr_table_unset(r->subprocess_env, "PHP_CONFIG"); - apr_table_unset(r->subprocess_env, "PHP_AUTH_USER"); - apr_table_unset(r->subprocess_env, "PHP_AUTH_PW"); + apr_table_unset(r->subprocess_env, "SUPHP_PHP_CONFIG"); + apr_table_unset(r->subprocess_env, "SUPHP_AUTH_USER"); + apr_table_unset(r->subprocess_env, "SUPHP_AUTH_PW"); #ifdef SUPHP_USE_USERGROUP - apr_table_unset(r->subprocess_env, "PHP_SU_USER"); - apr_table_unset(r->subprocess_env, "PHP_SU_GROUP"); + apr_table_unset(r->subprocess_env, "SUPHP_USER"); + apr_table_unset(r->subprocess_env, "SUPHP_GROUP"); #endif if (dconf->php_config) { - apr_table_setn(r->subprocess_env, "PHP_CONFIG", apr_pstrdup(p, dconf->php_config)); + apr_table_setn(r->subprocess_env, "SUPHP_PHP_CONFIG", apr_pstrdup(p, dconf->php_config)); } + apr_table_setn(r->subprocess_env, "SUPHP_HANDLER", r->handler); + if (r->headers_in) { const char *auth = NULL; @@ -429,32 +658,42 @@ static int suphp_handler(request_rec *r) if (auth_user && auth_pass) { - apr_table_setn(r->subprocess_env, "PHP_AUTH_USER", auth_user); - apr_table_setn(r->subprocess_env, "PHP_AUTH_PW", auth_pass); + apr_table_setn(r->subprocess_env, "SUPHP_AUTH_USER", auth_user); + apr_table_setn(r->subprocess_env, "SUPHP_AUTH_PW", auth_pass); } #ifdef SUPHP_USE_USERGROUP if (dconf->target_user) { - apr_table_setn(r->subprocess_env, "PHP_SU_USER", + apr_table_setn(r->subprocess_env, "SUPHP_USER", apr_pstrdup(r->pool, dconf->target_user)); } - else + else if (sconf->target_user) { - apr_table_setn(r->subprocess_env, "PHP_SU_USER", + apr_table_setn(r->subprocess_env, "SUPHP_USER", apr_pstrdup(r->pool, sconf->target_user)); } + else + { + apr_table_setn(r->subprocess_env, "SUPHP_USER", + apr_pstrdup(r->pool, ud_user)); + } if (dconf->target_group) { - apr_table_setn(r->subprocess_env, "PHP_SU_GROUP", + apr_table_setn(r->subprocess_env, "SUPHP_GROUP", apr_pstrdup(r->pool, dconf->target_group)); } - else + else if (sconf->target_group) { - apr_table_setn(r->subprocess_env, "PHP_SU_GROUP", + apr_table_setn(r->subprocess_env, "SUPHP_GROUP", apr_pstrdup(r->pool, sconf->target_group)); } + else + { + apr_table_setn(r->subprocess_env, "SUPHP_GROUP", + apr_pstrdup(r->pool, ud_group)); + } #endif env = ap_create_environment(p, r->subprocess_env); @@ -524,7 +763,7 @@ static int suphp_handler(request_rec *r) return rv; } - APR_BRIGADE_FOREACH(bucket, bb) + for (bucket = APR_BRIGADE_FIRST(bb); bucket != APR_BRIGADE_SENTINEL(bb); bucket = APR_BUCKET_NEXT(bucket)) { const char *data; apr_size_t len; @@ -558,28 +797,41 @@ static int suphp_handler(request_rec *r) /* get output from script and check if non-parsed headers are used */ +#if APR_FILES_AS_SOCKETS + apr_file_pipe_timeout_set(proc->out, 0); + apr_file_pipe_timeout_set(proc->err, 0); + b = suphp_bucket_create(r, proc->out, proc->err, r->connection->bucket_alloc); +#else b = apr_bucket_pipe_create(proc->out, r->connection->bucket_alloc); +#endif + APR_BRIGADE_INSERT_TAIL(bb, b); - len = 8; - if ((suphp_bucket_read(b, strbuf, len) == len) - && !(strcmp(strbuf, "HTTP/1.0") && strcmp(strbuf, "HTTP/1.1"))) + b = apr_bucket_eos_create(r->connection->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, b); + + tmpbuf = suphp_brigade_read(p, bb, 8); + if (strlen(tmpbuf) == 8 && !(strncmp(tmpbuf, "HTTP/1.0", 8) && strncmp(tmpbuf, "HTTP/1.1", 8))) { nph = 1; } - b = apr_bucket_eos_create(r->connection->bucket_alloc); - APR_BRIGADE_INSERT_TAIL(bb, b); - - if (proc->out && !nph) + if (!nph) { - /* normal cgi headers, so we have to create the real headers by hand */ + /* normal cgi headers, so we have to create the real headers ourselves */ int ret; const char *location; - - if ((ret = ap_scan_script_header_err_brigade(r, bb, strbuf)) != APR_SUCCESS) + + ret = ap_scan_script_header_err_brigade(r, bb, strbuf); + if (ret == HTTP_NOT_MODIFIED) + { + return ret; + } + else if (ret != APR_SUCCESS) { + suphp_discard_output(bb); + apr_brigade_destroy(bb); suphp_log_script_err(r, proc->err); /* ap_scan_script_header_err_brigade does logging itself, @@ -593,15 +845,7 @@ static int suphp_handler(request_rec *r) { /* empty brigade (script output) and modify headers */ - const char *buf; - apr_size_t blen; - APR_BRIGADE_FOREACH(b, bb) - { - if (APR_BUCKET_IS_EOS(b)) - break; - if (apr_bucket_read(b, &buf, &blen, APR_BLOCK_READ) != APR_SUCCESS) - break; - } + suphp_discard_output(bb); apr_brigade_destroy(bb); suphp_log_script_err(r, proc->err); r->method = apr_pstrdup(r->pool, "GET"); @@ -614,16 +858,9 @@ static int suphp_handler(request_rec *r) else if (location && r->status == 200) { /* empty brigade (script output) */ - const char *buf; - apr_size_t blen; - APR_BRIGADE_FOREACH(b, bb) - { - if (APR_BUCKET_IS_EOS(b)) - break; - if (apr_bucket_read(b, &buf, &blen, APR_BLOCK_READ) != APR_SUCCESS) - break; - } + suphp_discard_output(bb); apr_brigade_destroy(bb); + suphp_log_script_err(r, proc->err); return HTTP_MOVED_TEMPORARILY; }