0.6.1.20060928-1 release
[manu/suphp.git] / src / apache2 / mod_suphp.c
index c330844..e056574 100644 (file)
@@ -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;
         }