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 "ap_config.h"
18#include "httpd.h"
19#include "http_config.h"
20#include "http_main.h"
21#include "http_log.h"
22#include "http_core.h"
23#include "mpm_common.h"
24#include "os.h"
25#include "ap_mpm.h"
26#include "mod_unixd.h"
27#include "apr_thread_proc.h"
28#include "apr_strings.h"
29#include "apr_portable.h"
30#ifdef HAVE_PWD_H
31#include <pwd.h>
32#endif
33#ifdef HAVE_SYS_RESOURCE_H
34#include <sys/resource.h>
35#endif
36/* XXX */
37#include <sys/stat.h>
38#ifdef HAVE_UNISTD_H
39#include <unistd.h>
40#endif
41#ifdef HAVE_GRP_H
42#include <grp.h>
43#endif
44#ifdef HAVE_STRINGS_H
45#include <strings.h>
46#endif
47#ifdef HAVE_SYS_SEM_H
48#include <sys/sem.h>
49#endif
50#ifdef HAVE_SYS_PRCTL_H
51#include <sys/prctl.h>
52#endif
53
54#ifndef DEFAULT_USER
55#define DEFAULT_USER "#-1"
56#endif
57#ifndef DEFAULT_GROUP
58#define DEFAULT_GROUP "#-1"
59#endif
60
61#if 0
62typedef struct {
63  const char *user_name;
64  uid_t user_id;
65  gid_t group_id;
66  const char *chroot_dir;
67} unixd_config_t;
68#else
69/*
70 * TODO: clean up the separation between this code
71 *       and its data structures and unixd.c, as shown
72 *       by the fact that we include unixd.h. Create
73 *       mod_unixd.h which does what we need and
74 *       clean up unixd.h for what it no longer needs
75 */
76#include "unixd.h"
77#endif
78
79
80/* Set group privileges.
81 *
82 * Note that we use the username as set in the config files, rather than
83 * the lookup of to uid --- the same uid may have multiple passwd entries,
84 * with different sets of groups for each.
85 */
86
87static int set_group_privs(void)
88{
89    if (!geteuid()) {
90        const char *name;
91
92        /* Get username if passed as a uid */
93
94        if (ap_unixd_config.user_name[0] == '#') {
95            struct passwd *ent;
96            uid_t uid = atol(&ap_unixd_config.user_name[1]);
97
98            if ((ent = getpwuid(uid)) == NULL) {
99                ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02155)
100                         "getpwuid: couldn't determine user name from uid %ld, "
101                         "you probably need to modify the User directive",
102                         (long)uid);
103                return -1;
104            }
105
106            name = ent->pw_name;
107        }
108        else
109            name = ap_unixd_config.user_name;
110
111#if !defined(OS2)
112        /* OS/2 doesn't support groups. */
113        /*
114         * Set the GID before initgroups(), since on some platforms
115         * setgid() is known to zap the group list.
116         */
117        if (setgid(ap_unixd_config.group_id) == -1) {
118            ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02156)
119                        "setgid: unable to set group id to Group %ld",
120                        (long)ap_unixd_config.group_id);
121            return -1;
122        }
123
124        /* Reset `groups' attributes. */
125
126        if (initgroups(name, ap_unixd_config.group_id) == -1) {
127            ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02157)
128                        "initgroups: unable to set groups for User %s "
129                        "and Group %ld", name, (long)ap_unixd_config.group_id);
130            return -1;
131        }
132#endif /* !defined(OS2) */
133    }
134    return 0;
135}
136
137
138static int
139unixd_drop_privileges(apr_pool_t *pool, server_rec *s)
140{
141    int rv = set_group_privs();
142
143    if (rv) {
144        return rv;
145    }
146
147    if (NULL != ap_unixd_config.chroot_dir) {
148        if (geteuid()) {
149            rv = errno;
150            ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02158)
151                         "Cannot chroot when not started as root");
152            return rv;
153        }
154
155        if (chdir(ap_unixd_config.chroot_dir) != 0) {
156            rv = errno;
157            ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02159)
158                         "Can't chdir to %s", ap_unixd_config.chroot_dir);
159            return rv;
160        }
161
162        if (chroot(ap_unixd_config.chroot_dir) != 0) {
163            rv = errno;
164            ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02160)
165                         "Can't chroot to %s", ap_unixd_config.chroot_dir);
166            return rv;
167        }
168
169        if (chdir("/") != 0) {
170            rv = errno;
171            ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02161)
172                         "Can't chdir to new root");
173            return rv;
174        }
175    }
176
177    /* Only try to switch if we're running as root */
178    if (!geteuid() && (
179#ifdef _OSD_POSIX
180        os_init_job_environment(NULL, ap_unixd_config.user_name, ap_exists_config_define("DEBUG")) != 0 ||
181#endif
182        setuid(ap_unixd_config.user_id) == -1)) {
183        rv = errno;
184        ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02162)
185                    "setuid: unable to change to uid: %ld",
186                    (long) ap_unixd_config.user_id);
187        return rv;
188    }
189#if defined(HAVE_PRCTL) && defined(PR_SET_DUMPABLE)
190    /* this applies to Linux 2.4+ */
191    if (ap_coredumpdir_configured) {
192        if (prctl(PR_SET_DUMPABLE, 1)) {
193            rv = errno;
194            ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02163)
195                         "set dumpable failed - this child will not coredump"
196                         " after software errors");
197            return rv;
198        }
199    }
200#endif
201
202    return OK;
203}
204
205
206static const char *
207unixd_set_user(cmd_parms *cmd, void *dummy,
208               const char *arg)
209{
210    const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
211    if (err != NULL) {
212        return err;
213    }
214
215    ap_unixd_config.user_name = arg;
216    ap_unixd_config.user_id = ap_uname2id(arg);
217#if !defined (BIG_SECURITY_HOLE) && !defined (OS2)
218    if (ap_unixd_config.user_id == 0) {
219        return "Error:\tApache has not been designed to serve pages while\n"
220                "\trunning as root.  There are known race conditions that\n"
221                "\twill allow any local user to read any file on the system.\n"
222                "\tIf you still desire to serve pages as root then\n"
223                "\tadd -DBIG_SECURITY_HOLE to the CFLAGS env variable\n"
224                "\tand then rebuild the server.\n"
225                "\tIt is strongly suggested that you instead modify the User\n"
226                "\tdirective in your httpd.conf file to list a non-root\n"
227                "\tuser.\n";
228    }
229#endif
230
231    return NULL;
232}
233
234static const char*
235unixd_set_group(cmd_parms *cmd, void *dummy,
236                                         const char *arg)
237{
238    const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
239    if (err != NULL) {
240        return err;
241    }
242
243    ap_unixd_config.group_name = arg;
244    ap_unixd_config.group_id = ap_gname2id(arg);
245
246    return NULL;
247}
248
249static const char*
250unixd_set_chroot_dir(cmd_parms *cmd, void *dummy,
251                    const char *arg)
252{
253    const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
254    if (err != NULL) {
255        return err;
256    }
257    if (!ap_is_directory(cmd->pool, arg)) {
258        return "ChrootDir must be a valid directory";
259    }
260
261    ap_unixd_config.chroot_dir = arg;
262    return NULL;
263}
264
265static const char *
266unixd_set_suexec(cmd_parms *cmd, void *dummy, int arg)
267{
268    const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
269
270    if (err != NULL) {
271        return err;
272    }
273
274    if (!ap_unixd_config.suexec_enabled && arg) {
275        return apr_pstrcat(cmd->pool, "suEXEC isn't supported: ",
276                           ap_unixd_config.suexec_disabled_reason, NULL);
277    }
278
279    if (!arg) {
280        ap_unixd_config.suexec_disabled_reason = "Suexec directive is Off";
281    }
282
283    ap_unixd_config.suexec_enabled = arg;
284    return NULL;
285}
286
287static int
288unixd_pre_config(apr_pool_t *pconf, apr_pool_t *plog,
289                 apr_pool_t *ptemp)
290{
291    apr_finfo_t wrapper;
292    ap_unixd_config.user_name = DEFAULT_USER;
293    ap_unixd_config.user_id = ap_uname2id(DEFAULT_USER);
294    ap_unixd_config.group_name = DEFAULT_GROUP;
295    ap_unixd_config.group_id = ap_gname2id(DEFAULT_GROUP);
296
297    ap_unixd_config.chroot_dir = NULL; /* none */
298
299    /* Check for suexec */
300    ap_unixd_config.suexec_enabled = 0;
301    if ((apr_stat(&wrapper, SUEXEC_BIN, APR_FINFO_NORM, ptemp))
302         == APR_SUCCESS) {
303        if ((wrapper.protection & APR_USETID) && wrapper.user == 0
304            && (access(SUEXEC_BIN, R_OK|X_OK) == 0)) {
305            ap_unixd_config.suexec_enabled = 1;
306            ap_unixd_config.suexec_disabled_reason = "";
307        }
308        else {
309            ap_unixd_config.suexec_disabled_reason =
310                "Invalid owner or file mode for " SUEXEC_BIN;
311        }
312    }
313    else {
314        ap_unixd_config.suexec_disabled_reason =
315            "Missing suexec binary " SUEXEC_BIN;
316    }
317
318    ap_sys_privileges_handlers(1);
319    return OK;
320}
321
322AP_DECLARE(int) ap_unixd_setup_child(void)
323{
324    if (set_group_privs()) {
325        return -1;
326    }
327
328    if (NULL != ap_unixd_config.chroot_dir) {
329        if (geteuid()) {
330            ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02164)
331                         "Cannot chroot when not started as root");
332            return -1;
333        }
334        if (chdir(ap_unixd_config.chroot_dir) != 0) {
335            ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02165)
336                         "Can't chdir to %s", ap_unixd_config.chroot_dir);
337            return -1;
338        }
339        if (chroot(ap_unixd_config.chroot_dir) != 0) {
340            ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02166)
341                         "Can't chroot to %s", ap_unixd_config.chroot_dir);
342            return -1;
343        }
344        if (chdir("/") != 0) {
345            ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02167)
346                         "Can't chdir to new root");
347            return -1;
348        }
349    }
350
351    /* Only try to switch if we're running as root */
352    if (!geteuid() && (
353#ifdef _OSD_POSIX
354        os_init_job_environment(NULL, ap_unixd_config.user_name, ap_exists_config_define("DEBUG")) != 0 ||
355#endif
356        setuid(ap_unixd_config.user_id) == -1)) {
357        ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02168)
358                    "setuid: unable to change to uid: %ld",
359                    (long) ap_unixd_config.user_id);
360        return -1;
361    }
362#if defined(HAVE_PRCTL) && defined(PR_SET_DUMPABLE)
363    /* this applies to Linux 2.4+ */
364    if (ap_coredumpdir_configured) {
365        if (prctl(PR_SET_DUMPABLE, 1)) {
366            ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL, APLOGNO(02169)
367                         "set dumpable failed - this child will not coredump"
368                         " after software errors");
369        }
370    }
371#endif
372    return 0;
373}
374
375static void unixd_dump_config(apr_pool_t *p, server_rec *s)
376{
377    apr_file_t *out = NULL;
378    apr_uid_t uid = ap_unixd_config.user_id;
379    apr_gid_t gid = ap_unixd_config.group_id;
380    char *no_root = "";
381    if (!ap_exists_config_define("DUMP_RUN_CFG"))
382        return;
383    if (geteuid() != 0)
384        no_root = " not_used";
385    apr_file_open_stdout(&out, p);
386    apr_file_printf(out, "User: name=\"%s\" id=%lu%s\n",
387                    ap_unixd_config.user_name, (unsigned long)uid, no_root);
388    apr_file_printf(out, "Group: name=\"%s\" id=%lu%s\n",
389                    ap_unixd_config.group_name, (unsigned long)gid, no_root);
390    if (ap_unixd_config.chroot_dir)
391        apr_file_printf(out, "ChrootDir: \"%s\"%s\n",
392                        ap_unixd_config.chroot_dir, no_root);
393}
394
395static void unixd_hooks(apr_pool_t *pool)
396{
397    ap_hook_pre_config(unixd_pre_config,
398                       NULL, NULL, APR_HOOK_FIRST);
399    ap_hook_test_config(unixd_dump_config,
400                        NULL, NULL, APR_HOOK_FIRST);
401    ap_hook_drop_privileges(unixd_drop_privileges,
402                            NULL, NULL, APR_HOOK_MIDDLE);
403}
404
405static const command_rec unixd_cmds[] = {
406    AP_INIT_TAKE1("User", unixd_set_user, NULL, RSRC_CONF,
407                  "Effective user id for this server"),
408    AP_INIT_TAKE1("Group", unixd_set_group, NULL, RSRC_CONF,
409                  "Effective group id for this server"),
410    AP_INIT_TAKE1("ChrootDir", unixd_set_chroot_dir, NULL, RSRC_CONF,
411                  "The directory to chroot(2) into"),
412    AP_INIT_FLAG("Suexec", unixd_set_suexec, NULL, RSRC_CONF,
413                 "Enable or disable suEXEC support"),
414    {NULL}
415};
416
417AP_DECLARE_MODULE(unixd) = {
418    STANDARD20_MODULE_STUFF,
419    NULL,
420    NULL,
421    NULL,
422    NULL,
423    unixd_cmds,
424    unixd_hooks
425};
426
427