[33] | 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 | /* |
---|
| 18 | * suexec.c -- "Wrapper" support program for suEXEC behaviour for Apache |
---|
| 19 | * |
---|
| 20 | *********************************************************************** |
---|
| 21 | * |
---|
| 22 | * NOTE! : DO NOT edit this code!!! Unless you know what you are doing, |
---|
| 23 | * editing this code might open up your system in unexpected |
---|
| 24 | * ways to would-be crackers. Every precaution has been taken |
---|
| 25 | * to make this code as safe as possible; alter it at your own |
---|
| 26 | * risk. |
---|
| 27 | * |
---|
| 28 | *********************************************************************** |
---|
| 29 | * |
---|
| 30 | * |
---|
| 31 | */ |
---|
| 32 | |
---|
| 33 | #include "apr.h" |
---|
| 34 | #include "ap_config.h" |
---|
| 35 | #include "suexec.h" |
---|
| 36 | |
---|
| 37 | #include <sys/param.h> |
---|
| 38 | #include <sys/stat.h> |
---|
| 39 | #include <sys/types.h> |
---|
| 40 | #include <string.h> |
---|
| 41 | #include <time.h> |
---|
| 42 | #if APR_HAVE_UNISTD_H |
---|
| 43 | #include <unistd.h> |
---|
| 44 | #endif |
---|
| 45 | |
---|
| 46 | #include <stdio.h> |
---|
| 47 | #include <stdarg.h> |
---|
| 48 | #include <stdlib.h> |
---|
| 49 | |
---|
| 50 | #ifdef HAVE_PWD_H |
---|
| 51 | #include <pwd.h> |
---|
| 52 | #endif |
---|
| 53 | |
---|
| 54 | #ifdef HAVE_GRP_H |
---|
| 55 | #include <grp.h> |
---|
| 56 | #endif |
---|
| 57 | |
---|
| 58 | /* |
---|
| 59 | *********************************************************************** |
---|
| 60 | * There is no initgroups() in QNX, so I believe this is safe :-) |
---|
| 61 | * Use cc -osuexec -3 -O -mf -DQNX suexec.c to compile. |
---|
| 62 | * |
---|
| 63 | * May 17, 1997. |
---|
| 64 | * Igor N. Kovalenko -- infoh mail.wplus.net |
---|
| 65 | *********************************************************************** |
---|
| 66 | */ |
---|
| 67 | |
---|
| 68 | #if defined(NEED_INITGROUPS) |
---|
| 69 | int initgroups(const char *name, gid_t basegid) |
---|
| 70 | { |
---|
| 71 | /* QNX and MPE do not appear to support supplementary groups. */ |
---|
| 72 | return 0; |
---|
| 73 | } |
---|
| 74 | #endif |
---|
| 75 | |
---|
| 76 | #if defined(SUNOS4) |
---|
| 77 | extern char *sys_errlist[]; |
---|
| 78 | #define strerror(x) sys_errlist[(x)] |
---|
| 79 | #endif |
---|
| 80 | |
---|
| 81 | #if defined(PATH_MAX) |
---|
| 82 | #define AP_MAXPATH PATH_MAX |
---|
| 83 | #elif defined(MAXPATHLEN) |
---|
| 84 | #define AP_MAXPATH MAXPATHLEN |
---|
| 85 | #else |
---|
| 86 | #define AP_MAXPATH 8192 |
---|
| 87 | #endif |
---|
| 88 | |
---|
| 89 | #define AP_ENVBUF 256 |
---|
| 90 | |
---|
| 91 | extern char **environ; |
---|
| 92 | static FILE *log = NULL; |
---|
| 93 | |
---|
| 94 | static const char *const safe_env_lst[] = |
---|
| 95 | { |
---|
| 96 | /* variable name starts with */ |
---|
| 97 | "HTTP_", |
---|
| 98 | "SSL_", |
---|
| 99 | |
---|
| 100 | /* variable name is */ |
---|
| 101 | "AUTH_TYPE=", |
---|
| 102 | "CONTENT_LENGTH=", |
---|
| 103 | "CONTENT_TYPE=", |
---|
| 104 | "DATE_GMT=", |
---|
| 105 | "DATE_LOCAL=", |
---|
| 106 | "DOCUMENT_NAME=", |
---|
| 107 | "DOCUMENT_PATH_INFO=", |
---|
| 108 | "DOCUMENT_ROOT=", |
---|
| 109 | "DOCUMENT_URI=", |
---|
| 110 | "GATEWAY_INTERFACE=", |
---|
| 111 | "HTTPS=", |
---|
| 112 | "LAST_MODIFIED=", |
---|
| 113 | "PATH_INFO=", |
---|
| 114 | "PATH_TRANSLATED=", |
---|
| 115 | "QUERY_STRING=", |
---|
| 116 | "QUERY_STRING_UNESCAPED=", |
---|
| 117 | "REMOTE_ADDR=", |
---|
| 118 | "REMOTE_HOST=", |
---|
| 119 | "REMOTE_IDENT=", |
---|
| 120 | "REMOTE_PORT=", |
---|
| 121 | "REMOTE_USER=", |
---|
| 122 | "REDIRECT_HANDLER=", |
---|
| 123 | "REDIRECT_QUERY_STRING=", |
---|
| 124 | "REDIRECT_REMOTE_USER=", |
---|
| 125 | "REDIRECT_STATUS=", |
---|
| 126 | "REDIRECT_URL=", |
---|
| 127 | "REQUEST_METHOD=", |
---|
| 128 | "REQUEST_URI=", |
---|
| 129 | "SCRIPT_FILENAME=", |
---|
| 130 | "SCRIPT_NAME=", |
---|
| 131 | "SCRIPT_URI=", |
---|
| 132 | "SCRIPT_URL=", |
---|
| 133 | "SERVER_ADMIN=", |
---|
| 134 | "SERVER_NAME=", |
---|
| 135 | "SERVER_ADDR=", |
---|
| 136 | "SERVER_PORT=", |
---|
| 137 | "SERVER_PROTOCOL=", |
---|
| 138 | "SERVER_SIGNATURE=", |
---|
| 139 | "SERVER_SOFTWARE=", |
---|
| 140 | "UNIQUE_ID=", |
---|
| 141 | "USER_NAME=", |
---|
| 142 | "TZ=", |
---|
| 143 | NULL |
---|
| 144 | }; |
---|
| 145 | |
---|
| 146 | |
---|
| 147 | static void err_output(int is_error, const char *fmt, va_list ap) |
---|
| 148 | { |
---|
| 149 | #ifdef AP_LOG_EXEC |
---|
| 150 | time_t timevar; |
---|
| 151 | struct tm *lt; |
---|
| 152 | |
---|
| 153 | if (!log) { |
---|
| 154 | if ((log = fopen(AP_LOG_EXEC, "a")) == NULL) { |
---|
| 155 | fprintf(stderr, "suexec failure: could not open log file\n"); |
---|
| 156 | perror("fopen"); |
---|
| 157 | exit(1); |
---|
| 158 | } |
---|
| 159 | } |
---|
| 160 | |
---|
| 161 | if (is_error) { |
---|
| 162 | fprintf(stderr, "suexec policy violation: see suexec log for more " |
---|
| 163 | "details\n"); |
---|
| 164 | } |
---|
| 165 | |
---|
| 166 | time(&timevar); |
---|
| 167 | lt = localtime(&timevar); |
---|
| 168 | |
---|
| 169 | fprintf(log, "[%d-%.2d-%.2d %.2d:%.2d:%.2d]: ", |
---|
| 170 | lt->tm_year + 1900, lt->tm_mon + 1, lt->tm_mday, |
---|
| 171 | lt->tm_hour, lt->tm_min, lt->tm_sec); |
---|
| 172 | |
---|
| 173 | vfprintf(log, fmt, ap); |
---|
| 174 | |
---|
| 175 | fflush(log); |
---|
| 176 | #endif /* AP_LOG_EXEC */ |
---|
| 177 | return; |
---|
| 178 | } |
---|
| 179 | |
---|
| 180 | static void log_err(const char *fmt,...) |
---|
| 181 | { |
---|
| 182 | #ifdef AP_LOG_EXEC |
---|
| 183 | va_list ap; |
---|
| 184 | |
---|
| 185 | va_start(ap, fmt); |
---|
| 186 | err_output(1, fmt, ap); /* 1 == is_error */ |
---|
| 187 | va_end(ap); |
---|
| 188 | #endif /* AP_LOG_EXEC */ |
---|
| 189 | return; |
---|
| 190 | } |
---|
| 191 | |
---|
| 192 | static void log_no_err(const char *fmt,...) |
---|
| 193 | { |
---|
| 194 | #ifdef AP_LOG_EXEC |
---|
| 195 | va_list ap; |
---|
| 196 | |
---|
| 197 | va_start(ap, fmt); |
---|
| 198 | err_output(0, fmt, ap); /* 0 == !is_error */ |
---|
| 199 | va_end(ap); |
---|
| 200 | #endif /* AP_LOG_EXEC */ |
---|
| 201 | return; |
---|
| 202 | } |
---|
| 203 | |
---|
| 204 | static void clean_env(void) |
---|
| 205 | { |
---|
| 206 | char pathbuf[512]; |
---|
| 207 | char **cleanenv; |
---|
| 208 | char **ep; |
---|
| 209 | int cidx = 0; |
---|
| 210 | int idx; |
---|
| 211 | |
---|
| 212 | /* While cleaning the environment, the environment should be clean. |
---|
| 213 | * (e.g. malloc() may get the name of a file for writing debugging info. |
---|
| 214 | * Bad news if MALLOC_DEBUG_FILE is set to /etc/passwd. Sprintf() may be |
---|
| 215 | * susceptible to bad locale settings....) |
---|
| 216 | * (from PR 2790) |
---|
| 217 | */ |
---|
| 218 | char **envp = environ; |
---|
| 219 | char *empty_ptr = NULL; |
---|
| 220 | |
---|
| 221 | environ = &empty_ptr; /* VERY safe environment */ |
---|
| 222 | |
---|
| 223 | if ((cleanenv = (char **) calloc(AP_ENVBUF, sizeof(char *))) == NULL) { |
---|
| 224 | log_err("failed to malloc memory for environment\n"); |
---|
| 225 | exit(120); |
---|
| 226 | } |
---|
| 227 | |
---|
| 228 | sprintf(pathbuf, "PATH=%s", AP_SAFE_PATH); |
---|
| 229 | cleanenv[cidx] = strdup(pathbuf); |
---|
| 230 | cidx++; |
---|
| 231 | |
---|
| 232 | for (ep = envp; *ep && cidx < AP_ENVBUF-1; ep++) { |
---|
| 233 | for (idx = 0; safe_env_lst[idx]; idx++) { |
---|
| 234 | if (!strncmp(*ep, safe_env_lst[idx], |
---|
| 235 | strlen(safe_env_lst[idx]))) { |
---|
| 236 | cleanenv[cidx] = *ep; |
---|
| 237 | cidx++; |
---|
| 238 | break; |
---|
| 239 | } |
---|
| 240 | } |
---|
| 241 | } |
---|
| 242 | |
---|
| 243 | cleanenv[cidx] = NULL; |
---|
| 244 | |
---|
| 245 | environ = cleanenv; |
---|
| 246 | } |
---|
| 247 | |
---|
| 248 | int main(int argc, char *argv[]) |
---|
| 249 | { |
---|
| 250 | int userdir = 0; /* ~userdir flag */ |
---|
| 251 | uid_t uid; /* user information */ |
---|
| 252 | gid_t gid; /* target group placeholder */ |
---|
| 253 | char *target_uname; /* target user name */ |
---|
| 254 | char *target_gname; /* target group name */ |
---|
| 255 | char *target_homedir; /* target home directory */ |
---|
| 256 | char *actual_uname; /* actual user name */ |
---|
| 257 | char *actual_gname; /* actual group name */ |
---|
| 258 | char *prog; /* name of this program */ |
---|
| 259 | char *cmd; /* command to be executed */ |
---|
| 260 | char cwd[AP_MAXPATH]; /* current working directory */ |
---|
| 261 | char dwd[AP_MAXPATH]; /* docroot working directory */ |
---|
| 262 | struct passwd *pw; /* password entry holder */ |
---|
| 263 | struct group *gr; /* group entry holder */ |
---|
| 264 | struct stat dir_info; /* directory info holder */ |
---|
| 265 | struct stat prg_info; /* program info holder */ |
---|
| 266 | |
---|
| 267 | /* |
---|
| 268 | * Start with a "clean" environment |
---|
| 269 | */ |
---|
| 270 | clean_env(); |
---|
| 271 | |
---|
| 272 | prog = argv[0]; |
---|
| 273 | /* |
---|
| 274 | * Check existence/validity of the UID of the user |
---|
| 275 | * running this program. Error out if invalid. |
---|
| 276 | */ |
---|
| 277 | uid = getuid(); |
---|
| 278 | if ((pw = getpwuid(uid)) == NULL) { |
---|
| 279 | log_err("crit: invalid uid: (%ld)\n", uid); |
---|
| 280 | exit(102); |
---|
| 281 | } |
---|
| 282 | /* |
---|
| 283 | * See if this is a 'how were you compiled' request, and |
---|
| 284 | * comply if so. |
---|
| 285 | */ |
---|
| 286 | if ((argc > 1) |
---|
| 287 | && (! strcmp(argv[1], "-V")) |
---|
| 288 | && ((uid == 0) |
---|
| 289 | #ifdef _OSD_POSIX |
---|
| 290 | /* User name comparisons are case insensitive on BS2000/OSD */ |
---|
| 291 | || (! strcasecmp(AP_HTTPD_USER, pw->pw_name))) |
---|
| 292 | #else /* _OSD_POSIX */ |
---|
| 293 | || (! strcmp(AP_HTTPD_USER, pw->pw_name))) |
---|
| 294 | #endif /* _OSD_POSIX */ |
---|
| 295 | ) { |
---|
| 296 | #ifdef AP_DOC_ROOT |
---|
| 297 | fprintf(stderr, " -D AP_DOC_ROOT=\"%s\"\n", AP_DOC_ROOT); |
---|
| 298 | #endif |
---|
| 299 | #ifdef AP_GID_MIN |
---|
| 300 | fprintf(stderr, " -D AP_GID_MIN=%d\n", AP_GID_MIN); |
---|
| 301 | #endif |
---|
| 302 | #ifdef AP_HTTPD_USER |
---|
| 303 | fprintf(stderr, " -D AP_HTTPD_USER=\"%s\"\n", AP_HTTPD_USER); |
---|
| 304 | #endif |
---|
| 305 | #ifdef AP_LOG_EXEC |
---|
| 306 | fprintf(stderr, " -D AP_LOG_EXEC=\"%s\"\n", AP_LOG_EXEC); |
---|
| 307 | #endif |
---|
| 308 | #ifdef AP_SAFE_PATH |
---|
| 309 | fprintf(stderr, " -D AP_SAFE_PATH=\"%s\"\n", AP_SAFE_PATH); |
---|
| 310 | #endif |
---|
| 311 | #ifdef AP_SUEXEC_UMASK |
---|
| 312 | fprintf(stderr, " -D AP_SUEXEC_UMASK=%03o\n", AP_SUEXEC_UMASK); |
---|
| 313 | #endif |
---|
| 314 | #ifdef AP_UID_MIN |
---|
| 315 | fprintf(stderr, " -D AP_UID_MIN=%d\n", AP_UID_MIN); |
---|
| 316 | #endif |
---|
| 317 | #ifdef AP_USERDIR_SUFFIX |
---|
| 318 | fprintf(stderr, " -D AP_USERDIR_SUFFIX=\"%s\"\n", AP_USERDIR_SUFFIX); |
---|
| 319 | #endif |
---|
| 320 | exit(0); |
---|
| 321 | } |
---|
| 322 | /* |
---|
| 323 | * If there are a proper number of arguments, set |
---|
| 324 | * all of them to variables. Otherwise, error out. |
---|
| 325 | */ |
---|
| 326 | if (argc < 4) { |
---|
| 327 | log_err("too few arguments\n"); |
---|
| 328 | exit(101); |
---|
| 329 | } |
---|
| 330 | target_uname = argv[1]; |
---|
| 331 | target_gname = argv[2]; |
---|
| 332 | cmd = argv[3]; |
---|
| 333 | |
---|
| 334 | /* |
---|
| 335 | * Check to see if the user running this program |
---|
| 336 | * is the user allowed to do so as defined in |
---|
| 337 | * suexec.h. If not the allowed user, error out. |
---|
| 338 | */ |
---|
| 339 | #ifdef _OSD_POSIX |
---|
| 340 | /* User name comparisons are case insensitive on BS2000/OSD */ |
---|
| 341 | if (strcasecmp(AP_HTTPD_USER, pw->pw_name)) { |
---|
| 342 | log_err("user mismatch (%s instead of %s)\n", pw->pw_name, AP_HTTPD_USER); |
---|
| 343 | exit(103); |
---|
| 344 | } |
---|
| 345 | #else /*_OSD_POSIX*/ |
---|
| 346 | if (strcmp(AP_HTTPD_USER, pw->pw_name)) { |
---|
| 347 | log_err("user mismatch (%s instead of %s)\n", pw->pw_name, AP_HTTPD_USER); |
---|
| 348 | exit(103); |
---|
| 349 | } |
---|
| 350 | #endif /*_OSD_POSIX*/ |
---|
| 351 | |
---|
| 352 | /* |
---|
| 353 | * Check for a leading '/' (absolute path) in the command to be executed, |
---|
| 354 | * or attempts to back up out of the current directory, |
---|
| 355 | * to protect against attacks. If any are |
---|
| 356 | * found, error out. Naughty naughty crackers. |
---|
| 357 | */ |
---|
| 358 | if ((cmd[0] == '/') || (!strncmp(cmd, "../", 3)) |
---|
| 359 | || (strstr(cmd, "/../") != NULL)) { |
---|
| 360 | log_err("invalid command (%s)\n", cmd); |
---|
| 361 | exit(104); |
---|
| 362 | } |
---|
| 363 | |
---|
| 364 | /* |
---|
| 365 | * Check to see if this is a ~userdir request. If |
---|
| 366 | * so, set the flag, and remove the '~' from the |
---|
| 367 | * target username. |
---|
| 368 | */ |
---|
| 369 | if (!strncmp("~", target_uname, 1)) { |
---|
| 370 | target_uname++; |
---|
| 371 | userdir = 1; |
---|
| 372 | } |
---|
| 373 | |
---|
| 374 | /* |
---|
| 375 | * Error out if the target username is invalid. |
---|
| 376 | */ |
---|
| 377 | if (strspn(target_uname, "1234567890") != strlen(target_uname)) { |
---|
| 378 | if ((pw = getpwnam(target_uname)) == NULL) { |
---|
| 379 | log_err("invalid target user name: (%s)\n", target_uname); |
---|
| 380 | exit(105); |
---|
| 381 | } |
---|
| 382 | } |
---|
| 383 | else { |
---|
| 384 | if ((pw = getpwuid(atoi(target_uname))) == NULL) { |
---|
| 385 | log_err("invalid target user id: (%s)\n", target_uname); |
---|
| 386 | exit(121); |
---|
| 387 | } |
---|
| 388 | } |
---|
| 389 | |
---|
| 390 | /* |
---|
| 391 | * Error out if the target group name is invalid. |
---|
| 392 | */ |
---|
| 393 | if (strspn(target_gname, "1234567890") != strlen(target_gname)) { |
---|
| 394 | if ((gr = getgrnam(target_gname)) == NULL) { |
---|
| 395 | log_err("invalid target group name: (%s)\n", target_gname); |
---|
| 396 | exit(106); |
---|
| 397 | } |
---|
| 398 | gid = gr->gr_gid; |
---|
| 399 | actual_gname = strdup(gr->gr_name); |
---|
| 400 | } |
---|
| 401 | else { |
---|
| 402 | gid = atoi(target_gname); |
---|
| 403 | actual_gname = strdup(target_gname); |
---|
| 404 | } |
---|
| 405 | |
---|
| 406 | #ifdef _OSD_POSIX |
---|
| 407 | /* |
---|
| 408 | * Initialize BS2000 user environment |
---|
| 409 | */ |
---|
| 410 | { |
---|
| 411 | pid_t pid; |
---|
| 412 | int status; |
---|
| 413 | |
---|
| 414 | switch (pid = ufork(target_uname)) { |
---|
| 415 | case -1: /* Error */ |
---|
| 416 | log_err("failed to setup bs2000 environment for user %s: %s\n", |
---|
| 417 | target_uname, strerror(errno)); |
---|
| 418 | exit(150); |
---|
| 419 | case 0: /* Child */ |
---|
| 420 | break; |
---|
| 421 | default: /* Father */ |
---|
| 422 | while (pid != waitpid(pid, &status, 0)) |
---|
| 423 | ; |
---|
| 424 | /* @@@ FIXME: should we deal with STOP signals as well? */ |
---|
| 425 | if (WIFSIGNALED(status)) { |
---|
| 426 | kill (getpid(), WTERMSIG(status)); |
---|
| 427 | } |
---|
| 428 | exit(WEXITSTATUS(status)); |
---|
| 429 | } |
---|
| 430 | } |
---|
| 431 | #endif /*_OSD_POSIX*/ |
---|
| 432 | |
---|
| 433 | /* |
---|
| 434 | * Save these for later since initgroups will hose the struct |
---|
| 435 | */ |
---|
| 436 | uid = pw->pw_uid; |
---|
| 437 | actual_uname = strdup(pw->pw_name); |
---|
| 438 | target_homedir = strdup(pw->pw_dir); |
---|
| 439 | |
---|
| 440 | /* |
---|
| 441 | * Log the transaction here to be sure we have an open log |
---|
| 442 | * before we setuid(). |
---|
| 443 | */ |
---|
| 444 | log_no_err("uid: (%s/%s) gid: (%s/%s) cmd: %s\n", |
---|
| 445 | target_uname, actual_uname, |
---|
| 446 | target_gname, actual_gname, |
---|
| 447 | cmd); |
---|
| 448 | |
---|
| 449 | /* |
---|
| 450 | * Error out if attempt is made to execute as root or as |
---|
| 451 | * a UID less than AP_UID_MIN. Tsk tsk. |
---|
| 452 | */ |
---|
| 453 | if ((uid == 0) || (uid < AP_UID_MIN)) { |
---|
| 454 | log_err("cannot run as forbidden uid (%d/%s)\n", uid, cmd); |
---|
| 455 | exit(107); |
---|
| 456 | } |
---|
| 457 | |
---|
| 458 | /* |
---|
| 459 | * Error out if attempt is made to execute as root group |
---|
| 460 | * or as a GID less than AP_GID_MIN. Tsk tsk. |
---|
| 461 | */ |
---|
| 462 | if ((gid == 0) || (gid < AP_GID_MIN)) { |
---|
| 463 | log_err("cannot run as forbidden gid (%d/%s)\n", gid, cmd); |
---|
| 464 | exit(108); |
---|
| 465 | } |
---|
| 466 | |
---|
| 467 | /* |
---|
| 468 | * Change UID/GID here so that the following tests work over NFS. |
---|
| 469 | * |
---|
| 470 | * Initialize the group access list for the target user, |
---|
| 471 | * and setgid() to the target group. If unsuccessful, error out. |
---|
| 472 | */ |
---|
| 473 | if (((setgid(gid)) != 0) || (initgroups(actual_uname, gid) != 0)) { |
---|
| 474 | log_err("failed to setgid (%ld: %s)\n", gid, cmd); |
---|
| 475 | exit(109); |
---|
| 476 | } |
---|
| 477 | |
---|
| 478 | /* |
---|
| 479 | * setuid() to the target user. Error out on fail. |
---|
| 480 | */ |
---|
| 481 | if ((setuid(uid)) != 0) { |
---|
| 482 | log_err("failed to setuid (%ld: %s)\n", uid, cmd); |
---|
| 483 | exit(110); |
---|
| 484 | } |
---|
| 485 | |
---|
| 486 | /* |
---|
| 487 | * Get the current working directory, as well as the proper |
---|
| 488 | * document root (dependant upon whether or not it is a |
---|
| 489 | * ~userdir request). Error out if we cannot get either one, |
---|
| 490 | * or if the current working directory is not in the docroot. |
---|
| 491 | * Use chdir()s and getcwd()s to avoid problems with symlinked |
---|
| 492 | * directories. Yuck. |
---|
| 493 | */ |
---|
| 494 | if (getcwd(cwd, AP_MAXPATH) == NULL) { |
---|
| 495 | log_err("cannot get current working directory\n"); |
---|
| 496 | exit(111); |
---|
| 497 | } |
---|
| 498 | |
---|
| 499 | if (userdir) { |
---|
| 500 | if (((chdir(target_homedir)) != 0) || |
---|
| 501 | ((chdir(AP_USERDIR_SUFFIX)) != 0) || |
---|
| 502 | ((getcwd(dwd, AP_MAXPATH)) == NULL) || |
---|
| 503 | ((chdir(cwd)) != 0)) { |
---|
| 504 | log_err("cannot get docroot information (%s)\n", target_homedir); |
---|
| 505 | exit(112); |
---|
| 506 | } |
---|
| 507 | } |
---|
| 508 | else { |
---|
| 509 | if (((chdir(AP_DOC_ROOT)) != 0) || |
---|
| 510 | ((getcwd(dwd, AP_MAXPATH)) == NULL) || |
---|
| 511 | ((chdir(cwd)) != 0)) { |
---|
| 512 | log_err("cannot get docroot information (%s)\n", AP_DOC_ROOT); |
---|
| 513 | exit(113); |
---|
| 514 | } |
---|
| 515 | } |
---|
| 516 | |
---|
| 517 | if ((strncmp(cwd, dwd, strlen(dwd))) != 0) { |
---|
| 518 | log_err("command not in docroot (%s/%s)\n", cwd, cmd); |
---|
| 519 | exit(114); |
---|
| 520 | } |
---|
| 521 | |
---|
| 522 | /* |
---|
| 523 | * Stat the cwd and verify it is a directory, or error out. |
---|
| 524 | */ |
---|
| 525 | if (((lstat(cwd, &dir_info)) != 0) || !(S_ISDIR(dir_info.st_mode))) { |
---|
| 526 | log_err("cannot stat directory: (%s)\n", cwd); |
---|
| 527 | exit(115); |
---|
| 528 | } |
---|
| 529 | |
---|
| 530 | /* |
---|
| 531 | * Error out if cwd is writable by others. |
---|
| 532 | */ |
---|
| 533 | if ((dir_info.st_mode & S_IWOTH) || (dir_info.st_mode & S_IWGRP)) { |
---|
| 534 | log_err("directory is writable by others: (%s)\n", cwd); |
---|
| 535 | exit(116); |
---|
| 536 | } |
---|
| 537 | |
---|
| 538 | /* |
---|
| 539 | * Error out if we cannot stat the program. |
---|
| 540 | */ |
---|
[296] | 541 | if (((lstat(cmd, &prg_info)) != 0) || (S_ISLNK(prg_info.st_mode))) { |
---|
[33] | 542 | log_err("cannot stat program: (%s)\n", cmd); |
---|
| 543 | exit(117); |
---|
| 544 | } |
---|
| 545 | |
---|
| 546 | /* |
---|
| 547 | * Error out if the program is writable by others. |
---|
| 548 | */ |
---|
| 549 | if ((prg_info.st_mode & S_IWOTH) || (prg_info.st_mode & S_IWGRP)) { |
---|
| 550 | log_err("file is writable by others: (%s/%s)\n", cwd, cmd); |
---|
| 551 | exit(118); |
---|
| 552 | } |
---|
| 553 | |
---|
| 554 | /* |
---|
| 555 | * Error out if the file is setuid or setgid. |
---|
| 556 | */ |
---|
| 557 | if ((prg_info.st_mode & S_ISUID) || (prg_info.st_mode & S_ISGID)) { |
---|
| 558 | log_err("file is either setuid or setgid: (%s/%s)\n", cwd, cmd); |
---|
| 559 | exit(119); |
---|
| 560 | } |
---|
| 561 | |
---|
| 562 | /* |
---|
| 563 | * Error out if the target name/group is different from |
---|
| 564 | * the name/group of the cwd or the program. |
---|
| 565 | */ |
---|
| 566 | if ((uid != dir_info.st_uid) || |
---|
| 567 | (gid != dir_info.st_gid) || |
---|
| 568 | (uid != prg_info.st_uid) || |
---|
| 569 | (gid != prg_info.st_gid)) { |
---|
| 570 | log_err("target uid/gid (%ld/%ld) mismatch " |
---|
| 571 | "with directory (%ld/%ld) or program (%ld/%ld)\n", |
---|
| 572 | uid, gid, |
---|
| 573 | dir_info.st_uid, dir_info.st_gid, |
---|
| 574 | prg_info.st_uid, prg_info.st_gid); |
---|
| 575 | exit(120); |
---|
| 576 | } |
---|
| 577 | /* |
---|
| 578 | * Error out if the program is not executable for the user. |
---|
| 579 | * Otherwise, she won't find any error in the logs except for |
---|
| 580 | * "[error] Premature end of script headers: ..." |
---|
| 581 | */ |
---|
| 582 | if (!(prg_info.st_mode & S_IXUSR)) { |
---|
| 583 | log_err("file has no execute permission: (%s/%s)\n", cwd, cmd); |
---|
| 584 | exit(121); |
---|
| 585 | } |
---|
| 586 | |
---|
| 587 | #ifdef AP_SUEXEC_UMASK |
---|
| 588 | /* |
---|
| 589 | * umask() uses inverse logic; bits are CLEAR for allowed access. |
---|
| 590 | */ |
---|
| 591 | if ((~AP_SUEXEC_UMASK) & 0022) { |
---|
| 592 | log_err("notice: AP_SUEXEC_UMASK of %03o allows " |
---|
| 593 | "write permission to group and/or other\n", AP_SUEXEC_UMASK); |
---|
| 594 | } |
---|
| 595 | umask(AP_SUEXEC_UMASK); |
---|
| 596 | #endif /* AP_SUEXEC_UMASK */ |
---|
| 597 | |
---|
| 598 | /* |
---|
| 599 | * Be sure to close the log file so the CGI can't |
---|
| 600 | * mess with it. If the exec fails, it will be reopened |
---|
| 601 | * automatically when log_err is called. Note that the log |
---|
| 602 | * might not actually be open if AP_LOG_EXEC isn't defined. |
---|
| 603 | * However, the "log" cell isn't ifdef'd so let's be defensive |
---|
| 604 | * and assume someone might have done something with it |
---|
| 605 | * outside an ifdef'd AP_LOG_EXEC block. |
---|
| 606 | */ |
---|
| 607 | if (log != NULL) { |
---|
| 608 | fclose(log); |
---|
| 609 | log = NULL; |
---|
| 610 | } |
---|
| 611 | |
---|
| 612 | /* |
---|
| 613 | * Execute the command, replacing our image with its own. |
---|
| 614 | */ |
---|
| 615 | #ifdef NEED_HASHBANG_EMUL |
---|
| 616 | /* We need the #! emulation when we want to execute scripts */ |
---|
| 617 | { |
---|
| 618 | extern char **environ; |
---|
| 619 | |
---|
| 620 | ap_execve(cmd, &argv[3], environ); |
---|
| 621 | } |
---|
| 622 | #else /*NEED_HASHBANG_EMUL*/ |
---|
| 623 | execv(cmd, &argv[3]); |
---|
| 624 | #endif /*NEED_HASHBANG_EMUL*/ |
---|
| 625 | |
---|
| 626 | /* |
---|
| 627 | * (I can't help myself...sorry.) |
---|
| 628 | * |
---|
| 629 | * Uh oh. Still here. Where's the kaboom? There was supposed to be an |
---|
| 630 | * EARTH-shattering kaboom! |
---|
| 631 | * |
---|
| 632 | * Oh well, log the failure and error out. |
---|
| 633 | */ |
---|
| 634 | log_err("(%d)%s: exec failed (%s)\n", errno, strerror(errno), cmd); |
---|
| 635 | exit(255); |
---|
| 636 | } |
---|