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