Index: modules/privsep/README
===================================================================
--- modules/privsep/README	(revision 0)
+++ modules/privsep/README	(revision 0)
@@ -0,0 +1,150 @@
+Apache Privilege Separation for WebDAV
+
+SYNOPSIS
+
+ The Privilege Separation module addresses the problem of a WebDAV
+ virtual server only being able to write files as a single user id
+ (usually something like 'nobody' or 'www').
+
+ The Privilege Separation module addresses this problem in a secure
+ way by adding privilege separation to the Apache web server
+ (conceptually similar to ssh privilege separation).
+
+ A privilege separated Apache can be used to allow WebDAV write access
+ to users home directories while both preserving and honouring unix
+ permissions and allowing the use of unix quotas and PAM authentication.
+
+ARCHITECTURE
+
+ In Privilege Separation mode Apache continues to run as an unprivileged
+ user although an additional secure process runs as 'root'.
+
+ The apache worker processes communicate with the 'root' privileged
+ separated process via unix sockets to carry out system authentication
+ and privileged file system operations. Namely:
+
+ * Responding to PAM authentication requests
+   pam_unix authentication is not normally possible in apache due to
+   the unprivileged process not being able to access the shadow file.
+   Due to the privilege separated design this is now possible.
+   Authentication responses include a cryptographic token which encodes
+   the users credentials and is verified in successive privileged
+   filesystem requests made to the privsep process.
+
+ * Performing privileged filesystem operations on behalf of the
+   unprivileged apache worker processes with the privileges of the
+   authenticated user. The cryptographic token is verified and then the
+   effective userid is set and the filesystem operation is performed.
+
+ Certain auditable points in the apache core code (core.c, request.c),
+ mod_autoindex and mod_dav code have been changed to use the privilege
+ separated file io calls which communicate via unix sockets to the
+ privileged daemon. File descriptors are passed back over unix sockets
+ for open calls.
+
+INTERFACES
+
+ APR file IO calls are modified to use the privsep versions which take
+ an additional context argument (privsep_token_t*):
+
+  ap_privsep_stat
+  ap_privsep_file_perms_set
+  ap_privsep_file_open
+  ap_privsep_dir_open
+  ap_privsep_dir_read
+  ap_privsep_dir_close
+  ap_privsep_dir_make
+  ap_privsep_dir_remove
+  ap_privsep_file_remove
+  ap_privsep_file_rename
+
+ The privsep versions use a token fetched from from r->notes.
+ If the privsep token exists then the request is dispatched to the
+ privilege separated daemon, otherwise the file io method is
+ executed directly in the regular way.
+
+ One special case exists for the stat and lstat calls made from
+ ap_directory_walk. This is called before map_to_storage has been
+ done and any authentication hooks run. This special case needs the stat
+ and lstat calls to be executed as root to know if the path actually
+ exists. The security leak potential is minimised by making stat
+ the only call that is able to be made as uid 0 and ap_directory_walk
+ is the only routine that calls ap_privsep_stat with the special
+ ap_preauth_stat_token.
+
+PORTABILITY
+
+ mod_privsep has been tested on Linux and FreeBSD.
+
+ apr interfaces have been used where possible but mod_privsep relies on
+ unix sockets (socketpair,sendmsg,recvmsg) and the ability to send file
+ descriptors over unix sockets. It also currently depends on PAM.
+
+ It should be relatively easily ported to other unix-like OS.
+
+ NOTE: the opendir implementation relies on platform specific details.
+ Specifically, that directories can be opened as file descriptors and
+ also that the file descriptor is the first structure member of the
+ private DIR* structure.
+
+ It is potentially possible to implement this feature for Windows as
+ a preliminary investigation shows that it is possible to pass file
+ handles between processes. More investigation is needed.
+
+BUILDING
+
+  # apply patch
+
+  # run buildconf to pick up configure.in change
+  ./buildconf
+
+  ./configure \
+    --enable-dav \
+    --enable-privsep \
+    --enable-authn-privsep
+
+CONFIGURATION
+
+ Example configuration:
+
+  # Output error log messages for privileged accesses
+  LogLevel info
+  PrivilegeSeparationDebug On
+
+  <Directory "/opt/apache2.2/uploads/">
+
+    # Enable WebDAV and privilege separation for this directory
+    Dav On
+    PrivilegeSeparation On
+
+    # System authentication through privilege separated PAM
+    AuthType Basic
+    AuthName DAV-upload
+    AuthBasicProvider privsep
+
+    Options Indexes
+    AllowOverride None
+    Require valid-user
+
+    # Workaround for WebDAV PROPFIND bug on dirs with index files
+    DirectoryIndex /no_directory_index
+
+  </Directory>
+
+DESIGN ISSUES
+
+ * Single privileged separated process seriliazes stat/lstat/open
+   * Potential solution: change to a pool of privsep processes
+
+ * Overhead of PAM authentication for each request
+   * Potential solution: cache authentication
+
+ * Overhead of setgroups in each privileged operation
+   * Route requests to a pooled process that is already switched
+     to the required user
+
+CREDITS
+
+  Michael Clark <michael at metaparadigm dot com>
+  Jamie Clark <jamie at zeroth dot org>
+  Iain Wade <iain dot wade at gmail dot com>

This header defines the message structure for privileged operations to be sent
to the privileged process. It also defines the client functions to be used in
modules that need access to privileged file operations.

Index: modules/privsep/mod_privsep.h
===================================================================
--- modules/privsep/mod_privsep.h	(revision 0)
+++ modules/privsep/mod_privsep.h	(revision 0)
@@ -0,0 +1,150 @@
+/* Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MOD_PRIVSEP_H
+#define MOD_PRIVSEP_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <limits.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "apr.h"
+#include "apr_file_io.h"
+#include "apr_md5.h"
+#include "apr_portable.h"
+
+
+#define PRIVSEP_AUTH_KEY_LEN            16
+#define PRIVSEP_TOKEN_SALT_LEN          8
+#define PRIVSEP_USERNAME_LEN            32
+#define PRIVSEP_AUTH_FAIL_DELAY         2000000 /* usecs */
+#define PRIVSEP_BUF_SIZE                (sizeof(privsep_message_t) + PATH_MAX * 2)
+#define PRIVSEP_PAM_SERVICE_NAME        "apache"
+#define PRIVSEP_TOKEN_NOTE              "privsep-token"
+
+
+#if APR_HAS_THREADS
+extern apr_thread_mutex_t *privsep_worker_mutex;
+#define privsep_worker_mutex_lock() \
+  apr_thread_mutex_lock(privsep_worker_mutex)
+#define privsep_worker_mutex_unlock() \
+  apr_thread_mutex_unlock(privsep_worker_mutex)
+#else
+#define privsep_worker_mutex_lock() (APR_SUCCESS)
+#define privsep_worker_mutex_unlock() (APR_SUCCESS)
+#endif
+
+/* definitions */
+
+/* privileged command type */
+typedef enum {
+  privsep_command_authenticate,
+  privsep_command_verify_token,
+  privsep_command_file_open,
+  privsep_command_dir_open,
+  privsep_command_file_perms_set,
+  privsep_command_stat,
+  privsep_command_dir_make,
+  privsep_command_file_rename,
+  privsep_command_dir_remove,
+  privsep_command_file_remove
+} privsep_command_t;
+
+typedef enum {
+    PRIVSEP_AUTH_DENIED,
+    PRIVSEP_AUTH_GRANTED,
+    PRIVSEP_AUTH_USER_FOUND,
+    PRIVSEP_AUTH_USER_NOT_FOUND,
+    PRIVSEP_AUTH_GENERAL_ERROR
+} privsep_authn_status;
+
+/* authinfo for PAM */
+typedef struct {
+  const char *user;
+  const char *pass;
+} auth_pam_userinfo;
+
+/* privileged token with cyrpto hash */
+typedef struct {
+  uid_t uid;
+  gid_t gid;
+  unsigned char salt[PRIVSEP_TOKEN_SALT_LEN];
+  unsigned char digest[APR_MD5_DIGESTSIZE];
+} privsep_token_t;
+#define HAVE_PRIVSEP_TOKEN_T
+
+/* privileged command message */
+typedef struct {
+  privsep_command_t             command;
+  int                           retval;
+  int                           flags;
+  apr_fileperms_t               perms;
+  apr_finfo_t                   finfo;
+  privsep_token_t               token;
+  /*
+    TODO - add remote address into request so that we can make
+    token hash include address so it can not be used for a
+    request being made from another address
+
+    struct in_addr             remote;
+  */
+} privsep_message_t;
+
+/* module directory config */
+typedef struct {
+  int enabled;
+} privsep_dir_conf;
+
+
+/* globals */
+extern int privsep_enabled;
+extern int privsep_inited;
+extern int privsep_client_fd;
+extern privsep_token_t *privsep_root_token;
+extern int privsep_worker_sp[2];
+
+
+/* module directory configuration interface used by wrappers */
+
+int privsep_is_enabled(const request_rec *r);
+
+/* setup message header and control message to send fd */
+
+void privsep_send_fd(struct msghdr *mh, char *buf, int fd);
+
+
+/* privileged wrapper client functions used by authn-privsep module */
+
+AP_DECLARE(privsep_authn_status) ap_privsep_authenticate(const request_rec *r,
+							 const char *user,
+							 const char *pass);
+
+AP_DECLARE(privsep_authn_status) ap_privsep_verify_token(privsep_token_t *token,
+							 const char *user);
+
+/* install file io privsep hooks */
+
+void ap_privsep_register_file_io_hooks();
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif	/* !MOD_PRIVSEP_H */

The module proper, implements the privsep privileged operation process that
listens on a unix socket for privileged operations and performs them on
behalf of the unprivileged apache worker processes.

Index: modules/privsep/mod_privsep.c
===================================================================
--- modules/privsep/mod_privsep.c	(revision 0)
+++ modules/privsep/mod_privsep.c	(revision 0)
@@ -0,0 +1,793 @@
+/* Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <sys/types.h>    
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <pwd.h>
+#include <grp.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <security/pam_appl.h>
+
+#include "apr.h"
+#include "apr_md5.h"
+#include "apr_portable.h"
+#include "apr_random.h"
+#include "apr_file_io.h"
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_log.h"
+
+#include "mod_privsep.h"
+
+
+module AP_MODULE_DECLARE_DATA privsep_module;
+
+/* global */
+int privsep_enabled = 0;
+int privsep_inited = 0;
+int privsep_client_fd;
+privsep_token_t *privsep_root_token;
+int privsep_worker_sp[2];
+
+#if APR_HAS_THREADS
+/* If we have a threaded worker, we use a per worker child process lock
+   to be used around sending and recieving to the privsep process
+   as there is only one socketpair per child process */
+apr_thread_mutex_t *privsep_worker_mutex;
+#endif
+
+/* server */
+static int privsep_debug = 0;
+static pid_t privsep_pid = 0;
+static int privsep_server_fd;
+static char privsep_msgbuf[PRIVSEP_BUF_SIZE];
+
+
+/* remove /./'s, /../'s and trailing .. */
+static int privsep_secure_path(char *s)
+{
+    char *r, *w;
+    int last_was_slash = 0;
+
+    r = w = s;
+    while(*r != 0)
+	{
+	    /* Ignore duplicate /'s */
+	    if (*r == '/' && last_was_slash)
+		{
+		    r++;
+		    continue;
+		}
+	    /* Calculate /../ in a secure way and avoid */
+	    if (last_was_slash && *r == '.')
+		{
+		    if (*(r+1) == '.') {
+			/* skip past .. or ../ with read pointer */
+			if (*(r+2) == '/') r += 3;
+			else if (*(r+2) == 0) r += 2;
+			/* skip back to last / with write pointer */
+			if (w > s+1)
+			    {
+				w--;
+				while(*(w-1) != '/') { w--; }
+				continue;
+			    }
+			else
+			    {
+				return -1; /* Bad Request */
+			    }
+		    } else if (*(r+1) == '/') {
+			r += 2;
+			continue;
+		    }
+		}
+	    *w = *r;
+	    last_was_slash = (*r == '/');
+	    r++;
+	    w++;
+	}
+    *w = 0;
+    return 0;
+}
+
+/*
+ * auth_pam_talker: supply authentication information to PAM when asked
+ *
+ * (code from mod_auth_pam)
+ *
+ * Assumptions:
+ *   A password is asked for by requesting input without echoing
+ *   A username is asked for by requesting input _with_ echoing
+ *
+ */
+static int auth_pam_talker(int num_msg,
+			   const struct pam_message **msg,
+			   struct pam_response **resp,
+			   void *appdata_ptr)
+{
+    unsigned short i = 0;
+    auth_pam_userinfo *userinfo = (auth_pam_userinfo*)appdata_ptr;
+    struct pam_response *response = 0;
+
+    /* parameter sanity checking */
+    if (!resp || !msg || !userinfo)
+	return PAM_CONV_ERR;
+
+    /* allocate memory to store response */
+    response = malloc(num_msg * sizeof(struct pam_response));
+    if (!response)
+	return PAM_CONV_ERR;
+
+    /* copy values */
+    for(i = 0; i < num_msg; i++) {
+	/* initialize to safe values */
+	response[i].resp_retcode = 0;
+	response[i].resp = 0;
+
+	/* select response based on requested output style */
+	switch(msg[i]->msg_style) {
+	case PAM_PROMPT_ECHO_ON:
+	    /* on memory allocation failure, auth fails */
+	    response[i].resp = strdup(userinfo->user);
+	    break;
+	case PAM_PROMPT_ECHO_OFF:
+	    response[i].resp = strdup(userinfo->pass);
+	    break;
+	default:
+	    if (response) free(response);
+	    return PAM_CONV_ERR;
+	}
+    }
+    /* everything okay, set PAM response values */
+    *resp = response;
+    return PAM_SUCCESS;
+}
+
+static int privsep_authenticate(const char **errormsg,
+				const char *user, const char *pass,
+				server_rec *server_conf)
+{
+    int rv;
+    auth_pam_userinfo userinfo = { user, pass };
+    struct pam_conv conv_info = { &auth_pam_talker, (void*)&userinfo};
+    pam_handle_t *pamh  = NULL;
+
+    /* initialize pam */
+    rv = pam_start(PRIVSEP_PAM_SERVICE_NAME, user, &conv_info, &pamh);
+    if (rv != PAM_SUCCESS) {
+	*errormsg = pam_strerror(pamh, rv);
+	return PRIVSEP_AUTH_GENERAL_ERROR;
+    }
+
+#ifdef HAVE_PAM_FAIL_DELAY
+    /* We need to delay in client so we don't block other requests.
+       You also need to set "nodelay" flag in the apache auth configuration
+       so the auth module doesn't delay (pam_fail_delay only sets the
+       minimum delay which modules can override). */
+    pam_fail_delay(pamh, 0 /* usec */);
+#endif
+
+    /* try to authenticate user, log error on failure */
+    rv = pam_authenticate(pamh, PAM_DISALLOW_NULL_AUTHTOK);
+    if (rv != PAM_SUCCESS) {
+	*errormsg = pam_strerror(pamh, rv);
+	pam_end(pamh, PAM_SUCCESS);
+	switch(rv) {
+	case PAM_AUTH_ERR:
+	    return PRIVSEP_AUTH_DENIED;
+	case PAM_USER_UNKNOWN:
+	    return PRIVSEP_AUTH_USER_NOT_FOUND;
+	default:
+	    return PRIVSEP_AUTH_GENERAL_ERROR;
+	}
+    }
+
+    /* check that the account is healthy */
+    rv = pam_acct_mgmt(pamh, PAM_DISALLOW_NULL_AUTHTOK);
+    if (rv != PAM_SUCCESS) {
+	*errormsg = pam_strerror(pamh, rv);
+	pam_end(pamh, PAM_SUCCESS);
+	return PRIVSEP_AUTH_GENERAL_ERROR;
+    }
+
+    pam_end(pamh, PAM_SUCCESS);
+    return PRIVSEP_AUTH_GRANTED;
+}
+
+static int privsep_salt_token(privsep_token_t *token)
+{
+    return apr_generate_random_bytes(token->salt, sizeof(token->salt));
+}
+
+static int privsep_check_token(privsep_token_t *token, unsigned char* auth_key)
+{
+    char digest[APR_MD5_DIGESTSIZE];
+    apr_md5_ctx_t md5_ctx;
+
+    /* save digest for comparison */
+    memcpy(digest, token->digest, APR_MD5_DIGESTSIZE);
+
+    /* hash auth_key, uid, gid and salt */
+    apr_md5_init(&md5_ctx);
+    apr_md5_update(&md5_ctx, auth_key, PRIVSEP_AUTH_KEY_LEN);
+    apr_md5_update(&md5_ctx, (unsigned char*)&token->uid, sizeof(token->uid));
+    apr_md5_update(&md5_ctx, (unsigned char*)&token->gid, sizeof(token->gid));
+    apr_md5_update(&md5_ctx, token->salt, sizeof(token->salt));
+    apr_md5_final(token->digest, &md5_ctx);
+
+    return memcmp(digest, token->digest, APR_MD5_DIGESTSIZE);
+}
+
+static char* privsep_print_token(privsep_token_t *token)
+{
+    static char buf[sizeof(privsep_token_t) * 2 + 32];
+    sprintf(buf, "uid=%d gid=%d salt=%04x%04x hash=%04x%04x%04x%04x",
+	    token->uid, token->gid,
+	    *((unsigned*)token->salt),
+	    *(((unsigned*)token->salt)+1),
+	    *((unsigned*)token->digest),
+	    *(((unsigned*)token->digest)+1),
+	    *(((unsigned*)token->digest)+2),
+	    *(((unsigned*)token->digest)+3));
+    return buf;
+}
+
+/* setup message header and control message to send fd */
+
+void privsep_send_fd(struct msghdr *mh, char *buf, int fd)
+{
+    struct cmsghdr *cmsg;
+
+    mh->msg_control = buf;
+    mh->msg_controllen = CMSG_SPACE(sizeof (fd));
+    cmsg = CMSG_FIRSTHDR(mh);
+    cmsg->cmsg_level = SOL_SOCKET;
+    cmsg->cmsg_type = SCM_RIGHTS;
+    cmsg->cmsg_len = CMSG_LEN(sizeof (fd));
+    *(int *) CMSG_DATA (cmsg) = fd;
+    mh->msg_controllen = cmsg->cmsg_len;
+}
+
+/* main privileged request handler */
+
+static void privsep_service_request (unsigned char *auth_key,
+				     server_rec *server_conf,
+				     apr_pool_t *ptemp)
+{
+    int rv;
+    struct msghdr mh;
+    struct iovec iv;
+    privsep_message_t *msg = (privsep_message_t*)privsep_msgbuf;
+    struct cmsghdr *cmsg;
+    char *path1, *path2;
+    int replyfd;
+    char ccmsg[CMSG_SPACE(sizeof (int))];
+    apr_file_t *tmpfile = NULL;
+    apr_dir_t *tmpdir = NULL;
+
+    /* construct an iovec large enough to receive the input parameters */
+    iv.iov_base = privsep_msgbuf;
+    iv.iov_len = PRIVSEP_BUF_SIZE;
+
+    /* link the iovec into the message header */
+    memset (&mh, 0, sizeof (mh));
+    mh.msg_iov = &iv;
+    mh.msg_iovlen = 1;
+
+    /* prepare the header to receive a control message with the replyfd */
+    mh.msg_control = ccmsg;
+    mh.msg_controllen = sizeof (ccmsg);
+
+    /* wait for a message */
+    if ((rv = recvmsg (privsep_server_fd, &mh, 0)) < 0) {
+	ap_log_error(APLOG_MARK, APLOG_ERR, 0, server_conf,
+		     "privsep_service_request: recvmsg");
+	return;
+    }
+
+    /* pull out the replyfd that was sent in the control message */
+    cmsg = CMSG_FIRSTHDR (&mh);
+    if (cmsg == NULL) {
+	ap_log_error(APLOG_MARK, APLOG_ERR, 0, server_conf,
+		     "privsep_service_request: missing control header");
+	goto fail_perm;
+    } else if (!cmsg->cmsg_type == SCM_RIGHTS) {
+	ap_log_error(APLOG_MARK, APLOG_ERR, 0, server_conf,
+		     "privsep_service_request: control message not fd");
+	goto fail_perm;
+    } else {
+	replyfd = (*(int *) CMSG_DATA (cmsg));
+    }
+
+    /* pull out the path arguments */
+    path1 = privsep_msgbuf + sizeof(privsep_message_t);
+    path2 = path1 + strlen(path1) + 1;
+
+    /* check the token */
+    if (privsep_check_token(&msg->token, auth_key) != 0) {
+	ap_log_error(APLOG_MARK, APLOG_ERR, 0, server_conf,
+		     "privsep_service_request: invalid token");
+	goto fail_perm;
+    }
+
+    /* set iovec len for reply */
+    iv.iov_len = sizeof (*msg);
+
+    mh.msg_flags = 0;
+    mh.msg_control = NULL;
+    mh.msg_controllen = 0;
+
+    /* full authentication */
+    if (msg->command == privsep_command_authenticate) {
+	struct passwd *pw = NULL;
+	const char *errormsg = "success";
+
+	msg->retval = privsep_authenticate(&errormsg, path1, path2,
+					       server_conf);
+	if (msg->retval != PRIVSEP_AUTH_GRANTED) {
+
+	} else if ((pw = getpwnam (path1)) < 0) {
+	    errormsg = "no such user";
+	    msg->retval = PRIVSEP_AUTH_USER_NOT_FOUND;
+	} else if (pw->pw_uid == 0) {
+	    errormsg = "disallowed uid 0";
+	    msg->retval = PRIVSEP_AUTH_DENIED;
+	} else if (pw->pw_gid == 0) {
+	    errormsg = "disallowed gid 0";
+	    msg->retval = PRIVSEP_AUTH_DENIED;
+	} else if (privsep_salt_token(&msg->token) != APR_SUCCESS) {
+	    errormsg = "failed to create salt";
+	    msg->retval = PRIVSEP_AUTH_GENERAL_ERROR;
+	} else {
+	    msg->token.uid = pw->pw_uid;
+	    msg->token.gid = pw->pw_gid;
+	    privsep_check_token(&msg->token, auth_key);
+	    errormsg = privsep_print_token(&msg->token);
+	    msg->retval = PRIVSEP_AUTH_GRANTED;
+	}
+	if (privsep_debug || msg->retval != PRIVSEP_AUTH_GRANTED)
+	    ap_log_error(APLOG_MARK, APLOG_INFO, 0, server_conf,
+			 "privsep_%-15s %-5d \"%s\" %s (rc=%d)",
+			 "authenticate", pw ? pw->pw_uid : -1, path1,
+			 errormsg, msg->retval);
+	goto send_reply;
+    }
+
+    /* uid and gid for username must match hashed token
+       (this method is used by subrequests to short circuit full
+        authentication when the parent request is already authenticated) */
+    if (msg->command == privsep_command_verify_token) {
+	struct passwd *pw = NULL;
+	const char *errormsg = "success";
+
+	if ((pw = getpwnam (path1)) < 0) {
+	    errormsg = "no such user";
+	    msg->retval = PRIVSEP_AUTH_USER_NOT_FOUND;
+	} else if (msg->token.uid != pw->pw_uid) {
+	    errormsg = "user uid does not match token uid";
+	    msg->retval = PRIVSEP_AUTH_GENERAL_ERROR;
+	} else if (msg->token.gid != pw->pw_gid) {
+	    errormsg = "user gid does not match token gid";
+	    msg->retval = PRIVSEP_AUTH_GENERAL_ERROR;
+	} else {
+	    msg->retval = PRIVSEP_AUTH_GRANTED;
+	}
+	if (privsep_debug || msg->retval != PRIVSEP_AUTH_GRANTED)
+	    ap_log_error(APLOG_MARK, APLOG_INFO, 0, server_conf,
+			 "privsep_%-15s %-5d \"%s\" %s (rc=%d)",
+			 "verify_token", pw ? pw->pw_uid : -1, path1,
+			 errormsg, msg->retval);
+	goto send_reply;
+    }
+
+    if ( privsep_secure_path (path1) < 0 ||
+	 (strlen(path2) && privsep_secure_path (path2) < 0) ) {
+	ap_log_error(APLOG_MARK, APLOG_ERR, 0, server_conf,
+		     "privsep_service_request: invalid path ignored.");
+	goto fail_perm;
+    }
+
+    /* only allow stat for root uid or gid */
+    if ((msg->token.uid == 0 || msg->token.gid == 0) &&
+	!(msg->command == privsep_command_stat)) {
+	ap_log_error(APLOG_MARK, APLOG_ERR, 0, server_conf,
+		     "privsep_service_request: uid 0 disallowed");
+	goto fail_perm;
+    }
+
+    /* initialise groups and set effective user and group */
+    /* TODO - use NGROUPS so we can have supplementary groups, this will
+       need cached setuid processes for performance reasons */
+    if (setgroups (1 /* NGROUPS */, &msg->token.gid) < 0) {
+	ap_log_error(APLOG_MARK, APLOG_ERR, 0, server_conf,
+		     "privsep_service_request: setgroups(1, [%d])",
+		     msg->token.gid);
+	goto fail_perm;
+    }
+    if (setegid (msg->token.gid) < 0) {
+	ap_log_error(APLOG_MARK, APLOG_ERR, 0, server_conf,
+		     "privsep_service_request: setegid(%d)\n",
+		     msg->token.gid);
+	goto fail_perm;
+    }
+    if (seteuid (msg->token.uid) < 0) {
+	ap_log_error(APLOG_MARK, APLOG_ERR, 0, server_conf,
+		     "privsep_service_request: seteuid(%d)",
+		     msg->token.uid);
+	goto fail_perm;
+    }
+
+    /* dispatch privileged command */
+    switch (msg->command)
+	{
+	case privsep_command_file_open:
+	    msg->retval = apr_file_open(&tmpfile, path1,
+					    msg->flags, msg->perms, ptemp);
+	    if(msg->retval == APR_SUCCESS) {
+		apr_os_file_t fd;
+		apr_os_file_get(&fd, tmpfile);
+		privsep_send_fd(&mh, ccmsg, fd);
+	    }
+	    if (privsep_debug)
+		ap_log_error(APLOG_MARK, APLOG_INFO, 0, server_conf,
+			     "privsep_%-15s %-5d \"%s\", %d, %04o (rc=%d)",
+			     "file_open", msg->token.uid, path1,
+			     msg->flags, msg->perms, msg->retval);
+	    break;
+
+	case privsep_command_dir_open:
+	    msg->retval = apr_dir_open(&tmpdir, path1, ptemp);
+	    if(msg->retval == APR_SUCCESS) {
+		apr_os_dir_t *dir;
+		apr_os_dir_get(&dir, tmpdir);
+		privsep_send_fd(&mh, ccmsg, dirfd(dir));
+	    }
+	    if (privsep_debug)
+		ap_log_error(APLOG_MARK, APLOG_INFO, 0, server_conf,
+			     "privsep_%-15s %-5d \"%s\" (rc=%d)",
+			     "dir_open", msg->token.uid, path1,
+			     msg->retval);
+	    break;
+
+	case privsep_command_file_perms_set:
+	    msg->retval = apr_file_perms_set(path1, msg->perms);
+	    if (privsep_debug)
+		ap_log_error(APLOG_MARK, APLOG_INFO, 0, server_conf,
+			     "privsep_%-15s  %-5d \"%s\", %04o (rc=%d)",
+			     "file_perms_set", msg->token.uid, path1,
+			     msg->perms, msg->retval);
+	    break;
+
+	case privsep_command_stat:
+	    msg->retval = apr_stat(&msg->finfo, path1, msg->flags, NULL);
+	    if (privsep_debug)
+		ap_log_error(APLOG_MARK, APLOG_INFO, 0, server_conf,
+			     "privsep_%-15s %-5d \"%s\" (rc=%d)",
+			     (msg->flags & APR_FINFO_LINK) ? "lstat" : "stat",
+			     msg->token.uid, path1, msg->retval);
+	    break;
+
+	case privsep_command_dir_make:
+	    msg->retval = apr_dir_make(path1, msg->perms, NULL);
+	    if (privsep_debug)
+		ap_log_error(APLOG_MARK, APLOG_INFO, 0, server_conf,
+			     "privsep_%-15s %-5d \"%s\", %04o (rc=%d)",
+			     "dir_make", msg->token.uid, path1,
+			     msg->perms, msg->retval);
+	    break;
+
+	case privsep_command_file_rename:
+	    msg->retval = apr_file_rename(path1, path2, NULL);
+	    if (privsep_debug)
+		ap_log_error(APLOG_MARK, APLOG_INFO, 0, server_conf,
+			     "privsep_%-15s %-5d \"%s\", \"%s\" (rc=%d)",
+			     "file_rename", msg->token.uid, path1, path2,
+			     msg->retval);
+	    break;
+
+	case privsep_command_dir_remove:
+	    msg->retval = apr_dir_remove(path1, NULL);
+	    if (privsep_debug)
+		ap_log_error(APLOG_MARK, APLOG_INFO, 0, server_conf,
+			     "privsep_%-15s %-5d \"%s\" (rc=%d)",
+			     "dir_remove", msg->token.uid, path1,
+			     msg->retval);
+	    break;
+
+	case privsep_command_file_remove:
+	    msg->retval = apr_file_remove(path1, NULL);
+	    if (privsep_debug)
+		ap_log_error(APLOG_MARK, APLOG_INFO, 0, server_conf,
+			     "privsep_%-15s %-5d \"%s\" (rc=%d)",
+			     "file_remove", msg->token.uid, path1,
+			     msg->retval);
+	    break;
+
+	}
+send_reply:
+    /* send success reply */
+    rv = sendmsg (replyfd, &mh, 0);
+    if(tmpdir != NULL)
+	apr_dir_close(tmpdir);
+    if(tmpfile != NULL)
+	apr_file_close(tmpfile);
+    close(replyfd);
+    goto out;
+
+fail_perm:
+    /* send error reply */
+    memset (msg, 0, sizeof(*msg));
+    msg->retval = APR_EACCES;
+    rv = sendmsg (replyfd, &mh, 0);
+    close(replyfd);
+
+out:
+    seteuid (0);
+    setegid (0);
+}
+
+
+/* module clean up - server */
+
+static void privsep_server_signal(int sig)
+{
+    close (privsep_server_fd);
+    exit(0);
+}
+
+static apr_status_t privsep_server_cleanup(void *theworker)
+{
+    privsep_inited = 0;
+    close (privsep_client_fd);
+    if(privsep_pid)
+	kill(privsep_pid, SIGTERM);
+}
+
+
+/* module initialisation - server */
+
+static apr_status_t privsep_server_init(apr_pool_t *pconf, apr_pool_t *plog,
+					apr_pool_t *ptemp, server_rec *server_conf)
+{
+    int sv[2];
+    int arg, bytes;
+    unsigned char auth_key[PRIVSEP_AUTH_KEY_LEN];
+
+    void *data = NULL;
+    const char *userdata_key = "privsep_post_config";
+
+    /* Apache loads DSO modules twice. We want to wait until the second
+     * load before setting up our global mutex and shared memory segment.
+     * To avoid the first call to the post_config hook, we set some
+     * dummy userdata in a pool that lives longer than the first DSO
+     * load, and only run if that data is set on subsequent calls to
+     * this hook. */
+    apr_pool_userdata_get(&data, userdata_key, server_conf->process->pool);
+    if (data == NULL) {
+	/* WARNING: This must *not* be apr_pool_userdata_setn(). The
+	 * reason for this is because the static symbol section of the
+	 * DSO may not be at the same address offset when it is reloaded.
+	 * Since setn() does not make a copy and only compares addresses,
+	 * the get() will be unable to find the original userdata. */
+	apr_pool_userdata_set((const void *)1, userdata_key,
+			      apr_pool_cleanup_null, server_conf->process->pool);
+	return APR_SUCCESS; /* This would be the first time through */
+    }
+
+    if(!privsep_enabled || privsep_inited) return 0;
+
+    /* create auth key on stack and clear in client when we fork */
+    if(apr_generate_random_bytes(auth_key, PRIVSEP_AUTH_KEY_LEN) != APR_SUCCESS) {
+	ap_log_error(APLOG_MARK, APLOG_CRIT, 0, server_conf,
+		     "privsep_server_init: couldn't create random key");
+	return APR_EGENERAL;
+    }
+
+    /* create special limited root token (stat only) */
+    privsep_root_token = apr_pcalloc(pconf, sizeof(privsep_token_t));
+    memset(privsep_root_token, 0, sizeof(privsep_token_t));
+    /* privsep_root_token->uid = 0;
+       privsep_root_token->gid = 0; */
+    if (privsep_salt_token(privsep_root_token) < 0) {
+	ap_log_error(APLOG_MARK, APLOG_CRIT, 0, server_conf,
+		     "privsep_server_init: failed to initialize stat token\n");
+	return APR_EGENERAL;
+    }
+    privsep_check_token(privsep_root_token, auth_key);
+
+    /* create socket pair for communication with worker processes */
+    if (socketpair(PF_UNIX, SOCK_DGRAM, 0, sv) < 0) {
+	ap_log_error(APLOG_MARK, APLOG_CRIT, 0, server_conf,
+		     "privsep_server_init: error creating socketpair");
+	return APR_EGENERAL;
+    }
+    privsep_client_fd = sv[0];
+    privsep_server_fd = sv[1];
+    apr_pool_cleanup_register(pconf, NULL, privsep_server_cleanup,
+			      apr_pool_cleanup_null);
+
+    /* create privsep child process */
+    if ((privsep_pid = fork ()) < 0) {
+	ap_log_error(APLOG_MARK, APLOG_CRIT, 0, server_conf,
+		     "privsep_server_init: fork");
+	return APR_EGENERAL;
+    }
+    else if (privsep_pid > 0) {
+	/* in apache parent process */
+
+	/* clear auth key off stack */
+	memset(auth_key, 0, PRIVSEP_AUTH_KEY_LEN);
+	/* close server side of the privsep socketpair */
+	close(privsep_server_fd);
+	privsep_inited = 1;
+	ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, server_conf,
+		     "Privilege separation started (pid %d)", privsep_pid);
+	return APR_SUCCESS;
+    }
+
+    /* in privsep child */
+
+    /* close client side of the privsep socketpair */
+    close(privsep_client_fd);
+    signal(SIGHUP, privsep_server_signal);
+    signal(SIGTERM, privsep_server_signal);
+    signal(SIGUSR1, privsep_server_signal);
+    umask(002);
+
+    /* okay, here's the main loop */
+    while (1) privsep_service_request(auth_key, server_conf, ptemp);
+}
+
+
+/* module clean up - worker */
+
+static apr_status_t privsep_worker_cleanup(void *theworker)
+{
+    close(privsep_worker_sp[0]);
+    close(privsep_worker_sp[1]);
+}
+
+
+/* module initialisation - worker */
+
+static void privsep_worker_init(apr_pool_t *pchild, server_rec *server_conf)
+{
+    if(!privsep_enabled) return;
+
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server_conf,
+		 "Privilege separation child init (pid %d)", getpid());
+
+    if (socketpair(PF_UNIX, SOCK_DGRAM, 0, privsep_worker_sp) < 0) {
+	ap_log_error(APLOG_MARK, APLOG_CRIT, errno, server_conf,
+		     "privsep_worker_init: error creating socketpair");
+	return;
+    }
+
+#if APR_HAS_THREADS
+    apr_thread_mutex_create(&privsep_worker_mutex,
+			    APR_THREAD_MUTEX_DEFAULT, pchild);
+#endif
+
+    apr_pool_cleanup_register(pchild, NULL, privsep_worker_cleanup,
+			      apr_pool_cleanup_null);
+}
+
+
+/* module configuration */
+
+int privsep_is_enabled(const request_rec *r)
+{
+    privsep_dir_conf *conf = (privsep_dir_conf *)
+        ap_get_module_config(r->per_dir_config, &privsep_module);
+    return (conf ? conf->enabled : 0);
+}
+
+static const char *privsep_set_enabled(cmd_parms *cmd, void *dconf,
+				       const char *arg)
+{
+    privsep_dir_conf *conf = dconf;
+
+    if (!strcasecmp(arg, "off") || !strcmp(arg, "0")) {
+        conf->enabled = 0;
+    } else {
+        conf->enabled = 1;
+	privsep_enabled = 1; /* we set a global flag if we are enabled on
+                             any directory to enable start up of the daemon */
+    }
+    return NULL;
+}
+
+static const char *privsep_set_debug(cmd_parms *cmd, void *dconf,
+				     const char *arg)
+{
+    privsep_dir_conf *conf = dconf;
+
+    if (!strcasecmp(arg, "off") || !strcmp(arg, "0")) {
+        privsep_debug = 0;
+    } else {
+        privsep_debug = 1;
+    }
+    return NULL;
+}
+
+static void *create_privsep_dir_config(apr_pool_t *p, char *dir)
+{
+    privsep_dir_conf *new =
+        (privsep_dir_conf *) apr_pcalloc(p, sizeof(privsep_dir_conf));
+
+    new->enabled = 0;
+
+    return (void *) new;
+}
+
+static void *merge_privsep_dir_config(apr_pool_t *p, void *basev, void *addv)
+{
+    privsep_dir_conf *new = (privsep_dir_conf *)
+	apr_pcalloc(p, sizeof(privsep_dir_conf));
+    privsep_dir_conf *add = (privsep_dir_conf *) addv;
+    privsep_dir_conf *base = (privsep_dir_conf *) basev;
+
+    new->enabled = add->enabled || base->enabled;
+
+    return new;
+}
+
+static void privsep_create_subrequest(request_rec *rnew, request_rec *r)
+{
+    /* copy privsep token to the subrequest if it exists */
+    char* priv_token = (char *)apr_table_get(r->notes, PRIVSEP_TOKEN_NOTE);
+    if(priv_token) {
+      char *new_priv_token =
+	  (char *)apr_palloc(rnew->pool, sizeof(privsep_token_t));
+      memcpy(new_priv_token, priv_token, sizeof(privsep_token_t));
+      apr_table_setn(rnew->notes, PRIVSEP_TOKEN_NOTE, new_priv_token);
+    }
+}
+
+static const command_rec privsep_cmds[] = {
+    AP_INIT_TAKE1("PrivilegeSeparation", privsep_set_enabled, NULL, ACCESS_CONF,
+		  "Set to 'On' to enabled privilege separation"),
+    AP_INIT_TAKE1("PrivilegeSeparationDebug", privsep_set_debug, NULL, RSRC_CONF,
+		  "Set to 'On' to enabled privilege separation debug messages"),
+    {NULL}
+};
+
+static void register_hooks(apr_pool_t *p)
+{
+    ap_hook_post_config(privsep_server_init, NULL, NULL, APR_HOOK_MIDDLE);
+    ap_hook_child_init(privsep_worker_init, NULL, NULL, APR_HOOK_MIDDLE);
+    ap_hook_create_subrequest(privsep_create_subrequest, NULL, NULL, APR_HOOK_MIDDLE);
+    ap_privsep_register_file_io_hooks();
+}
+
+module AP_MODULE_DECLARE_DATA privsep_module = {
+    STANDARD20_MODULE_STUFF,
+    create_privsep_dir_config, /* create per-directory config structure */
+    merge_privsep_dir_config,  /* merge per-directory config structures */
+    NULL,                   /* create per-server config structure */
+    NULL,                   /* merge per-server config structures */
+    privsep_cmds,           /* command apr_table_t */
+    register_hooks          /* register hooks */
+};

The client implementation for the privileged operations. Sends requests from
apache worker processes to the privileged process to perform.

Index: modules/privsep/util_privsep_client.c
===================================================================
--- modules/privsep/util_privsep_client.c	(revision 0)
+++ modules/privsep/util_privsep_client.c	(revision 0)
@@ -0,0 +1,464 @@
+/* Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <pwd.h>
+#include <fcntl.h>
+#include <dirent.h>
+
+#include "apr.h"
+#include "apr_md5.h"
+#include "apr_portable.h"
+#include "apr_strings.h"
+#include "apr_file_io.h"
+#include "arch/unix/apr_arch_file_io.h"
+
+#include "httpd.h"
+#include "http_request.h"
+#include "http_log.h"
+
+#include "mod_privsep.h"
+#include "privsep.h"
+
+
+/* function to send privileged command requests used by wrappers */
+
+static apr_status_t privsep_send_request(privsep_message_t *msg,
+					 privsep_token_t *token,
+					 const char *path1, ...)
+{
+    va_list ap;
+    struct msghdr mh;
+    struct iovec iv[3];
+    int rv;
+    struct cmsghdr *cmsg;
+    char ccmsg[CMSG_SPACE (sizeof (int))];
+    const char *path2 = "\0";
+
+    va_start (ap, path1);
+    if (msg->command == privsep_command_file_rename ||
+	msg->command == privsep_command_authenticate)
+	path2 = va_arg (ap, const char*);
+    va_end (ap);
+
+    /* Make sure we don't send too much */
+    if (sizeof(privsep_message_t) + strlen(path1) + strlen(path2)
+	> PRIVSEP_BUF_SIZE)
+	{
+	    ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
+			 "privsep_send_request: args to large");
+	    return APR_EGENERAL;
+	}
+
+    /* copy the token and request addr into the command structure */
+    memcpy(&msg->token, token, sizeof(msg->token));
+
+    /* construct an iovec with the input parameters */
+    iv[0].iov_base = msg;
+    iv[0].iov_len = sizeof (*msg);
+    iv[1].iov_base = (void*)path1;
+    iv[1].iov_len = strlen(path1)+1;
+    iv[2].iov_base = (void*)path2;
+    iv[2].iov_len = strlen(path2)+1;
+
+    /* link the iovec into the message header */
+    memset(&mh, 0, sizeof(mh));
+    mh.msg_iov = iv;
+    mh.msg_iovlen = 3;
+
+    /* send child socketpair side that we listen for the reply on */
+    mh.msg_flags = 0;
+    privsep_send_fd(&mh, ccmsg, privsep_worker_sp[0]);
+
+    /* lock per child message serialization mutex */
+    if(privsep_worker_mutex_lock() != APR_SUCCESS) {
+	ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
+		     "privsep_send_request: privsep_worker_mutex_lock failed");
+	return APR_EGENERAL;
+    }
+
+    /* fire away */
+    if ((rv = sendmsg (privsep_client_fd, &mh, 0)) < 0) {
+	ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
+		     "privsep_send_request: sendmsg (rc=%d)", errno);
+
+	/* unlock message serialization mutex */
+	privsep_worker_mutex_unlock();
+
+	return APR_EGENERAL;
+    }
+
+    /* prepare the header to receive a control message */
+    /* with the return payload */
+    mh.msg_control = ccmsg;
+    mh.msg_controllen = sizeof (ccmsg);
+
+    /* let the reply fall into the same struct */
+    mh.msg_iovlen = 1;
+    rv = recvmsg (privsep_worker_sp[1], &mh, 0);
+
+    if (rv < 0) {
+	ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
+		     "privsep_send_request: recvmsg (rc=%d)", errno);
+
+	/* unlock message serialization mutex */
+	privsep_worker_mutex_unlock();
+
+	return APR_EGENERAL;
+    }
+
+    /* unlock message serialization mutex */
+    privsep_worker_mutex_unlock();
+
+    if (rv != sizeof(privsep_message_t)) {
+	ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
+		     "privsep_send_request: invalid reply length");
+	return APR_EGENERAL;
+    }
+
+    if (msg->command == privsep_command_file_open ||
+	msg->command == privsep_command_dir_open) {
+	/* attempt to extract control message containing fd */
+	cmsg = CMSG_FIRSTHDR (&mh);
+	if (cmsg == NULL) {
+	    /* might be an error return */
+	    return APR_EGENERAL;
+	}
+	if (!cmsg->cmsg_type == SCM_RIGHTS) {
+	    ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
+			 "privsep_send_request: control message unknown type %d",
+			 cmsg->cmsg_type);
+	    return APR_EGENERAL;
+	}
+	/* otherwise return with the fd in the message */
+	return (*(int *) CMSG_DATA (cmsg));
+    }
+
+    return msg->retval;
+}
+
+
+/* privsep token fetching functions */
+
+static privsep_token_t* privsep_get_preauth_stat_token(const request_rec *r)
+{
+    privsep_token_t *token = (privsep_token_t*)
+	apr_table_get(r->notes, PRIVSEP_TOKEN_NOTE);
+
+    if(token)
+	return token;
+    else if(privsep_inited)
+	return privsep_root_token;
+    else
+	return NULL;
+}
+
+static privsep_token_t* privsep_get_token(const request_rec *r)
+{
+    return (privsep_token_t*)apr_table_get(r->notes, PRIVSEP_TOKEN_NOTE);
+}
+
+
+/* prvisep auth client wrapper functions */
+
+AP_DECLARE(privsep_authn_status) ap_privsep_authenticate(const request_rec *r,
+							 const char *user,
+							 const char *pass)
+{
+    privsep_message_t msg;
+    int result;
+
+    if(!privsep_inited)
+	return PRIVSEP_AUTH_GENERAL_ERROR;
+
+    memset(&msg, 0, sizeof(msg));
+    msg.command = privsep_command_authenticate;
+    result = privsep_send_request(&msg, privsep_root_token, user, pass);
+
+    if(result == PRIVSEP_AUTH_GRANTED) {
+	char *privsep_token =
+	    (char *)apr_palloc(r->pool, sizeof(msg.token));
+	memcpy(privsep_token, &msg.token, sizeof(msg.token));
+	apr_table_setn(r->notes, PRIVSEP_TOKEN_NOTE, privsep_token);
+    } else {
+	/* we've disabled delay in the privsep process so as to not
+	   block it, instead we need to delay here */
+	usleep(PRIVSEP_AUTH_FAIL_DELAY);
+    }
+
+    return result;
+}
+
+AP_DECLARE(privsep_authn_status) ap_privsep_verify_token(privsep_token_t *token,
+							 const char *user)
+{
+    privsep_message_t msg;
+
+    if(!privsep_inited)
+	return PRIVSEP_AUTH_GENERAL_ERROR;
+
+    memset(&msg, 0, sizeof(msg));
+    msg.command = privsep_command_verify_token;
+    return privsep_send_request(&msg, token, user);
+}
+
+
+/* privsep portable apr wrappers */
+
+static apr_status_t privsep_stat(privsep_token_t *token,
+				 apr_finfo_t *finfo, 
+				 const char *fname, 
+				 apr_int32_t wanted,
+				 apr_pool_t *pool)
+{
+    privsep_message_t msg;
+    int result;
+
+    if (!privsep_inited || !token)
+	return apr_stat(finfo, fname, wanted, pool);
+
+    memset(&msg, 0, sizeof(msg));
+    msg.command = privsep_command_stat;
+    msg.flags = wanted;
+    result = privsep_send_request(&msg, token, fname);
+    if (result == 0) memcpy(finfo, &msg.finfo, sizeof(*finfo));
+    return result;
+}
+
+static apr_status_t privsep_file_perms_set(privsep_token_t *token,
+					   const char *fname, 
+					   apr_fileperms_t perms)
+{
+    privsep_message_t msg;
+
+    if(!privsep_inited || !token)
+	return apr_file_perms_set(fname, perms);
+
+    memset(&msg, 0, sizeof(msg));
+    msg.command = privsep_command_file_perms_set;
+    msg.perms = perms;
+    return privsep_send_request(&msg, token, fname);
+}
+
+
+static apr_status_t privsep_dir_make(privsep_token_t *token,
+				     const char *path,
+				     apr_fileperms_t perm, 
+				     apr_pool_t *pool)
+{
+    privsep_message_t msg;
+
+    if(!privsep_inited || !token)
+      return apr_dir_make(path, perm, pool);
+
+    memset(&msg, 0, sizeof(msg));
+    msg.command = privsep_command_dir_make;
+    msg.perms = perm;
+    return privsep_send_request(&msg, token, path);
+}
+
+
+static apr_status_t privsep_dir_remove(privsep_token_t *token,
+				       const char *path,
+				       apr_pool_t *pool)
+{
+    privsep_message_t msg;
+
+    if(!privsep_inited || !token)
+      return apr_dir_remove(path, pool);
+
+    memset(&msg, 0, sizeof(msg));
+    msg.command = privsep_command_dir_remove;
+    return privsep_send_request(&msg, token, path);
+}
+
+
+static apr_status_t privsep_file_remove(privsep_token_t *token,
+					const char *path,
+					apr_pool_t *pool)
+{
+    privsep_message_t msg;
+
+    if(!privsep_inited || !token)
+      return apr_file_remove(path, pool);
+
+    memset(&msg, 0, sizeof(msg));
+    msg.command = privsep_command_file_remove;
+    return privsep_send_request(&msg, token, path);
+}
+
+
+static apr_status_t privsep_file_rename(privsep_token_t *token,
+					const char *from_path, 
+					const char *to_path,
+					apr_pool_t *p)
+{
+    privsep_message_t msg;
+
+    if(!privsep_inited || !token)
+      return apr_file_rename(from_path, to_path, p);
+
+    memset(&msg, 0, sizeof(msg));
+    msg.command = privsep_command_file_rename;
+    return privsep_send_request(&msg, token, from_path, to_path);
+}
+
+
+static apr_status_t privsep_file_open(privsep_token_t *token,
+				      apr_file_t **new, 
+				      const char *fname, 
+				      apr_int32_t flag, 
+				      apr_fileperms_t perm, 
+				      apr_pool_t *pool)
+{
+    privsep_message_t msg;
+    apr_os_file_t fd;
+    apr_status_t rv;
+
+    if(!privsep_inited || !token)
+      return apr_file_open(new, fname, flag, perm, pool);
+
+    memset(&msg, 0, sizeof(msg));
+    msg.command = privsep_command_file_open;
+    msg.flags = flag;
+    msg.perms = perm;
+    fd = privsep_send_request(&msg, token, fname);
+
+    if (msg.retval != APR_SUCCESS)
+        return msg.retval;
+
+    rv = apr_os_file_put(new, &fd, flag, pool);
+
+    if (!(flag & APR_FILE_NOCLEANUP)) {
+	apr_pool_cleanup_register(pool, (void *)(*new), 
+				  apr_unix_file_cleanup, 
+				  apr_unix_child_file_cleanup);
+    }
+    return rv;
+}
+
+
+static apr_status_t privsep_dir_open(privsep_token_t *token,
+				     apr_dir_t **new, const char *dirname, 
+				     apr_pool_t *pool)
+{
+    privsep_message_t msg;
+    apr_os_file_t fd;
+    DIR *dir;
+
+    if(!privsep_inited || !token)
+      return apr_dir_open(new, dirname, pool);
+
+    memset(&msg, 0, sizeof(msg));
+    msg.command = privsep_command_dir_open;
+    fd = privsep_send_request(&msg, token, dirname);
+
+    if (msg.retval != APR_SUCCESS)
+        return msg.retval;
+
+    /* apr_os_dir_put does not create a fullly allocated apr_dir_t
+       so we need to open a directory to create one then replace
+       the fd in the associated apr_os_dir */
+    if(apr_dir_open(new, "/", pool) != APR_SUCCESS) {
+	close(fd);
+	return APR_EGENERAL;
+    }
+
+    /* Ok, now the nasty bit:
+       Here we assume that an integer file descriptor is the first 
+       element of the opaque DIR structure pointed at by dir
+       This is the case on Linux, FreeBSD, Mac OS X and Solaris */
+    apr_os_dir_get(&dir, *new);
+    close(*(int *)dir);  
+    *(int *)dir = fd;
+    (*new)->dirname = apr_pstrdup(pool, dirname);
+    return apr_os_dir_put(new, dir, pool);
+}
+
+
+static apr_status_t privsep_dir_read(privsep_token_t *token,
+                                     apr_finfo_t *finfo,
+                                     apr_int32_t wanted,
+                                     apr_dir_t *thedir)
+{
+    apr_status_t ret = 0;
+    apr_int32_t stat_wanted = wanted & APR_FINFO_NORM;
+    wanted &= ~APR_FINFO_NORM;
+    const char *savename;
+
+    /* mask out any flags that would cause a call to apr_stat */
+    if((ret = apr_dir_read(finfo, wanted, thedir)) != APR_SUCCESS)
+	return ret;
+    stat_wanted &= ~finfo->valid;
+
+    /* now do the ap_privsep_stat */
+    savename = finfo->name;
+    stat_wanted &= ~APR_FINFO_NAME;
+    if (stat_wanted)
+    {
+        char fspec[APR_PATH_MAX];
+        char *end;
+
+        end = apr_cpystrn(fspec, thedir->dirname, sizeof fspec);
+
+        if (end > fspec && end[-1] != '/' && (end < fspec + APR_PATH_MAX))
+            *end++ = '/';
+
+        apr_cpystrn(end, finfo->name, sizeof fspec - (end - fspec));
+
+        ret = ap_privsep_stat(token, finfo, fspec,
+			      APR_FINFO_LINK | stat_wanted, thedir->pool);
+	stat_wanted &= ~finfo->valid;
+    }
+    finfo->name = savename;
+    finfo->valid |= APR_FINFO_NAME;
+
+    if (stat_wanted)
+        return APR_INCOMPLETE;
+
+    return ret;
+}
+
+
+/* install file io hooks */
+
+static ap_privsep_hooks_t hooks = {
+    privsep_get_preauth_stat_token,
+    privsep_get_token,
+    privsep_stat,
+    privsep_file_perms_set,
+    privsep_dir_make,
+    privsep_dir_remove,
+    privsep_file_remove,
+    privsep_file_rename,
+    privsep_file_open,
+    privsep_dir_open,
+    privsep_dir_read
+};
+
+void ap_privsep_register_file_io_hooks()
+{
+    ap_privsep_set_hooks(&hooks);
+}

Add build infrastructure for mod_privsep

Index: configure.in
===================================================================
--- configure.in	(revision 602780)
+++ configure.in	(working copy)
@@ -204,7 +204,7 @@
   APR_ADDTO(INCLUDES, [-I\$(top_builddir)/include])
 fi
 
-APR_ADDTO(INCLUDES, [-I\$(top_srcdir)/os/\$(OS_DIR) -I\$(top_srcdir)/server/mpm/\$(MPM_SUBDIR_NAME) -I\$(top_srcdir)/modules/http -I\$(top_srcdir)/modules/filters -I\$(top_srcdir)/modules/proxy -I\$(top_srcdir)/include -I\$(top_srcdir)/modules/generators -I\$(top_srcdir)/modules/mappers -I\$(top_srcdir)/modules/database])
+APR_ADDTO(INCLUDES, [-I\$(top_srcdir)/os/\$(OS_DIR) -I\$(top_srcdir)/server/mpm/\$(MPM_SUBDIR_NAME) -I\$(top_srcdir)/modules/http -I\$(top_srcdir)/modules/filters -I\$(top_srcdir)/modules/proxy -I\$(top_srcdir)/include -I\$(top_srcdir)/modules/generators -I\$(top_srcdir)/modules/mappers -I\$(top_srcdir)/modules/database -I\$(top_srcdir)/modules/privsep])
 
 # apr/apr-util --includes may pick up system paths for dependent
 # libraries, so ensure these are later in INCLUDES than local source
Index: modules/privsep/Makefile.in
===================================================================
--- modules/privsep/Makefile.in	(revision 0)
+++ modules/privsep/Makefile.in	(revision 0)
@@ -0,0 +1,3 @@
+# a modules Makefile has no explicit targets -- they will be defined by
+# whatever modules are enabled. just grab special.mk to deal with this.
+include $(top_srcdir)/build/special.mk
Index: modules/privsep/config.m4
===================================================================
--- modules/privsep/config.m4	(revision 0)
+++ modules/privsep/config.m4	(revision 0)
@@ -0,0 +1,8 @@
+APACHE_MODPATH_INIT(privsep)
+
+privsep_objects="mod_privsep.lo util_privsep_client.lo"
+APACHE_MODULE(privsep, Privilege Separation, $privsep_objects, , no, [
+  APR_ADDTO(LIBS, [-lpam])
+])
+
+APACHE_MODPATH_FINISH
Index: modules/privsep/modules.mk
===================================================================
--- modules/privsep/modules.mk	(revision 0)
+++ modules/privsep/modules.mk	(revision 0)
@@ -0,0 +1,5 @@
+libmod_privsep.la: mod_privsep.lo util_privsep_client.lo
+	$(MOD_LINK) mod_privsep.lo util_privsep_client.lo $(MOD_PRIVSEP_LDADD)
+DISTCLEAN_TARGETS = modules.mk
+static =  libmod_privsep.la
+shared = 
