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