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