diff -urNX apache-excludes apache_1.3.39.orig/src/include/ap_alloc.h apache_1.3.39/src/include/ap_alloc.h --- apache_1.3.39.orig/src/include/ap_alloc.h 2007-12-05 21:11:38.000000000 +0800 +++ apache_1.3.39/src/include/ap_alloc.h 2007-12-05 21:11:42.000000000 +0800 @@ -295,9 +295,14 @@ /* Common cases which want utility support.. * the note_cleanups_for_foo routines are for */ - +struct request_rec; +API_EXPORT(FILE *) ap_priv_pfopen(struct request_rec *r, const char *name, const char *fmode); API_EXPORT(FILE *) ap_pfopen(struct pool *, const char *name, const char *fmode); API_EXPORT(FILE *) ap_pfdopen(struct pool *, int fd, const char *fmode); +API_EXPORT(int) ap_priv_popenf(struct request_rec *r, const char *name, + int flg, int mode); +API_EXPORT(int) ap_priv_popenf_ex(struct request_rec *r, const char *name, + int flg, int mode, int domagic); API_EXPORT(int) ap_popenf(struct pool *, const char *name, int flg, int mode); API_EXPORT(int) ap_popenf_ex(struct pool *, const char *name, int flg, int mode, int domagic); @@ -332,6 +337,7 @@ #endif /* routines to deal with directories */ +API_EXPORT(DIR *) ap_priv_popendir(struct request_rec *r, const char *name); API_EXPORT(DIR *) ap_popendir(pool *p, const char *name); API_EXPORT(void) ap_pclosedir(pool *p, DIR * d); diff -urNX apache-excludes apache_1.3.39.orig/src/include/httpd.h apache_1.3.39/src/include/httpd.h --- apache_1.3.39.orig/src/include/httpd.h 2007-12-05 21:11:38.000000000 +0800 +++ apache_1.3.39/src/include/httpd.h 2007-12-05 21:11:42.000000000 +0800 @@ -1114,6 +1114,8 @@ API_EXPORT(gid_t) ap_gname2id(const char *name); API_EXPORT(int) ap_is_directory(const char *name); API_EXPORT(int) ap_is_rdirectory(const char *name); +API_EXPORT(int) ap_priv_is_directory(struct request_rec *r, const char *path); +API_EXPORT(int) ap_priv_is_rdirectory(struct request_rec *r, const char *path); API_EXPORT(int) ap_can_exec(const struct stat *); API_EXPORT(void) ap_chdir_file(const char *file); diff -urNX apache-excludes apache_1.3.39.orig/src/include/privsep.h apache_1.3.39/src/include/privsep.h --- apache_1.3.39.orig/src/include/privsep.h 1970-01-01 07:30:00.000000000 +0730 +++ apache_1.3.39/src/include/privsep.h 2007-12-05 21:11:42.000000000 +0800 @@ -0,0 +1,131 @@ +/* Copyright 2005 Metaparadigm Pte Ltd + * + * Authors: Jamie Clark, + * Michael Clark + * + * 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 APACHE_PRIVSEP_H +#define APACHE_PRIVSEP_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include "util_md5.h" + + +#define PRIV_AUTH_KEY_LEN 16 +#define PRIV_TOKEN_SALT_LEN 8 +#define PRIV_USERNAME_LEN 32 +#define PRIV_AUTH_FAIL_DELAY 2000000 /* usecs */ +#define PRIVD_BUF_SIZE (sizeof(struct priv_command_msg) + PATH_MAX * 2) + +extern int priv_enabled; +extern int priv_debug; +extern int priv_inited; +extern char priv_sep_root[PATH_MAX]; +extern struct priv_token priv_root_token; +extern int priv_sem_id; +extern int priv_client_fd; + + +typedef struct { + const char *user; + const char *pass; +} auth_pam_userinfo; + +struct priv_token { + uid_t uid; + gid_t gid; + unsigned char salt[PRIV_TOKEN_SALT_LEN]; + unsigned char digest[MD5_DIGESTSIZE]; +}; + +enum priv_command { + priv_command_auth, + priv_command_open, + priv_command_chmod, + priv_command_stat, + priv_command_lstat, + priv_command_mkdir, + priv_command_rename, + priv_command_remove +}; + +struct priv_command_msg { + enum priv_command command; + int command_rc; + int command_errno; + int flags; + mode_t mode; + struct stat stat; + struct priv_token token; + struct in_addr remote; +}; + + +/* start privilege separation */ + +extern int +ap_do_privd (pool *pconf, server_rec *server_conf); + + +/* privileged wrapper functions */ + +extern int +priv_auth (request_rec *r, const char *pass); + +extern int +priv_open (const request_rec *r, const char *path, int flags, ...); + +extern FILE* +priv_fopen (const request_rec *r, const char *path, const char *fmode); + +extern int +priv_chmod (const request_rec *r, const char *path, mode_t mode); + +extern int +priv_stat (const request_rec *r, const char *path, struct stat *buf); + +extern int +priv_lstat (const request_rec *r, const char *path, struct stat *buf); + +extern int +priv_special_stat (const request_rec *r, const char *path, struct stat *buf); + +extern int +priv_special_lstat (const request_rec *r, const char *path, struct stat *buf); + +extern int +priv_mkdir (const request_rec *r, const char *path, mode_t mode); + +extern int +priv_rename (const request_rec *r, const char *path, const char *newpath); + +extern DIR* +priv_opendir (const request_rec *r, const char *path); + +extern int +priv_remove (const request_rec *r, const char *path); + + + +#ifdef __cplusplus +} +#endif + +#endif /* !APACHE_PRIVSEP_H */ diff -urNX apache-excludes apache_1.3.39.orig/src/main/alloc.c apache_1.3.39/src/main/alloc.c --- apache_1.3.39.orig/src/main/alloc.c 2007-12-05 21:11:38.000000000 +0800 +++ apache_1.3.39/src/main/alloc.c 2007-12-05 21:11:42.000000000 +0800 @@ -28,6 +28,7 @@ #endif #include "multithread.h" #include "http_log.h" +#include "privsep.h" #include @@ -2070,6 +2071,31 @@ ap_kill_cleanup(p, (void *) (long) fd, fd_cleanup); } + +API_EXPORT(int) ap_priv_popenf_ex(struct request_rec *r, const char *name, + int flg, int mode, int domagic) +{ + int fd; + int save_errno; + + ap_block_alarms(); + fd = priv_open(r, name, flg, mode); + save_errno = errno; + if (fd >= 0) { + fd = ap_slack(fd, AP_SLACK_HIGH); + ap_note_cleanups_for_fd_ex(r->pool, fd, domagic); + } + ap_unblock_alarms(); + errno = save_errno; + return fd; +} + +API_EXPORT(int) ap_priv_popenf(struct request_rec *r, const char *name, + int flg, int mode) +{ + return ap_priv_popenf_ex(r, name, flg, mode, 0); +} + API_EXPORT(int) ap_popenf_ex(pool *a, const char *name, int flg, int mode, int domagic) { @@ -2184,6 +2210,42 @@ ap_note_cleanups_for_file_ex(p, fp, 0); } +API_EXPORT(FILE *) ap_priv_pfopen(request_rec *r, const char *name, const char *mode) +{ + FILE *fd = NULL; + int baseFlag, desc; + int modeFlags = 0; + int saved_errno; + +#ifdef WIN32 + modeFlags = _S_IREAD | _S_IWRITE; +#else + modeFlags = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; +#endif + + ap_block_alarms(); + + if (*mode == 'a') { + /* Work around faulty implementations of fopen */ + baseFlag = (*(mode + 1) == '+') ? O_RDWR : O_WRONLY; + desc = priv_open(r, name, baseFlag | O_APPEND | O_CREAT, + modeFlags); + if (desc >= 0) { + desc = ap_slack(desc, AP_SLACK_LOW); + fd = ap_fdopen(desc, mode); + } + } + else { + fd = priv_fopen(r, name, mode); + } + saved_errno = errno; + if (fd != NULL) + ap_note_cleanups_for_file(r->pool, fd); + ap_unblock_alarms(); + errno = saved_errno; + return fd; +} + API_EXPORT(FILE *) ap_pfopen(pool *a, const char *name, const char *mode) { FILE *fd = NULL; @@ -2256,6 +2318,24 @@ closedir((DIR *) dv); } +API_EXPORT(DIR *) ap_priv_popendir(request_rec *r, const char *name) +{ + DIR *d; + int save_errno; + + ap_block_alarms(); + d = priv_opendir(r, name); + if (d == NULL) { + save_errno = errno; + ap_unblock_alarms(); + errno = save_errno; + return NULL; + } + ap_register_cleanup(r->pool, (void *) d, dir_cleanup, dir_cleanup); + ap_unblock_alarms(); + return d; +} + API_EXPORT(DIR *) ap_popendir(pool *p, const char *name) { DIR *d; diff -urNX apache-excludes apache_1.3.39.orig/src/main/http_core.c apache_1.3.39/src/main/http_core.c --- apache_1.3.39.orig/src/main/http_core.c 2006-07-12 16:16:05.000000000 +0800 +++ apache_1.3.39/src/main/http_core.c 2007-12-05 21:11:42.000000000 +0800 @@ -29,6 +29,7 @@ #include "util_md5.h" #include "scoreboard.h" #include "fnmatch.h" +#include "privsep.h" #ifdef USE_MMAP_FILES #include @@ -3370,6 +3371,53 @@ return NULL; } +static const char *set_priv_sep(cmd_parms *cmd, void *dummy, char *arg) +{ + const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_LOC_FILE|NOT_IN_LIMIT); + if (err != NULL) return err; + + /* We've changed it to On/Off, but used to use numbers + * so we accept anything but "Off" or "0" as "On" + */ + if (!strcasecmp(arg, "off") || !strcmp(arg, "0")) + priv_enabled = 0; + else + priv_enabled = 1; + + return NULL; +} + +static const char *set_priv_sep_debug(cmd_parms *cmd, void *dummy, char *arg) +{ + const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_LOC_FILE|NOT_IN_LIMIT); + if (err != NULL) return err; + + /* We've changed it to On/Off, but used to use numbers + * so we accept anything but "Off" or "0" as "On" + */ + if (!strcasecmp(arg, "off") || !strcmp(arg, "0")) + priv_debug = 0; + else + priv_debug = 1; + + return NULL; +} + +static const char *set_priv_sep_root(cmd_parms *cmd, void *dummy, char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + + if (err != NULL) return err; + + arg = ap_os_canonical_filename(cmd->pool, arg); + + if (!ap_is_directory(arg)) { + return "PrivilegeSeparationRoot must be a valid directory"; + } + ap_cpystrn(priv_sep_root, arg, sizeof(priv_sep_root)); + return NULL; +} + static void log_backtrace(const request_rec *r) { const request_rec *top = r; @@ -3773,6 +3821,14 @@ { "TraceEnable", set_trace_enable, NULL, RSRC_CONF, TAKE1, "'on' (default), 'off' or 'extended' to trace request body content"}, + +{ "PrivilegeSeparation", set_priv_sep, NULL, RSRC_CONF, TAKE1, + "Whether privilege separation should be On or Off" }, +{ "PrivilegeSeparationDebug", set_priv_sep_debug, NULL, RSRC_CONF, TAKE1, + "Whether privilege separation debugging should be On or Off" }, +{ "PrivilegeSeparationRoot", set_priv_sep_root, NULL, RSRC_CONF, TAKE1, + "Root directory under which privileged operations are allowed" }, + { NULL } }; @@ -4151,7 +4207,7 @@ /* Need binary mode for OS/2 */ f = ap_pfopen(r->pool, r->filename, "rb"); #else - f = ap_pfopen(r->pool, r->filename, "r"); + f = ap_priv_pfopen(r, r->filename, "r"); #endif if (f == NULL) { diff -urNX apache-excludes apache_1.3.39.orig/src/main/http_main.c apache_1.3.39/src/main/http_main.c --- apache_1.3.39.orig/src/main/http_main.c 2007-12-05 21:11:38.000000000 +0800 +++ apache_1.3.39/src/main/http_main.c 2007-12-05 21:11:42.000000000 +0800 @@ -62,6 +62,7 @@ #include "util_uri.h" #include "scoreboard.h" #include "multithread.h" +#include "privsep.h" #include #ifdef USE_SHMGET_SCOREBOARD #include @@ -3574,7 +3575,7 @@ } #endif #ifndef NO_SETSID - if ((pgrp = setsid()) == -1) { + if (do_detach && (pgrp = setsid()) == -1) { perror("setsid"); fprintf(stderr, "%s: setsid failed\n", ap_server_argv0); if (!do_detach) @@ -5470,6 +5471,8 @@ ap_init_mutex_method(ap_default_mutex_method()); server_conf = ap_read_config(pconf, ptrans, ap_server_confname); + if(priv_enabled) + ap_do_privd(pconf, server_conf); setup_listeners(pconf); ap_clear_pool(plog); ap_open_logs(server_conf, plog); diff -urNX apache-excludes apache_1.3.39.orig/src/main/http_request.c apache_1.3.39/src/main/http_request.c --- apache_1.3.39.orig/src/main/http_request.c 2007-12-05 21:11:38.000000000 +0800 +++ apache_1.3.39/src/main/http_request.c 2007-12-05 21:11:42.000000000 +0800 @@ -35,6 +35,7 @@ #include "http_main.h" #include "scoreboard.h" #include "fnmatch.h" +#include "privsep.h" /***************************************************************** * @@ -70,7 +71,7 @@ } -static int check_symlinks(char *d, int opts) +static int check_symlinks(const request_rec *r, char *d, int opts) { #if defined(OS2) || defined(WIN32) || defined(NETWARE) /* OS/2 doesn't have symlinks */ @@ -102,7 +103,9 @@ else lastp = NULL; - res = lstat(d, &lfi); + /* we need to lstat as root if we are not authenticated yet to make + sure we can do the symlink down paths not reachable by apache user */ + res = priv_special_lstat(r, d, &lfi); if (lastp) *lastp = '/'; @@ -120,8 +123,10 @@ if (!(opts & OPT_SYM_OWNER)) return HTTP_FORBIDDEN; - if (stat(d, &fi) < 0) - return HTTP_FORBIDDEN; + /* we need to stat as root if we are not authenticated yet to make + sure we can do the symlink down paths not reachable by apache user */ + if (priv_special_stat(r, d, &fi) < 0) + return HTTP_FORBIDDEN; return (fi.st_uid == lfi.st_uid) ? OK : HTTP_FORBIDDEN; @@ -204,7 +209,10 @@ } else { errno = 0; - rv = stat(path, &r->finfo); + /* We need to do the stat as root if we are not + authenticated yet. This is pretty safe as privd + will only allow stat and lstat as root */ + rv = priv_special_stat(r, path, &r->finfo); #ifdef OS2 r->finfo.st_ino = 0; #endif @@ -466,7 +474,7 @@ /* Test only legal names against the real filesystem */ if (i >= iStart) #endif - if ((res = check_symlinks(test_dirname, core_dir->opts))) { + if ((res = check_symlinks(r, test_dirname, core_dir->opts))) { ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, "Symbolic link not allowed: %s", test_dirname); return res; @@ -584,7 +592,7 @@ * you would *not* get the 403. */ if (!S_ISDIR(r->finfo.st_mode) - && (res = check_symlinks(r->filename, ap_allow_options(r)))) { + && (res = check_symlinks(r, r->filename, ap_allow_options(r)))) { ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, "Symbolic link not allowed: %s", r->filename); return res; @@ -888,7 +896,7 @@ rnew->uri = ap_make_full_path(rnew->pool, udir, new_file); rnew->filename = ap_make_full_path(rnew->pool, fdir, new_file); ap_parse_uri(rnew, rnew->uri); /* fill in parsed_uri values */ - if (stat(rnew->filename, &rnew->finfo) < 0) { + if (priv_stat(r, rnew->filename, &rnew->finfo) < 0) { rnew->finfo.st_mode = 0; #ifdef ENAMETOOLONG /* Special case for filenames which exceed the maximum limit @@ -927,7 +935,7 @@ } } else { - if ((res = check_symlinks(rnew->filename, ap_allow_options(rnew)))) { + if ((res = check_symlinks(r, rnew->filename, ap_allow_options(rnew)))) { ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, rnew, "Symbolic link not allowed: %s", rnew->filename); rnew->status = res; @@ -1066,7 +1074,7 @@ */ if ((r->status != HTTP_NOT_MODIFIED) && (r->status != HTTP_NO_CONTENT) && !ap_status_drops_connection(r->status) - && r->connection && (r->connection->keepalive > 0)) { + && r->connection && (r->connection->keepalive != -1)) { (void) ap_discard_request_body(r); } diff -urNX apache-excludes apache_1.3.39.orig/src/main/Makefile.tmpl apache_1.3.39/src/main/Makefile.tmpl --- apache_1.3.39.orig/src/main/Makefile.tmpl 2004-11-25 03:10:19.000000000 +0800 +++ apache_1.3.39/src/main/Makefile.tmpl 2007-12-05 21:11:42.000000000 +0800 @@ -7,7 +7,7 @@ LIB= libmain.a HEADERS= test_char.h uri_delims.h -OBJS= alloc.o buff.o \ +OBJS= alloc.o buff.o privsep_server.o privsep_client.o \ http_config.o http_core.o http_log.o \ http_main.o http_protocol.o http_request.o http_vhost.o \ util.o util_date.o util_script.o util_uri.o util_md5.o \ diff -urNX apache-excludes apache_1.3.39.orig/src/main/privsep_client.c apache_1.3.39/src/main/privsep_client.c --- apache_1.3.39.orig/src/main/privsep_client.c 1970-01-01 07:30:00.000000000 +0730 +++ apache_1.3.39/src/main/privsep_client.c 2007-12-05 21:11:42.000000000 +0800 @@ -0,0 +1,506 @@ +/* Copyright 2005 Metaparadigm Pte Ltd + * + * Authors: Jamie Clark, + * Michael Clark + * + * 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. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "httpd.h" +#include "http_request.h" +#include "http_log.h" +#include "http_conf_globals.h" +#include "ap_md5.h" +#include "privsep.h" + + +static int +priv_mutex_lock() +{ + int rv; + + struct sembuf semb[] = { + { 0, 0, 0 }, + { 0, 1, SEM_UNDO } + }; + + while ((rv = semop(priv_sem_id, semb, 2)) < 0 && + errno == EINTR); + + return rv; +} + +static void +priv_mutex_unlock() +{ + int rv; + + struct sembuf semb[] = { + { 0, -1, SEM_UNDO } + }; + + while ((rv = semop(priv_sem_id, semb, 1)) < 0 && + errno == EINTR); + + if(rv < 0) { + perror ("priv_mutex_unlock: fatal error unlocking mutex. " + "Child will exit to undo semaphore."); + exit(1); + } +} + +static int +priv_send_request (const request_rec *r, + struct priv_command_msg *om, + const struct priv_token *token, + const char *path1, ...) +{ + va_list ap; + struct sockaddr_un clientname; + 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 (om->command == priv_command_rename || + om->command == priv_command_auth) + path2 = va_arg (ap, const char*); + va_end (ap); + + /* Make sure we don't send too much */ + if (sizeof(struct priv_command_msg) + strlen(path1) + strlen(path2) + > PRIVD_BUF_SIZE) + { + perror ("priv_send_request: args to large"); + errno = EIO; + return -1; + } + + /* copy the token and request addr into the command structure */ + memcpy(&om->token, token, sizeof(om->token)); + om->remote = r->connection->remote_addr.sin_addr; + + /* construct an iovec with the input parameters */ + iv[0].iov_base = om; + iv[0].iov_len = sizeof (*om); + 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; + + /* lock message serialization mutex */ + if(priv_mutex_lock() < 0) { + perror ("priv_mutex_lock: error locking mutex"); + errno = EIO; + return -1; + } + + /* fire away */ + if ((rv = sendmsg (priv_client_fd, &mh, 0)) < 0) + { + perror ("priv_send_request: sendmsg"); + + /* unlock message serialization mutex */ + priv_mutex_unlock(); + + errno = EIO; + return -1; + } + + /* 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 (priv_client_fd, &mh, 0); + + if (rv < 0) { + perror ("priv_send_request: recvmsg"); + + /* unlock message serialization mutex */ + priv_mutex_unlock(); + + errno = EIO; + return -1; + } + + /* unlock message serialization mutex */ + priv_mutex_unlock(); + + if (rv != sizeof(struct priv_command_msg)) { + fprintf(stderr, "priv_send_request: invalid reply length\n"); + errno = EIO; + return -1; + } + + if (om->command == priv_command_open) + { + /* attempt to extract control message containing fd */ + cmsg = CMSG_FIRSTHDR (&mh); + if (cmsg == NULL) + { + /* might be an error return */ + return -1; + } + if (!cmsg->cmsg_type == SCM_RIGHTS) + { + fprintf (stderr, "got control message of unknown type %d\n", + cmsg->cmsg_type); + return -1; + } + /* otherwise return with the fd in the message */ + return (*(int *) CMSG_DATA (cmsg)); + } + + return om->command_rc; +} + + +/* client wrapper functions */ + +int +priv_auth (request_rec *r, const char *pass) +{ + struct priv_command_msg om; + int result; + + if (!priv_inited) return -1; + + om.command = priv_command_auth; + result = priv_send_request (r, &om, &priv_root_token, + r->connection->user, pass); + errno = om.command_errno; + + if(result == PAM_SUCCESS) { + char *priv_token = ap_palloc(r->pool, sizeof(om.token)); + memcpy(priv_token, &om.token, sizeof(om.token)); + ap_table_setn(r->notes, "privsep-token", priv_token); + } else { + /* we've disabled delay in privd so we need to delay here */ + usleep(PRIV_AUTH_FAIL_DELAY); + } + + return result; +} + +int +priv_open (const request_rec *r, const char *path, int flags, ...) +{ + va_list ap; + struct priv_command_msg om; + int result; + + const struct priv_token *priv_token = + (struct priv_token*)ap_table_get(r->notes, "privsep-token"); + + om.mode = 0; + va_start (ap, flags); + if (flags & O_CREAT) + om.mode = va_arg (ap, mode_t); + va_end (ap); + + if (!priv_inited || !priv_token || + strncmp(priv_sep_root, path, strlen(priv_sep_root))) + return open (path, flags, om.mode); + + om.command = priv_command_open; + om.flags = flags; + result = priv_send_request (r, &om, priv_token, path); + errno = om.command_errno; + + return result; +} + +FILE* +priv_fopen (const request_rec *r, const char *path, const char *fmode) +{ + int flags, fd; + FILE *f = NULL; + mode_t mode = 0; + + const struct priv_token *priv_token = + (struct priv_token*)ap_table_get(r->notes, "privsep-token"); + + if (!priv_inited || !priv_token) + return fopen (path, fmode); + + switch (*fmode) + { + case 'r': + flags = (*(fmode + 1) == '+') ? O_RDWR : O_RDONLY; + break; + case 'w': + flags = O_CREAT | O_TRUNC | ((*(fmode + 1) == '+') ? O_RDWR : O_WRONLY); + mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + break; + case 'a': + flags = + O_CREAT | O_APPEND | ((*(fmode + 1) == '+') ? O_RDWR : O_WRONLY); + mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + break; + } + + fd = priv_open (r, path, flags, mode); + + if (fd > 0) + f = fdopen (fd, fmode); + + return f; +} + +int +priv_chmod (const request_rec *r, const char *path, mode_t mode) +{ + struct priv_command_msg om; + int result; + + const struct priv_token *priv_token = + (struct priv_token*)ap_table_get(r->notes, "privsep-token"); + + if (!priv_inited || !priv_token) + return chmod (path, mode); + + om.command = priv_command_chmod; + om.mode = mode; + result = priv_send_request (r, &om, priv_token, path); + errno = om.command_errno; + + return result; +} + +int +priv_stat (const request_rec *r, const char *path, struct stat *buf) +{ + struct priv_command_msg om; + int result; + + const struct priv_token *priv_token = + (struct priv_token*)ap_table_get(r->notes, "privsep-token"); + + if (!priv_inited || !priv_token || + strncmp(priv_sep_root, path, strlen(priv_sep_root))) + return stat (path, buf); + + om.command = priv_command_stat; + result = priv_send_request (r, &om, priv_token, path); + if (result == 0) memcpy( buf, &om.stat, sizeof(*buf)); + errno = om.command_errno; + + return result; +} + +int +priv_lstat (const request_rec *r, const char *path, struct stat *buf) +{ + struct priv_command_msg om; + int result; + + const struct priv_token *priv_token = + (struct priv_token*)ap_table_get(r->notes, "privsep-token"); + + if (!priv_inited || !priv_token || + strncmp(priv_sep_root, path, strlen(priv_sep_root))) + return lstat (path, buf); + + om.command = priv_command_lstat; + result = priv_send_request (r, &om, priv_token, path); + if (result == 0) memcpy( buf, &om.stat, sizeof(*buf)); + errno = om.command_errno; + + return result; +} + +int +priv_special_stat (const request_rec *r, const char *path, struct stat *buf) +{ + struct priv_command_msg om; + int result; + + const struct priv_token *priv_token = + (struct priv_token*)ap_table_get(r->notes, "privsep-token"); + + if (!priv_inited || strncmp(priv_sep_root, path, strlen(priv_sep_root))) + return stat (path, buf); + + om.command = priv_command_stat; + if(priv_token) + result = priv_send_request (r, &om, priv_token, path); + else + result = priv_send_request (r, &om, &priv_root_token, path); + + if (result == 0) memcpy( buf, &om.stat, sizeof(*buf)); + errno = om.command_errno; + + return result; +} + +int +priv_special_lstat (const request_rec *r, const char *path, struct stat *buf) +{ + struct priv_command_msg om; + int result; + + const struct priv_token *priv_token = + (struct priv_token*)ap_table_get(r->notes, "privsep-token"); + + if (!priv_inited || strncmp(priv_sep_root, path, strlen(priv_sep_root))) + return lstat (path, buf); + + om.command = priv_command_lstat; + if(priv_token) + result = priv_send_request (r, &om, priv_token, path); + else + result = priv_send_request (r, &om, &priv_root_token, path); + + if (result == 0) memcpy( buf, &om.stat, sizeof(*buf)); + errno = om.command_errno; + + return result; +} + + +int +priv_mkdir (const request_rec *r, const char *path, mode_t mode) +{ + struct priv_command_msg om; + int result; + + const struct priv_token *priv_token = + (struct priv_token*)ap_table_get(r->notes, "privsep-token"); + + if (!priv_inited || !priv_token || + strncmp(priv_sep_root, path, strlen(priv_sep_root))) + return mkdir (path, mode); + + om.command = priv_command_mkdir; + om.mode = mode; + result = priv_send_request (r, &om, priv_token, path); + errno = om.command_errno; + + return result; +} + +int +priv_rename (const request_rec *r, const char *path, + const char *newpath) +{ + struct priv_command_msg om; + int result; + + const struct priv_token *priv_token = + (struct priv_token*)ap_table_get(r->notes, "privsep-token"); + + if (!priv_inited || !priv_token || + strncmp(priv_sep_root, path, strlen(priv_sep_root)) || + strncmp(priv_sep_root, newpath, strlen(priv_sep_root))) + return rename (path, newpath); + + om.command = priv_command_rename; + result = priv_send_request (r, &om, priv_token, path, newpath); + errno = om.command_errno; + + return result; +} + +DIR* +priv_opendir(const request_rec *r, const char *path) +{ + DIR *dir; + int fd; + + const struct priv_token *priv_token = + (struct priv_token*)ap_table_get(r->notes, "privsep-token"); + + if (!priv_inited || !priv_token || + strncmp(priv_sep_root, path, strlen(priv_sep_root))) + return opendir (path); + + fd = priv_open (r, path, O_RDONLY|O_NDELAY|O_DIRECTORY ); + + if ( fd < 0) + return NULL; + + fcntl (fd, F_SETFD, FD_CLOEXEC); + + if ( NULL == (dir = opendir("/"))) + { + close(fd); + return NULL; + } + + /* Ok, now the nasty bit: + Here we assume that an integer file descriptor is the first + element of the opaque __dirstream structure pointed at by dir */ + + if ( close( *(int *)dir)) + { + fprintf(stderr, "priv_opendir: couldn't close fake dir\n"); + } + + *(int *)dir = fd; + + return dir; +} + +int +priv_remove (const request_rec *r, const char *path) +{ + struct priv_command_msg om; + int result; + + const struct priv_token *priv_token = + (struct priv_token*)ap_table_get(r->notes, "privsep-token"); + + if (!priv_inited || !priv_token || + strncmp(priv_sep_root, path, strlen(priv_sep_root))) + return remove (path); + + om.command = priv_command_remove; + result = priv_send_request (r, &om, priv_token, path); + errno = om.command_errno; + + return result; +} diff -urNX apache-excludes apache_1.3.39.orig/src/main/privsep_server.c apache_1.3.39/src/main/privsep_server.c --- apache_1.3.39.orig/src/main/privsep_server.c 1970-01-01 07:30:00.000000000 +0730 +++ apache_1.3.39/src/main/privsep_server.c 2007-12-05 21:11:42.000000000 +0800 @@ -0,0 +1,605 @@ +/* Copyright 2005 Metaparadigm Pte Ltd + * + * Authors: Jamie Clark, + * Michael Clark + * + * 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. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "httpd.h" +#include "http_request.h" +#include "http_log.h" +#include "http_conf_globals.h" +#include "ap_md5.h" +#include "privsep.h" + + +/* global */ +int priv_enabled = 0; +int priv_debug = 0; +int priv_inited = 0; +char priv_sep_root[PATH_MAX]; +struct priv_token priv_root_token; +int priv_sem_id; +int priv_client_fd; + +/* server */ +static int priv_server_fd; +static int urandom_fd; +static const char pam_servicename[] = "apache"; +static char privd_msgbuf[PRIVD_BUF_SIZE]; + + +/* remove /./'s, /../'s and trailing .. */ +static int +priv_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 +privd_authenticate (const char **msg, const char *user, const char *pass) +{ + 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 */ + if ((rv = pam_start(pam_servicename, user, + &conv_info, &pamh)) != PAM_SUCCESS) { + *msg = pam_strerror(pamh, rv); + return rv; + } + + /* 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 */); + + /* try to authenticate user, log error on failure */ + if ((rv = pam_authenticate(pamh, PAM_DISALLOW_NULL_AUTHTOK)) != PAM_SUCCESS) { + *msg = pam_strerror(pamh, rv); + pam_end(pamh, PAM_SUCCESS); + return rv; + } + + /* check that the account is healthy */ + if ((rv = pam_acct_mgmt(pamh, PAM_DISALLOW_NULL_AUTHTOK)) != PAM_SUCCESS) { + *msg = pam_strerror(pamh, rv); + pam_end(pamh, PAM_SUCCESS); + return rv; + } + + pam_end(pamh, PAM_SUCCESS); + return rv; +} + +static int +privd_salt_token(struct priv_token *token) +{ + int bytes; + + bytes = read( urandom_fd, &token->salt, sizeof(token->salt)); + if ( bytes < sizeof(token->salt)) return -1; + return 0; +} + +static int +privd_check_token(struct priv_token *token, unsigned char* auth_key) +{ + char digest[MD5_DIGESTSIZE]; + AP_MD5_CTX md5_ctx; + + /* save digest for comparison */ + memcpy(digest, token->digest, MD5_DIGESTSIZE); + + /* hash auth_key, uid, gid and salt */ + ap_MD5Init(&md5_ctx); + ap_MD5Update(&md5_ctx, auth_key, PRIV_AUTH_KEY_LEN); + ap_MD5Update(&md5_ctx, (unsigned char*)&token->uid, sizeof(token->uid)); + ap_MD5Update(&md5_ctx, (unsigned char*)&token->gid, sizeof(token->gid)); + ap_MD5Update(&md5_ctx, token->salt, sizeof(token->salt)); + ap_MD5Final(token->digest, &md5_ctx); + + return memcmp(digest, token->digest, MD5_DIGESTSIZE); +} + +static char* +privd_print_token(struct priv_token *token) +{ + static char buf[sizeof(struct priv_token) * 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; +} + +static void +privd_service_request (unsigned char *auth_key, server_rec *server_conf) +{ + int rv; + struct msghdr mh; + struct iovec iv; + struct priv_command_msg *om = (struct priv_command_msg*)privd_msgbuf; + char *path1, *path2; + struct cmsghdr *cmsg; + int openfd; + char ccmsg[CMSG_SPACE (sizeof (openfd))]; + + /* construct an iovec large enough to receive the input parameters */ + iv.iov_base = privd_msgbuf; + iv.iov_len = PRIVD_BUF_SIZE; + + /* link the iovec into the message header */ + memset (&mh, 0, sizeof (mh)); + mh.msg_iov = &iv; + mh.msg_iovlen = 1; + + /* wait for a message */ + if ((rv = recvmsg (priv_server_fd, &mh, 0)) < 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, server_conf, + "privd_service_request: recvmsg"); + return; + } + + /* pull out the path arguments */ + path1 = privd_msgbuf + sizeof(struct priv_command_msg); + path2 = path1 + strlen(path1) + 1; + + /* check the token */ + if (privd_check_token(&om->token, auth_key) != 0) { + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, server_conf, + "[client %s] privd_service_request: invalid token", + inet_ntoa(om->remote)); + goto fail_perm; + } + + /* set iovec len for reply */ + iv.iov_len = sizeof (*om); + + mh.msg_control = NULL; + mh.msg_controllen = 0; + + if (om->command == priv_command_auth) + { + struct passwd *pw = NULL; + const char *msg = "success"; + + if ((om->command_rc = privd_authenticate (&msg, path1, path2)) != 0) { + om->command_rc = -1; + om->command_errno = EPERM; + } else if ((pw = getpwnam (path1)) < 0) { + msg = "no such user"; + om->command_rc = -1; + om->command_errno = EPERM; + } else if (pw->pw_uid == 0) { + msg = "disallowed uid 0"; + om->command_rc = -1; + om->command_errno = EPERM; + } else if (pw->pw_gid == 0) { + msg = "disallowed gid 0"; + om->command_rc = -1; + om->command_errno = EPERM; + } else if (privd_salt_token(&om->token) < 0) { + msg = "failed to create salt"; + om->command_rc = -1; + om->command_errno = EPERM; + } else { + om->token.uid = pw->pw_uid; + om->token.gid = pw->pw_gid; + privd_check_token(&om->token, auth_key); + msg = privd_print_token(&om->token); + } + if (priv_debug || om->command_rc < 0) + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, server_conf, + "[client %s] priv_%-6s %-5d \"%s\" %s (rc=%d)", + inet_ntoa(om->remote), "auth", + pw ? pw->pw_uid : -1, path1, msg, om->command_rc); + rv = sendmsg (priv_server_fd, &mh, 0); + return; + } + + if ( priv_secure_path (path1) < 0 || + (strlen(path2) && priv_secure_path (path2) < 0) ) { + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, server_conf, + "privd_service_request: invalid path ignored."); + goto fail_perm; + } + + /* Just do request as the apache user if it's outsite priv_sep_root, + We repeat the client side test for security reasons */ + if (strncmp(priv_sep_root, path1, strlen(priv_sep_root)) || + (strlen(path2) && + strncmp(priv_sep_root, path2, strlen(priv_sep_root))) ) { + om->token.uid = ap_user_id; + om->token.gid = ap_group_id; + } + + /* only allow stat for root uid or gid */ + if ((om->token.uid == 0 || om->token.gid == 0) && + !(om->command == priv_command_stat || + om->command == priv_command_lstat)) { + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, server_conf, + "privd_service_request: uid 0 disallowed"); + goto fail_perm; + } + + if (setgroups (1, &om->token.gid) < 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, server_conf, + "privd_service_request: setgroups(1, [%d])", om->token.gid); + goto fail_perm; + } + + if (setegid (om->token.gid) < 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, server_conf, + "privd_service_request: setegid(%d)\n", om->token.gid); + goto fail_perm; + } + + if (seteuid (om->token.uid) < 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, server_conf, + "privd_service_request: seteuid(%d)", om->token.uid); + goto fail_perm; + } + + switch (om->command) + { + case priv_command_open: + openfd = open (path1, om->flags, om->mode); + if (priv_debug || openfd < 0) + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, server_conf, + "[client %s] priv_%-6s %-5d \"%s\", %d, %04o (rc=%d)", + inet_ntoa(om->remote), "open", + om->token.uid, path1, om->flags, om->mode, openfd); + if (openfd >= 0) { + om->command_errno = 0; + mh.msg_control = ccmsg; + mh.msg_controllen = sizeof (ccmsg); + cmsg = CMSG_FIRSTHDR (&mh); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN (sizeof (openfd)); + *(int *) CMSG_DATA (cmsg) = openfd; + mh.msg_controllen = cmsg->cmsg_len; + mh.msg_flags = 0; + rv = sendmsg (priv_server_fd, &mh, 0); + close (openfd); + } + else { + om->command_rc = -1; + om->command_errno = errno; + rv = sendmsg (priv_server_fd, &mh, 0); + } + break; + + case priv_command_chmod: + om->command_rc = chmod (path1, om->mode); + om->command_errno = errno; + if (priv_debug || om->command_rc < 0) + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, server_conf, + "[client %s] priv_%-6s %-5d \"%s\", %04o (rc=%d)", + inet_ntoa(om->remote), "chmod", + om->token.uid, path1, om->mode, om->command_rc); + rv = sendmsg (priv_server_fd, &mh, 0); + break; + + case priv_command_stat: + om->command_rc = stat (path1, &om->stat); + om->command_errno = errno; + if (priv_debug || om->command_rc < 0) + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, server_conf, + "[client %s] priv_%-6s %-5d \"%s\" (rc=%d)", + inet_ntoa(om->remote), "stat", + om->token.uid, path1, om->command_rc); + rv = sendmsg (priv_server_fd, &mh, 0); + break; + + case priv_command_lstat: + om->command_rc = lstat (path1, &om->stat); + om->command_errno = errno; + if (priv_debug || om->command_rc < 0) + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, server_conf, + "[client %s] priv_%-6s %-5d \"%s\" (rc=%d)", + inet_ntoa(om->remote), "lstat", + om->token.uid, path1, om->command_rc); + rv = sendmsg (priv_server_fd, &mh, 0); + break; + + case priv_command_mkdir: + om->command_rc = mkdir (path1, om->mode); + om->command_errno = errno; + if (priv_debug || om->command_rc < 0) + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, server_conf, + "[client %s] priv_%-6s %-5d \"%s\", %04o (rc=%d)", + inet_ntoa(om->remote), "mkdir", + om->token.uid, path1, om->mode, om->command_rc); + rv = sendmsg (priv_server_fd, &mh, 0); + break; + + case priv_command_rename: + om->command_rc = rename (path1, path2); + om->command_errno = errno; + if (priv_debug || om->command_rc < 0) + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, server_conf, + "[client %s] priv_%-6s %-5d \"%s\", \"%s\" (rc=%d)", + inet_ntoa(om->remote), "rename", + om->token.uid, path1, path2, om->command_rc); + rv = sendmsg (priv_server_fd, &mh, 0); + break; + + case priv_command_remove: + om->command_rc = remove (path1); + om->command_errno = errno; + if (priv_debug || om->command_rc < 0) + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, server_conf, + "[client %s] priv_%-6s %-5d \"%s\" (rc=%d)", + inet_ntoa(om->remote), "remove", + om->token.uid, path1, om->command_rc); + rv = sendmsg (priv_server_fd, &mh, 0); + break; + + } + goto out; + + fail_perm: + memset (om, 0, sizeof(*om)); + om->command_rc = -1; + om->command_errno = EPERM; + rv = sendmsg (priv_server_fd, &mh, 0); + + out: + seteuid (0); + setegid (0); +} + +static void +privd_cleanup(void *foo) +{ + if (priv_sem_id < 0) return; + semctl(priv_sem_id, 0, IPC_RMID, 0); + priv_inited = 0; + close (priv_client_fd); +} + +static void +privd_signal(int sig) +{ + close (priv_server_fd); + close (urandom_fd); + exit(0); +} + +int +ap_do_privd (pool *pconf, server_rec *server_conf) +{ + int sv[2]; + int arg, bytes; + pid_t pid; + struct ipc_perm sem_perms; + unsigned char auth_key[PRIV_AUTH_KEY_LEN]; + + /* create auth key on stack and clear in client when we fork */ + if ((urandom_fd = open("/dev/urandom", O_RDONLY)) < 0) { + ap_log_error(APLOG_MARK, APLOG_CRIT, server_conf, + "ap_do_privd: couldn't open /dev/urandom"); + exit(1); + } + if (read(urandom_fd, auth_key, PRIV_AUTH_KEY_LEN) != PRIV_AUTH_KEY_LEN) { + ap_log_error(APLOG_MARK, APLOG_CRIT, server_conf, + "ap_do_privd: failed to initialize auth key\n"); + exit(1); + } + + /* create special limited root token (stat and lstat only) */ + if (privd_salt_token(&priv_root_token) < 0) { + ap_log_error(APLOG_MARK, APLOG_CRIT, server_conf, + "ap_do_privd: failed to initialize stat token\n"); + exit(1); + } + priv_root_token.uid = 0; + priv_root_token.gid = 0; + privd_check_token(&priv_root_token, auth_key); + + if (socketpair(PF_UNIX, SOCK_DGRAM, 0, sv) < 0) { + ap_log_error(APLOG_MARK, APLOG_CRIT, server_conf, + "ap_do_privd: error creating socketpair"); + exit(1); + } + + if ((priv_sem_id = semget(IPC_PRIVATE, 1, IPC_CREAT | 0600)) < 0) { + ap_log_error(APLOG_MARK, APLOG_CRIT, server_conf, + "ap_do_privd: error initializing semaphore: semget"); + exit(1); + } + ap_register_cleanup(pconf, NULL, privd_cleanup, ap_null_cleanup); + + if (semctl(priv_sem_id, 0, SETVAL, 0) <0) { + ap_log_error(APLOG_MARK, APLOG_CRIT, server_conf, + "ap_do_privd: error initializing semaphore: semctl"); + exit(1); + } + + sem_perms.uid = ap_user_id; + sem_perms.gid = ap_group_id; + sem_perms.mode = 0600; + if (semctl(priv_sem_id, 0, IPC_SET, &sem_perms) <0) { + ap_log_error(APLOG_MARK, APLOG_CRIT, server_conf, + "ap_do_privd: semctl"); + exit(1); + } + + priv_client_fd = sv[0]; + priv_server_fd = sv[1]; + + arg = 1; + if (setsockopt(priv_server_fd, SOL_SOCKET, SO_PASSCRED, + &arg, sizeof(arg))) { + ap_log_error(APLOG_MARK, APLOG_CRIT, server_conf, + "ap_do_privd: setsockopt"); + exit(1); + } + + if ((pid = fork ()) < 0) { + ap_log_error(APLOG_MARK, APLOG_CRIT, server_conf, + "do_privd: fork"); + exit (1); + } + else if (pid > 0) { + /* clear auth key off stack */ + memset(auth_key, 0, PRIV_AUTH_KEY_LEN); + close(urandom_fd); + close(priv_server_fd); + priv_inited = 1; + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, server_conf, + "Privilege separation started (pid %d)", pid); + return 0; + } + + close(priv_client_fd); + signal(SIGHUP, privd_signal); + signal(SIGTERM, privd_signal); + signal(SIGUSR1, privd_signal); + umask(002); + + /* okay, here's the main loop */ + while (1) privd_service_request (auth_key, server_conf); + + return 0; +} diff -urNX apache-excludes apache_1.3.39.orig/src/main/util.c apache_1.3.39/src/main/util.c --- apache_1.3.39.orig/src/main/util.c 2006-07-12 16:16:05.000000000 +0800 +++ apache_1.3.39/src/main/util.c 2007-12-05 21:11:42.000000000 +0800 @@ -30,6 +30,8 @@ #include "httpd.h" #include "http_conf_globals.h" /* for user_id & group_id */ #include "http_log.h" +#include "http_request.h" +#include "privsep.h" #if defined(SUNOS4) /* stdio.h has been read in ap_config.h already. Add missing prototypes here: */ extern int fgetc(FILE *); @@ -1754,6 +1756,30 @@ return x; } +API_EXPORT(int) ap_priv_is_directory(struct request_rec *r, const char *path) +{ + struct stat finfo; + + if (priv_stat(r, path, &finfo) == -1) + return 0; /* in error condition, just return no */ + + return (S_ISDIR(finfo.st_mode)); +} + +/* + * see ap_is_directory() except this one is symlink aware, so it + * checks for a "real" directory + */ +API_EXPORT(int) ap_priv_is_rdirectory(struct request_rec *r, const char *path) +{ + struct stat finfo; + + if (priv_lstat(r, path, &finfo) == -1) + return 0; /* in error condition, just return no */ + + return ((!(S_ISLNK(finfo.st_mode))) && (S_ISDIR(finfo.st_mode))); +} + API_EXPORT(int) ap_is_directory(const char *path) { struct stat finfo; diff -urNX apache-excludes apache_1.3.39.orig/src/.mkcert.serial apache_1.3.39/src/.mkcert.serial --- apache_1.3.39.orig/src/.mkcert.serial 1970-01-01 07:30:00.000000000 +0730 +++ apache_1.3.39/src/.mkcert.serial 2007-12-05 21:19:14.000000000 +0800 @@ -0,0 +1 @@ +03 diff -urNX apache-excludes apache_1.3.39.orig/src/modules/experimental/mod_auth_privsep.c apache_1.3.39/src/modules/experimental/mod_auth_privsep.c --- apache_1.3.39.orig/src/modules/experimental/mod_auth_privsep.c 1970-01-01 07:30:00.000000000 +0730 +++ apache_1.3.39/src/modules/experimental/mod_auth_privsep.c 2007-12-05 21:11:42.000000000 +0800 @@ -0,0 +1,195 @@ +/* Copyright 2005 Metaparadigm Pte Ltd + * + * Authors: Jamie Clark, + * Michael Clark + * + * 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 +#include + +#include "httpd.h" +#include "http_config.h" +#include "http_core.h" +#include "http_log.h" +#include "privsep.h" + + +module MODULE_VAR_EXPORT privsep_auth_module; + + +struct privsep_auth_config +{ + int authoritative; /* 0 to DECLINE instead of AUTH_REQUIRED if we + can't find the username (defaults to 1) */ + int enabled; /* 1 to use mod_auth_privsep, 0 otherwise + (defaults to 0) */ +}; + +static void* +privsep_create_dir_config (pool *p, char *d) +{ + struct privsep_auth_config *privsep_conf; + + privsep_conf = ap_palloc (p, sizeof(struct privsep_auth_config)); + privsep_conf->authoritative = 1; + privsep_conf->enabled = 0; + + return privsep_conf; +} + +static +const char* auth_authoritative(cmd_parms *cmd, void *config, int arg) +{ + struct privsep_auth_config *privsep_conf = + (struct privsep_auth_config *)config; + privsep_conf->authoritative = arg; + return NULL; +} + +static +const char* auth_enable(cmd_parms *cmd, void *config, int arg) +{ + struct privsep_auth_config *privsep_conf = + (struct privsep_auth_config *)config; + privsep_conf->enabled = arg; + return NULL; +} + +static command_rec privsep_auth_cmds[] = { + { "PrivSepAuthoritative", auth_authoritative, NULL, OR_AUTHCFG, FLAG, + "on|off - fall through to other auth methods for failures; default on" }, + { "PrivSepAuth", auth_enable, NULL, OR_AUTHCFG, FLAG, + "on|off - determines if privsep authentication is enabled; default off" }, + { 0 } +}; + + +static +int privsep_auth_basic_user (request_rec *r) +{ + int rv; + const char *pass; + struct privsep_auth_config *privsep_conf = (struct privsep_auth_config *) + ap_get_module_config(r->per_dir_config, &privsep_auth_module); + + if (!privsep_conf->enabled) + return DECLINED; + + /* read sent pw */ + if ((rv = ap_get_basic_auth_pw (r, &pass))) return rv; + + if ((rv = priv_auth(r, pass)) != PAM_SUCCESS) { + if (!privsep_conf->authoritative) return DECLINED; + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "user \"%s\" failed authentication, path=%s", + r->connection->user, r->filename); + ap_note_basic_auth_failure(r); + return AUTH_REQUIRED; + } + + return OK; +} + +static +int privsep_check_user_access (request_rec *r) +{ + struct privsep_auth_config *privsep_conf = (struct privsep_auth_config *) + ap_get_module_config(r->per_dir_config, &privsep_auth_module); + char *user = r->connection->user; + int m = r->method_number; + struct passwd *pwent; + + const array_header *reqs_arr = ap_requires(r); + require_line *reqs = reqs_arr ? (require_line *) reqs_arr->elts : NULL; + + register int x; + const char *line; + char *w; + + if (!privsep_conf->enabled) + return DECLINED; + if(!(pwent = getpwnam(r->connection->user))) + return DECLINED; + if (!reqs_arr) + return DECLINED; + + for (x = 0; x < reqs_arr->nelts; x++) { + + if (!(reqs[x].method_mask & (1 << m))) + continue; + + line = reqs[x].requirement; + w = ap_getword_white(r->pool, &line); + + if (!strcmp(w, "group")) { + /* for each of the groups listed in the configuration line, + * retrieve a list of the group members and check wether the + * authenticated user is present in the list. */ + while (*line) { + struct group *grent; + char* groupname = ap_getword_conf(r->pool, (const char**)&line); + + if (!(grent = getgrnam (groupname))) { + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_CRIT, r, + "group \"%s\" does not exist, path=%s", + groupname, r->filename); + return DECLINED; + } + if (grent->gr_mem) { + char **members = grent->gr_mem; + + /* maybe its the primary group? saves the comparisons */ + if(pwent->pw_gid == grent->gr_gid) + return OK; + + while (*members) { + if (strcmp (*members, pwent->pw_name) == 0) + return OK; + members ++; + } + } + } + ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, + "user \"%s\" not in right group, path=%s", + user, r->filename); + ap_note_basic_auth_failure(r); + return AUTH_REQUIRED; + } + } + + return DECLINED; +} + +module privsep_auth_module = { + STANDARD_MODULE_STUFF, + NULL, /* initializer */ + privsep_create_dir_config, /* dir config creater */ + NULL, /* dir merger --- default is to override */ + NULL, /* server config */ + NULL, /* merge server config */ + privsep_auth_cmds, /* command table */ + NULL, /* [7] list of handlers */ + NULL, /* [2] filename-to-URI translation */ + privsep_auth_basic_user, /* [5] check/validate user_id */ + privsep_check_user_access, /* [6] check auth */ + NULL, /* [4] check access by host address */ + NULL, /* [7] MIME type checker/setter */ + NULL, /* [8] fixups */ + NULL, /* [10] logger */ + NULL, /* [3] header parser */ + NULL, /* process initializer */ + NULL, /* process exit/cleanup */ + NULL /* [1] post read_request handling */ +}; diff -urNX apache-excludes apache_1.3.39.orig/src/modules/standard/mod_autoindex.c apache_1.3.39/src/modules/standard/mod_autoindex.c --- apache_1.3.39.orig/src/modules/standard/mod_autoindex.c 2006-07-12 16:16:05.000000000 +0800 +++ apache_1.3.39/src/modules/standard/mod_autoindex.c 2007-12-05 21:11:42.000000000 +0800 @@ -32,6 +32,7 @@ #include "http_main.h" #include "util_script.h" #include "fnmatch.h" +#include "privsep.h" module MODULE_VAR_EXPORT autoindex_module; @@ -1020,7 +1021,7 @@ * the file's contents, any HTML header it had won't end up * where it belongs. */ - if ((f = ap_pfopen(r->pool, rr->filename, "r")) != 0) { + if ((f = ap_priv_pfopen(r, rr->filename, "r")) != 0) { emit_preamble(r, title); emit_amble = 0; do_emit_plain(r, f); @@ -1116,7 +1117,7 @@ /* * If we can open the file, suppress the signature. */ - if ((f = ap_pfopen(r->pool, rr->filename, "r")) != 0) { + if ((f = ap_priv_pfopen(r, rr->filename, "r")) != 0) { do_emit_plain(r, f); ap_pfclose(r->pool, f); suppress_sig = 1; @@ -1162,7 +1163,7 @@ "text/html") || !strcmp(r->content_type, INCLUDES_MAGIC_TYPE)) && !r->content_encoding) { - if (!(thefile = ap_pfopen(r->pool, r->filename, "r"))) { + if (!(thefile = ap_priv_pfopen(r, r->filename, "r"))) { return NULL; } n = fread(titlebuf, sizeof(char), MAX_STRING_LEN - 1, thefile); @@ -1642,7 +1643,7 @@ char keyid; char direction; - if (!(d = ap_popendir(r->pool, name))) { + if (!(d = ap_priv_popendir(r, name))) { ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "Can't open directory for index: %s", r->filename); return HTTP_FORBIDDEN; diff -urNX apache-excludes apache_1.3.39.orig/src/modules/standard/mod_mime_magic.c apache_1.3.39/src/modules/standard/mod_mime_magic.c --- apache_1.3.39.orig/src/modules/standard/mod_mime_magic.c 2006-07-12 16:16:05.000000000 +0800 +++ apache_1.3.39/src/modules/standard/mod_mime_magic.c 2007-12-05 21:11:42.000000000 +0800 @@ -830,7 +830,7 @@ return result; } - if ((fd = ap_popenf(r->pool, r->filename, O_RDONLY, 0)) < 0) { + if ((fd = ap_priv_popenf(r, r->filename, O_RDONLY, 0)) < 0) { /* We can't open it, but we were able to stat it. */ ap_log_rerror(APLOG_MARK, APLOG_ERR, r, MODNAME ": can't read `%s'", r->filename); diff -urNX apache-excludes apache_1.3.39.orig/src/modules/standard/mod_negotiation.c apache_1.3.39/src/modules/standard/mod_negotiation.c --- apache_1.3.39.orig/src/modules/standard/mod_negotiation.c 2006-07-12 16:16:05.000000000 +0800 +++ apache_1.3.39/src/modules/standard/mod_negotiation.c 2007-12-05 21:11:42.000000000 +0800 @@ -28,6 +28,7 @@ #include "http_core.h" #include "http_log.h" #include "util_script.h" +#include "privsep.h" /* Commands --- configuring document caching on a per (virtual?) * server basis... @@ -740,7 +741,7 @@ /* We are not using multiviews */ neg->count_multiviews_variants = 0; - map = ap_pfopen(neg->pool, rr->filename, "r"); + map = ap_priv_pfopen(rr, rr->filename, "r"); if (map == NULL) { ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "cannot access type map file: %s", rr->filename); @@ -868,7 +869,7 @@ ++filp; prefix_len = strlen(filp); - dirp = ap_popendir(neg->pool, neg->dir_name); + dirp = ap_priv_popendir(r, neg->dir_name); if (dirp == NULL) { ap_log_rerror(APLOG_MARK, APLOG_ERR, r, @@ -1486,7 +1487,7 @@ char *fullname = ap_make_full_path(neg->pool, neg->dir_name, variant->file_name); - if (stat(fullname, &statb) >= 0) { + if (priv_stat(neg->r, fullname, &statb) >= 0) { /* Note, precision may be lost */ variant->bytes = (float) statb.st_size; }