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 "apr_arch_threadproc.h" 18#include "apr_strings.h" 19#include "apr_portable.h" 20#include "apr_signal.h" 21#include "apr_random.h" 22 23/* Heavy on no'ops, here's what we want to pass if there is APR_NO_FILE 24 * requested for a specific child handle; 25 */ 26static apr_file_t no_file = { NULL, -1, }; 27 28APR_DECLARE(apr_status_t) apr_procattr_create(apr_procattr_t **new, 29 apr_pool_t *pool) 30{ 31 (*new) = (apr_procattr_t *)apr_pcalloc(pool, sizeof(apr_procattr_t)); 32 33 if ((*new) == NULL) { 34 return APR_ENOMEM; 35 } 36 (*new)->pool = pool; 37 (*new)->cmdtype = APR_PROGRAM; 38 (*new)->uid = (*new)->gid = -1; 39 return APR_SUCCESS; 40} 41 42APR_DECLARE(apr_status_t) apr_procattr_io_set(apr_procattr_t *attr, 43 apr_int32_t in, 44 apr_int32_t out, 45 apr_int32_t err) 46{ 47 apr_status_t rv; 48 49 if ((in != APR_NO_PIPE) && (in != APR_NO_FILE)) { 50 /* APR_CHILD_BLOCK maps to APR_WRITE_BLOCK, while 51 * APR_PARENT_BLOCK maps to APR_READ_BLOCK, so transpose 52 * the CHILD/PARENT blocking flags for the stdin pipe. 53 * stdout/stderr map to the correct mode by default. 54 */ 55 if (in == APR_CHILD_BLOCK) 56 in = APR_READ_BLOCK; 57 else if (in == APR_PARENT_BLOCK) 58 in = APR_WRITE_BLOCK; 59 60 if ((rv = apr_file_pipe_create_ex(&attr->child_in, &attr->parent_in, 61 in, attr->pool)) == APR_SUCCESS) 62 rv = apr_file_inherit_unset(attr->parent_in); 63 if (rv != APR_SUCCESS) 64 return rv; 65 } 66 else if (in == APR_NO_FILE) 67 attr->child_in = &no_file; 68 69 if ((out != APR_NO_PIPE) && (out != APR_NO_FILE)) { 70 if ((rv = apr_file_pipe_create_ex(&attr->parent_out, &attr->child_out, 71 out, attr->pool)) == APR_SUCCESS) 72 rv = apr_file_inherit_unset(attr->parent_out); 73 if (rv != APR_SUCCESS) 74 return rv; 75 } 76 else if (out == APR_NO_FILE) 77 attr->child_out = &no_file; 78 79 if ((err != APR_NO_PIPE) && (err != APR_NO_FILE)) { 80 if ((rv = apr_file_pipe_create_ex(&attr->parent_err, &attr->child_err, 81 err, attr->pool)) == APR_SUCCESS) 82 rv = apr_file_inherit_unset(attr->parent_err); 83 if (rv != APR_SUCCESS) 84 return rv; 85 } 86 else if (err == APR_NO_FILE) 87 attr->child_err = &no_file; 88 89 return APR_SUCCESS; 90} 91 92 93APR_DECLARE(apr_status_t) apr_procattr_child_in_set(apr_procattr_t *attr, 94 apr_file_t *child_in, 95 apr_file_t *parent_in) 96{ 97 apr_status_t rv = APR_SUCCESS; 98 99 if (attr->child_in == NULL && attr->parent_in == NULL 100 && child_in == NULL && parent_in == NULL) 101 if ((rv = apr_file_pipe_create(&attr->child_in, &attr->parent_in, 102 attr->pool)) == APR_SUCCESS) 103 rv = apr_file_inherit_unset(attr->parent_in); 104 105 if (child_in != NULL && rv == APR_SUCCESS) { 106 if (attr->child_in && (attr->child_in->filedes != -1)) 107 rv = apr_file_dup2(attr->child_in, child_in, attr->pool); 108 else { 109 attr->child_in = NULL; 110 if ((rv = apr_file_dup(&attr->child_in, child_in, attr->pool)) 111 == APR_SUCCESS) 112 rv = apr_file_inherit_set(attr->child_in); 113 } 114 } 115 116 if (parent_in != NULL && rv == APR_SUCCESS) { 117 if (attr->parent_in) 118 rv = apr_file_dup2(attr->parent_in, parent_in, attr->pool); 119 else 120 rv = apr_file_dup(&attr->parent_in, parent_in, attr->pool); 121 } 122 123 return rv; 124} 125 126 127APR_DECLARE(apr_status_t) apr_procattr_child_out_set(apr_procattr_t *attr, 128 apr_file_t *child_out, 129 apr_file_t *parent_out) 130{ 131 apr_status_t rv = APR_SUCCESS; 132 133 if (attr->child_out == NULL && attr->parent_out == NULL 134 && child_out == NULL && parent_out == NULL) 135 if ((rv = apr_file_pipe_create(&attr->parent_out, &attr->child_out, 136 attr->pool)) == APR_SUCCESS) 137 rv = apr_file_inherit_unset(attr->parent_out); 138 139 if (child_out != NULL && rv == APR_SUCCESS) { 140 if (attr->child_out && (attr->child_out->filedes != -1)) 141 rv = apr_file_dup2(attr->child_out, child_out, attr->pool); 142 else { 143 attr->child_out = NULL; 144 if ((rv = apr_file_dup(&attr->child_out, child_out, attr->pool)) 145 == APR_SUCCESS) 146 rv = apr_file_inherit_set(attr->child_out); 147 } 148 } 149 150 if (parent_out != NULL && rv == APR_SUCCESS) { 151 if (attr->parent_out) 152 rv = apr_file_dup2(attr->parent_out, parent_out, attr->pool); 153 else 154 rv = apr_file_dup(&attr->parent_out, parent_out, attr->pool); 155 } 156 157 return rv; 158} 159 160 161APR_DECLARE(apr_status_t) apr_procattr_child_err_set(apr_procattr_t *attr, 162 apr_file_t *child_err, 163 apr_file_t *parent_err) 164{ 165 apr_status_t rv = APR_SUCCESS; 166 167 if (attr->child_err == NULL && attr->parent_err == NULL 168 && child_err == NULL && parent_err == NULL) 169 if ((rv = apr_file_pipe_create(&attr->parent_err, &attr->child_err, 170 attr->pool)) == APR_SUCCESS) 171 rv = apr_file_inherit_unset(attr->parent_err); 172 173 if (child_err != NULL && rv == APR_SUCCESS) { 174 if (attr->child_err && (attr->child_err->filedes != -1)) 175 rv = apr_file_dup2(attr->child_err, child_err, attr->pool); 176 else { 177 attr->child_err = NULL; 178 if ((rv = apr_file_dup(&attr->child_err, child_err, attr->pool)) 179 == APR_SUCCESS) 180 rv = apr_file_inherit_set(attr->child_err); 181 } 182 } 183 if (parent_err != NULL && rv == APR_SUCCESS) { 184 if (attr->parent_err) 185 rv = apr_file_dup2(attr->parent_err, parent_err, attr->pool); 186 else 187 rv = apr_file_dup(&attr->parent_err, parent_err, attr->pool); 188 } 189 190 return rv; 191} 192 193 194APR_DECLARE(apr_status_t) apr_procattr_dir_set(apr_procattr_t *attr, 195 const char *dir) 196{ 197 attr->currdir = apr_pstrdup(attr->pool, dir); 198 if (attr->currdir) { 199 return APR_SUCCESS; 200 } 201 202 return APR_ENOMEM; 203} 204 205APR_DECLARE(apr_status_t) apr_procattr_cmdtype_set(apr_procattr_t *attr, 206 apr_cmdtype_e cmd) 207{ 208 attr->cmdtype = cmd; 209 return APR_SUCCESS; 210} 211 212APR_DECLARE(apr_status_t) apr_procattr_detach_set(apr_procattr_t *attr, 213 apr_int32_t detach) 214{ 215 attr->detached = detach; 216 return APR_SUCCESS; 217} 218 219APR_DECLARE(apr_status_t) apr_proc_fork(apr_proc_t *proc, apr_pool_t *pool) 220{ 221 int pid; 222 223 memset(proc, 0, sizeof(apr_proc_t)); 224 225 if ((pid = fork()) < 0) { 226 return errno; 227 } 228 else if (pid == 0) { 229 proc->pid = getpid(); 230 231 apr_random_after_fork(proc); 232 233 return APR_INCHILD; 234 } 235 236 proc->pid = pid; 237 238 return APR_INPARENT; 239} 240 241static apr_status_t limit_proc(apr_procattr_t *attr) 242{ 243#if APR_HAVE_STRUCT_RLIMIT && APR_HAVE_SETRLIMIT 244#ifdef RLIMIT_CPU 245 if (attr->limit_cpu != NULL) { 246 if ((setrlimit(RLIMIT_CPU, attr->limit_cpu)) != 0) { 247 return errno; 248 } 249 } 250#endif 251#ifdef RLIMIT_NPROC 252 if (attr->limit_nproc != NULL) { 253 if ((setrlimit(RLIMIT_NPROC, attr->limit_nproc)) != 0) { 254 return errno; 255 } 256 } 257#endif 258#ifdef RLIMIT_NOFILE 259 if (attr->limit_nofile != NULL) { 260 if ((setrlimit(RLIMIT_NOFILE, attr->limit_nofile)) != 0) { 261 return errno; 262 } 263 } 264#endif 265#if defined(RLIMIT_AS) 266 if (attr->limit_mem != NULL) { 267 if ((setrlimit(RLIMIT_AS, attr->limit_mem)) != 0) { 268 return errno; 269 } 270 } 271#elif defined(RLIMIT_DATA) 272 if (attr->limit_mem != NULL) { 273 if ((setrlimit(RLIMIT_DATA, attr->limit_mem)) != 0) { 274 return errno; 275 } 276 } 277#elif defined(RLIMIT_VMEM) 278 if (attr->limit_mem != NULL) { 279 if ((setrlimit(RLIMIT_VMEM, attr->limit_mem)) != 0) { 280 return errno; 281 } 282 } 283#endif 284#else 285 /* 286 * Maybe make a note in error_log that setrlimit isn't supported?? 287 */ 288 289#endif 290 return APR_SUCCESS; 291} 292 293APR_DECLARE(apr_status_t) apr_procattr_child_errfn_set(apr_procattr_t *attr, 294 apr_child_errfn_t *errfn) 295{ 296 attr->errfn = errfn; 297 return APR_SUCCESS; 298} 299 300APR_DECLARE(apr_status_t) apr_procattr_error_check_set(apr_procattr_t *attr, 301 apr_int32_t chk) 302{ 303 attr->errchk = chk; 304 return APR_SUCCESS; 305} 306 307APR_DECLARE(apr_status_t) apr_procattr_addrspace_set(apr_procattr_t *attr, 308 apr_int32_t addrspace) 309{ 310 /* won't ever be used on this platform, so don't save the flag */ 311 return APR_SUCCESS; 312} 313 314APR_DECLARE(apr_status_t) apr_procattr_user_set(apr_procattr_t *attr, 315 const char *username, 316 const char *password) 317{ 318 apr_status_t rv; 319 apr_gid_t gid; 320 321 if ((rv = apr_uid_get(&attr->uid, &gid, username, 322 attr->pool)) != APR_SUCCESS) { 323 attr->uid = -1; 324 return rv; 325 } 326 327 /* Use default user group if not already set */ 328 if (attr->gid == -1) { 329 attr->gid = gid; 330 } 331 return APR_SUCCESS; 332} 333 334APR_DECLARE(apr_status_t) apr_procattr_group_set(apr_procattr_t *attr, 335 const char *groupname) 336{ 337 apr_status_t rv; 338 339 if ((rv = apr_gid_get(&attr->gid, groupname, attr->pool)) != APR_SUCCESS) 340 attr->gid = -1; 341 return rv; 342} 343 344APR_DECLARE(apr_status_t) apr_proc_create(apr_proc_t *new, 345 const char *progname, 346 const char * const *args, 347 const char * const *env, 348 apr_procattr_t *attr, 349 apr_pool_t *pool) 350{ 351 int i; 352 const char * const empty_envp[] = {NULL}; 353 354 if (!env) { /* Specs require an empty array instead of NULL; 355 * Purify will trigger a failure, even if many 356 * implementations don't. 357 */ 358 env = empty_envp; 359 } 360 361 new->in = attr->parent_in; 362 new->err = attr->parent_err; 363 new->out = attr->parent_out; 364 365 if (attr->errchk) { 366 if (attr->currdir) { 367 if (access(attr->currdir, X_OK) == -1) { 368 /* chdir() in child wouldn't have worked */ 369 return errno; 370 } 371 } 372 373 if (attr->cmdtype == APR_PROGRAM || 374 attr->cmdtype == APR_PROGRAM_ENV || 375 *progname == '/') { 376 /* for both of these values of cmdtype, caller must pass 377 * full path, so it is easy to check; 378 * caller can choose to pass full path for other 379 * values of cmdtype 380 */ 381 if (access(progname, X_OK) == -1) { 382 /* exec*() in child wouldn't have worked */ 383 return errno; 384 } 385 } 386 else { 387 /* todo: search PATH for progname then try to access it */ 388 } 389 } 390 391 if ((new->pid = fork()) < 0) { 392 return errno; 393 } 394 else if (new->pid == 0) { 395 /* child process */ 396 397 /* 398 * If we do exec cleanup before the dup2() calls to set up pipes 399 * on 0-2, we accidentally close the pipes used by programs like 400 * mod_cgid. 401 * 402 * If we do exec cleanup after the dup2() calls, cleanup can accidentally 403 * close our pipes which replaced any files which previously had 404 * descriptors 0-2. 405 * 406 * The solution is to kill the cleanup for the pipes, then do 407 * exec cleanup, then do the dup2() calls. 408 */ 409 410 if (attr->child_in) { 411 apr_pool_cleanup_kill(apr_file_pool_get(attr->child_in), 412 attr->child_in, apr_unix_file_cleanup); 413 } 414 415 if (attr->child_out) { 416 apr_pool_cleanup_kill(apr_file_pool_get(attr->child_out), 417 attr->child_out, apr_unix_file_cleanup); 418 } 419 420 if (attr->child_err) { 421 apr_pool_cleanup_kill(apr_file_pool_get(attr->child_err), 422 attr->child_err, apr_unix_file_cleanup); 423 } 424 425 apr_pool_cleanup_for_exec(); 426 427 if ((attr->child_in) && (attr->child_in->filedes == -1)) { 428 close(STDIN_FILENO); 429 } 430 else if (attr->child_in && 431 attr->child_in->filedes != STDIN_FILENO) { 432 dup2(attr->child_in->filedes, STDIN_FILENO); 433 apr_file_close(attr->child_in); 434 } 435 436 if ((attr->child_out) && (attr->child_out->filedes == -1)) { 437 close(STDOUT_FILENO); 438 } 439 else if (attr->child_out && 440 attr->child_out->filedes != STDOUT_FILENO) { 441 dup2(attr->child_out->filedes, STDOUT_FILENO); 442 apr_file_close(attr->child_out); 443 } 444 445 if ((attr->child_err) && (attr->child_err->filedes == -1)) { 446 close(STDERR_FILENO); 447 } 448 else if (attr->child_err && 449 attr->child_err->filedes != STDERR_FILENO) { 450 dup2(attr->child_err->filedes, STDERR_FILENO); 451 apr_file_close(attr->child_err); 452 } 453 454 apr_signal(SIGCHLD, SIG_DFL); /* not sure if this is needed or not */ 455 456 if (attr->currdir != NULL) { 457 if (chdir(attr->currdir) == -1) { 458 if (attr->errfn) { 459 attr->errfn(pool, errno, "change of working directory failed"); 460 } 461 _exit(-1); /* We have big problems, the child should exit. */ 462 } 463 } 464 465 /* Only try to switch if we are running as root */ 466 if (attr->gid != -1 && !geteuid()) { 467 if (setgid(attr->gid)) { 468 if (attr->errfn) { 469 attr->errfn(pool, errno, "setting of group failed"); 470 } 471 _exit(-1); /* We have big problems, the child should exit. */ 472 } 473 } 474 475 if (attr->uid != -1 && !geteuid()) { 476 if (setuid(attr->uid)) { 477 if (attr->errfn) { 478 attr->errfn(pool, errno, "setting of user failed"); 479 } 480 _exit(-1); /* We have big problems, the child should exit. */ 481 } 482 } 483 484 if (limit_proc(attr) != APR_SUCCESS) { 485 if (attr->errfn) { 486 attr->errfn(pool, errno, "setting of resource limits failed"); 487 } 488 _exit(-1); /* We have big problems, the child should exit. */ 489 } 490 491 if (attr->cmdtype == APR_SHELLCMD || 492 attr->cmdtype == APR_SHELLCMD_ENV) { 493 int onearg_len = 0; 494 const char *newargs[4]; 495 496 newargs[0] = SHELL_PATH; 497 newargs[1] = "-c"; 498 499 i = 0; 500 while (args[i]) { 501 onearg_len += strlen(args[i]); 502 onearg_len++; /* for space delimiter */ 503 i++; 504 } 505 506 switch(i) { 507 case 0: 508 /* bad parameters; we're doomed */ 509 break; 510 case 1: 511 /* no args, or caller already built a single string from 512 * progname and args 513 */ 514 newargs[2] = args[0]; 515 break; 516 default: 517 { 518 char *ch, *onearg; 519 520 ch = onearg = apr_palloc(pool, onearg_len); 521 i = 0; 522 while (args[i]) { 523 size_t len = strlen(args[i]); 524 525 memcpy(ch, args[i], len); 526 ch += len; 527 *ch = ' '; 528 ++ch; 529 ++i; 530 } 531 --ch; /* back up to trailing blank */ 532 *ch = '\0'; 533 newargs[2] = onearg; 534 } 535 } 536 537 newargs[3] = NULL; 538 539 if (attr->detached) { 540 apr_proc_detach(APR_PROC_DETACH_DAEMONIZE); 541 } 542 543 if (attr->cmdtype == APR_SHELLCMD) { 544 execve(SHELL_PATH, (char * const *) newargs, (char * const *)env); 545 } 546 else { 547 execv(SHELL_PATH, (char * const *)newargs); 548 } 549 } 550 else if (attr->cmdtype == APR_PROGRAM) { 551 if (attr->detached) { 552 apr_proc_detach(APR_PROC_DETACH_DAEMONIZE); 553 } 554 555 execve(progname, (char * const *)args, (char * const *)env); 556 } 557 else if (attr->cmdtype == APR_PROGRAM_ENV) { 558 if (attr->detached) { 559 apr_proc_detach(APR_PROC_DETACH_DAEMONIZE); 560 } 561 562 execv(progname, (char * const *)args); 563 } 564 else { 565 /* APR_PROGRAM_PATH */ 566 if (attr->detached) { 567 apr_proc_detach(APR_PROC_DETACH_DAEMONIZE); 568 } 569 570 execvp(progname, (char * const *)args); 571 } 572 if (attr->errfn) { 573 char *desc; 574 575 desc = apr_psprintf(pool, "exec of '%s' failed", 576 progname); 577 attr->errfn(pool, errno, desc); 578 } 579 580 _exit(-1); /* if we get here, there is a problem, so exit with an 581 * error code. */ 582 } 583 584 /* Parent process */ 585 if (attr->child_in && (attr->child_in->filedes != -1)) { 586 apr_file_close(attr->child_in); 587 } 588 589 if (attr->child_out && (attr->child_out->filedes != -1)) { 590 apr_file_close(attr->child_out); 591 } 592 593 if (attr->child_err && (attr->child_err->filedes != -1)) { 594 apr_file_close(attr->child_err); 595 } 596 597 return APR_SUCCESS; 598} 599 600APR_DECLARE(apr_status_t) apr_proc_wait_all_procs(apr_proc_t *proc, 601 int *exitcode, 602 apr_exit_why_e *exitwhy, 603 apr_wait_how_e waithow, 604 apr_pool_t *p) 605{ 606 proc->pid = -1; 607 return apr_proc_wait(proc, exitcode, exitwhy, waithow); 608} 609 610APR_DECLARE(apr_status_t) apr_proc_wait(apr_proc_t *proc, 611 int *exitcode, apr_exit_why_e *exitwhy, 612 apr_wait_how_e waithow) 613{ 614 pid_t pstatus; 615 int waitpid_options = WUNTRACED; 616 int exit_int; 617 int ignore; 618 apr_exit_why_e ignorewhy; 619 620 if (exitcode == NULL) { 621 exitcode = &ignore; 622 } 623 624 if (exitwhy == NULL) { 625 exitwhy = &ignorewhy; 626 } 627 628 if (waithow != APR_WAIT) { 629 waitpid_options |= WNOHANG; 630 } 631 632 do { 633 pstatus = waitpid(proc->pid, &exit_int, waitpid_options); 634 } while (pstatus < 0 && errno == EINTR); 635 636 if (pstatus > 0) { 637 proc->pid = pstatus; 638 639 if (WIFEXITED(exit_int)) { 640 *exitwhy = APR_PROC_EXIT; 641 *exitcode = WEXITSTATUS(exit_int); 642 } 643 else if (WIFSIGNALED(exit_int)) { 644 *exitwhy = APR_PROC_SIGNAL; 645 646#ifdef WCOREDUMP 647 if (WCOREDUMP(exit_int)) { 648 *exitwhy |= APR_PROC_SIGNAL_CORE; 649 } 650#endif 651 652 *exitcode = WTERMSIG(exit_int); 653 } 654 else { 655 /* unexpected condition */ 656 return APR_EGENERAL; 657 } 658 659 return APR_CHILD_DONE; 660 } 661 else if (pstatus == 0) { 662 return APR_CHILD_NOTDONE; 663 } 664 665 return errno; 666} 667 668#if APR_HAVE_STRUCT_RLIMIT 669APR_DECLARE(apr_status_t) apr_procattr_limit_set(apr_procattr_t *attr, 670 apr_int32_t what, 671 struct rlimit *limit) 672{ 673 switch(what) { 674 case APR_LIMIT_CPU: 675#ifdef RLIMIT_CPU 676 attr->limit_cpu = limit; 677 break; 678#else 679 return APR_ENOTIMPL; 680#endif 681 682 case APR_LIMIT_MEM: 683#if defined(RLIMIT_DATA) || defined(RLIMIT_VMEM) || defined(RLIMIT_AS) 684 attr->limit_mem = limit; 685 break; 686#else 687 return APR_ENOTIMPL; 688#endif 689 690 case APR_LIMIT_NPROC: 691#ifdef RLIMIT_NPROC 692 attr->limit_nproc = limit; 693 break; 694#else 695 return APR_ENOTIMPL; 696#endif 697 698 case APR_LIMIT_NOFILE: 699#ifdef RLIMIT_NOFILE 700 attr->limit_nofile = limit; 701 break; 702#else 703 return APR_ENOTIMPL; 704#endif 705 706 } 707 708 return APR_SUCCESS; 709} 710#endif /* APR_HAVE_STRUCT_RLIMIT */ 711 712