1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2  * contributor license agreements.  See the NOTICE file distributed with
3  * this work for additional information regarding copyright ownership.
4  * The ASF licenses this file to You under the Apache License, Version 2.0
5  * (the "License"); you may not use this file except in compliance with
6  * the License.  You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <priv.h>
18 #include <sys/types.h>
19 #include <unistd.h>
20 
21 #include "httpd.h"
22 #include "http_config.h"
23 #include "http_protocol.h"
24 #include "http_log.h"
25 #include "mpm_common.h"
26 #include "ap_mpm.h"
27 #include "apr_strings.h"
28 
29 /* TODO - get rid of unixd dependency */
30 #include "unixd.h"
31 
32 #define CFG_CHECK(x) if ((x) == -1) { \
33     char msgbuf[128]; \
34     apr_strerror(errno, msgbuf, sizeof(msgbuf)); \
35     return apr_pstrdup(cmd->pool, msgbuf); \
36 }
37 #define CR_CHECK(x) if (x == -1) \
38     ap_log_error(APLOG_MARK, APLOG_CRIT, errno, 0, \
39                  "Failed to initialise privileges")
40 
41 module AP_MODULE_DECLARE_DATA privileges_module;
42 
43 /* #define BIG_SECURITY_HOLE 1 */
44 
45 typedef enum { PRIV_UNSET, PRIV_FAST, PRIV_SECURE, PRIV_SELECTIVE } priv_mode;
46 
47 typedef struct {
48     priv_set_t *priv;
49     priv_set_t *child_priv;
50     uid_t uid;
51     gid_t gid;
52     priv_mode mode;
53 } priv_cfg;
54 
55 typedef struct {
56     priv_mode mode;
57 } priv_dir_cfg;
58 
59 static priv_set_t *priv_setid;
60 static priv_set_t *priv_default = NULL;
61 static int dtrace_enabled = 0;
62 
priv_cfg_cleanup(void * CFG)63 static apr_status_t priv_cfg_cleanup(void *CFG)
64 {
65     priv_cfg *cfg = CFG;
66     priv_freeset(cfg->priv);
67     priv_freeset(cfg->child_priv);
68     return APR_SUCCESS;
69 }
privileges_merge_cfg(apr_pool_t * pool,void * BASE,void * ADD)70 static void *privileges_merge_cfg(apr_pool_t *pool, void *BASE, void *ADD)
71 {
72     /* inherit the mode if it's not set; the rest won't be inherited */
73     priv_cfg *base = BASE;
74     priv_cfg *add = ADD;
75     priv_cfg *ret = apr_pmemdup(pool, add, sizeof(priv_cfg));
76     ret->mode = (add->mode == PRIV_UNSET) ? base->mode : add->mode;
77     return ret;
78 }
privileges_create_cfg(apr_pool_t * pool,server_rec * s)79 static void *privileges_create_cfg(apr_pool_t *pool, server_rec *s)
80 {
81     priv_cfg *cfg = apr_palloc(pool, sizeof(priv_cfg));
82 
83     /* Start at basic privileges all round. */
84     cfg->priv = priv_str_to_set("basic", ",", NULL);
85     cfg->child_priv = priv_str_to_set("basic", ",", NULL);
86 
87     /* By default, run in secure vhost mode.
88      * That means dropping basic privileges we don't usually need.
89      */
90     CR_CHECK(priv_delset(cfg->priv, PRIV_FILE_LINK_ANY));
91     CR_CHECK(priv_delset(cfg->priv, PRIV_PROC_INFO));
92     CR_CHECK(priv_delset(cfg->priv, PRIV_PROC_SESSION));
93 
94 /* Hmmm, should CGI default to secure too ? */
95 /*
96     CR_CHECK(priv_delset(cfg->child_priv, PRIV_FILE_LINK_ANY));
97     CR_CHECK(priv_delset(cfg->child_priv, PRIV_PROC_INFO));
98     CR_CHECK(priv_delset(cfg->child_priv, PRIV_PROC_SESSION));
99     CR_CHECK(priv_delset(cfg->child_priv, PRIV_PROC_FORK));
100     CR_CHECK(priv_delset(cfg->child_priv, PRIV_PROC_EXEC));
101 */
102 
103     /* we´ll use 0 for unset */
104     cfg->uid = 0;
105     cfg->gid = 0;
106     cfg->mode = PRIV_UNSET;
107     apr_pool_cleanup_register(pool, cfg, priv_cfg_cleanup,
108                               apr_pool_cleanup_null);
109 
110     /* top-level default_priv wants the top-level cfg */
111     if (priv_default == NULL) {
112         priv_default = cfg->priv;
113     }
114     return cfg;
115 }
privileges_create_dir_cfg(apr_pool_t * pool,char * dummy)116 static void *privileges_create_dir_cfg(apr_pool_t *pool, char *dummy)
117 {
118     priv_dir_cfg *cfg = apr_palloc(pool, sizeof(priv_dir_cfg));
119     cfg->mode = PRIV_UNSET;
120     return cfg;
121 }
privileges_merge_dir_cfg(apr_pool_t * pool,void * BASE,void * ADD)122 static void *privileges_merge_dir_cfg(apr_pool_t *pool, void *BASE, void *ADD)
123 {
124     priv_dir_cfg *base = BASE;
125     priv_dir_cfg *add = ADD;
126     priv_dir_cfg *ret = apr_palloc(pool, sizeof(priv_dir_cfg));
127     ret->mode = (add->mode == PRIV_UNSET) ? base->mode : add->mode;
128     return ret;
129 }
130 
privileges_end_req(void * data)131 static apr_status_t privileges_end_req(void *data)
132 {
133     request_rec *r = data;
134     priv_cfg *cfg = ap_get_module_config(r->server->module_config,
135                                          &privileges_module);
136     priv_dir_cfg *dcfg = ap_get_module_config(r->per_dir_config,
137                                               &privileges_module);
138 
139     /* ugly hack: grab default uid and gid from unixd */
140     extern unixd_config_rec ap_unixd_config;
141 
142     /* If we forked a child, we dropped privilege to revert, so
143      * all we can do now is exit
144      */
145     if ((cfg->mode == PRIV_SECURE) ||
146         ((cfg->mode == PRIV_SELECTIVE) && (dcfg->mode == PRIV_SECURE))) {
147         exit(0);
148     }
149 
150     /* if either user or group are not the default, restore them */
151     if (cfg->uid || cfg->gid) {
152         if (setppriv(PRIV_ON, PRIV_EFFECTIVE, priv_setid) == -1) {
153             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02136)
154                           "PRIV_ON failed restoring default user/group");
155         }
156         if (cfg->uid && (setuid(ap_unixd_config.user_id) == -1)) {
157             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02137)
158                           "Error restoring default userid");
159         }
160         if (cfg->gid && (setgid(ap_unixd_config.group_id) == -1)) {
161             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02138)
162                           "Error restoring default group");
163         }
164     }
165 
166     /* restore default privileges */
167     if (setppriv(PRIV_SET, PRIV_EFFECTIVE, priv_default) == -1) {
168         ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r, APLOGNO(02139)
169                       "Error restoring default privileges");
170     }
171     return APR_SUCCESS;
172 }
privileges_req(request_rec * r)173 static int privileges_req(request_rec *r)
174 {
175     /* secure mode: fork a process to handle the request */
176     apr_proc_t proc;
177     apr_status_t rv;
178     int exitcode;
179     apr_exit_why_e exitwhy;
180     int fork_req;
181     priv_cfg *cfg = ap_get_module_config(r->server->module_config,
182                                          &privileges_module);
183 
184     void *breadcrumb = ap_get_module_config(r->request_config,
185                                             &privileges_module);
186 
187     if (!breadcrumb) {
188         /* first call: this is the vhost */
189         fork_req = (cfg->mode == PRIV_SECURE);
190 
191         /* set breadcrumb */
192         ap_set_module_config(r->request_config, &privileges_module, &cfg->mode);
193 
194         /* If we have per-dir config, defer doing anything */
195         if ((cfg->mode == PRIV_SELECTIVE)) {
196             /* Defer dropping privileges 'til we have a directory
197              * context that'll tell us whether to fork.
198              */
199             return DECLINED;
200         }
201     }
202     else {
203         /* second call is for per-directory. */
204         priv_dir_cfg *dcfg;
205         if ((cfg->mode != PRIV_SELECTIVE)) {
206             /* Our fate was already determined for the vhost -
207              * nothing to do per-directory
208              */
209             return DECLINED;
210         }
211         dcfg = ap_get_module_config(r->per_dir_config, &privileges_module);
212         fork_req = (dcfg->mode == PRIV_SECURE);
213     }
214 
215     if (fork_req) {
216        rv = apr_proc_fork(&proc, r->pool);
217         switch (rv) {
218         case APR_INPARENT:
219             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02140)
220                           "parent waiting for child");
221             /* FIXME - does the child need to run synchronously?
222              * esp. if we enable mod_privileges with threaded MPMs?
223              * We do need at least to ensure r outlives the child.
224              */
225             rv = apr_proc_wait(&proc, &exitcode, &exitwhy, APR_WAIT);
226             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02141) "parent: child %s",
227                           (rv == APR_CHILD_DONE) ? "done" : "notdone");
228 
229             /* The child has taken responsibility for reading all input
230              * and sending all output.  So we need to bow right out,
231              * and even abandon "normal" housekeeping.
232              */
233             r->eos_sent = 1;
234             apr_table_unset(r->headers_in, "Content-Type");
235             apr_table_unset(r->headers_in, "Content-Length");
236             /* Testing with ab and 100k requests reveals no nasties
237              * so I infer we're not leaking anything like memory
238              * or file descriptors.  That's nice!
239              */
240             return DONE;
241         case APR_INCHILD:
242             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02142) "In child!");
243             break;  /* now we'll drop privileges in the child */
244         default:
245             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02143)
246                           "Failed to fork secure child process!");
247             return HTTP_INTERNAL_SERVER_ERROR;
248         }
249     }
250 
251     /* OK, now drop privileges. */
252 
253     /* cleanup should happen even if something fails part-way through here */
254     apr_pool_cleanup_register(r->pool, r, privileges_end_req,
255                               apr_pool_cleanup_null);
256     /* set user and group if configured */
257     if (cfg->uid || cfg->gid) {
258         if (setppriv(PRIV_ON, PRIV_EFFECTIVE, priv_setid) == -1) {
259             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02144)
260                           "No privilege to set user/group");
261         }
262         /* if we should be able to set these but can't, it could be
263          * a serious security issue.  Bail out rather than risk it!
264          */
265         if (cfg->uid && (setuid(cfg->uid) == -1)) {
266             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02145)
267                           "Error setting userid");
268             return HTTP_INTERNAL_SERVER_ERROR;
269         }
270         if (cfg->gid && (setgid(cfg->gid) == -1)) {
271             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02146)
272                           "Error setting group");
273             return HTTP_INTERNAL_SERVER_ERROR;
274         }
275     }
276     /* set vhost's privileges */
277     if (setppriv(PRIV_SET, PRIV_EFFECTIVE, cfg->priv) == -1) {
278         ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r, APLOGNO(02147)
279                       "Error setting effective privileges");
280         return HTTP_INTERNAL_SERVER_ERROR;
281     }
282 
283     /* ... including those of any subprocesses */
284     if (setppriv(PRIV_SET, PRIV_INHERITABLE, cfg->child_priv) == -1) {
285         ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r, APLOGNO(02148)
286                       "Error setting inheritable privileges");
287         return HTTP_INTERNAL_SERVER_ERROR;
288     }
289     if (setppriv(PRIV_SET, PRIV_LIMIT, cfg->child_priv) == -1) {
290         ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r, APLOGNO(02149)
291                       "Error setting limit privileges");
292         return HTTP_INTERNAL_SERVER_ERROR;
293     }
294 
295     /* If we're in a child process, drop down PPERM too */
296     if (fork_req) {
297         if (setppriv(PRIV_SET, PRIV_PERMITTED, cfg->priv) == -1) {
298             ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r, APLOGNO(02150)
299                           "Error setting permitted privileges");
300             return HTTP_INTERNAL_SERVER_ERROR;
301         }
302     }
303 
304     return OK;
305 }
306 #define PDROP_CHECK(x) if (x == -1) { \
307         ap_log_error(APLOG_MARK, APLOG_CRIT, errno, s, APLOGNO(02151) \
308                      "Error dropping privileges"); \
309         return !OK; \
310     }
311 
privileges_drop_first(apr_pool_t * pool,server_rec * s)312 static int privileges_drop_first(apr_pool_t *pool, server_rec *s)
313 {
314     /* We need to set privileges before mod_unixd,
315      * 'cos otherwise setuid will wipe our privilege to do so
316      */
317     priv_cfg *spcfg;
318     server_rec *sp;
319     priv_set_t *ppriv = priv_allocset();
320 
321     /* compute ppriv from the union of all the vhosts plus setid */
322     priv_copyset(priv_setid, ppriv);
323     for (sp = s; sp != NULL; sp=sp->next) {
324         spcfg = ap_get_module_config(sp->module_config, &privileges_module);
325         priv_union(spcfg->priv, ppriv);
326     }
327     PDROP_CHECK(setppriv(PRIV_SET, PRIV_PERMITTED, ppriv))
328     PDROP_CHECK(setppriv(PRIV_SET, PRIV_EFFECTIVE, ppriv))
329     priv_freeset(ppriv);
330 
331     return OK;
332 }
privileges_drop_last(apr_pool_t * pool,server_rec * s)333 static int privileges_drop_last(apr_pool_t *pool, server_rec *s)
334 {
335     /* Our config stuff has set the privileges we need, so now
336      * we just set them to those of the parent server_rec
337      *
338      * This has to happen after mod_unixd, 'cos mod_unixd needs
339      * privileges we drop here.
340      */
341     priv_cfg *cfg = ap_get_module_config(s->module_config, &privileges_module);
342 
343     /* defaults - the default vhost */
344     PDROP_CHECK(setppriv(PRIV_SET, PRIV_LIMIT, cfg->child_priv))
345     PDROP_CHECK(setppriv(PRIV_SET, PRIV_INHERITABLE, cfg->child_priv))
346     PDROP_CHECK(setppriv(PRIV_SET, PRIV_EFFECTIVE, cfg->priv))
347 
348     return OK;
349 }
privileges_term(void * rec)350 static apr_status_t privileges_term(void *rec)
351 {
352     priv_freeset(priv_setid);
353     return APR_SUCCESS;
354 }
privileges_postconf(apr_pool_t * pconf,apr_pool_t * plog,apr_pool_t * ptemp,server_rec * s)355 static int privileges_postconf(apr_pool_t *pconf, apr_pool_t *plog,
356                                apr_pool_t *ptemp, server_rec *s)
357 {
358     priv_cfg *cfg;
359     server_rec *sp;
360 
361     /* if we have dtrace enabled, merge it into everything */
362     if (dtrace_enabled) {
363         for (sp = s; sp != NULL; sp = sp->next) {
364             cfg = ap_get_module_config(sp->module_config, &privileges_module);
365             CR_CHECK(priv_addset(cfg->priv, PRIV_DTRACE_KERNEL));
366             CR_CHECK(priv_addset(cfg->priv, PRIV_DTRACE_PROC));
367             CR_CHECK(priv_addset(cfg->priv, PRIV_DTRACE_USER));
368             CR_CHECK(priv_addset(cfg->child_priv, PRIV_DTRACE_KERNEL));
369             CR_CHECK(priv_addset(cfg->child_priv, PRIV_DTRACE_PROC));
370             CR_CHECK(priv_addset(cfg->child_priv, PRIV_DTRACE_USER));
371         }
372         CR_CHECK(priv_addset(priv_default, PRIV_DTRACE_KERNEL));
373         CR_CHECK(priv_addset(priv_default, PRIV_DTRACE_PROC));
374         CR_CHECK(priv_addset(priv_default, PRIV_DTRACE_USER));
375     }
376 
377     /* set up priv_setid for per-request use */
378     priv_setid = priv_allocset();
379     apr_pool_cleanup_register(pconf, NULL, privileges_term,
380                               apr_pool_cleanup_null);
381     priv_emptyset(priv_setid);
382     if (priv_addset(priv_setid, PRIV_PROC_SETID) == -1) {
383         ap_log_perror(APLOG_MARK, APLOG_CRIT, errno, ptemp, APLOGNO(02152)
384                       "priv_addset");
385         return !OK;
386     }
387     return OK;
388 }
privileges_init(apr_pool_t * pconf,apr_pool_t * plog,apr_pool_t * ptemp)389 static int privileges_init(apr_pool_t *pconf, apr_pool_t *plog,
390                            apr_pool_t *ptemp)
391 {
392     /* refuse to work if the MPM is threaded */
393     int threaded;
394     int rv = ap_mpm_query(AP_MPMQ_IS_THREADED, &threaded);
395     if (rv != APR_SUCCESS) {
396         ap_log_perror(APLOG_MARK, APLOG_NOTICE, rv, ptemp, APLOGNO(02153)
397                       "mod_privileges: unable to determine MPM characteristics."
398                       "  Please ensure you are using a non-threaded MPM "
399                       "with this module.");
400     }
401     if (threaded) {
402         ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, ptemp, APLOGNO(02154)
403                       "mod_privileges is not compatible with a threaded MPM.");
404         return !OK;
405     }
406     return OK;
407 }
privileges_hooks(apr_pool_t * pool)408 static void privileges_hooks(apr_pool_t *pool)
409 {
410     ap_hook_post_read_request(privileges_req, NULL, NULL,
411                               APR_HOOK_REALLY_FIRST);
412     ap_hook_header_parser(privileges_req, NULL, NULL, APR_HOOK_REALLY_FIRST);
413     ap_hook_drop_privileges(privileges_drop_first, NULL, NULL, APR_HOOK_FIRST);
414     ap_hook_drop_privileges(privileges_drop_last, NULL, NULL, APR_HOOK_LAST);
415     ap_hook_post_config(privileges_postconf, NULL, NULL, APR_HOOK_MIDDLE);
416     ap_hook_pre_config(privileges_init, NULL, NULL, APR_HOOK_FIRST);
417 }
418 
vhost_user(cmd_parms * cmd,void * dir,const char * arg)419 static const char *vhost_user(cmd_parms *cmd, void *dir, const char *arg)
420 {
421     priv_cfg *cfg = ap_get_module_config(cmd->server->module_config,
422                                          &privileges_module);
423     cfg->uid = ap_uname2id(arg);
424     if (cfg->uid == 0) {
425         return apr_pstrcat(cmd->pool, "Invalid userid for VHostUser: ",
426                            arg, NULL);
427     }
428     return NULL;
429 }
vhost_group(cmd_parms * cmd,void * dir,const char * arg)430 static const char *vhost_group(cmd_parms *cmd, void *dir, const char *arg)
431 {
432     priv_cfg *cfg = ap_get_module_config(cmd->server->module_config,
433                                          &privileges_module);
434     cfg->gid = ap_gname2id(arg);
435     if (cfg->uid == 0) {
436         return apr_pstrcat(cmd->pool, "Invalid groupid for VHostGroup: ",
437                            arg, NULL);
438     }
439     return NULL;
440 }
vhost_secure(cmd_parms * cmd,void * dir,int arg)441 static const char *vhost_secure(cmd_parms *cmd, void *dir, int arg)
442 {
443     priv_cfg *cfg = ap_get_module_config(cmd->server->module_config,
444                                          &privileges_module);
445     if (!arg) {
446         /* add basic privileges, excluding those covered by cgimode */
447         CFG_CHECK(priv_addset(cfg->priv, PRIV_FILE_LINK_ANY));
448         CFG_CHECK(priv_addset(cfg->priv, PRIV_PROC_INFO));
449         CFG_CHECK(priv_addset(cfg->priv, PRIV_PROC_SESSION));
450     }
451     return NULL;
452 }
vhost_cgimode(cmd_parms * cmd,void * dir,const char * arg)453 static const char *vhost_cgimode(cmd_parms *cmd, void *dir, const char *arg)
454 {
455     priv_cfg *cfg = ap_get_module_config(cmd->server->module_config,
456                                          &privileges_module);
457     if (!strcasecmp(arg, "on")) {
458         /* default - nothing to do */
459     }
460     else if (!strcasecmp(arg, "off")) {
461         /* drop fork+exec privs */
462         CFG_CHECK(priv_delset(cfg->priv, PRIV_PROC_FORK));
463         CFG_CHECK(priv_delset(cfg->priv, PRIV_PROC_EXEC));
464     }
465     else if (!strcasecmp(arg, "secure")) {
466         /* deny privileges to CGI procs */
467         CFG_CHECK(priv_delset(cfg->child_priv, PRIV_PROC_FORK));
468         CFG_CHECK(priv_delset(cfg->child_priv, PRIV_PROC_EXEC));
469         CFG_CHECK(priv_delset(cfg->child_priv, PRIV_FILE_LINK_ANY));
470         CFG_CHECK(priv_delset(cfg->child_priv, PRIV_PROC_INFO));
471         CFG_CHECK(priv_delset(cfg->child_priv, PRIV_PROC_SESSION));
472     }
473     else {
474         return "VHostCGIMode must be On, Off or Secure";
475     }
476 
477     return NULL;
478 }
dtraceenable(cmd_parms * cmd,void * dir,int arg)479 static const char *dtraceenable(cmd_parms *cmd, void *dir, int arg)
480 {
481     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
482     if (err != NULL) {
483         return err;
484     }
485     dtrace_enabled = arg;
486     return NULL;
487 }
488 
privs_mode(cmd_parms * cmd,void * dir,const char * arg)489 static const char *privs_mode(cmd_parms *cmd, void *dir, const char *arg)
490 {
491     priv_mode mode = PRIV_UNSET;
492     if (!strcasecmp(arg, "FAST")) {
493         mode = PRIV_FAST;
494     }
495     else if (!strcasecmp(arg, "SECURE")) {
496         mode = PRIV_SECURE;
497     }
498     else if (!strcasecmp(arg, "SELECTIVE")) {
499         mode = PRIV_SELECTIVE;
500     }
501 
502     if (cmd->path) {
503         /* In a directory context, set the per_dir_config */
504         priv_dir_cfg *cfg = dir;
505         cfg->mode = mode;
506         if ((mode == PRIV_UNSET) || (mode == PRIV_SELECTIVE)) {
507             return "PrivilegesMode in a Directory context must be FAST or SECURE";
508         }
509     }
510     else {
511         /* In a global or vhost context, set the server config */
512         priv_cfg *cfg = ap_get_module_config(cmd->server->module_config,
513                                              &privileges_module);
514         cfg->mode = mode;
515         if (mode == PRIV_UNSET) {
516             return "PrivilegesMode must be FAST, SECURE or SELECTIVE";
517         }
518     }
519     return NULL;
520 }
521 
522 #ifdef BIG_SECURITY_HOLE
vhost_privs(cmd_parms * cmd,void * dir,const char * arg)523 static const char *vhost_privs(cmd_parms *cmd, void *dir, const char *arg)
524 {
525     priv_cfg *cfg = ap_get_module_config(cmd->server->module_config,
526                                          &privileges_module);
527     const char *priv = arg;
528 
529     if (*priv == '-') {
530         CFG_CHECK(priv_delset(cfg->priv, priv+1));
531     }
532     else if (*priv == '+') {
533         CFG_CHECK(priv_addset(cfg->priv, priv+1));
534     }
535     else {
536         priv_emptyset(cfg->priv);
537         CFG_CHECK(priv_addset(cfg->priv, priv));
538     }
539     return NULL;
540 }
vhost_cgiprivs(cmd_parms * cmd,void * dir,const char * arg)541 static const char *vhost_cgiprivs(cmd_parms *cmd, void *dir, const char *arg)
542 {
543     priv_cfg *cfg = ap_get_module_config(cmd->server->module_config,
544                                          &privileges_module);
545     const char *priv = arg;
546     if (*priv == '-') {
547         CFG_CHECK(priv_delset(cfg->child_priv, priv+1));
548     }
549     else if (*priv == '+') {
550         CFG_CHECK(priv_addset(cfg->child_priv, priv+1));
551     }
552     else {
553         priv_emptyset(cfg->child_priv);
554         CFG_CHECK(priv_addset(cfg->child_priv, priv));
555     }
556     return NULL;
557 }
558 #endif
559 static const command_rec privileges_cmds[] = {
560     AP_INIT_TAKE1("VHostUser", vhost_user, NULL, RSRC_CONF,
561                   "Userid under which the virtualhost will run"),
562     AP_INIT_TAKE1("VHostGroup", vhost_group, NULL, RSRC_CONF,
563                   "Group under which the virtualhost will run"),
564     AP_INIT_FLAG("VHostSecure", vhost_secure, NULL, RSRC_CONF,
565                  "Run in enhanced security mode (default ON)"),
566     AP_INIT_TAKE1("VHostCGIMode", vhost_cgimode, NULL, RSRC_CONF,
567                   "Enable fork+exec for this virtualhost (Off|Secure|On)"),
568     AP_INIT_FLAG("DTracePrivileges", dtraceenable, NULL, RSRC_CONF,
569                  "Enable DTrace"),
570     AP_INIT_TAKE1("PrivilegesMode", privs_mode, NULL, RSRC_CONF|ACCESS_CONF,
571                   "tradeoff performance vs security (fast or secure)"),
572 #ifdef BIG_SECURITY_HOLE
573     AP_INIT_ITERATE("VHostPrivs", vhost_privs, NULL, RSRC_CONF,
574                     "Privileges available in the (virtual) server"),
575     AP_INIT_ITERATE("VHostCGIPrivs", vhost_cgiprivs, NULL, RSRC_CONF,
576                     "Privileges available to external programs"),
577 #endif
578     {NULL}
579 };
580 AP_DECLARE_MODULE(privileges) = {
581     STANDARD20_MODULE_STUFF,
582     privileges_create_dir_cfg,
583     privileges_merge_dir_cfg,
584     privileges_create_cfg,
585     privileges_merge_cfg,
586     privileges_cmds,
587     privileges_hooks
588 };
589