1/*
2 * "$Id: cupsfilter.c 11780 2014-03-28 20:51:12Z msweet $"
3 *
4 * Filtering program for CUPS.
5 *
6 * Copyright 2007-2014 by Apple Inc.
7 * Copyright 1997-2006 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
16/*
17 * Include necessary headers...
18 */
19
20#include <cups/cups-private.h>
21#include <cups/file-private.h>
22#include <cups/ppd-private.h>
23#include "mime.h"
24#include <limits.h>
25#include <unistd.h>
26#include <fcntl.h>
27#include <signal.h>
28#include <sys/wait.h>
29#if defined(__APPLE__)
30#  include <libgen.h>
31#endif /* __APPLE__ */
32
33
34/*
35 * Local globals...
36 */
37
38static char		*DataDir = NULL;/* CUPS_DATADIR environment variable */
39static char		*FontPath = NULL;
40					/* CUPS_FONTPATH environment variable */
41static mime_filter_t	GZIPFilter =	/* gziptoany filter */
42{
43  NULL,					/* Source type */
44  NULL,					/* Destination type */
45  0,					/* Cost */
46  "gziptoany"				/* Filter program to run */
47};
48static char		*Path = NULL;	/* PATH environment variable */
49static char		*ServerBin = NULL;
50					/* CUPS_SERVERBIN environment variable */
51static char		*ServerRoot = NULL;
52					/* CUPS_SERVERROOT environment variable */
53static char		*RIPCache = NULL;
54					/* RIP_MAX_CACHE environment variable */
55static char		TempFile[1024] = "";
56					/* Temporary file */
57
58
59/*
60 * Local functions...
61 */
62
63static void		add_printer_filter(const char *command, mime_t *mime,
64			                   mime_type_t *printer_type,
65		                           const char  *filter);
66static mime_type_t	*add_printer_filters(const char *command,
67					     mime_t *mime, const char *printer,
68			                     const char *ppdfile,
69					     mime_type_t **prefilter_type);
70static void		check_cb(void *context, _cups_fc_result_t result,
71				 const char *message);
72static int		compare_pids(mime_filter_t *a, mime_filter_t *b);
73static char		*escape_options(int num_options, cups_option_t *options);
74static int		exec_filter(const char *filter, char **argv,
75			            char **envp, int infd, int outfd);
76static int		exec_filters(mime_type_t *srctype,
77			             cups_array_t *filters, const char *infile,
78		                     const char *outfile, const char *ppdfile,
79			             const char *printer, const char *user,
80				     const char *title, int num_options,
81			             cups_option_t *options);
82static void		get_job_file(const char *job);
83static int		open_pipe(int *fds);
84static int		read_cups_files_conf(const char *filename);
85static void		set_string(char **s, const char *val);
86static void		sighandler(int sig);
87static void		usage(const char *opt) __attribute__((noreturn));
88
89
90/*
91 * 'main()' - Main entry for the test program.
92 */
93
94int					/* O - Exit status */
95main(int  argc,				/* I - Number of command-line args */
96     char *argv[])			/* I - Command-line arguments */
97{
98  int		i;			/* Looping vars */
99  const char	*command,		/* Command name */
100		*opt,			/* Current option */
101		*printer;		/* Printer name */
102  mime_type_t	*printer_type,		/* Printer MIME type */
103		*prefilter_type;	/* Printer prefilter MIME type */
104  char		*srctype,		/* Source type */
105		*dsttype,		/* Destination type */
106		super[MIME_MAX_SUPER],	/* Super-type name */
107		type[MIME_MAX_TYPE];	/* Type name */
108  int		compression;		/* Compression of file */
109  int		cost;			/* Cost of filters */
110  mime_t	*mime;			/* MIME database */
111  char		mimedir[1024];		/* MIME directory */
112  char		*infile,		/* File to filter */
113		*outfile;		/* File to create */
114  char		cupsfilesconf[1024];	/* cups-files.conf file */
115  const char	*server_root;		/* CUPS_SERVERROOT environment variable */
116  mime_type_t	*src,			/* Source type */
117		*dst;			/* Destination type */
118  cups_array_t	*filters;		/* Filters for the file */
119  int		num_options;		/* Number of options */
120  cups_option_t	*options;		/* Options */
121  const char	*ppdfile;		/* PPD file */
122  const char	*title,			/* Title string */
123		*user;			/* Username */
124  int		all_filters,		/* Use all filters */
125		removeppd,		/* Remove PPD file */
126		removeinfile;		/* Remove input file */
127  int		status;			/* Execution status */
128
129
130 /*
131  * Setup defaults...
132  */
133
134  if ((command = strrchr(argv[0], '/')) != NULL)
135    command ++;
136  else
137    command = argv[0];
138
139  printer      = !strcmp(command, "convert") ? "tofile" : "cupsfilter";
140  mime         = NULL;
141  srctype      = NULL;
142  compression  = 0;
143  dsttype      = "application/pdf";
144  infile       = NULL;
145  outfile      = NULL;
146  num_options  = 0;
147  options      = NULL;
148  ppdfile      = NULL;
149  title        = NULL;
150  user         = cupsUser();
151  all_filters  = 0;
152  removeppd    = 0;
153  removeinfile = 0;
154
155  if ((server_root = getenv("CUPS_SERVERROOT")) == NULL)
156    server_root = CUPS_SERVERROOT;
157
158  snprintf(cupsfilesconf, sizeof(cupsfilesconf), "%s/cups-files.conf", server_root);
159
160 /*
161  * Process command-line arguments...
162  */
163
164  _cupsSetLocale(argv);
165
166  for (i = 1; i < argc; i ++)
167    if (argv[i][0] == '-')
168    {
169      for (opt = argv[i] + 1; *opt; opt ++)
170        switch (*opt)
171	{
172	  case '-' : /* Next argument is a filename... */
173	      i ++;
174	      if (i < argc && !infile)
175	        infile = argv[i];
176	      else
177	        usage(opt);
178	      break;
179
180          case 'a' : /* Specify option... */
181	      i ++;
182	      if (i < argc)
183	        num_options = cupsParseOptions(argv[i], num_options, &options);
184	      else
185	        usage(opt);
186	      break;
187
188          case 'c' : /* Specify cups-files.conf file location... */
189	      i ++;
190	      if (i < argc)
191	      {
192	        if (!strcmp(command, "convert"))
193		  num_options = cupsAddOption("copies", argv[i], num_options,
194					      &options);
195		else
196		  strlcpy(cupsfilesconf, argv[i], sizeof(cupsfilesconf));
197	      }
198	      else
199	        usage(opt);
200	      break;
201
202          case 'd' : /* Specify the real printer name */
203	      i ++;
204	      if (i < argc)
205	        printer = argv[i];
206	      else
207	        usage(opt);
208	      break;
209
210	  case 'D' : /* Delete input file after conversion */
211	      removeinfile = 1;
212	      break;
213
214          case 'e' : /* Use every filter from the PPD file */
215	      all_filters = 1;
216	      break;
217
218          case 'f' : /* Specify input file... */
219	      i ++;
220	      if (i < argc && !infile)
221	        infile = argv[i];
222	      else
223	        usage(opt);
224	      break;
225
226          case 'i' : /* Specify source MIME type... */
227	      i ++;
228	      if (i < argc)
229	      {
230	        if (sscanf(argv[i], "%15[^/]/%255s", super, type) != 2)
231		  usage(opt);
232
233                srctype = argv[i];
234	      }
235	      else
236	        usage(opt);
237	      break;
238
239          case 'j' : /* Get job file or specify destination MIME type... */
240              if (strcmp(command, "convert"))
241	      {
242	        i ++;
243		if (i < argc)
244		{
245		  get_job_file(argv[i]);
246		  infile = TempFile;
247		}
248		else
249		  usage(opt);
250
251                break;
252	      }
253
254          case 'm' : /* Specify destination MIME type... */
255	      i ++;
256	      if (i < argc)
257	      {
258	        if (sscanf(argv[i], "%15[^/]/%255s", super, type) != 2)
259		  usage(opt);
260
261                dsttype = argv[i];
262	      }
263	      else
264	        usage(opt);
265	      break;
266
267          case 'n' : /* Specify number of copies... */
268	      i ++;
269	      if (i < argc)
270	        num_options = cupsAddOption("copies", argv[i], num_options,
271		                            &options);
272	      else
273	        usage(opt);
274	      break;
275
276          case 'o' : /* Specify option(s) or output filename */
277	      i ++;
278	      if (i < argc)
279	      {
280	        if (!strcmp(command, "convert"))
281		{
282		  if (outfile)
283		    usage(NULL);
284		  else
285		    outfile = argv[i];
286		}
287		else
288	          num_options = cupsParseOptions(argv[i], num_options,
289		                                 &options);
290	      }
291	      else
292	        usage(opt);
293	      break;
294
295          case 'p' : /* Specify PPD file... */
296          case 'P' : /* Specify PPD file... */
297	      i ++;
298	      if (i < argc)
299	        ppdfile = argv[i];
300	      else
301	        usage(opt);
302	      break;
303
304          case 't' : /* Specify title... */
305          case 'J' : /* Specify title... */
306	      i ++;
307	      if (i < argc)
308	        title = argv[i];
309	      else
310	        usage(opt);
311	      break;
312
313	  case 'u' : /* Delete PPD file after conversion */
314	      removeppd = 1;
315	      break;
316
317          case 'U' : /* Specify username... */
318	      i ++;
319	      if (i < argc)
320	        user = argv[i];
321	      else
322	        usage(opt);
323	      break;
324
325	  default : /* Something we don't understand... */
326	      usage(opt);
327	      break;
328	}
329    }
330    else if (!infile)
331    {
332      if (strcmp(command, "convert"))
333	infile = argv[i];
334      else
335	usage(NULL);
336    }
337    else
338    {
339      _cupsLangPuts(stderr,
340                    _("cupsfilter: Only one filename can be specified."));
341      usage(NULL);
342    }
343
344  if (!infile && !srctype)
345    usage(NULL);
346
347  if (!title)
348  {
349    if (!infile)
350      title = "(stdin)";
351    else if ((title = strrchr(infile, '/')) != NULL)
352      title ++;
353    else
354      title = infile;
355  }
356
357 /*
358  * Load the cups-files.conf file and create the MIME database...
359  */
360
361  if (read_cups_files_conf(cupsfilesconf))
362    return (1);
363
364  snprintf(mimedir, sizeof(mimedir), "%s/mime", DataDir);
365
366  mime = mimeLoadTypes(NULL, mimedir);
367  mime = mimeLoadTypes(mime, ServerRoot);
368  mime = mimeLoadFilters(mime, mimedir, Path);
369  mime = mimeLoadFilters(mime, ServerRoot, Path);
370
371  if (!mime)
372  {
373    _cupsLangPrintf(stderr,
374                    _("%s: Unable to read MIME database from \"%s\" or "
375		      "\"%s\"."),
376		    command, mimedir, ServerRoot);
377    return (1);
378  }
379
380  prefilter_type = NULL;
381
382  if (all_filters)
383    printer_type = add_printer_filters(command, mime, printer, ppdfile,
384				       &prefilter_type);
385  else
386    printer_type   = mimeType(mime, "application", "vnd.cups-postscript");
387
388 /*
389  * Get the source and destination types...
390  */
391
392  if (srctype)
393  {
394   /* sscanf return value already checked above */
395    sscanf(srctype, "%15[^/]/%255s", super, type);
396    if ((src = mimeType(mime, super, type)) == NULL)
397    {
398      _cupsLangPrintf(stderr,
399		      _("%s: Unknown source MIME type %s/%s."),
400		      command, super, type);
401      return (1);
402    }
403  }
404  else if ((src = mimeFileType(mime, infile, infile, &compression)) == NULL)
405  {
406    _cupsLangPrintf(stderr,
407                    _("%s: Unable to determine MIME type of \"%s\"."),
408		    command, infile);
409    return (1);
410  }
411
412 /* sscanf return value already checked above */
413  sscanf(dsttype, "%15[^/]/%255s", super, type);
414  if (!_cups_strcasecmp(super, "printer"))
415    dst = printer_type;
416  else if ((dst = mimeType(mime, super, type)) == NULL)
417  {
418    _cupsLangPrintf(stderr,
419                    _("%s: Unknown destination MIME type %s/%s."),
420		    command, super, type);
421    return (1);
422  }
423
424 /*
425  * Figure out how to filter the file...
426  */
427
428  if (src == dst)
429  {
430   /*
431    * Special case - no filtering needed...
432    */
433
434    filters = cupsArrayNew(NULL, NULL);
435    cupsArrayAdd(filters, &GZIPFilter);
436    GZIPFilter.src = src;
437    GZIPFilter.dst = dst;
438  }
439  else if ((filters = mimeFilter(mime, src, dst, &cost)) == NULL)
440  {
441    _cupsLangPrintf(stderr,
442                    _("%s: No filter to convert from %s/%s to %s/%s."),
443		    command, src->super, src->type, dst->super, dst->type);
444    return (1);
445  }
446  else if (compression)
447    cupsArrayInsert(filters, &GZIPFilter);
448
449  if (prefilter_type)
450  {
451   /*
452    * Add pre-filters...
453    */
454
455    mime_filter_t	*filter,	/* Current filter */
456			*prefilter;	/* Current pre-filter */
457    cups_array_t	*prefilters = cupsArrayNew(NULL, NULL);
458					/* New filters array */
459
460
461    for (filter = (mime_filter_t *)cupsArrayFirst(filters);
462	 filter;
463	 filter = (mime_filter_t *)cupsArrayNext(filters))
464    {
465      if ((prefilter = mimeFilterLookup(mime, filter->src,
466                                        prefilter_type)) != NULL)
467	cupsArrayAdd(prefilters, prefilter);
468
469      cupsArrayAdd(prefilters, filter);
470    }
471
472    cupsArrayDelete(filters);
473    filters = prefilters;
474  }
475
476 /*
477  * Do it!
478  */
479
480  status = exec_filters(src, filters, infile, outfile, ppdfile, printer, user,
481                        title, num_options, options);
482
483 /*
484  * Remove files as needed, then exit...
485  */
486
487  if (TempFile[0])
488    unlink(TempFile);
489
490  if (removeppd && ppdfile)
491    unlink(ppdfile);
492
493  if (removeinfile && infile)
494    unlink(infile);
495
496  return (status);
497}
498
499
500/*
501 * 'add_printer_filter()' - Add a single filters from a PPD file.
502 */
503
504static void
505add_printer_filter(
506    const char  *command,		/* I - Command name */
507    mime_t      *mime,			/* I - MIME database */
508    mime_type_t *filtertype,		/* I - Printer or prefilter MIME type */
509    const char  *filter)		/* I - Filter to add */
510{
511  char		super[MIME_MAX_SUPER],	/* Super-type for filter */
512		type[MIME_MAX_TYPE],	/* Type for filter */
513		dsuper[MIME_MAX_SUPER],	/* Destination super-type for filter */
514		dtype[MIME_MAX_TYPE],	/* Destination type for filter */
515		dest[MIME_MAX_SUPER + MIME_MAX_TYPE + 2],
516					/* Destination super/type */
517		program[1024];		/* Program/filter name */
518  int		cost;			/* Cost of filter */
519  size_t	maxsize = 0;		/* Maximum supported file size */
520  mime_type_t	*temptype,		/* MIME type looping var */
521		*desttype;		/* Destination MIME type */
522  mime_filter_t	*filterptr;		/* MIME filter */
523
524
525 /*
526  * Parse the filter string; it should be in one of the following formats:
527  *
528  *     source/type cost program
529  *     source/type cost maxsize(nnnn) program
530  *     source/type dest/type cost program
531  *     source/type dest/type cost maxsize(nnnn) program
532  */
533
534  if (sscanf(filter, "%15[^/]/%255s%*[ \t]%15[^/]/%255s%d%*[ \t]%1023[^\n]",
535             super, type, dsuper, dtype, &cost, program) == 6)
536  {
537    snprintf(dest, sizeof(dest), "%s/%s/%s", filtertype->type, dsuper, dtype);
538
539    if ((desttype = mimeType(mime, "printer", dest)) == NULL)
540      desttype = mimeAddType(mime, "printer", dest);
541  }
542  else
543  {
544    if (sscanf(filter, "%15[^/]/%255s%d%*[ \t]%1023[^\n]", super, type, &cost,
545               program) == 4)
546    {
547      desttype = filtertype;
548    }
549    else
550    {
551      _cupsLangPrintf(stderr, _("%s: Invalid filter string \"%s\"."), command,
552		      filter);
553      return;
554    }
555  }
556
557  if (!strncmp(program, "maxsize(", 8))
558  {
559    char	*ptr;			/* Pointer into maxsize(nnnn) program */
560
561    maxsize = strtoll(program + 8, &ptr, 10);
562
563    if (*ptr != ')')
564    {
565      printf("testmime: Invalid filter string \"%s\".\n", filter);
566      return;
567    }
568
569    ptr ++;
570    while (_cups_isspace(*ptr))
571      ptr ++;
572
573    _cups_strcpy(program, ptr);
574  }
575
576 /*
577  * See if the filter program exists; if not, stop the printer and flag
578  * the error!
579  */
580
581  if (strcmp(program, "-"))
582  {
583    char filename[1024];		/* Full path to program */
584
585    if (program[0] == '/')
586      strlcpy(filename, program, sizeof(filename));
587    else
588      snprintf(filename, sizeof(filename), "%s/filter/%s", ServerBin, program);
589
590    if (_cupsFileCheck(filename, _CUPS_FILE_CHECK_PROGRAM, !geteuid(), check_cb,
591                       (void *)command))
592      return;
593  }
594
595 /*
596  * Add the filter to the MIME database, supporting wildcards as needed...
597  */
598
599  for (temptype = mimeFirstType(mime);
600       temptype;
601       temptype = mimeNextType(mime))
602    if (((super[0] == '*' && _cups_strcasecmp(temptype->super, "printer")) ||
603         !_cups_strcasecmp(temptype->super, super)) &&
604        (type[0] == '*' || !_cups_strcasecmp(temptype->type, type)))
605    {
606      if (desttype != filtertype)
607      {
608        filterptr = mimeAddFilter(mime, temptype, desttype, cost, program);
609
610        if (!mimeFilterLookup(mime, desttype, filtertype))
611          mimeAddFilter(mime, desttype, filtertype, 0, "-");
612      }
613      else
614        filterptr = mimeAddFilter(mime, temptype, filtertype, cost, program);
615
616      if (filterptr)
617	filterptr->maxsize = maxsize;
618    }
619}
620
621
622/*
623 * 'add_printer_filters()' - Add filters from a PPD file.
624 */
625
626static mime_type_t *			/* O - Printer type or NULL on error */
627add_printer_filters(
628    const char  *command,		/* I - Command name */
629    mime_t      *mime,			/* I - MIME database */
630    const char  *printer,		/* I - Printer name */
631    const char  *ppdfile,		/* I - PPD file */
632    mime_type_t **prefilter_type)	/* O - Prefilter type */
633{
634  ppd_file_t	*ppd;			/* PPD file data */
635  _ppd_cache_t	*pc;			/* Cache data for PPD */
636  const char	*value;			/* Filter definition value */
637  mime_type_t	*printer_type;		/* Printer filter type */
638
639
640  if ((ppd = _ppdOpenFile(ppdfile, _PPD_LOCALIZATION_NONE)) == NULL)
641  {
642    ppd_status_t	status;		/* PPD load status */
643    int			linenum;	/* Line number */
644
645    status = ppdLastError(&linenum);
646    _cupsLangPrintf(stderr, _("%s: Unable to open PPD file: %s on line %d."),
647                    command, ppdErrorString(status), linenum);
648    return (NULL);
649  }
650
651  pc = _ppdCacheCreateWithPPD(ppd);
652  if (!pc)
653    return (NULL);
654
655  printer_type    = mimeAddType(mime, "printer", printer);
656  *prefilter_type = NULL;
657
658  if (pc->filters)
659  {
660    for (value = (const char *)cupsArrayFirst(pc->filters);
661         value;
662         value = (const char *)cupsArrayNext(pc->filters))
663      add_printer_filter(command, mime, printer_type, value);
664  }
665  else
666  {
667    add_printer_filter(command, mime, printer_type,
668                       "application/vnd.cups-raw 0 -");
669    add_printer_filter(command, mime, printer_type,
670                       "application/vnd.cups-postscript 0 -");
671  }
672
673  if (pc->prefilters)
674  {
675    *prefilter_type = mimeAddType(mime, "prefilter", printer);
676
677    for (value = (const char *)cupsArrayFirst(pc->prefilters);
678         value;
679         value = (const char *)cupsArrayNext(pc->prefilters))
680      add_printer_filter(command, mime, *prefilter_type, value);
681  }
682
683  return (printer_type);
684}
685
686
687/*
688 * 'check_cb()' - Callback function for _cupsFileCheck.
689 */
690
691static void
692check_cb(void              *context,	/* I - Context (command name) */
693         _cups_fc_result_t result,	/* I - Result of check */
694	 const char        *message)	/* I - Localized message */
695{
696  (void)result;
697
698  _cupsLangPrintf(stderr, _("%s: %s"), (char *)context, message);
699}
700
701
702/*
703 * 'compare_pids()' - Compare two filter PIDs...
704 */
705
706static int				/* O - Result of comparison */
707compare_pids(mime_filter_t *a,		/* I - First filter */
708             mime_filter_t *b)		/* I - Second filter */
709{
710 /*
711  * Because we're particularly lazy, we store the process ID in the "cost"
712  * variable...
713  */
714
715  return (a->cost - b->cost);
716}
717
718
719/*
720 * 'escape_options()' - Convert an options array to a string.
721 */
722
723static char *				/* O - Option string */
724escape_options(
725    int           num_options,		/* I - Number of options */
726    cups_option_t *options)		/* I - Options */
727{
728  int		i;			/* Looping var */
729  cups_option_t	*option;		/* Current option */
730  int		bytes;			/* Number of bytes needed */
731  char		*s,			/* Option string */
732		*sptr,			/* Pointer into string */
733		*vptr;			/* Pointer into value */
734
735
736 /*
737  * Figure out the worst-case number of bytes we need for the option string.
738  */
739
740  for (i = num_options, option = options, bytes = 1; i > 0; i --, option ++)
741    bytes += 2 * (strlen(option->name) + strlen(option->value)) + 2;
742
743  if ((s = malloc(bytes)) == NULL)
744    return (NULL);
745
746 /*
747  * Copy the options to the string...
748  */
749
750  for (i = num_options, option = options, sptr = s; i > 0; i --, option ++)
751  {
752    if (!strcmp(option->name, "copies"))
753      continue;
754
755    if (sptr > s)
756      *sptr++ = ' ';
757
758    strlcpy(sptr, option->name, bytes - (sptr - s));
759    sptr += strlen(sptr);
760    *sptr++ = '=';
761
762    for (vptr = option->value; *vptr;)
763    {
764      if (strchr("\\ \t\n", *vptr))
765        *sptr++ = '\\';
766
767      *sptr++ = *vptr++;
768    }
769  }
770
771  *sptr = '\0';
772
773  return (s);
774}
775
776
777/*
778 * 'exec_filter()' - Execute a single filter.
779 */
780
781static int				/* O - Process ID or -1 on error */
782exec_filter(const char *filter,		/* I - Filter to execute */
783            char       **argv,		/* I - Argument list */
784	    char       **envp,		/* I - Environment list */
785	    int        infd,		/* I - Stdin file descriptor */
786	    int        outfd)		/* I - Stdout file descriptor */
787{
788  int		pid,			/* Process ID */
789		fd;			/* Temporary file descriptor */
790#if defined(__APPLE__)
791  char		processPath[1024],	/* CFProcessPath environment variable */
792		linkpath[1024];		/* Link path for symlinks... */
793  int		linkbytes;		/* Bytes for link path */
794
795
796 /*
797  * Add special voodoo magic for MacOS X - this allows MacOS X
798  * programs to access their bundle resources properly...
799  */
800
801  if ((linkbytes = readlink(filter, linkpath, sizeof(linkpath) - 1)) > 0)
802  {
803   /*
804    * Yes, this is a symlink to the actual program, nul-terminate and
805    * use it...
806    */
807
808    linkpath[linkbytes] = '\0';
809
810    if (linkpath[0] == '/')
811      snprintf(processPath, sizeof(processPath), "CFProcessPath=%s",
812	       linkpath);
813    else
814      snprintf(processPath, sizeof(processPath), "CFProcessPath=%s/%s",
815	       dirname((char *)filter), linkpath);
816  }
817  else
818    snprintf(processPath, sizeof(processPath), "CFProcessPath=%s", filter);
819
820  envp[0] = processPath;		/* Replace <CFProcessPath> string */
821#endif	/* __APPLE__ */
822
823  if ((pid = fork()) == 0)
824  {
825   /*
826    * Child process goes here...
827    *
828    * Update stdin/stdout/stderr as needed...
829    */
830
831    if (infd != 0)
832    {
833      if (infd < 0)
834        infd = open("/dev/null", O_RDONLY);
835
836      if (infd > 0)
837      {
838        dup2(infd, 0);
839	close(infd);
840      }
841    }
842
843    if (outfd != 1)
844    {
845      if (outfd < 0)
846        outfd = open("/dev/null", O_WRONLY);
847
848      if (outfd > 1)
849      {
850	dup2(outfd, 1);
851	close(outfd);
852      }
853    }
854
855    if ((fd = open("/dev/null", O_RDWR)) > 3)
856    {
857      dup2(fd, 3);
858      close(fd);
859    }
860    fcntl(3, F_SETFL, O_NDELAY);
861
862    if ((fd = open("/dev/null", O_RDWR)) > 4)
863    {
864      dup2(fd, 4);
865      close(fd);
866    }
867    fcntl(4, F_SETFL, O_NDELAY);
868
869   /*
870    * Execute command...
871    */
872
873    execve(filter, argv, envp);
874
875    perror(filter);
876
877    exit(errno);
878  }
879
880  return (pid);
881}
882
883
884/*
885 * 'exec_filters()' - Execute filters for the given file and options.
886 */
887
888static int				/* O - 0 on success, 1 on error */
889exec_filters(mime_type_t   *srctype,	/* I - Source type */
890             cups_array_t  *filters,	/* I - Array of filters to run */
891             const char    *infile,	/* I - File to filter */
892	     const char    *outfile,	/* I - File to create */
893	     const char    *ppdfile,	/* I - PPD file, if any */
894	     const char    *printer,	/* I - Printer name */
895	     const char    *user,	/* I - Username */
896	     const char    *title,	/* I - Job title */
897             int           num_options,	/* I - Number of filter options */
898	     cups_option_t *options)	/* I - Filter options */
899{
900  int		i;			/* Looping var */
901  const char	*argv[8],		/* Command-line arguments */
902		*envp[17],		/* Environment variables */
903		*temp;			/* Temporary string */
904  char		*optstr,		/* Filter options */
905		content_type[1024],	/* CONTENT_TYPE */
906		cups_datadir[1024],	/* CUPS_DATADIR */
907		cups_fontpath[1024],	/* CUPS_FONTPATH */
908		cups_serverbin[1024],	/* CUPS_SERVERBIN */
909		cups_serverroot[1024],	/* CUPS_SERVERROOT */
910		final_content_type[1024] = "",
911					/* FINAL_CONTENT_TYPE */
912		lang[1024],		/* LANG */
913		path[1024],		/* PATH */
914		ppd[1024],		/* PPD */
915		printer_info[255],	/* PRINTER_INFO env variable */
916		printer_location[255],	/* PRINTER_LOCATION env variable */
917		printer_name[255],	/* PRINTER env variable */
918		rip_max_cache[1024],	/* RIP_MAX_CACHE */
919		userenv[1024],		/* USER */
920		program[1024];		/* Program to run */
921  mime_filter_t	*filter,		/* Current filter */
922		*next;			/* Next filter */
923  int		current,		/* Current filter */
924		filterfds[2][2],	/* Pipes for filters */
925		pid,			/* Process ID of filter */
926		status,			/* Exit status */
927		retval;			/* Return value */
928  cups_array_t	*pids;			/* Executed filters array */
929  mime_filter_t	key;			/* Search key for filters */
930  cups_lang_t	*language;		/* Current language */
931  cups_dest_t	*dest;			/* Destination information */
932
933
934 /*
935  * Figure out the final content type...
936  */
937
938  for (filter = (mime_filter_t *)cupsArrayLast(filters);
939       filter && filter->dst;
940       filter = (mime_filter_t *)cupsArrayPrev(filters))
941    if (strcmp(filter->dst->super, "printer"))
942      break;
943
944  if (filter && filter->dst)
945  {
946    const char *ptr;			/* Pointer in type name */
947
948    if ((ptr = strchr(filter->dst->type, '/')) != NULL)
949      snprintf(final_content_type, sizeof(final_content_type),
950	       "FINAL_CONTENT_TYPE=%s", ptr + 1);
951    else
952      snprintf(final_content_type, sizeof(final_content_type),
953	       "FINAL_CONTENT_TYPE=%s/%s", filter->dst->super,
954	       filter->dst->type);
955  }
956
957 /*
958  * Remove NULL ("-") filters...
959  */
960
961  for (filter = (mime_filter_t *)cupsArrayFirst(filters);
962       filter;
963       filter = (mime_filter_t *)cupsArrayNext(filters))
964    if (!strcmp(filter->filter, "-"))
965      cupsArrayRemove(filters, filter);
966
967 /*
968  * Setup the filter environment and command-line...
969  */
970
971  optstr = escape_options(num_options, options);
972
973  snprintf(content_type, sizeof(content_type), "CONTENT_TYPE=%s/%s",
974           srctype->super, srctype->type);
975  snprintf(cups_datadir, sizeof(cups_datadir), "CUPS_DATADIR=%s", DataDir);
976  snprintf(cups_fontpath, sizeof(cups_fontpath), "CUPS_FONTPATH=%s", FontPath);
977  snprintf(cups_serverbin, sizeof(cups_serverbin), "CUPS_SERVERBIN=%s",
978           ServerBin);
979  snprintf(cups_serverroot, sizeof(cups_serverroot), "CUPS_SERVERROOT=%s",
980           ServerRoot);
981  language = cupsLangDefault();
982  snprintf(lang, sizeof(lang), "LANG=%s.UTF8", language->language);
983  snprintf(path, sizeof(path), "PATH=%s", Path);
984  if (ppdfile)
985    snprintf(ppd, sizeof(ppd), "PPD=%s", ppdfile);
986  else if ((temp = getenv("PPD")) != NULL)
987    snprintf(ppd, sizeof(ppd), "PPD=%s", temp);
988  else
989#ifdef __APPLE__
990  if (!access("/System/Library/Frameworks/ApplicationServices.framework/"
991	      "Versions/A/Frameworks/PrintCore.framework/Versions/A/"
992	      "Resources/English.lproj/Generic.ppd", 0))
993    strlcpy(ppd, "PPD=/System/Library/Frameworks/ApplicationServices.framework/"
994                 "Versions/A/Frameworks/PrintCore.framework/Versions/A/"
995		 "Resources/English.lproj/Generic.ppd", sizeof(ppd));
996  else
997    strlcpy(ppd, "PPD=/System/Library/Frameworks/ApplicationServices.framework/"
998                 "Versions/A/Frameworks/PrintCore.framework/Versions/A/"
999		 "Resources/Generic.ppd", sizeof(ppd));
1000#else
1001    snprintf(ppd, sizeof(ppd), "PPD=%s/model/laserjet.ppd", DataDir);
1002#endif /* __APPLE__ */
1003  snprintf(rip_max_cache, sizeof(rip_max_cache), "RIP_MAX_CACHE=%s", RIPCache);
1004  snprintf(userenv, sizeof(userenv), "USER=%s", user);
1005
1006  if (printer &&
1007      (dest = cupsGetNamedDest(CUPS_HTTP_DEFAULT, printer, NULL)) != NULL)
1008  {
1009    if ((temp = cupsGetOption("printer-info", dest->num_options,
1010                              dest->options)) != NULL)
1011      snprintf(printer_info, sizeof(printer_info), "PRINTER_INFO=%s", temp);
1012    else
1013      snprintf(printer_info, sizeof(printer_info), "PRINTER_INFO=%s", printer);
1014
1015    if ((temp = cupsGetOption("printer-location", dest->num_options,
1016                              dest->options)) != NULL)
1017      snprintf(printer_location, sizeof(printer_location),
1018               "PRINTER_LOCATION=%s", temp);
1019    else
1020      strlcpy(printer_location, "PRINTER_LOCATION=Unknown",
1021              sizeof(printer_location));
1022  }
1023  else
1024  {
1025    snprintf(printer_info, sizeof(printer_info), "PRINTER_INFO=%s",
1026             printer ? printer : "Unknown");
1027    strlcpy(printer_location, "PRINTER_LOCATION=Unknown",
1028            sizeof(printer_location));
1029  }
1030
1031  snprintf(printer_name, sizeof(printer_name), "PRINTER=%s",
1032	   printer ? printer : "Unknown");
1033
1034  argv[0] = (char *)printer;
1035  argv[1] = "1";
1036  argv[2] = user;
1037  argv[3] = title;
1038  argv[4] = cupsGetOption("copies", num_options, options);
1039  argv[5] = optstr;
1040  argv[6] = infile;
1041  argv[7] = NULL;
1042
1043  if (!argv[4])
1044    argv[4] = "1";
1045
1046  envp[0]  = "<CFProcessPath>";
1047  envp[1]  = content_type;
1048  envp[2]  = cups_datadir;
1049  envp[3]  = cups_fontpath;
1050  envp[4]  = cups_serverbin;
1051  envp[5]  = cups_serverroot;
1052  envp[6]  = lang;
1053  envp[7]  = path;
1054  envp[8]  = ppd;
1055  envp[9]  = printer_info;
1056  envp[10] = printer_location;
1057  envp[11] = printer_name;
1058  envp[12] = rip_max_cache;
1059  envp[13] = userenv;
1060  envp[14] = "CHARSET=utf-8";
1061  if (final_content_type[0])
1062  {
1063    envp[15] = final_content_type;
1064    envp[16] = NULL;
1065  }
1066  else
1067    envp[15] = NULL;
1068
1069  for (i = 0; argv[i]; i ++)
1070    fprintf(stderr, "DEBUG: argv[%d]=\"%s\"\n", i, argv[i]);
1071
1072  for (i = 0; envp[i]; i ++)
1073    fprintf(stderr, "DEBUG: envp[%d]=\"%s\"\n", i, envp[i]);
1074
1075 /*
1076  * Execute all of the filters...
1077  */
1078
1079  pids            = cupsArrayNew((cups_array_func_t)compare_pids, NULL);
1080  current         = 0;
1081  filterfds[0][0] = -1;
1082  filterfds[0][1] = -1;
1083  filterfds[1][0] = -1;
1084  filterfds[1][1] = -1;
1085
1086  if (!infile)
1087    filterfds[0][0] = 0;
1088
1089  for (filter = (mime_filter_t *)cupsArrayFirst(filters);
1090       filter;
1091       filter = next, current = 1 - current)
1092  {
1093    next = (mime_filter_t *)cupsArrayNext(filters);
1094
1095    if (filter->filter[0] == '/')
1096      strlcpy(program, filter->filter, sizeof(program));
1097    else
1098      snprintf(program, sizeof(program), "%s/filter/%s", ServerBin,
1099	       filter->filter);
1100
1101    if (filterfds[!current][1] > 1)
1102    {
1103      close(filterfds[1 - current][0]);
1104      close(filterfds[1 - current][1]);
1105
1106      filterfds[1 - current][0] = -1;
1107      filterfds[1 - current][0] = -1;
1108    }
1109
1110    if (next)
1111      open_pipe(filterfds[1 - current]);
1112    else if (outfile)
1113    {
1114      filterfds[1 - current][1] = open(outfile, O_CREAT | O_TRUNC | O_WRONLY,
1115                                       0666);
1116
1117      if (filterfds[1 - current][1] < 0)
1118        fprintf(stderr, "ERROR: Unable to create \"%s\" - %s\n", outfile,
1119	        strerror(errno));
1120    }
1121    else
1122      filterfds[1 - current][1] = 1;
1123
1124    pid = exec_filter(program, (char **)argv, (char **)envp,
1125                      filterfds[current][0], filterfds[1 - current][1]);
1126
1127    if (pid > 0)
1128    {
1129      fprintf(stderr, "INFO: %s (PID %d) started.\n", filter->filter, pid);
1130
1131      filter->cost = pid;
1132      cupsArrayAdd(pids, filter);
1133    }
1134    else
1135      break;
1136
1137    argv[6] = NULL;
1138  }
1139
1140 /*
1141  * Close remaining pipes...
1142  */
1143
1144  if (filterfds[0][1] > 1)
1145  {
1146    close(filterfds[0][0]);
1147    close(filterfds[0][1]);
1148  }
1149
1150  if (filterfds[1][1] > 1)
1151  {
1152    close(filterfds[1][0]);
1153    close(filterfds[1][1]);
1154  }
1155
1156 /*
1157  * Wait for the children to exit...
1158  */
1159
1160  retval = 0;
1161
1162  while (cupsArrayCount(pids) > 0)
1163  {
1164    if ((pid = wait(&status)) < 0)
1165      continue;
1166
1167    key.cost = pid;
1168    if ((filter = (mime_filter_t *)cupsArrayFind(pids, &key)) != NULL)
1169    {
1170      cupsArrayRemove(pids, filter);
1171
1172      if (status)
1173      {
1174	if (WIFEXITED(status))
1175	  fprintf(stderr, "ERROR: %s (PID %d) stopped with status %d\n",
1176		  filter->filter, pid, WEXITSTATUS(status));
1177	else
1178	  fprintf(stderr, "ERROR: %s (PID %d) crashed on signal %d\n",
1179		  filter->filter, pid, WTERMSIG(status));
1180
1181        retval = 1;
1182      }
1183      else
1184        fprintf(stderr, "INFO: %s (PID %d) exited with no errors.\n",
1185	        filter->filter, pid);
1186    }
1187  }
1188
1189  cupsArrayDelete(pids);
1190
1191  return (retval);
1192}
1193
1194
1195/*
1196 * 'get_job_file()' - Get the specified job file.
1197 */
1198
1199static void
1200get_job_file(const char *job)		/* I - Job ID */
1201{
1202  long		jobid,			/* Job ID */
1203		docnum;			/* Document number */
1204  const char	*jobptr;		/* Pointer into job ID string */
1205  char		uri[1024];		/* job-uri */
1206  http_t	*http;			/* Connection to server */
1207  ipp_t		*request;		/* Request data */
1208  int		tempfd;			/* Temporary file */
1209
1210
1211 /*
1212  * Get the job ID and document number, if any...
1213  */
1214
1215  if ((jobptr = strrchr(job, '-')) != NULL)
1216    jobptr ++;
1217  else
1218    jobptr = job;
1219
1220  jobid = strtol(jobptr, (char **)&jobptr, 10);
1221
1222  if (*jobptr == ',')
1223    docnum = strtol(jobptr + 1, NULL, 10);
1224  else
1225    docnum = 1;
1226
1227  if (jobid < 1 || jobid > INT_MAX)
1228  {
1229    _cupsLangPrintf(stderr, _("cupsfilter: Invalid job ID %d."), (int)jobid);
1230    exit(1);
1231  }
1232
1233  if (docnum < 1 || docnum > INT_MAX)
1234  {
1235    _cupsLangPrintf(stderr, _("cupsfilter: Invalid document number %d."),
1236                    (int)docnum);
1237    exit(1);
1238  }
1239
1240 /*
1241  * Ask the server for the document file...
1242  */
1243
1244  if ((http = httpConnectEncrypt(cupsServer(), ippPort(),
1245                                 cupsEncryption())) == NULL)
1246  {
1247    _cupsLangPrintf(stderr, _("%s: Unable to connect to server."),
1248                    "cupsfilter");
1249    exit(1);
1250  }
1251
1252  request = ippNewRequest(CUPS_GET_DOCUMENT);
1253
1254  snprintf(uri, sizeof(uri), "ipp://localhost/jobs/%d", (int)jobid);
1255
1256  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri", NULL, uri);
1257  ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "document-number",
1258                (int)docnum);
1259
1260  if ((tempfd = cupsTempFd(TempFile, sizeof(TempFile))) == -1)
1261  {
1262    _cupsLangPrintError("ERROR", _("Unable to create temporary file"));
1263    httpClose(http);
1264    exit(1);
1265  }
1266
1267  signal(SIGTERM, sighandler);
1268
1269  ippDelete(cupsDoIORequest(http, request, "/", -1, tempfd));
1270
1271  close(tempfd);
1272
1273  httpClose(http);
1274
1275  if (cupsLastError() != IPP_OK)
1276  {
1277    _cupsLangPrintf(stderr, _("cupsfilter: Unable to get job file - %s"),
1278                    cupsLastErrorString());
1279    unlink(TempFile);
1280    exit(1);
1281  }
1282}
1283
1284
1285/*
1286 * 'open_pipe()' - Create a pipe which is closed on exec.
1287 */
1288
1289static int				/* O - 0 on success, -1 on error */
1290open_pipe(int *fds)			/* O - Pipe file descriptors (2) */
1291{
1292 /*
1293  * Create the pipe...
1294  */
1295
1296  if (pipe(fds))
1297  {
1298    fds[0] = -1;
1299    fds[1] = -1;
1300
1301    return (-1);
1302  }
1303
1304 /*
1305  * Set the "close on exec" flag on each end of the pipe...
1306  */
1307
1308  if (fcntl(fds[0], F_SETFD, fcntl(fds[0], F_GETFD) | FD_CLOEXEC))
1309  {
1310    close(fds[0]);
1311    close(fds[1]);
1312
1313    fds[0] = -1;
1314    fds[1] = -1;
1315
1316    return (-1);
1317  }
1318
1319  if (fcntl(fds[1], F_SETFD, fcntl(fds[1], F_GETFD) | FD_CLOEXEC))
1320  {
1321    close(fds[0]);
1322    close(fds[1]);
1323
1324    fds[0] = -1;
1325    fds[1] = -1;
1326
1327    return (-1);
1328  }
1329
1330 /*
1331  * Return 0 indicating success...
1332  */
1333
1334  return (0);
1335}
1336
1337
1338/*
1339 * 'read_cups_files_conf()' - Read the cups-files.conf file to get the filter settings.
1340 */
1341
1342static int				/* O - 0 on success, 1 on error */
1343read_cups_files_conf(
1344    const char *filename)		/* I - File to read */
1345{
1346  cups_file_t	*fp;			/* cups-files.conf file */
1347  const char	*temp;			/* Temporary string */
1348  char		line[1024],		/* Line from file */
1349		*ptr;			/* Pointer into line */
1350  int		linenum;		/* Current line number */
1351
1352
1353  if ((temp = getenv("CUPS_DATADIR")) != NULL)
1354    set_string(&DataDir, temp);
1355  else
1356    set_string(&DataDir, CUPS_DATADIR);
1357
1358  if ((temp = getenv("CUPS_FONTPATH")) != NULL)
1359    set_string(&FontPath, temp);
1360  else
1361    set_string(&FontPath, CUPS_FONTPATH);
1362
1363  set_string(&RIPCache, "128m");
1364
1365  if ((temp = getenv("CUPS_SERVERBIN")) != NULL)
1366    set_string(&ServerBin, temp);
1367  else
1368    set_string(&ServerBin, CUPS_SERVERBIN);
1369
1370  strlcpy(line, filename, sizeof(line));
1371  if ((ptr = strrchr(line, '/')) != NULL)
1372    *ptr = '\0';
1373  else
1374    getcwd(line, sizeof(line));
1375
1376  set_string(&ServerRoot, line);
1377
1378  if ((fp = cupsFileOpen(filename, "r")) != NULL)
1379  {
1380    linenum = 0;
1381
1382    while (cupsFileGetConf(fp, line, sizeof(line), &ptr, &linenum))
1383    {
1384      if (!_cups_strcasecmp(line, "DataDir"))
1385        set_string(&DataDir, ptr);
1386      else if (!_cups_strcasecmp(line, "FontPath"))
1387        set_string(&FontPath, ptr);
1388      else if (!_cups_strcasecmp(line, "RIPCache"))
1389        set_string(&RIPCache, ptr);
1390      else if (!_cups_strcasecmp(line, "ServerBin"))
1391        set_string(&ServerBin, ptr);
1392      else if (!_cups_strcasecmp(line, "ServerRoot"))
1393        set_string(&ServerRoot, ptr);
1394    }
1395
1396    cupsFileClose(fp);
1397  }
1398
1399  snprintf(line, sizeof(line),
1400           "%s/filter:" CUPS_BINDIR ":" CUPS_SBINDIR ":/bin:/usr/bin",
1401	   ServerBin);
1402  set_string(&Path, line);
1403
1404  return (0);
1405}
1406
1407
1408/*
1409 * 'set_string()' - Copy and set a string.
1410 */
1411
1412static void
1413set_string(char       **s,		/* O - Copy of string */
1414           const char *val)		/* I - String to copy */
1415{
1416  if (*s)
1417    free(*s);
1418
1419  *s = strdup(val);
1420}
1421
1422
1423/*
1424 * 'sighandler()' - Signal catcher for when we print from stdin...
1425 */
1426
1427static void
1428sighandler(int s)			/* I - Signal number */
1429{
1430 /*
1431  * Remove the temporary file we're using to print a job file...
1432  */
1433
1434  if (TempFile[0])
1435    unlink(TempFile);
1436
1437 /*
1438  * Exit...
1439  */
1440
1441  exit(s);
1442}
1443
1444
1445/*
1446 * 'usage()' - Show program usage...
1447 */
1448
1449static void
1450usage(const char *opt)			/* I - Incorrect option, if any */
1451{
1452  if (opt)
1453    _cupsLangPrintf(stderr, _("%s: Unknown option \"%c\"."), "cupsfilter",
1454                    *opt);
1455
1456  _cupsLangPuts(stdout, _("Usage: cupsfilter [ options ] filename"));
1457  _cupsLangPuts(stdout, _("Options:"));
1458  _cupsLangPuts(stdout, _("  -D                      Remove the input file "
1459			  "when finished."));
1460  _cupsLangPuts(stdout, _("  -P filename.ppd         Set PPD file."));
1461  _cupsLangPuts(stdout, _("  -U username             Specify username."));
1462  _cupsLangPuts(stdout, _("  -c cups-files.conf      Set cups-files.conf file to "
1463			  "use."));
1464  _cupsLangPuts(stdout, _("  -d printer              Use the named "
1465			  "printer."));
1466  _cupsLangPuts(stdout, _("  -e                      Use every filter from "
1467			  "the PPD file."));
1468  _cupsLangPuts(stdout, _("  -i mime/type            Set input MIME type "
1469			  "(otherwise auto-typed)."));
1470  _cupsLangPuts(stdout, _("  -j job-id[,N]           Filter file N from the "
1471			  "specified job (default is file 1)."));
1472  _cupsLangPuts(stdout, _("  -m mime/type            Set output MIME type "
1473			  "(otherwise application/pdf)."));
1474  _cupsLangPuts(stdout, _("  -n copies               Set number of copies."));
1475  _cupsLangPuts(stdout, _("  -o name=value           Set option(s)."));
1476  _cupsLangPuts(stdout, _("  -p filename.ppd         Set PPD file."));
1477  _cupsLangPuts(stdout, _("  -t title                Set title."));
1478  _cupsLangPuts(stdout, _("  -u                      Remove the PPD file "
1479			  "when finished."));
1480
1481  exit(1);
1482}
1483
1484
1485/*
1486 * End of "$Id: cupsfilter.c 11780 2014-03-28 20:51:12Z msweet $".
1487 */
1488