2022-05-13 20:08:20 +08:00
|
|
|
|
/*
|
|
|
|
|
* Process management routines for the CUPS scheduler.
|
|
|
|
|
*
|
2023-01-12 15:27:08 +08:00
|
|
|
|
* Copyright © 2021-2022 by OpenPrinting.
|
|
|
|
|
* Copyright © 2007-2017 by Apple Inc.
|
|
|
|
|
* Copyright © 1997-2007 by Easy Software Products, all rights reserved.
|
2022-05-13 20:08:20 +08:00
|
|
|
|
*
|
2023-01-12 15:27:08 +08:00
|
|
|
|
* Licensed under Apache License v2.0. See the file "LICENSE" for more
|
|
|
|
|
* information.
|
2022-05-13 20:08:20 +08:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Include necessary headers...
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "cupsd.h"
|
|
|
|
|
#include <grp.h>
|
|
|
|
|
#ifdef __APPLE__
|
|
|
|
|
# include <libgen.h>
|
|
|
|
|
#endif /* __APPLE__ */
|
|
|
|
|
#ifdef HAVE_POSIX_SPAWN
|
|
|
|
|
# include <spawn.h>
|
|
|
|
|
extern char **environ;
|
|
|
|
|
/* Don't use posix_spawn on systems with bugs in their implementations... */
|
|
|
|
|
# if defined(OpenBSD) && OpenBSD < 201505
|
|
|
|
|
# define USE_POSIX_SPAWN 0
|
|
|
|
|
# elif defined(__UCLIBC__) && __UCLIBC_MAJOR__ == 1 && __UCLIBC_MINOR__ == 0 && __UCLIBC_SUBLEVEL__ < 27
|
|
|
|
|
# define USE_POSIX_SPAWN 0
|
|
|
|
|
# elif defined(__UCLIBC__) && __UCLIBC_MAJOR__ < 1
|
|
|
|
|
# define USE_POSIX_SPAWN 0
|
|
|
|
|
# else /* All other platforms */
|
|
|
|
|
# define USE_POSIX_SPAWN 1
|
|
|
|
|
# endif /* ... */
|
|
|
|
|
#else
|
|
|
|
|
# define USE_POSIX_SPAWN 0
|
|
|
|
|
#endif /* HAVE_POSIX_SPAWN */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* 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 */
|
|
|
|
|
int allow_networking)/* I - Allow networking off machine? */
|
|
|
|
|
{
|
|
|
|
|
#ifdef HAVE_SANDBOX_H
|
|
|
|
|
cups_file_t *fp; /* File pointer */
|
|
|
|
|
char profile[1024], /* File containing the profile */
|
|
|
|
|
bin[1024], /* Quoted ServerBin */
|
|
|
|
|
cache[1024], /* Quoted CacheDir */
|
|
|
|
|
domain[1024], /* Domain socket, if any */
|
|
|
|
|
request[1024], /* Quoted RequestRoot */
|
|
|
|
|
root[1024], /* Quoted ServerRoot */
|
|
|
|
|
state[1024], /* Quoted StateDir */
|
|
|
|
|
temp[1024]; /* Quoted TempDir */
|
|
|
|
|
const char *nodebug; /* " (with no-log)" for no debug */
|
|
|
|
|
cupsd_listener_t *lis; /* Current listening socket */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!UseSandboxing || Sandboxing == CUPSD_SANDBOXING_OFF)
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* Only use sandbox profiles as root...
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* This should never happen, and is fatal when sandboxing is enabled.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdCreateProfile(job_id=%d, allow_networking=%d) = NULL", job_id, allow_networking);
|
|
|
|
|
cupsdLogMessage(CUPSD_LOG_EMERG, "Unable to create security profile: %s", strerror(errno));
|
|
|
|
|
kill(getpid(), SIGTERM);
|
|
|
|
|
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, "(import \"com.apple.corefoundation.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$\"" /* 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 and some other system folders */
|
|
|
|
|
cupsFilePrintf(fp,
|
|
|
|
|
"(allow file-read-metadata\n"
|
|
|
|
|
" (regex"
|
|
|
|
|
" #\"^/$\"" /* / */
|
|
|
|
|
" #\"^/usr$\"" /* /usr */
|
|
|
|
|
" #\"^/Library$\"" /* /Library */
|
|
|
|
|
" #\"^/Library/Printers$\"" /* /Library/Printers */
|
|
|
|
|
" #\"^%s$\"" /* RequestRoot */
|
|
|
|
|
"))\n",
|
|
|
|
|
request);
|
|
|
|
|
/* Read and write TempDir, CacheDir, and other common folders */
|
|
|
|
|
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$\"" /* TempDir */
|
|
|
|
|
" #\"^%s/\"" /* TempDir/... */
|
|
|
|
|
" #\"^%s$\"" /* CacheDir */
|
|
|
|
|
" #\"^%s/\"" /* CacheDir/... */
|
|
|
|
|
"))\n",
|
|
|
|
|
temp, temp, cache, cache);
|
|
|
|
|
/* Read common folders */
|
|
|
|
|
cupsFilePrintf(fp,
|
|
|
|
|
"(allow file-read-data file-read-metadata\n"
|
|
|
|
|
" (regex"
|
|
|
|
|
" #\"^/AppleInternal$\""
|
|
|
|
|
" #\"^/AppleInternal/\""
|
|
|
|
|
" #\"^/bin$\"" /* /bin */
|
|
|
|
|
" #\"^/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/bin/\"" /* /usr/bin/... */
|
|
|
|
|
" #\"^/usr/libexec/cups$\"" /* /usr/libexec/cups */
|
|
|
|
|
" #\"^/usr/libexec/cups/\"" /* /usr/libexec/cups/... */
|
|
|
|
|
" #\"^/usr/libexec/fax$\"" /* /usr/libexec/fax */
|
|
|
|
|
" #\"^/usr/libexec/fax/\"" /* /usr/libexec/fax/... */
|
|
|
|
|
" #\"^/usr/sbin$\"" /* /usr/sbin */
|
|
|
|
|
" #\"^/usr/sbin/\"" /* /usr/sbin/... */
|
|
|
|
|
" #\"^/Library$\"" /* /Library */
|
|
|
|
|
" #\"^/Library/\"" /* /Library/... */
|
|
|
|
|
" #\"^/System$\"" /* /System */
|
|
|
|
|
" #\"^/System/\"" /* /System/... */
|
|
|
|
|
" #\"^%s/Library$\"" /* RequestRoot/Library */
|
|
|
|
|
" #\"^%s/Library/\"" /* RequestRoot/Library/... */
|
|
|
|
|
" #\"^%s$\"" /* ServerBin */
|
|
|
|
|
" #\"^%s/\"" /* ServerBin/... */
|
|
|
|
|
" #\"^%s$\"" /* ServerRoot */
|
|
|
|
|
" #\"^%s/\"" /* ServerRoot/... */
|
|
|
|
|
" #\"^%s$\"" /* StateDir */
|
|
|
|
|
" #\"^%s/\"" /* StateDir/... */
|
|
|
|
|
"))\n",
|
|
|
|
|
request, request, bin, bin, root, root, state, state);
|
|
|
|
|
if (Sandboxing == CUPSD_SANDBOXING_RELAXED)
|
|
|
|
|
{
|
|
|
|
|
/* Limited write access to /Library/Printers/... */
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
/* Allow execution of child processes as long as the programs are not in a user directory */
|
|
|
|
|
cupsFilePuts(fp, "(allow process*)\n");
|
|
|
|
|
cupsFilePuts(fp, "(deny process-exec (regex #\"^/Users/\"))\n");
|
|
|
|
|
if (RunUser && getenv("CUPS_TESTROOT"))
|
|
|
|
|
{
|
|
|
|
|
/* Allow source directory access in "make test" environment */
|
|
|
|
|
char testroot[1024]; /* Root directory of test files */
|
|
|
|
|
|
|
|
|
|
cupsd_requote(testroot, getenv("CUPS_TESTROOT"), sizeof(testroot));
|
|
|
|
|
|
|
|
|
|
cupsFilePrintf(fp,
|
|
|
|
|
"(allow file-write* file-read-data file-read-metadata\n"
|
|
|
|
|
" (regex"
|
|
|
|
|
" #\"^%s$\"" /* CUPS_TESTROOT */
|
|
|
|
|
" #\"^%s/\"" /* CUPS_TESTROOT/... */
|
|
|
|
|
"))\n",
|
|
|
|
|
testroot, testroot);
|
|
|
|
|
cupsFilePrintf(fp,
|
|
|
|
|
"(allow process-exec\n"
|
|
|
|
|
" (regex"
|
|
|
|
|
" #\"^%s/\"" /* CUPS_TESTROOT/... */
|
|
|
|
|
"))\n",
|
|
|
|
|
testroot);
|
|
|
|
|
cupsFilePrintf(fp, "(allow sysctl*)\n");
|
|
|
|
|
}
|
|
|
|
|
if (job_id)
|
|
|
|
|
{
|
|
|
|
|
/* Allow job filters to read the current job files... */
|
|
|
|
|
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");
|
|
|
|
|
}
|
|
|
|
|
/* Allow access to Bluetooth, USB, and notify_post. */
|
|
|
|
|
cupsFilePuts(fp, "(allow iokit*)\n");
|
|
|
|
|
cupsFilePuts(fp, "(allow distributed-notification-post)\n");
|
|
|
|
|
/* Allow outbound networking to local services */
|
|
|
|
|
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)
|
|
|
|
|
{
|
|
|
|
|
/* Allow TCP and UDP networking off the machine... */
|
|
|
|
|
cupsFilePuts(fp, "\n (remote tcp))\n");
|
|
|
|
|
cupsFilePuts(fp, "(allow network-bind)\n"); /* for LPD resvport */
|
|
|
|
|
cupsFilePuts(fp, "(allow network*\n"
|
|
|
|
|
" (local udp \"*:*\")\n"
|
|
|
|
|
" (remote udp \"*:*\"))\n");
|
|
|
|
|
|
|
|
|
|
/* Also allow access to device files... */
|
|
|
|
|
cupsFilePuts(fp, "(allow file-write* file-read-data file-read-metadata file-ioctl\n"
|
|
|
|
|
" (regex #\"^/dev/\"))\n");
|
|
|
|
|
|
|
|
|
|
/* And allow kernel extensions to be loaded, e.g., SMB */
|
|
|
|
|
cupsFilePuts(fp, "(allow system-kext-load)\n");
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* Only allow SNMP (UDP) and LPD (TCP) off the machine... */
|
|
|
|
|
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 /* 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 */
|
|
|
|
|
size_t 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=" CUPS_LLFMT ", job_id=%p(%d)) = \"%s\"", pid, name, CUPS_LLCAST 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[110], /* Real command-line arguments */
|
|
|
|
|
cups_exec[1024], /* Path to "cups-exec" program */
|
|
|
|
|
user_str[16], /* User string */
|
|
|
|
|
group_str[16], /* Group string */
|
|
|
|
|
nice_str[16]; /* FilterNice string */
|
|
|
|
|
uid_t user; /* Command UID */
|
|
|
|
|
cupsd_proc_t *proc; /* New process record */
|
|
|
|
|
#if USE_POSIX_SPAWN
|
|
|
|
|
posix_spawn_file_actions_t actions; /* Spawn file actions */
|
|
|
|
|
posix_spawnattr_t attrs; /* Spawn attributes */
|
|
|
|
|
sigset_t defsignals; /* Default signals */
|
|
|
|
|
#elif defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
|
|
|
|
|
struct sigaction action; /* POSIX signal handler */
|
|
|
|
|
#endif /* USE_POSIX_SPAWN */
|
|
|
|
|
#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 macOS - this allows macOS 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 <CFProcessPath> string */
|
|
|
|
|
}
|
|
|
|
|
#endif /* __APPLE__ */
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Use helper program when we have a sandbox profile...
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#if !USE_POSIX_SPAWN
|
|
|
|
|
if (profile)
|
|
|
|
|
#endif /* !USE_POSIX_SPAWN */
|
|
|
|
|
{
|
|
|
|
|
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
|
|
|
|
|
/*
|
|
|
|
|
* Setup attributes and file actions for the 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
|
|
|
|
|
/*
|
|
|
|
|
* 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);
|
|
|
|
|
|
2023-01-12 15:27:08 +08:00
|
|
|
|
# if CUPS_SNAP
|
|
|
|
|
if (!RunUser && setgroups(0, NULL))
|
|
|
|
|
# else
|
2022-05-13 20:08:20 +08:00
|
|
|
|
if (!RunUser && setgroups(1, &Group))
|
2023-01-12 15:27:08 +08:00
|
|
|
|
# endif /* CUPS_SNAP */
|
2022-05-13 20:08:20 +08:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cupsdReleaseSignals();
|
|
|
|
|
#endif /* USE_POSIX_SPAWN */
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* '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++ = (char)ch;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*dstptr = '\0';
|
|
|
|
|
|
|
|
|
|
return (dst);
|
|
|
|
|
}
|
|
|
|
|
#endif /* HAVE_SANDBOX_H */
|