KaiGai Kohei wrote:
> Stefan Fritsch wrote:
>> On Thursday 09 April 2009, Graham Dumpleton wrote:
>>> Only you would know that. But then, I could be pointing you at the
>>> wrong MPM. There is from memory another by another name developed
>>> outside of ASF which intends to do the same think. The way it is
>>> implemented is probably going to be different and may be the one I
>>> am actually thinking of. I can't remember the name of it right now.
>> Maybe you mean MPM itk, which can change to different users for
>> different vhosts?
>>
>> http://mpm-itk.sesse.net/
>
> Thanks for your information.
>
> It is designed on the prefork. It makes a child process for each
> connection to call ap_process_connection() in separated context,
> and the parent waits for the completion of this.
> In addition, it assigns configured uid/gid on the header_parser hook,
> then contents handlers are invoked.
>
> It seems to me that we can share its basic idea and design.
> The mpm-itk also has separatable two functionalities:
> 1. it makes a process for each connection.
> 2. it assigns privileges on a process.
>
> I believe we are now on the right direction.
At first, I planed to implement a new mpm from the scratch, but I
reconsidered it may be a burden for the reviewers, so the attached
patch is implemented as an enhancement of the latest prefork.
(I guess it is a preferable manner.)
The first attached patch adds a new "security" mpm which enables to
launch a new process for each connections, and gives a chance to assign
appropriate privileges for external modules. The newly spawned process
is always one-time purpose, because SELinux does not allow to revert
its privileges.
The second patch is an implementation of the mod_selinux module which
assigns the working process a security context based on authentication
process at the fixups hook, prior to invocations of contents handler.
Step to apply the patches:
% svn co http://svn.apache.org/repos/asf/httpd/httpd/trunk httpd-devel
% cd httpd-devel
% mkdir -p server/mpm/security
% cp -f server/mpm/prefork/prefork.c server/mpm/security
% cp -f server/mpm/prefork/mpm_default.h server/mpm/security
% cat ~/apache-httpd-security-mpm.1.patch | patch -p1
% cat ~/apache-httpd-mod_selinux.1.patch | patch -p1
(*) The attached patch contains only differences from the prefork.
I would like to push this kind of features to the upstreamed httpd
eventually. If you have any suggestion, please feel free to comment.
Thanks,
--
OSS Platform Development Division, NEC
KaiGai Kohei <[email protected]>
Index: httpd-devel/server/mpm/MPM.NAMING
===================================================================
--- httpd-devel/server/mpm/MPM.NAMING (revision 763518)
+++ httpd-devel/server/mpm/MPM.NAMING (working copy)
@@ -9,3 +9,5 @@
worker ........ Multi Process model with threads. One acceptor thread,
multiple worker threads.
netware ....... Multi-threaded MPM for Netware
+ security ...... Forks a one-time process for each request. External module
+ can assign appropriate privileges prior to handlers.
Index: httpd-devel/server/mpm/config.m4
===================================================================
--- httpd-devel/server/mpm/config.m4 (revision 763518)
+++ httpd-devel/server/mpm/config.m4 (working copy)
@@ -1,7 +1,7 @@
AC_MSG_CHECKING(which MPM to use)
AC_ARG_WITH(mpm,
APACHE_HELP_STRING(--with-mpm=MPM,Choose the process model for Apache to use.
- MPM={simple|event|worker|prefork|winnt}
+ MPM={simple|event|worker|prefork|winnt|security}
Specify "shared" instead of an MPM name to load MPMs dynamically.
),[
APACHE_MPM=$withval
Index: httpd-devel/server/mpm/security/Makefile.in
===================================================================
--- httpd-devel/server/mpm/security/Makefile.in (revision 0)
+++ httpd-devel/server/mpm/security/Makefile.in (revision 0)
@@ -0,0 +1,5 @@
+
+LTLIBRARY_NAME = libsecurity.la
+LTLIBRARY_SOURCES = security.c
+
+include $(top_srcdir)/build/ltlib.mk
Index: httpd-devel/server/mpm/security/config.m4
===================================================================
--- httpd-devel/server/mpm/security/config.m4 (revision 0)
+++ httpd-devel/server/mpm/security/config.m4 (revision 0)
@@ -0,0 +1,3 @@
+if test "$MPM_NAME" = "security" ; then
+ APACHE_FAST_OUTPUT(server/mpm/$MPM_NAME/Makefile)
+fi
--- httpd-devel/server/mpm/security/prefork.c 2009-04-13 09:14:47.000000000 +0900
+++ httpd-devel/server/mpm/security/security.c 2009-04-13 15:17:12.000000000 +0900
@@ -94,15 +94,15 @@
static int mpm_state = AP_MPMQ_STARTING;
static ap_pod_t *pod;
-/* data retained by prefork across load/unload of the module
+/* data retained by security across load/unload of the module
* allocated on first call to pre-config hook; located on
* subsequent calls to pre-config hook
*/
-typedef struct prefork_retained_data {
+typedef struct security_retained_data {
int first_server_limit;
int module_loads;
-} prefork_retained_data;
-static prefork_retained_data *retained;
+} security_retained_data;
+static security_retained_data *retained;
#define MPM_CHILD_PID(i) (ap_scoreboard_image->parent[i].pid)
@@ -131,6 +131,7 @@
static pid_t ap_my_pid; /* it seems silly to call getpid all the time */
static pid_t parent_pid;
+static volatile pid_t worker_pid = 0;
static int my_child_num;
static ap_generation_t volatile my_generation=0;
@@ -242,7 +243,7 @@
#define SAFE_ACCEPT(stmt) do {stmt;} while(0)
#endif
-static int prefork_query(int query_code, int *result, apr_status_t *rv)
+static int security_query(int query_code, int *result, apr_status_t *rv)
{
*rv = APR_SUCCESS;
switch(query_code){
@@ -295,15 +296,15 @@
return OK;
}
-static apr_status_t prefork_note_child_killed(int childnum)
+static apr_status_t security_note_child_killed(int childnum)
{
ap_scoreboard_image->parent[childnum].pid = 0;
return APR_SUCCESS;
}
-static const char *prefork_get_name(void)
+static const char *security_get_name(void)
{
- return "prefork";
+ return "security";
}
/*****************************************************************
@@ -312,6 +313,8 @@
static void just_die(int sig)
{
+ if (worker_pid > 0)
+ kill(worker_pid, sig);
clean_child_exit(0);
}
@@ -444,10 +447,6 @@
static void child_main(int child_num_arg)
{
-#if APR_HAS_THREADS
- apr_thread_t *thd = NULL;
- apr_os_thread_t osthd;
-#endif
apr_pool_t *ptrans;
apr_allocator_t *allocator;
apr_status_t status;
@@ -477,11 +476,6 @@
apr_allocator_owner_set(allocator, pchild);
apr_pool_tag(pchild, "pchild");
-#if APR_HAS_THREADS
- osthd = apr_os_thread_current();
- apr_os_thread_put(&thd, &osthd, pchild);
-#endif
-
apr_pool_create(&ptrans, pchild);
apr_pool_tag(ptrans, "transaction");
@@ -650,9 +644,49 @@
current_conn = ap_run_create_connection(ptrans, ap_server_conf, csd, my_child_num, sbh, bucket_alloc);
if (current_conn) {
+ int status;
+
+ worker_pid = fork();
+ if (worker_pid < 0) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, errno, ap_server_conf,
+ "unable to fork a worker process");
+ ap_lingering_close(current_conn);
+ } else if (worker_pid == 0) {
#if APR_HAS_THREADS
- current_conn->current_thread = thd;
+ apr_thread_t *thd = NULL;
+ apr_os_thread_t osthd;
+
+ osthd = apr_os_thread_current();
+ apr_os_thread_put(&thd, &osthd, pchild);
+ current_conn->current_thread = thd;
#endif
+ /* no need to hold server sockets */
+ ap_close_listeners();
+
+ getcon_raw(&context);
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf,
+ "fork a worker process (%s)", context);
+
+ ap_process_connection(current_conn, csd);
+ ap_lingering_close(current_conn);
+
+ exit(0);
+ } else {
+ /* no need to hold client socket */
+ ap_lingering_close(current_conn);
+
+ /* wait for completion of worker process */
+ while (waitpid(worker_pid, &status, 0) < 0 && errno == EINTR);
+
+ if (WIFSIGNALED(status) || WEXITSTATUS(status)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf,
+ "worker process exit abnormally");
+ die_now = 1;
+ }
+ worker_pid = 0;
+ }
+
ap_process_connection(current_conn, csd);
ap_lingering_close(current_conn);
}
@@ -895,7 +929,7 @@
* Executive routines.
*/
-static int prefork_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s)
+static int security_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s)
{
int index;
int remaining_children_to_start;
@@ -1229,7 +1263,7 @@
/* This really should be a post_config hook, but the error log is already
* redirected by that point, so we need to do this in the open_logs phase.
*/
-static int prefork_open_logs(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
+static int security_open_logs(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
{
int startup = 0;
int level_flags = 0;
@@ -1259,11 +1293,11 @@
return OK;
}
-static int prefork_pre_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp)
+static int security_pre_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp)
{
int no_detach, debug, foreground;
apr_status_t rv;
- const char *userdata_key = "mpm_prefork_module";
+ const char *userdata_key = "mpm_security_module";
mpm_state = AP_MPMQ_STARTING;
@@ -1319,7 +1353,7 @@
return OK;
}
-static int prefork_check_config(apr_pool_t *p, apr_pool_t *plog,
+static int security_check_config(apr_pool_t *p, apr_pool_t *plog,
apr_pool_t *ptemp, server_rec *s)
{
int startup = 0;
@@ -1405,7 +1439,7 @@
ap_daemons_limit = 1;
}
- /* ap_daemons_to_start > ap_daemons_limit checked in prefork_run() */
+ /* ap_daemons_to_start > ap_daemons_limit checked in security_run() */
if (ap_daemons_to_start < 0) {
if (startup) {
ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL,
@@ -1436,12 +1470,12 @@
ap_daemons_min_free = 1;
}
- /* ap_daemons_max_free < ap_daemons_min_free + 1 checked in prefork_run() */
+ /* ap_daemons_max_free < ap_daemons_min_free + 1 checked in security_run() */
return OK;
}
-static void prefork_hooks(apr_pool_t *p)
+static void security_hooks(apr_pool_t *p)
{
/* Our open_logs hook function must run before the core's, or stderr
* will be redirected to a file, and the messages won't print to the
@@ -1449,16 +1483,16 @@
*/
static const char *const aszSucc[] = {"core.c", NULL};
- ap_hook_open_logs(prefork_open_logs, NULL, aszSucc, APR_HOOK_REALLY_FIRST);
+ ap_hook_open_logs(security_open_logs, NULL, aszSucc, APR_HOOK_REALLY_FIRST);
/* we need to set the MPM state before other pre-config hooks use MPM query
* to retrieve it, so register as REALLY_FIRST
*/
- ap_hook_pre_config(prefork_pre_config, NULL, NULL, APR_HOOK_REALLY_FIRST);
- ap_hook_check_config(prefork_check_config, NULL, NULL, APR_HOOK_MIDDLE);
- ap_hook_mpm(prefork_run, NULL, NULL, APR_HOOK_MIDDLE);
- ap_hook_mpm_query(prefork_query, NULL, NULL, APR_HOOK_MIDDLE);
- ap_hook_mpm_note_child_killed(prefork_note_child_killed, NULL, NULL, APR_HOOK_MIDDLE);
- ap_hook_mpm_get_name(prefork_get_name, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_pre_config(security_pre_config, NULL, NULL, APR_HOOK_REALLY_FIRST);
+ ap_hook_check_config(security_check_config, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_mpm(security_run, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_mpm_query(security_query, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_mpm_note_child_killed(security_note_child_killed, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_mpm_get_name(security_get_name, NULL, NULL, APR_HOOK_MIDDLE);
}
static const char *set_daemons_to_start(cmd_parms *cmd, void *dummy, const char *arg)
@@ -1516,7 +1550,7 @@
return NULL;
}
-static const command_rec prefork_cmds[] = {
+static const command_rec security_cmds[] = {
LISTEN_COMMANDS,
AP_INIT_TAKE1("StartServers", set_daemons_to_start, NULL, RSRC_CONF,
"Number of child processes launched at server startup"),
@@ -1532,13 +1566,13 @@
{ NULL }
};
-module AP_MODULE_DECLARE_DATA mpm_prefork_module = {
+module AP_MODULE_DECLARE_DATA mpm_security_module = {
MPM20_MODULE_STUFF,
NULL, /* hook to run before apache parses args */
NULL, /* create per-directory config structure */
NULL, /* merge per-directory config structures */
NULL, /* create per-server config structure */
NULL, /* merge per-server config structures */
- prefork_cmds, /* command apr_table_t */
- prefork_hooks, /* register hooks */
+ security_cmds, /* command apr_table_t */
+ security_hooks, /* register hooks */
};
Index: httpd-devel/modules/arch/unix/mod_selinux.c
===================================================================
--- httpd-devel/modules/arch/unix/mod_selinux.c (revision 0)
+++ httpd-devel/modules/arch/unix/mod_selinux.c (revision 0)
@@ -0,0 +1,306 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 "apr_strings.h"
+#include "apr_thread_proc.h"
+
+#include "httpd.h"
+#include "http_request.h"
+#include "http_config.h"
+#include "http_log.h"
+#include <stdio.h>
+#include <string.h>
+#include <selinux/selinux.h>
+#include <selinux/context.h>
+
+typedef struct
+{
+ char *dirname;
+ char *config_file;
+ char *default_domain;
+} selinux_config;
+
+module AP_MODULE_DECLARE_DATA selinux_module;
+
+static char *
+selinux_lookup_entry(request_rec *r, const char *filename)
+{
+ const char *white_space = " \t\r\n";
+ ap_configfile_t *filp;
+ char buffer[MAX_STRING_LEN];
+ apr_status_t status;
+ char *ident, *entry, *mask, *pos;
+ apr_ipsubnet_t *ipsub;
+ int negative, lineno = 0;
+
+ status = ap_pcfg_openfile(&filp, r->pool, filename);
+ if (status != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, LOG_WARNING, status, r,
+ "Unable to open: %s", filename);
+ return NULL;
+ }
+
+ while (ap_cfg_getline(buffer, sizeof(buffer), filp) == 0) {
+ negative = 0;
+ lineno++;
+
+ /* skip empty line */
+ pos = strchr(buffer, '#');
+ if (pos)
+ *pos = '\0';
+
+ ident = strtok_r(buffer, white_space, &pos);
+ if (!ident)
+ continue;
+
+ /* if the line begins with '!', it means negative. */
+ if (*ident == '!') {
+ ident++;
+ negative = 1;
+ }
+
+ /* fetch domain and range */
+ entry = strtok_r(NULL, white_space, &pos);
+ if (!entry || strtok_r(NULL, white_space, &pos)) {
+ ap_log_rerror(APLOG_MARK, LOG_WARNING, 0, r,
+ "syntax error at %s:%d", filename, lineno);
+ continue;
+ }
+
+ /* ident is network address? or username? */
+ mask = strchr(ident, '/');
+ if (mask)
+ *mask++ = '\0';
+
+ if (apr_ipsubnet_create(&ipsub, ident, mask, r->pool) == APR_SUCCESS) {
+ if (apr_ipsubnet_test(ipsub, r->connection->remote_addr)) {
+ if (!negative)
+ goto found;
+ } else if (negative)
+ goto found;
+ }
+ else if (r->user) {
+ if (mask)
+ *--mask = '/'; /* fixup assumption of network address */
+ if (strcmp(r->user, ident) == 0) {
+ if (!negative)
+ goto found;
+ } else if (negative)
+ goto found;
+ }
+ }
+ /* not found */
+ ap_cfg_closefile(filp);
+ return NULL;
+
+found:
+ ap_cfg_closefile(filp);
+ return apr_pstrdup(r->pool, entry);
+}
+
+static int selinux_post_read_request(request_rec *r)
+{
+ selinux_config *sconf
+ = ap_get_module_config(r->per_dir_config,
+ &selinux_module);
+ /*
+ * If mod_selinux is available on the given request,
+ * it does not allow to cache the contents to keep
+ * consistency of access controls.
+ */
+ if (sconf && is_selinux_enabled() == 1)
+ r->no_cache = 1;
+
+ return DECLINED;
+}
+
+static int selinux_fixups(request_rec *r)
+{
+ selinux_config *sconf;
+ security_context_t old_context;
+ security_context_t new_context;
+ security_context_t tmp_context;
+ char *entry = NULL;
+
+ sconf = ap_get_module_config(r->per_dir_config,
+ &selinux_module);
+ if (!sconf)
+ return DECLINED;
+
+ if (is_selinux_enabled() < 1)
+ return DECLINED;
+
+ /*
+ * Is there any matched entry or default domain
+ * configured? If not, this module does not anything.
+ */
+ if (sconf->config_file)
+ entry = selinux_lookup_entry(r, sconf->config_file);
+ if (!entry)
+ entry = apr_pstrdup(r->pool, sconf->default_domain);
+ if (!entry)
+ return DECLINED; /* no matched and default domain */
+
+ /*
+ * Get the current security context
+ */
+ if (getcon_raw(&tmp_context) < 0) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, errno, r->server,
+ "SELinux: getcon_raw() failed");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ old_context = apr_pstrdup(r->pool, tmp_context);
+ freecon(tmp_context);
+
+ /*
+ * Compute a new security context
+ */
+ if (!strcasecmp(entry, "auth-module")) {
+ new_context = (security_context_t )apr_table_get(r->notes,
+ "auth-security-context");
+ if (!new_context) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r,
+ "No SELinux aware authentication module");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ } else {
+ context_t context;
+ char *domain = entry;
+ char *range = NULL;
+
+ range = strchr(domain, ':');
+ if (range)
+ *range++ = '\0';
+
+ context = context_new(old_context);
+ if (!context) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r,
+ "context_new('%s') failed", old_context);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ if (domain && strcmp(domain, "*") != 0)
+ context_type_set(context, domain);
+ if (range && strcmp(range, "*") != 0)
+ context_range_set(context, range);
+
+ tmp_context = context_str(context);
+ if (!tmp_context) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r,
+ "context_str() failed");
+ context_free(context);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ new_context = apr_pstrdup(r->pool, tmp_context);
+ freecon(tmp_context);
+ }
+
+ if (strcmp(old_context, new_context) == 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+ "no need to set security context: %s "
+ "(uri=%s dir=%s user=%s remote=%s)",
+ old_context,
+ r->uri, sconf->dirname, r->user,
+ r->connection->remote_ip);
+ return DECLINED;
+ }
+
+ if (setcon_raw(new_context) < 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r,
+ "setcon_raw('%s') failed", new_context);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+ "set security context: %s -> %s "
+ "(uri=%s dir=%s user=%s remote=%s)",
+ old_context, new_context,
+ r->uri, sconf->dirname, r->user,
+ r->connection->remote_ip);
+ return DECLINED;
+}
+
+static void *
+selinux_create_dir_config(apr_pool_t *p, char *dirname)
+{
+ selinux_config *sconf
+ = apr_palloc(p, sizeof(selinux_config));
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,
+ "SELinux: create dir config at: %s", dirname);
+
+ sconf->dirname = apr_pstrdup(p, dirname);
+ sconf->config_file = NULL;
+ sconf->default_domain = NULL;
+
+ return sconf;
+}
+
+static const char *
+set_config_file(cmd_parms *cmd, void *mconfig, const char *v1)
+{
+ selinux_config *sconf
+ = ap_get_module_config(cmd->context, &selinux_module);
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
+ "selinuxUserMappingFile = %s at %s",
+ v1, sconf->dirname);
+
+ sconf->config_file = apr_pstrdup(cmd->pool, v1);
+
+ return NULL;
+}
+
+static const char *
+set_default_domain(cmd_parms *cmd, void *mconfig, const char *v1)
+{
+ selinux_config *sconf
+ = ap_get_module_config(cmd->context, &selinux_module);
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
+ "selinuxDefaultDomain = %s at %s",
+ v1, sconf->dirname);
+
+ sconf->default_domain = apr_pstrdup(cmd->pool, v1);
+
+ return NULL;
+}
+
+static void selinux_register_hooks(apr_pool_t *p)
+{
+ ap_hook_post_read_request(selinux_post_read_request,
+ NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_fixups(selinux_fixups, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+static const command_rec selinux_cmds[] = {
+ AP_INIT_TAKE1("selinuxConfigFile",
+ set_config_file, NULL, OR_OPTIONS,
+ "SELinux user/domain mapping file"),
+ AP_INIT_TAKE1("selinuxDefaultDomain",
+ set_default_domain, NULL, OR_OPTIONS,
+ "SELinux default security context"),
+ {NULL},
+};
+
+module AP_MODULE_DECLARE_DATA selinux_module =
+{
+ STANDARD20_MODULE_STUFF,
+ selinux_create_dir_config, /* create per-directory config */
+ NULL, /* merge per-directory config */
+ NULL, /* server config creator */
+ NULL, /* server config merger */
+ selinux_cmds, /* command table */
+ selinux_register_hooks, /* set up other hooks */
+};
Index: httpd-devel/modules/arch/unix/config5.m4
===================================================================
--- httpd-devel/modules/arch/unix/config5.m4 (revision 763518)
+++ httpd-devel/modules/arch/unix/config5.m4 (working copy)
@@ -3,7 +3,7 @@
if test "$APACHE_MPM" = "simple" -o "$APACHE_MPM" = "worker" \
-o "$APACHE_MPM" = "event" -o "$APACHE_MPM" = "prefork" \
- -o "$APACHE_MPM" = "shared"; then
+ -o "$APACHE_MPM" = "shared" -o "$APACHE_MPM" = "security"; then
unixd_mods_enable=yes
else
unixd_mods_enable=no
@@ -18,5 +18,11 @@
fi
])
+APACHE_MODULE(selinux, Apache/SELinux plus support, , , no, [
+ AC_CHECK_LIB(selinux, getcon_raw,
+ [APR_ADDTO(MOD_SELINUX_LDADD, [-lselinux])],
+ [AC_MSG_ERROR([libselinux is not installed])]
+ )
+])
APACHE_MODPATH_FINISH