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