#include "cupsd.h"
#include <grp.h>
#ifdef __APPLE__
# include <libgen.h>
#endif
#ifdef HAVE_POSIX_SPAWN
# include <spawn.h>
extern char **environ;
#endif
#ifdef HAVE_POSIX_SPAWN
# if !defined(__OpenBSD__) || OpenBSD >= 201505
# define USE_POSIX_SPAWN 1
# else
# define USE_POSIX_SPAWN 0
# endif
#else
# define USE_POSIX_SPAWN 0
#endif
typedef struct
{
int pid,
job_id;
char name[1];
} cupsd_proc_t;
static cups_array_t *process_array = NULL;
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
void *
cupsdCreateProfile(int job_id,
int allow_networking)
{
#ifdef HAVE_SANDBOX_H
cups_file_t *fp;
char profile[1024],
bin[1024],
cache[1024],
domain[1024],
request[1024],
root[1024],
state[1024],
temp[1024];
const char *nodebug;
cupsd_listener_t *lis;
if (!UseSandboxing || Sandboxing == CUPSD_SANDBOXING_OFF)
{
cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdCreateProfile(job_id=%d, allow_networking=%d) = NULL", job_id, allow_networking);
return (NULL);
}
if ((fp = cupsTempFile2(profile, sizeof(profile))) == NULL)
{
cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdCreateProfile(job_id=%d, allow_networking=%d) = NULL", job_id, allow_networking);
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(bin, ServerBin, sizeof(bin));
cupsd_requote(cache, CacheDir, sizeof(cache));
cupsd_requote(request, RequestRoot, sizeof(request));
cupsd_requote(root, ServerRoot, sizeof(root));
cupsd_requote(state, StateDir, sizeof(state));
cupsd_requote(temp, TempDir, sizeof(temp));
nodebug = LogLevel < CUPSD_LOG_DEBUG ? " (with no-log)" : "";
cupsFilePuts(fp, "(version 1)\n");
if (Sandboxing == CUPSD_SANDBOXING_STRICT)
cupsFilePuts(fp, "(deny default)\n");
else
cupsFilePuts(fp, "(allow default)\n");
if (LogLevel >= CUPSD_LOG_DEBUG)
cupsFilePuts(fp, "(debug deny)\n");
cupsFilePuts(fp, "(import \"system.sb\")\n");
cupsFilePuts(fp, "(system-network)\n");
cupsFilePuts(fp, "(allow mach-per-user-lookup)\n");
cupsFilePuts(fp, "(allow ipc-posix-sem)\n");
cupsFilePuts(fp, "(allow ipc-posix-shm)\n");
cupsFilePuts(fp, "(allow ipc-sysv-shm)\n");
cupsFilePuts(fp, "(allow mach-lookup)\n");
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$\""
" #\"^%s/\""
" #\"^/private/etc$\""
" #\"^/private/etc/\""
" #\"^/usr/local/etc$\""
" #\"^/usr/local/etc/\""
" #\"^/Library$\""
" #\"^/Library/\""
" #\"^/System$\""
" #\"^/System/\""
")%s)\n",
root, root, nodebug);
cupsFilePrintf(fp,
"(allow file-read-metadata\n"
" (regex"
" #\"^/$\""
" #\"^/usr$\""
" #\"^/Library$\""
" #\"^/Library/Printers$\""
" #\"^%s$\""
"))\n",
request);
cupsFilePuts(fp,
"(allow file-write* file-read-data file-read-metadata\n"
" (regex"
" #\"^/private/var/db/\""
" #\"^/private/var/folders/\""
" #\"^/private/var/lib/\""
" #\"^/private/var/log/\""
" #\"^/private/var/mysql/\""
" #\"^/private/var/run/\""
" #\"^/private/var/spool/\""
" #\"^/Library/Application Support/\""
" #\"^/Library/Caches/\""
" #\"^/Library/Logs/\""
" #\"^/Library/Preferences/\""
" #\"^/Library/WebServer/\""
" #\"^/Users/Shared/\""
"))\n");
cupsFilePrintf(fp,
"(deny file-write*\n"
" (regex #\"^%s$\")%s)\n",
request, nodebug);
cupsFilePrintf(fp,
"(deny file-write* file-read-data file-read-metadata\n"
" (regex #\"^%s/\")%s)\n",
request, nodebug);
cupsFilePrintf(fp,
"(allow file-write* file-read-data file-read-metadata\n"
" (regex"
" #\"^%s$\""
" #\"^%s/\""
" #\"^%s$\""
" #\"^%s/\""
" #\"^%s$\""
" #\"^%s/\""
"))\n",
temp, temp, cache, cache, state, state);
cupsFilePrintf(fp,
"(allow file-read-data file-read-metadata\n"
" (regex"
" #\"^/AppleInternal$\""
" #\"^/AppleInternal/\""
" #\"^/bin$\""
" #\"^/bin/\""
" #\"^/private$\""
" #\"^/private/etc$\""
" #\"^/private/etc/\""
" #\"^/private/tmp$\""
" #\"^/private/tmp/\""
" #\"^/private/var$\""
" #\"^/private/var/db$\""
" #\"^/private/var/folders$\""
" #\"^/private/var/lib$\""
" #\"^/private/var/log$\""
" #\"^/private/var/mysql$\""
" #\"^/private/var/run$\""
" #\"^/private/var/spool$\""
" #\"^/private/var/tmp$\""
" #\"^/private/var/tmp/\""
" #\"^/usr/bin$\""
" #\"^/usr/bin/\""
" #\"^/usr/libexec/cups$\""
" #\"^/usr/libexec/cups/\""
" #\"^/usr/libexec/fax$\""
" #\"^/usr/libexec/fax/\""
" #\"^/usr/sbin$\""
" #\"^/usr/sbin/\""
" #\"^/Library$\""
" #\"^/Library/\""
" #\"^/System$\""
" #\"^/System/\""
" #\"^%s/Library$\""
" #\"^%s/Library/\""
" #\"^%s$\""
" #\"^%s/\""
" #\"^%s$\""
" #\"^%s/\""
"))\n",
request, request, bin, bin, root, root);
if (Sandboxing == CUPSD_SANDBOXING_RELAXED)
{
cupsFilePuts(fp,
"(allow file-write*\n"
" (regex"
" #\"^/Library/Printers/.*/\""
"))\n");
cupsFilePrintf(fp,
"(deny file-write*\n"
" (regex"
" #\"^/Library/Printers/PPDs$\""
" #\"^/Library/Printers/PPDs/\""
" #\"^/Library/Printers/PPD Plugins$\""
" #\"^/Library/Printers/PPD Plugins/\""
")%s)\n", nodebug);
}
cupsFilePuts(fp, "(allow process*)\n");
cupsFilePuts(fp, "(deny process-exec (regex #\"^/Users/\"))\n");
if (RunUser && getenv("CUPS_TESTROOT"))
{
char testroot[1024];
cupsd_requote(testroot, getenv("CUPS_TESTROOT"), sizeof(testroot));
cupsFilePrintf(fp,
"(allow file-write* file-read-data file-read-metadata\n"
" (regex"
" #\"^%s$\""
" #\"^%s/\""
"))\n",
testroot, testroot);
cupsFilePrintf(fp,
"(allow process-exec\n"
" (regex"
" #\"^%s/\""
"))\n",
testroot);
cupsFilePrintf(fp, "(allow sysctl*)\n");
}
if (job_id)
{
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
{
cupsFilePuts(fp,
"(allow process-exec\n"
" (literal \"/usr/sbin/sendmail\")\n"
" (with no-sandbox))\n");
}
cupsFilePuts(fp, "(allow iokit*)\n");
cupsFilePuts(fp, "(allow distributed-notification-post)\n");
cupsFilePuts(fp, "(allow network-outbound"
"\n (regex #\"^/private/var/run/\" #\"^/private/tmp/\" #\"^/private/var/tmp/\")");
for (lis = (cupsd_listener_t *)cupsArrayFirst(Listeners);
lis;
lis = (cupsd_listener_t *)cupsArrayNext(Listeners))
{
if (httpAddrFamily(&(lis->address)) == AF_LOCAL)
{
httpAddrString(&(lis->address), domain, sizeof(domain));
cupsFilePrintf(fp, "\n (literal \"%s\")", domain);
}
}
if (allow_networking)
{
cupsFilePuts(fp, "\n (remote tcp))\n");
cupsFilePuts(fp, "(allow network-bind)\n");
cupsFilePuts(fp, "(allow network*\n"
" (local udp \"*:*\")\n"
" (remote udp \"*:*\"))\n");
cupsFilePuts(fp, "(allow file-write* file-read-data file-read-metadata file-ioctl\n"
" (regex #\"^/dev/\"))\n");
cupsFilePuts(fp, "(allow system-kext-load)\n");
}
else
{
cupsFilePuts(fp, ")\n");
cupsFilePuts(fp, "(allow network-outbound\n"
" (remote udp \"*:161\")\n"
" (remote tcp \"*:515\"))\n");
cupsFilePuts(fp, "(allow network-inbound\n"
" (local udp \"localhost:*\"))\n");
}
cupsFileClose(fp);
cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdCreateProfile(job_id=%d,allow_networking=%d) = \"%s\"", job_id, allow_networking, profile);
return ((void *)strdup(profile));
#else
cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdCreateProfile(job_id=%d, allow_networking=%d) = NULL", job_id, allow_networking);
return (NULL);
#endif
}
void
cupsdDestroyProfile(void *profile)
{
cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdDeleteProfile(profile=\"%s\")",
profile ? (char *)profile : "(null)");
#ifdef HAVE_SANDBOX_H
if (profile)
{
unlink((char *)profile);
free(profile);
}
#endif
}
int
cupsdEndProcess(int pid,
int force)
{
cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdEndProcess(pid=%d, force=%d)", pid,
force);
if (!pid)
return (0);
if (!RunUser)
{
pid = -pid;
}
if (force)
return (kill(pid, SIGKILL));
else
return (kill(pid, SIGTERM));
}
const char *
cupsdFinishProcess(int pid,
char *name,
size_t namelen,
int *job_id)
{
cupsd_proc_t key,
*proc;
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=" CUPS_LLFMT ", job_id=%p(%d)) = \"%s\"", pid, name, CUPS_LLCAST namelen, job_id, job_id ? *job_id : 0, name);
return (name);
}
int
cupsdStartProcess(
const char *command,
char *argv[],
char *envp[],
int infd,
int outfd,
int errfd,
int backfd,
int sidefd,
int root,
void *profile,
cupsd_job_t *job,
int *pid)
{
int i;
const char *exec_path = command;
char *real_argv[110],
cups_exec[1024],
user_str[16],
group_str[16],
nice_str[16];
uid_t user;
cupsd_proc_t *proc;
#if USE_POSIX_SPAWN
posix_spawn_file_actions_t actions;
posix_spawnattr_t attrs;
sigset_t defsignals;
#elif defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
struct sigaction action;
#endif
#if defined(__APPLE__)
char processPath[1024],
linkpath[1024];
int linkbytes;
#endif
*pid = 0;
if (RunUser)
user = RunUser;
else if (root)
user = 0;
else
user = User;
if (_cupsFileCheck(command, _CUPS_FILE_CHECK_PROGRAM, !RunUser,
cupsdLogFCMessage, job ? job->printer : NULL))
return (0);
#if defined(__APPLE__)
if (envp)
{
if ((linkbytes = readlink(command, linkpath, sizeof(linkpath) - 1)) > 0)
{
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;
}
#endif
#if !USE_POSIX_SPAWN
if (profile)
#endif
{
snprintf(cups_exec, sizeof(cups_exec), "%s/daemon/cups-exec", ServerBin);
snprintf(user_str, sizeof(user_str), "%d", user);
snprintf(group_str, sizeof(group_str), "%d", Group);
snprintf(nice_str, sizeof(nice_str), "%d", FilterNice);
real_argv[0] = cups_exec;
real_argv[1] = (char *)"-g";
real_argv[2] = group_str;
real_argv[3] = (char *)"-n";
real_argv[4] = nice_str;
real_argv[5] = (char *)"-u";
real_argv[6] = user_str;
real_argv[7] = profile ? profile : "none";
real_argv[8] = (char *)command;
for (i = 0;
i < (int)(sizeof(real_argv) / sizeof(real_argv[0]) - 10) && argv[i];
i ++)
real_argv[i + 9] = argv[i];
real_argv[i + 9] = NULL;
argv = real_argv;
exec_path = cups_exec;
}
if (LogLevel == CUPSD_LOG_DEBUG2)
{
cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdStartProcess: Preparing to start \"%s\", arguments:", command);
for (i = 0; argv[i]; i ++)
cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdStartProcess: argv[%d] = \"%s\"", i, argv[i]);
}
#if USE_POSIX_SPAWN
cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdStartProcess: Setting spawn attributes.");
sigemptyset(&defsignals);
sigaddset(&defsignals, SIGTERM);
sigaddset(&defsignals, SIGCHLD);
sigaddset(&defsignals, SIGPIPE);
posix_spawnattr_init(&attrs);
posix_spawnattr_setflags(&attrs, POSIX_SPAWN_SETPGROUP | POSIX_SPAWN_SETSIGDEF);
posix_spawnattr_setpgroup(&attrs, 0);
posix_spawnattr_setsigdefault(&attrs, &defsignals);
cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdStartProcess: Setting file actions.");
posix_spawn_file_actions_init(&actions);
if (infd != 0)
{
if (infd < 0)
posix_spawn_file_actions_addopen(&actions, 0, "/dev/null", O_RDONLY, 0);
else
posix_spawn_file_actions_adddup2(&actions, infd, 0);
}
if (outfd != 1)
{
if (outfd < 0)
posix_spawn_file_actions_addopen(&actions, 1, "/dev/null", O_WRONLY, 0);
else
posix_spawn_file_actions_adddup2(&actions, outfd, 1);
}
if (errfd != 2)
{
if (errfd < 0)
posix_spawn_file_actions_addopen(&actions, 2, "/dev/null", O_WRONLY, 0);
else
posix_spawn_file_actions_adddup2(&actions, errfd, 2);
}
if (backfd != 3 && backfd >= 0)
posix_spawn_file_actions_adddup2(&actions, backfd, 3);
if (sidefd != 4 && sidefd >= 0)
posix_spawn_file_actions_adddup2(&actions, sidefd, 4);
cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdStartProcess: Calling posix_spawn.");
if (posix_spawn(pid, exec_path, &actions, &attrs, argv, envp ? envp : environ))
{
cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to fork %s - %s.", command, strerror(errno));
*pid = 0;
}
else
cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdStartProcess: pid=%d", (int)*pid);
posix_spawn_file_actions_destroy(&actions);
posix_spawnattr_destroy(&attrs);
#else
cupsdHoldSignals();
if ((*pid = fork()) == 0)
{
if (errfd != 2)
{
if (errfd < 0)
errfd = open("/dev/null", O_WRONLY);
if (errfd != 2)
{
dup2(errfd, 2);
close(errfd);
}
}
# ifdef HAVE_SETPGID
if (!RunUser && setpgid(0, 0))
exit(errno + 100);
# else
if (!RunUser && setpgrp())
exit(errno + 100);
# endif
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);
}
if (!root)
nice(FilterNice);
if (!RunUser && setgid(Group))
exit(errno + 100);
if (!RunUser && setgroups(1, &Group))
exit(errno + 100);
if (!RunUser && user && setuid(user))
exit(errno + 100);
umask(077);
# 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
cupsdReleaseSignals();
if (envp)
execve(exec_path, argv, envp);
else
execv(exec_path, argv);
exit(errno + 100);
}
else if (*pid < 0)
{
cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to fork %s - %s.", command,
strerror(errno));
*pid = 0;
}
cupsdReleaseSignals();
#endif
if (*pid)
{
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);
}
}
}
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);
}
static int
compare_procs(cupsd_proc_t *a,
cupsd_proc_t *b)
{
return (a->pid - b->pid);
}
#ifdef HAVE_SANDBOX_H
static char *
cupsd_requote(char *dst,
const char *src,
size_t dstsize)
{
int ch;
char *dstptr,
*dstend;
dstptr = dst;
dstend = dst + dstsize - 2;
while (*src && dstptr < dstend)
{
ch = *src++;
if (ch == '/' && !*src)
break;
if (strchr(".?*()[]^$\\", ch))
*dstptr++ = '\\';
*dstptr++ = (char)ch;
}
*dstptr = '\0';
return (dst);
}
#endif