X-Git-Url: http://git.home-dn.net/?p=manu%2Fsuphp.git;a=blobdiff_plain;f=src%2Fapache2%2Fmod_suphp.c;h=e056574ec7fd301e9335c4676d724f898627722c;hp=c330844facd7cf49b13bec4e628d4fb40e0eee19;hb=59c5bff8d9aef402c868519d13398ba4eb2dddb7;hpb=47bcf8ba77fa8011f9be728c23dbe6915d70251b diff --git a/src/apache2/mod_suphp.c b/src/apache2/mod_suphp.c index c330844..e056574 100644 --- a/src/apache2/mod_suphp.c +++ b/src/apache2/mod_suphp.c @@ -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 - 1; - 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; + while (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; } @@ -313,11 +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, RSRC_CONF | ACCESS_CONF, "Tells mod_suphp to handle these MIME-types"), - AP_INIT_ITERATE("suPHP_RemoveHandler", suphp_handle_cmd_remove_handler, NULL, RSRC_CONF | ACCESS_CONF, "Tells mod_suphp not to handle these MIME-types"), + 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; + APR_BRIGADE_FOREACH(b, bb) { + if (APR_BUCKET_IS_EOS(b)) { + break; + } + rv = apr_bucket_read(b, &buf, &len, APR_BLOCK_READ); + if (rv != APR_SUCCESS) { + break; + } + } +} + /****************** Hooks / handlers @@ -345,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; @@ -406,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 @@ -473,22 +668,32 @@ static int suphp_handler(request_rec *r) 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, "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, "SUPHP_GROUP", apr_pstrdup(r->pool, dconf->target_group)); } - else + else if (sconf->target_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); @@ -592,26 +797,32 @@ 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; - + ret = ap_scan_script_header_err_brigade(r, bb, strbuf); if (ret == HTTP_NOT_MODIFIED) { @@ -619,6 +830,8 @@ static int suphp_handler(request_rec *r) } 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, @@ -632,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"); @@ -653,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; }