1/* 2 * "$Id: process.c 11093 2013-07-03 20:48:42Z msweet $" 3 * 4 * Process management routines for the CUPS scheduler. 5 * 6 * Copyright 2007-2012 by Apple Inc. 7 * Copyright 1997-2007 by Easy Software Products, all rights reserved. 8 * 9 * These coded instructions, statements, and computer programs are the 10 * property of Apple Inc. and are protected by Federal copyright 11 * law. Distribution and use rights are outlined in the file "LICENSE.txt" 12 * which should have been included with this file. If this file is 13 * file is missing or damaged, see the license at "http://www.cups.org/". 14 * 15 * Contents: 16 * 17 * cupsdCreateProfile() - Create an execution profile for a subprocess. 18 * cupsdDestroyProfile() - Delete an execution profile. 19 * cupsdEndProcess() - End a process. 20 * cupsdFinishProcess() - Finish a process and get its name. 21 * cupsdStartProcess() - Start a process. 22 * compare_procs() - Compare two processes. 23 * cupsd_requote() - Make a regular-expression version of a string. 24 */ 25 26/* 27 * Include necessary headers... 28 */ 29 30#include "cupsd.h" 31#include <grp.h> 32#ifdef __APPLE__ 33# include <libgen.h> 34#endif /* __APPLE__ */ 35 36 37/* 38 * Process structure... 39 */ 40 41typedef struct 42{ 43 int pid, /* Process ID */ 44 job_id; /* Job associated with process */ 45 char name[1]; /* Name of process */ 46} cupsd_proc_t; 47 48 49/* 50 * Local globals... 51 */ 52 53static cups_array_t *process_array = NULL; 54 55 56/* 57 * Local functions... 58 */ 59 60static int compare_procs(cupsd_proc_t *a, cupsd_proc_t *b); 61#ifdef HAVE_SANDBOX_H 62static char *cupsd_requote(char *dst, const char *src, size_t dstsize); 63#endif /* HAVE_SANDBOX_H */ 64 65 66/* 67 * 'cupsdCreateProfile()' - Create an execution profile for a subprocess. 68 */ 69 70void * /* O - Profile or NULL on error */ 71cupsdCreateProfile(int job_id) /* I - Job ID or 0 for none */ 72{ 73#ifdef HAVE_SANDBOX_H 74 cups_file_t *fp; /* File pointer */ 75 char profile[1024], /* File containing the profile */ 76 cache[1024], /* Quoted CacheDir */ 77 request[1024], /* Quoted RequestRoot */ 78 root[1024], /* Quoted ServerRoot */ 79 temp[1024]; /* Quoted TempDir */ 80 const char *nodebug; /* " (with no-log)" for no debug */ 81 82 83 if (!UseProfiles) 84 { 85 /* 86 * Only use sandbox profiles as root... 87 */ 88 89 cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdCreateProfile(job_id=%d) = NULL", 90 job_id); 91 92 return (NULL); 93 } 94 95 if ((fp = cupsTempFile2(profile, sizeof(profile))) == NULL) 96 { 97 cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdCreateProfile(job_id=%d) = NULL", 98 job_id); 99 cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to create security profile: %s", 100 strerror(errno)); 101 return (NULL); 102 } 103 104 fchown(cupsFileNumber(fp), RunUser, Group); 105 fchmod(cupsFileNumber(fp), 0640); 106 107 cupsd_requote(cache, CacheDir, sizeof(cache)); 108 cupsd_requote(request, RequestRoot, sizeof(request)); 109 cupsd_requote(root, ServerRoot, sizeof(root)); 110 cupsd_requote(temp, TempDir, sizeof(temp)); 111 112 nodebug = LogLevel < CUPSD_LOG_DEBUG ? " (with no-log)" : ""; 113 114 cupsFilePuts(fp, "(version 1)\n"); 115 cupsFilePuts(fp, "(allow default)\n"); 116 cupsFilePrintf(fp, 117 "(deny file-write* file-read-data file-read-metadata\n" 118 " (regex" 119 " #\"^%s$\"" /* RequestRoot */ 120 " #\"^%s/\"" /* RequestRoot/... */ 121 ")%s)\n", 122 request, request, nodebug); 123 if (!RunUser) 124 cupsFilePrintf(fp, 125 "(deny file-write* file-read-data file-read-metadata\n" 126 " (regex" 127 " #\"^/Users$\"" 128 " #\"^/Users/\"" 129 ")%s)\n", nodebug); 130 cupsFilePrintf(fp, 131 "(deny file-write*\n" 132 " (regex" 133 " #\"^%s$\"" /* ServerRoot */ 134 " #\"^%s/\"" /* ServerRoot/... */ 135 " #\"^/private/etc$\"" 136 " #\"^/private/etc/\"" 137 " #\"^/usr/local/etc$\"" 138 " #\"^/usr/local/etc/\"" 139 " #\"^/Library$\"" 140 " #\"^/Library/\"" 141 " #\"^/System$\"" 142 " #\"^/System/\"" 143 ")%s)\n", 144 root, root, nodebug); 145 /* Specifically allow applications to stat RequestRoot */ 146 cupsFilePrintf(fp, 147 "(allow file-read-metadata\n" 148 " (regex" 149 " #\"^%s$\"" /* RequestRoot */ 150 "))\n", 151 request); 152 cupsFilePrintf(fp, 153 "(allow file-write* file-read-data file-read-metadata\n" 154 " (regex" 155 " #\"^%s$\"" /* TempDir */ 156 " #\"^%s/\"" /* TempDir/... */ 157 " #\"^%s$\"" /* CacheDir */ 158 " #\"^%s/\"" /* CacheDir/... */ 159 " #\"^%s/Library$\"" /* RequestRoot/Library */ 160 " #\"^%s/Library/\"" /* RequestRoot/Library/... */ 161 " #\"^/Library/Application Support/\"" 162 " #\"^/Library/Caches/\"" 163 " #\"^/Library/Preferences/\"" 164 " #\"^/Library/Printers/.*/\"" 165 " #\"^/Users/Shared/\"" 166 "))\n", 167 temp, temp, cache, cache, request, request); 168 cupsFilePrintf(fp, 169 "(deny file-write*\n" 170 " (regex" 171 " #\"^/Library/Printers/PPDs$\"" 172 " #\"^/Library/Printers/PPDs/\"" 173 " #\"^/Library/Printers/PPD Plugins$\"" 174 " #\"^/Library/Printers/PPD Plugins/\"" 175 ")%s)\n", nodebug); 176 if (job_id) 177 { 178 /* 179 * Allow job filters to read the spool file(s)... 180 */ 181 182 cupsFilePrintf(fp, 183 "(allow file-read-data file-read-metadata\n" 184 " (regex #\"^%s/([ac]%05d|d%05d-[0-9][0-9][0-9])$\"))\n", 185 request, job_id, job_id); 186 } 187 else 188 { 189 /* 190 * Allow email notifications from notifiers... 191 */ 192 193 cupsFilePuts(fp, 194 "(allow process-exec\n" 195 " (literal \"/usr/sbin/sendmail\")\n" 196 " (with no-sandbox)\n" 197 ")\n"); 198 } 199 200 cupsFileClose(fp); 201 202 cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdCreateProfile(job_id=%d) = \"%s\"", 203 job_id, profile); 204 return ((void *)strdup(profile)); 205 206#else 207 cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdCreateProfile(job_id=%d) = NULL", 208 job_id); 209 210 return (NULL); 211#endif /* HAVE_SANDBOX_H */ 212} 213 214 215/* 216 * 'cupsdDestroyProfile()' - Delete an execution profile. 217 */ 218 219void 220cupsdDestroyProfile(void *profile) /* I - Profile */ 221{ 222 cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdDeleteProfile(profile=\"%s\")", 223 profile ? (char *)profile : "(null)"); 224 225#ifdef HAVE_SANDBOX_H 226 if (profile) 227 { 228 unlink((char *)profile); 229 free(profile); 230 } 231#endif /* HAVE_SANDBOX_H */ 232} 233 234 235/* 236 * 'cupsdEndProcess()' - End a process. 237 */ 238 239int /* O - 0 on success, -1 on failure */ 240cupsdEndProcess(int pid, /* I - Process ID */ 241 int force) /* I - Force child to die */ 242{ 243 cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdEndProcess(pid=%d, force=%d)", pid, 244 force); 245 246 if (!pid) 247 return (0); 248 249 if (!RunUser) 250 { 251 /* 252 * When running as root, cupsd puts child processes in their own process 253 * group. Using "-pid" sends a signal to all processes in the group. 254 */ 255 256 pid = -pid; 257 } 258 259 if (force) 260 return (kill(pid, SIGKILL)); 261 else 262 return (kill(pid, SIGTERM)); 263} 264 265 266/* 267 * 'cupsdFinishProcess()' - Finish a process and get its name. 268 */ 269 270const char * /* O - Process name */ 271cupsdFinishProcess(int pid, /* I - Process ID */ 272 char *name, /* I - Name buffer */ 273 int namelen, /* I - Size of name buffer */ 274 int *job_id) /* O - Job ID pointer or NULL */ 275{ 276 cupsd_proc_t key, /* Search key */ 277 *proc; /* Matching process */ 278 279 280 key.pid = pid; 281 282 if ((proc = (cupsd_proc_t *)cupsArrayFind(process_array, &key)) != NULL) 283 { 284 if (job_id) 285 *job_id = proc->job_id; 286 287 strlcpy(name, proc->name, namelen); 288 cupsArrayRemove(process_array, proc); 289 free(proc); 290 } 291 else 292 { 293 if (job_id) 294 *job_id = 0; 295 296 strlcpy(name, "unknown", namelen); 297 } 298 299 cupsdLogMessage(CUPSD_LOG_DEBUG2, 300 "cupsdFinishProcess(pid=%d, name=%p, namelen=%d, " 301 "job_id=%p(%d)) = \"%s\"", pid, name, namelen, job_id, 302 job_id ? *job_id : 0, name); 303 304 return (name); 305} 306 307 308/* 309 * 'cupsdStartProcess()' - Start a process. 310 */ 311 312int /* O - Process ID or 0 */ 313cupsdStartProcess( 314 const char *command, /* I - Full path to command */ 315 char *argv[], /* I - Command-line arguments */ 316 char *envp[], /* I - Environment */ 317 int infd, /* I - Standard input file descriptor */ 318 int outfd, /* I - Standard output file descriptor */ 319 int errfd, /* I - Standard error file descriptor */ 320 int backfd, /* I - Backchannel file descriptor */ 321 int sidefd, /* I - Sidechannel file descriptor */ 322 int root, /* I - Run as root? */ 323 void *profile, /* I - Security profile to use */ 324 cupsd_job_t *job, /* I - Job associated with process */ 325 int *pid) /* O - Process ID */ 326{ 327 int i; /* Looping var */ 328 const char *exec_path = command; /* Command to be exec'd */ 329 char *real_argv[103], /* Real command-line arguments */ 330 cups_exec[1024]; /* Path to "cups-exec" program */ 331 int user; /* Command UID */ 332 cupsd_proc_t *proc; /* New process record */ 333#if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET) 334 struct sigaction action; /* POSIX signal handler */ 335#endif /* HAVE_SIGACTION && !HAVE_SIGSET */ 336#if defined(__APPLE__) 337 char processPath[1024], /* CFProcessPath environment variable */ 338 linkpath[1024]; /* Link path for symlinks... */ 339 int linkbytes; /* Bytes for link path */ 340#endif /* __APPLE__ */ 341 342 343 *pid = 0; 344 345 /* 346 * Figure out the UID for the child process... 347 */ 348 349 if (RunUser) 350 user = RunUser; 351 else if (root) 352 user = 0; 353 else 354 user = User; 355 356 /* 357 * Check the permissions of the command we are running... 358 */ 359 360 if (_cupsFileCheck(command, _CUPS_FILE_CHECK_PROGRAM, !RunUser, 361 cupsdLogFCMessage, job ? job->printer : NULL)) 362 return (0); 363 364#if defined(__APPLE__) 365 if (envp) 366 { 367 /* 368 * Add special voodoo magic for OS X - this allows OS X programs to access 369 * their bundle resources properly... 370 */ 371 372 if ((linkbytes = readlink(command, linkpath, sizeof(linkpath) - 1)) > 0) 373 { 374 /* 375 * Yes, this is a symlink to the actual program, nul-terminate and 376 * use it... 377 */ 378 379 linkpath[linkbytes] = '\0'; 380 381 if (linkpath[0] == '/') 382 snprintf(processPath, sizeof(processPath), "CFProcessPath=%s", 383 linkpath); 384 else 385 snprintf(processPath, sizeof(processPath), "CFProcessPath=%s/%s", 386 dirname((char *)command), linkpath); 387 } 388 else 389 snprintf(processPath, sizeof(processPath), "CFProcessPath=%s", command); 390 391 envp[0] = processPath; /* Replace <CFProcessPath> string */ 392 } 393#endif /* __APPLE__ */ 394 395 /* 396 * Use helper program when we have a sandbox profile... 397 */ 398 399 if (profile) 400 { 401 snprintf(cups_exec, sizeof(cups_exec), "%s/daemon/cups-exec", ServerBin); 402 403 real_argv[0] = cups_exec; 404 real_argv[1] = profile; 405 real_argv[2] = (char *)command; 406 407 for (i = 0; 408 i < (int)(sizeof(real_argv) / sizeof(real_argv[0]) - 4) && argv[i]; 409 i ++) 410 real_argv[i + 3] = argv[i]; 411 412 real_argv[i + 3] = NULL; 413 414 argv = real_argv; 415 exec_path = cups_exec; 416 } 417 418 /* 419 * Block signals before forking... 420 */ 421 422 cupsdHoldSignals(); 423 424 if ((*pid = fork()) == 0) 425 { 426 /* 427 * Child process goes here; update stderr as needed... 428 */ 429 430 if (errfd != 2) 431 { 432 if (errfd < 0) 433 errfd = open("/dev/null", O_WRONLY); 434 435 if (errfd != 2) 436 { 437 dup2(errfd, 2); 438 close(errfd); 439 } 440 } 441 442 /* 443 * Put this process in its own process group so that we can kill any child 444 * processes it creates. 445 */ 446 447#ifdef HAVE_SETPGID 448 if (!RunUser && setpgid(0, 0)) 449 exit(errno + 100); 450#else 451 if (!RunUser && setpgrp()) 452 exit(errno + 100); 453#endif /* HAVE_SETPGID */ 454 455 /* 456 * Update the remaining file descriptors as needed... 457 */ 458 459 if (infd != 0) 460 { 461 if (infd < 0) 462 infd = open("/dev/null", O_RDONLY); 463 464 if (infd != 0) 465 { 466 dup2(infd, 0); 467 close(infd); 468 } 469 } 470 471 if (outfd != 1) 472 { 473 if (outfd < 0) 474 outfd = open("/dev/null", O_WRONLY); 475 476 if (outfd != 1) 477 { 478 dup2(outfd, 1); 479 close(outfd); 480 } 481 } 482 483 if (backfd != 3 && backfd >= 0) 484 { 485 dup2(backfd, 3); 486 close(backfd); 487 fcntl(3, F_SETFL, O_NDELAY); 488 } 489 490 if (sidefd != 4 && sidefd >= 0) 491 { 492 dup2(sidefd, 4); 493 close(sidefd); 494 fcntl(4, F_SETFL, O_NDELAY); 495 } 496 497 /* 498 * Change the priority of the process based on the FilterNice setting. 499 * (this is not done for root processes...) 500 */ 501 502 if (!root) 503 nice(FilterNice); 504 505 /* 506 * Reset group membership to just the main one we belong to. 507 */ 508 509 if (!RunUser && setgid(Group)) 510 exit(errno + 100); 511 512 if (!RunUser && setgroups(1, &Group)) 513 exit(errno + 100); 514 515 /* 516 * Change user to something "safe"... 517 */ 518 519 if (!RunUser && user && setuid(user)) 520 exit(errno + 100); 521 522 /* 523 * Change umask to restrict permissions on created files... 524 */ 525 526 umask(077); 527 528 /* 529 * Unblock signals before doing the exec... 530 */ 531 532#ifdef HAVE_SIGSET 533 sigset(SIGTERM, SIG_DFL); 534 sigset(SIGCHLD, SIG_DFL); 535 sigset(SIGPIPE, SIG_DFL); 536#elif defined(HAVE_SIGACTION) 537 memset(&action, 0, sizeof(action)); 538 539 sigemptyset(&action.sa_mask); 540 action.sa_handler = SIG_DFL; 541 542 sigaction(SIGTERM, &action, NULL); 543 sigaction(SIGCHLD, &action, NULL); 544 sigaction(SIGPIPE, &action, NULL); 545#else 546 signal(SIGTERM, SIG_DFL); 547 signal(SIGCHLD, SIG_DFL); 548 signal(SIGPIPE, SIG_DFL); 549#endif /* HAVE_SIGSET */ 550 551 cupsdReleaseSignals(); 552 553 /* 554 * Execute the command; if for some reason this doesn't work, log an error 555 * exit with a non-zero value... 556 */ 557 558 if (envp) 559 execve(exec_path, argv, envp); 560 else 561 execv(exec_path, argv); 562 563 exit(errno + 100); 564 } 565 else if (*pid < 0) 566 { 567 /* 568 * Error - couldn't fork a new process! 569 */ 570 571 cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to fork %s - %s.", command, 572 strerror(errno)); 573 574 *pid = 0; 575 } 576 else 577 { 578 if (!process_array) 579 process_array = cupsArrayNew((cups_array_func_t)compare_procs, NULL); 580 581 if (process_array) 582 { 583 if ((proc = calloc(1, sizeof(cupsd_proc_t) + strlen(command))) != NULL) 584 { 585 proc->pid = *pid; 586 proc->job_id = job ? job->id : 0; 587 _cups_strcpy(proc->name, command); 588 589 cupsArrayAdd(process_array, proc); 590 } 591 } 592 } 593 594 cupsdReleaseSignals(); 595 596 cupsdLogMessage(CUPSD_LOG_DEBUG2, 597 "cupsdStartProcess(command=\"%s\", argv=%p, envp=%p, " 598 "infd=%d, outfd=%d, errfd=%d, backfd=%d, sidefd=%d, root=%d, " 599 "profile=%p, job=%p(%d), pid=%p) = %d", 600 command, argv, envp, infd, outfd, errfd, backfd, sidefd, 601 root, profile, job, job ? job->id : 0, pid, *pid); 602 603 return (*pid); 604} 605 606 607/* 608 * 'compare_procs()' - Compare two processes. 609 */ 610 611static int /* O - Result of comparison */ 612compare_procs(cupsd_proc_t *a, /* I - First process */ 613 cupsd_proc_t *b) /* I - Second process */ 614{ 615 return (a->pid - b->pid); 616} 617 618 619#ifdef HAVE_SANDBOX_H 620/* 621 * 'cupsd_requote()' - Make a regular-expression version of a string. 622 */ 623 624static char * /* O - Quoted string */ 625cupsd_requote(char *dst, /* I - Destination buffer */ 626 const char *src, /* I - Source string */ 627 size_t dstsize) /* I - Size of destination buffer */ 628{ 629 int ch; /* Current character */ 630 char *dstptr, /* Current position in buffer */ 631 *dstend; /* End of destination buffer */ 632 633 634 dstptr = dst; 635 dstend = dst + dstsize - 2; 636 637 while (*src && dstptr < dstend) 638 { 639 ch = *src++; 640 641 if (ch == '/' && !*src) 642 break; /* Don't add trailing slash */ 643 644 if (strchr(".?*()[]^$\\", ch)) 645 *dstptr++ = '\\'; 646 647 *dstptr++ = ch; 648 } 649 650 *dstptr = '\0'; 651 652 return (dst); 653} 654#endif /* HAVE_SANDBOX_H */ 655 656 657/* 658 * End of "$Id: process.c 11093 2013-07-03 20:48:42Z msweet $". 659 */ 660