/* * "$Id: process.c 11093 2013-07-03 20:48:42Z msweet $" * * Process management routines for the CUPS scheduler. * * Copyright 2007-2012 by Apple Inc. * Copyright 1997-2007 by Easy Software Products, all rights reserved. * * These coded instructions, statements, and computer programs are the * property of Apple Inc. and are protected by Federal copyright * law. Distribution and use rights are outlined in the file "LICENSE.txt" * which should have been included with this file. If this file is * file is missing or damaged, see the license at "http://www.cups.org/". * * Contents: * * cupsdCreateProfile() - Create an execution profile for a subprocess. * cupsdDestroyProfile() - Delete an execution profile. * cupsdEndProcess() - End a process. * cupsdFinishProcess() - Finish a process and get its name. * cupsdStartProcess() - Start a process. * compare_procs() - Compare two processes. * cupsd_requote() - Make a regular-expression version of a string. */ /* * Include necessary headers... */ #include "cupsd.h" #include #ifdef __APPLE__ # include #endif /* __APPLE__ */ /* * Process structure... */ typedef struct { int pid, /* Process ID */ job_id; /* Job associated with process */ char name[1]; /* Name of process */ } cupsd_proc_t; /* * Local globals... */ static cups_array_t *process_array = NULL; /* * Local functions... */ static int compare_procs(cupsd_proc_t *a, cupsd_proc_t *b); #ifdef HAVE_SANDBOX_H static char *cupsd_requote(char *dst, const char *src, size_t dstsize); #endif /* HAVE_SANDBOX_H */ /* * 'cupsdCreateProfile()' - Create an execution profile for a subprocess. */ void * /* O - Profile or NULL on error */ cupsdCreateProfile(int job_id) /* I - Job ID or 0 for none */ { #ifdef HAVE_SANDBOX_H cups_file_t *fp; /* File pointer */ char profile[1024], /* File containing the profile */ cache[1024], /* Quoted CacheDir */ request[1024], /* Quoted RequestRoot */ root[1024], /* Quoted ServerRoot */ temp[1024]; /* Quoted TempDir */ const char *nodebug; /* " (with no-log)" for no debug */ if (!UseProfiles) { /* * Only use sandbox profiles as root... */ cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdCreateProfile(job_id=%d) = NULL", job_id); return (NULL); } if ((fp = cupsTempFile2(profile, sizeof(profile))) == NULL) { cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdCreateProfile(job_id=%d) = NULL", job_id); cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to create security profile: %s", strerror(errno)); return (NULL); } fchown(cupsFileNumber(fp), RunUser, Group); fchmod(cupsFileNumber(fp), 0640); cupsd_requote(cache, CacheDir, sizeof(cache)); cupsd_requote(request, RequestRoot, sizeof(request)); cupsd_requote(root, ServerRoot, sizeof(root)); cupsd_requote(temp, TempDir, sizeof(temp)); nodebug = LogLevel < CUPSD_LOG_DEBUG ? " (with no-log)" : ""; cupsFilePuts(fp, "(version 1)\n"); cupsFilePuts(fp, "(allow default)\n"); cupsFilePrintf(fp, "(deny file-write* file-read-data file-read-metadata\n" " (regex" " #\"^%s$\"" /* RequestRoot */ " #\"^%s/\"" /* RequestRoot/... */ ")%s)\n", request, request, nodebug); if (!RunUser) cupsFilePrintf(fp, "(deny file-write* file-read-data file-read-metadata\n" " (regex" " #\"^/Users$\"" " #\"^/Users/\"" ")%s)\n", nodebug); cupsFilePrintf(fp, "(deny file-write*\n" " (regex" " #\"^%s$\"" /* ServerRoot */ " #\"^%s/\"" /* ServerRoot/... */ " #\"^/private/etc$\"" " #\"^/private/etc/\"" " #\"^/usr/local/etc$\"" " #\"^/usr/local/etc/\"" " #\"^/Library$\"" " #\"^/Library/\"" " #\"^/System$\"" " #\"^/System/\"" ")%s)\n", root, root, nodebug); /* Specifically allow applications to stat RequestRoot */ cupsFilePrintf(fp, "(allow file-read-metadata\n" " (regex" " #\"^%s$\"" /* RequestRoot */ "))\n", request); cupsFilePrintf(fp, "(allow file-write* file-read-data file-read-metadata\n" " (regex" " #\"^%s$\"" /* TempDir */ " #\"^%s/\"" /* TempDir/... */ " #\"^%s$\"" /* CacheDir */ " #\"^%s/\"" /* CacheDir/... */ " #\"^%s/Library$\"" /* RequestRoot/Library */ " #\"^%s/Library/\"" /* RequestRoot/Library/... */ " #\"^/Library/Application Support/\"" " #\"^/Library/Caches/\"" " #\"^/Library/Preferences/\"" " #\"^/Library/Printers/.*/\"" " #\"^/Users/Shared/\"" "))\n", temp, temp, cache, cache, request, request); cupsFilePrintf(fp, "(deny file-write*\n" " (regex" " #\"^/Library/Printers/PPDs$\"" " #\"^/Library/Printers/PPDs/\"" " #\"^/Library/Printers/PPD Plugins$\"" " #\"^/Library/Printers/PPD Plugins/\"" ")%s)\n", nodebug); if (job_id) { /* * Allow job filters to read the spool file(s)... */ cupsFilePrintf(fp, "(allow file-read-data file-read-metadata\n" " (regex #\"^%s/([ac]%05d|d%05d-[0-9][0-9][0-9])$\"))\n", request, job_id, job_id); } else { /* * Allow email notifications from notifiers... */ cupsFilePuts(fp, "(allow process-exec\n" " (literal \"/usr/sbin/sendmail\")\n" " (with no-sandbox)\n" ")\n"); } cupsFileClose(fp); cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdCreateProfile(job_id=%d) = \"%s\"", job_id, profile); return ((void *)strdup(profile)); #else cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdCreateProfile(job_id=%d) = NULL", job_id); return (NULL); #endif /* HAVE_SANDBOX_H */ } /* * 'cupsdDestroyProfile()' - Delete an execution profile. */ void cupsdDestroyProfile(void *profile) /* I - Profile */ { cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdDeleteProfile(profile=\"%s\")", profile ? (char *)profile : "(null)"); #ifdef HAVE_SANDBOX_H if (profile) { unlink((char *)profile); free(profile); } #endif /* HAVE_SANDBOX_H */ } /* * 'cupsdEndProcess()' - End a process. */ int /* O - 0 on success, -1 on failure */ cupsdEndProcess(int pid, /* I - Process ID */ int force) /* I - Force child to die */ { cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdEndProcess(pid=%d, force=%d)", pid, force); if (!pid) return (0); if (!RunUser) { /* * When running as root, cupsd puts child processes in their own process * group. Using "-pid" sends a signal to all processes in the group. */ pid = -pid; } if (force) return (kill(pid, SIGKILL)); else return (kill(pid, SIGTERM)); } /* * 'cupsdFinishProcess()' - Finish a process and get its name. */ const char * /* O - Process name */ cupsdFinishProcess(int pid, /* I - Process ID */ char *name, /* I - Name buffer */ int namelen, /* I - Size of name buffer */ int *job_id) /* O - Job ID pointer or NULL */ { cupsd_proc_t key, /* Search key */ *proc; /* Matching process */ key.pid = pid; if ((proc = (cupsd_proc_t *)cupsArrayFind(process_array, &key)) != NULL) { if (job_id) *job_id = proc->job_id; strlcpy(name, proc->name, namelen); cupsArrayRemove(process_array, proc); free(proc); } else { if (job_id) *job_id = 0; strlcpy(name, "unknown", namelen); } cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdFinishProcess(pid=%d, name=%p, namelen=%d, " "job_id=%p(%d)) = \"%s\"", pid, name, namelen, job_id, job_id ? *job_id : 0, name); return (name); } /* * 'cupsdStartProcess()' - Start a process. */ int /* O - Process ID or 0 */ cupsdStartProcess( const char *command, /* I - Full path to command */ char *argv[], /* I - Command-line arguments */ char *envp[], /* I - Environment */ int infd, /* I - Standard input file descriptor */ int outfd, /* I - Standard output file descriptor */ int errfd, /* I - Standard error file descriptor */ int backfd, /* I - Backchannel file descriptor */ int sidefd, /* I - Sidechannel file descriptor */ int root, /* I - Run as root? */ void *profile, /* I - Security profile to use */ cupsd_job_t *job, /* I - Job associated with process */ int *pid) /* O - Process ID */ { int i; /* Looping var */ const char *exec_path = command; /* Command to be exec'd */ char *real_argv[103], /* Real command-line arguments */ cups_exec[1024]; /* Path to "cups-exec" program */ int user; /* Command UID */ cupsd_proc_t *proc; /* New process record */ #if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET) struct sigaction action; /* POSIX signal handler */ #endif /* HAVE_SIGACTION && !HAVE_SIGSET */ #if defined(__APPLE__) char processPath[1024], /* CFProcessPath environment variable */ linkpath[1024]; /* Link path for symlinks... */ int linkbytes; /* Bytes for link path */ #endif /* __APPLE__ */ *pid = 0; /* * Figure out the UID for the child process... */ if (RunUser) user = RunUser; else if (root) user = 0; else user = User; /* * Check the permissions of the command we are running... */ if (_cupsFileCheck(command, _CUPS_FILE_CHECK_PROGRAM, !RunUser, cupsdLogFCMessage, job ? job->printer : NULL)) return (0); #if defined(__APPLE__) if (envp) { /* * Add special voodoo magic for OS X - this allows OS X programs to access * their bundle resources properly... */ if ((linkbytes = readlink(command, linkpath, sizeof(linkpath) - 1)) > 0) { /* * Yes, this is a symlink to the actual program, nul-terminate and * use it... */ linkpath[linkbytes] = '\0'; if (linkpath[0] == '/') snprintf(processPath, sizeof(processPath), "CFProcessPath=%s", linkpath); else snprintf(processPath, sizeof(processPath), "CFProcessPath=%s/%s", dirname((char *)command), linkpath); } else snprintf(processPath, sizeof(processPath), "CFProcessPath=%s", command); envp[0] = processPath; /* Replace string */ } #endif /* __APPLE__ */ /* * Use helper program when we have a sandbox profile... */ if (profile) { snprintf(cups_exec, sizeof(cups_exec), "%s/daemon/cups-exec", ServerBin); real_argv[0] = cups_exec; real_argv[1] = profile; real_argv[2] = (char *)command; for (i = 0; i < (int)(sizeof(real_argv) / sizeof(real_argv[0]) - 4) && argv[i]; i ++) real_argv[i + 3] = argv[i]; real_argv[i + 3] = NULL; argv = real_argv; exec_path = cups_exec; } /* * Block signals before forking... */ cupsdHoldSignals(); if ((*pid = fork()) == 0) { /* * Child process goes here; update stderr as needed... */ if (errfd != 2) { if (errfd < 0) errfd = open("/dev/null", O_WRONLY); if (errfd != 2) { dup2(errfd, 2); close(errfd); } } /* * Put this process in its own process group so that we can kill any child * processes it creates. */ #ifdef HAVE_SETPGID if (!RunUser && setpgid(0, 0)) exit(errno + 100); #else if (!RunUser && setpgrp()) exit(errno + 100); #endif /* HAVE_SETPGID */ /* * Update the remaining file descriptors as needed... */ if (infd != 0) { if (infd < 0) infd = open("/dev/null", O_RDONLY); if (infd != 0) { dup2(infd, 0); close(infd); } } if (outfd != 1) { if (outfd < 0) outfd = open("/dev/null", O_WRONLY); if (outfd != 1) { dup2(outfd, 1); close(outfd); } } if (backfd != 3 && backfd >= 0) { dup2(backfd, 3); close(backfd); fcntl(3, F_SETFL, O_NDELAY); } if (sidefd != 4 && sidefd >= 0) { dup2(sidefd, 4); close(sidefd); fcntl(4, F_SETFL, O_NDELAY); } /* * Change the priority of the process based on the FilterNice setting. * (this is not done for root processes...) */ if (!root) nice(FilterNice); /* * Reset group membership to just the main one we belong to. */ if (!RunUser && setgid(Group)) exit(errno + 100); if (!RunUser && setgroups(1, &Group)) exit(errno + 100); /* * Change user to something "safe"... */ if (!RunUser && user && setuid(user)) exit(errno + 100); /* * Change umask to restrict permissions on created files... */ umask(077); /* * Unblock signals before doing the exec... */ #ifdef HAVE_SIGSET sigset(SIGTERM, SIG_DFL); sigset(SIGCHLD, SIG_DFL); sigset(SIGPIPE, SIG_DFL); #elif defined(HAVE_SIGACTION) memset(&action, 0, sizeof(action)); sigemptyset(&action.sa_mask); action.sa_handler = SIG_DFL; sigaction(SIGTERM, &action, NULL); sigaction(SIGCHLD, &action, NULL); sigaction(SIGPIPE, &action, NULL); #else signal(SIGTERM, SIG_DFL); signal(SIGCHLD, SIG_DFL); signal(SIGPIPE, SIG_DFL); #endif /* HAVE_SIGSET */ cupsdReleaseSignals(); /* * Execute the command; if for some reason this doesn't work, log an error * exit with a non-zero value... */ if (envp) execve(exec_path, argv, envp); else execv(exec_path, argv); exit(errno + 100); } else if (*pid < 0) { /* * Error - couldn't fork a new process! */ cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to fork %s - %s.", command, strerror(errno)); *pid = 0; } else { if (!process_array) process_array = cupsArrayNew((cups_array_func_t)compare_procs, NULL); if (process_array) { if ((proc = calloc(1, sizeof(cupsd_proc_t) + strlen(command))) != NULL) { proc->pid = *pid; proc->job_id = job ? job->id : 0; _cups_strcpy(proc->name, command); cupsArrayAdd(process_array, proc); } } } cupsdReleaseSignals(); cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdStartProcess(command=\"%s\", argv=%p, envp=%p, " "infd=%d, outfd=%d, errfd=%d, backfd=%d, sidefd=%d, root=%d, " "profile=%p, job=%p(%d), pid=%p) = %d", command, argv, envp, infd, outfd, errfd, backfd, sidefd, root, profile, job, job ? job->id : 0, pid, *pid); return (*pid); } /* * 'compare_procs()' - Compare two processes. */ static int /* O - Result of comparison */ compare_procs(cupsd_proc_t *a, /* I - First process */ cupsd_proc_t *b) /* I - Second process */ { return (a->pid - b->pid); } #ifdef HAVE_SANDBOX_H /* * 'cupsd_requote()' - Make a regular-expression version of a string. */ static char * /* O - Quoted string */ cupsd_requote(char *dst, /* I - Destination buffer */ const char *src, /* I - Source string */ size_t dstsize) /* I - Size of destination buffer */ { int ch; /* Current character */ char *dstptr, /* Current position in buffer */ *dstend; /* End of destination buffer */ dstptr = dst; dstend = dst + dstsize - 2; while (*src && dstptr < dstend) { ch = *src++; if (ch == '/' && !*src) break; /* Don't add trailing slash */ if (strchr(".?*()[]^$\\", ch)) *dstptr++ = '\\'; *dstptr++ = ch; } *dstptr = '\0'; return (dst); } #endif /* HAVE_SANDBOX_H */ /* * End of "$Id: process.c 11093 2013-07-03 20:48:42Z msweet $". */