/*
 * Filtering program for CUPS.
 *
 * Copyright © 2021-2022 by OpenPrinting
 * Copyright © 2007-2016 by Apple Inc.
 * Copyright © 1997-2006 by Easy Software Products, all rights reserved.
 *
 * Licensed under Apache License v2.0.  See the file "LICENSE" for more information.
 */

/*
 * Include necessary headers...
 */

#include <cups/cups-private.h>
#include <cups/file-private.h>
#include <cups/ppd-private.h>
#include "mime.h"
#include <limits.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/wait.h>
#if defined(__APPLE__)
#  include <libgen.h>
#endif /* __APPLE__ */


/*
 * Local globals...
 */

static char		*DataDir = NULL;/* CUPS_DATADIR environment variable */
static mime_filter_t	GZIPFilter =	/* gziptoany filter */
{
  NULL,					/* Source type */
  NULL,					/* Destination type */
  0,					/* Cost */
  "gziptoany"				/* Filter program to run */
};
static char		*Path = NULL;	/* PATH environment variable */
static char		*ServerBin = NULL;
					/* CUPS_SERVERBIN environment variable */
static char		*ServerRoot = NULL;
					/* CUPS_SERVERROOT environment variable */
static char		TempFile[1024] = "";
					/* Temporary file */


/*
 * Local functions...
 */

static void		add_printer_filter(const char *command, mime_t *mime,
			                   mime_type_t *printer_type,
		                           const char  *filter);
static mime_type_t	*add_printer_filters(const char *command,
					     mime_t *mime, const char *printer,
			                     const char *ppdfile,
					     mime_type_t **prefilter_type);
static void		check_cb(void *context, _cups_fc_result_t result,
				 const char *message);
static int		compare_pids(mime_filter_t *a, mime_filter_t *b);
static char		*escape_options(int num_options, cups_option_t *options);
static int		exec_filter(const char *filter, char **argv,
			            char **envp, int infd, int outfd);
static int		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);
static void		get_job_file(const char *job);
static int		open_pipe(int *fds);
static int		read_cups_files_conf(const char *filename);
static void		set_string(char **s, const char *val);
static void		sighandler(int sig);
static void		usage(const char *opt) _CUPS_NORETURN;


/*
 * 'main()' - Main entry for the test program.
 */

int					/* O - Exit status */
main(int  argc,				/* I - Number of command-line args */
     char *argv[])			/* I - Command-line arguments */
{
  int		i,			/* Looping vars */
		list_filters = 0;	/* Just list the filters? */
  const char	*command,		/* Command name */
		*opt,			/* Current option */
		*printer;		/* Printer name */
  mime_type_t	*printer_type,		/* Printer MIME type */
		*prefilter_type;	/* Printer prefilter MIME type */
  char		*srctype,		/* Source type */
		*dsttype,		/* Destination type */
		super[MIME_MAX_SUPER],	/* Super-type name */
		type[MIME_MAX_TYPE];	/* Type name */
  int		compression;		/* Compression of file */
  int		cost;			/* Cost of filters */
  mime_t	*mime;			/* MIME database */
  char		mimedir[1024];		/* MIME directory */
  char		*infile,		/* File to filter */
		*outfile;		/* File to create */
  char		cupsfilesconf[1024];	/* cups-files.conf file */
  const char	*server_root;		/* CUPS_SERVERROOT environment variable */
  mime_type_t	*src,			/* Source type */
		*dst;			/* Destination type */
  cups_array_t	*filters;		/* Filters for the file */
  int		num_options;		/* Number of options */
  cups_option_t	*options;		/* Options */
  const char	*ppdfile;		/* PPD file */
  const char	*title,			/* Title string */
		*user;			/* Username */
  int		all_filters,		/* Use all filters */
		removeppd,		/* Remove PPD file */
		removeinfile;		/* Remove input file */
  int		status;			/* Execution status */


 /*
  * Setup defaults...
  */

  if ((command = strrchr(argv[0], '/')) != NULL)
    command ++;
  else
    command = argv[0];

  printer      = !strcmp(command, "convert") ? "tofile" : "cupsfilter";
  mime         = NULL;
  srctype      = NULL;
  compression  = 0;
  dsttype      = "application/pdf";
  infile       = NULL;
  outfile      = NULL;
  num_options  = 0;
  options      = NULL;
  ppdfile      = NULL;
  title        = NULL;
  user         = cupsUser();
  all_filters  = 0;
  removeppd    = 0;
  removeinfile = 0;

  if ((server_root = getenv("CUPS_SERVERROOT")) == NULL)
    server_root = CUPS_SERVERROOT;

  snprintf(cupsfilesconf, sizeof(cupsfilesconf), "%s/cups-files.conf", server_root);

 /*
  * Process command-line arguments...
  */

  _cupsSetLocale(argv);

  for (i = 1; i < argc; i ++)
  {
    if (argv[i][0] == '-')
    {
      if (!strcmp(argv[i], "--list-filters"))
      {
        list_filters = 1;
      }
      else if (!strcmp(argv[i], "--"))
      {
	i ++;
	if (i < argc && !infile)
	  infile = argv[i];
	else
	  usage(NULL);
      }
      else
      {
	for (opt = argv[i] + 1; *opt; opt ++)
	{
	  switch (*opt)
	  {
	    case 'a' : /* Specify option... */
		i ++;
		if (i < argc)
		{
		  num_options = cupsParseOptions(argv[i], num_options, &options);
		}
		else
		{
		  _cupsLangPrintf(stderr, _("%s: Error - expected name=value after \"-a\" option."), argv[0]);
		  usage(opt);
		}
		break;

	    case 'c' : /* Specify cups-files.conf file location... */
		i ++;
		if (i < argc)
		{
		  if (!strcmp(command, "convert"))
		    num_options = cupsAddOption("copies", argv[i], num_options, &options);
		  else
		    strlcpy(cupsfilesconf, argv[i], sizeof(cupsfilesconf));
		}
		else
		{
		  _cupsLangPrintf(stderr, _("%s: Error - expected filename after \"-c\" option."), argv[0]);
		  usage(NULL);
		}
		break;

	    case 'd' : /* Specify the real printer name */
		i ++;
		if (i < argc)
		{
		  printer = argv[i];
		}
		else
		{
		  _cupsLangPrintf(stderr, _("%s: Error - expected printer name after \"-d\" option."), argv[0]);
		  usage(NULL);
		}
		break;

	    case 'D' : /* Delete input file after conversion */
		removeinfile = 1;
		break;

	    case 'e' : /* Use every filter from the PPD file */
		all_filters = 1;
		break;

	    case 'f' : /* Specify input file... */
		i ++;
		if (i < argc && !infile)
		{
		  infile = argv[i];
		}
		else
		{
		  _cupsLangPrintf(stderr, _("%s: Error - expected input file after \"-f\" option."), argv[0]);
		  usage(NULL);
		}
		break;

	    case 'i' : /* Specify source MIME type... */
		i ++;
		if (i < argc)
		{
		  if (sscanf(argv[i], "%15[^/]/%255s", super, type) != 2)
		    usage(opt);

		  srctype = argv[i];
		}
		else
		{
		  _cupsLangPrintf(stderr, _("%s: Error - expected source MIME type after \"-i\" option."), argv[0]);
		  usage(NULL);
		}
		break;

	    case 'j' : /* Get job file or specify destination MIME type... */
		if (strcmp(command, "convert"))
		{
		  i ++;
		  if (i < argc)
		  {
		    get_job_file(argv[i]);
		    infile = TempFile;
		  }
		  else
		  {
		    _cupsLangPrintf(stdout, _("%s: Error - expected job-id after \"-j\" option."), argv[0]);
		    usage(NULL);
		  }
		  break;
		}

	    case 'm' : /* Specify destination MIME type... */
		i ++;
		if (i < argc)
		{
		  if (sscanf(argv[i], "%15[^/]/%255s", super, type) != 2)
		    usage(opt);

		  dsttype = argv[i];
		}
		else
		{
		  _cupsLangPrintf(stderr, _("%s: Error - expected destination MIME type after \"-m\" option."), argv[0]);
		  usage(NULL);
		}
		break;

	    case 'n' : /* Specify number of copies... */
		i ++;
		if (i < argc)
		{
		  num_options = cupsAddOption("copies", argv[i], num_options, &options);
		}
		else
		{
		  _cupsLangPrintf(stderr, _("%s: Error - expected number of copies after \"-n\" option."), argv[0]);
		  usage(NULL);
		}
		break;

	    case 'o' : /* Specify option(s) or output filename */
		i ++;
		if (i < argc)
		{
		  if (!strcmp(command, "convert"))
		  {
		    if (outfile)
		      usage(NULL);
		    else
		      outfile = argv[i];
		  }
		  else
		  {
		    num_options = cupsParseOptions(argv[i], num_options, &options);
		  }
		}
		else
		{
		  _cupsLangPrintf(stderr, _("%s: Error - expected name=value after \"-o\" option."), argv[0]);
		  usage(NULL);
		}
		break;

	    case 'p' : /* Specify PPD file... */
	    case 'P' : /* Specify PPD file... */
		i ++;
		if (i < argc)
		{
		  ppdfile = argv[i];
		}
		else
		{
		  _cupsLangPrintf(stderr, _("%s: Error - expected PPD file after \"-%c\" option."), argv[0], *opt);
		  usage(NULL);
		}
		break;

	    case 't' : /* Specify title... */
	    case 'J' : /* Specify title... */
		i ++;
		if (i < argc)
		{
		  title = argv[i];
		}
		else
		{
		  _cupsLangPrintf(stderr, _("%s: Error - expected title after \"-%c\" option."), argv[0], *opt);
		  usage(NULL);
		}
		break;

	    case 'u' : /* Delete PPD file after conversion */
		removeppd = 1;
		break;

	    case 'U' : /* Specify username... */
		i ++;
		if (i < argc)
		{
		  user = argv[i];
		}
		else
		{
		  _cupsLangPrintf(stderr, _("%s: Error - expected \"username\" after \"-U\" option."), argv[0]);
		  usage(NULL);
		}
		break;

	    default : /* Something we don't understand... */
		_cupsLangPrintf(stderr, _("%s: Error - unknown option \"-%c\"."), argv[0], *opt);
		usage(NULL);
	  }
	}
      }
    }
    else if (!infile)
    {
      if (strcmp(command, "convert"))
	infile = argv[i];
      else
	usage(NULL);
    }
    else
    {
      _cupsLangPuts(stderr, _("cupsfilter: Only one filename can be specified."));
      usage(NULL);
    }
  }

  if (!infile && !srctype)
    usage(NULL);

  if (!title)
  {
    if (!infile)
      title = "(stdin)";
    else if ((title = strrchr(infile, '/')) != NULL)
      title ++;
    else
      title = infile;
  }

 /*
  * Load the cups-files.conf file and create the MIME database...
  */

  if (read_cups_files_conf(cupsfilesconf))
    return (1);

  snprintf(mimedir, sizeof(mimedir), "%s/mime", DataDir);

  mime = mimeLoadTypes(NULL, mimedir);
  mime = mimeLoadTypes(mime, ServerRoot);
  mime = mimeLoadFilters(mime, mimedir, Path);
  mime = mimeLoadFilters(mime, ServerRoot, Path);

  if (!mime)
  {
    _cupsLangPrintf(stderr, _("%s: Unable to read MIME database from \"%s\" or \"%s\"."), command, mimedir, ServerRoot);
    return (1);
  }

  prefilter_type = NULL;

  if (all_filters)
    printer_type = add_printer_filters(command, mime, printer, ppdfile,
				       &prefilter_type);
  else
    printer_type   = mimeType(mime, "application", "vnd.cups-postscript");

 /*
  * Get the source and destination types...
  */

  if (srctype)
  {
   /* sscanf return value already checked above */
    sscanf(srctype, "%15[^/]/%255s", super, type);
    if ((src = mimeType(mime, super, type)) == NULL)
    {
      _cupsLangPrintf(stderr,
		      _("%s: Unknown source MIME type %s/%s."),
		      command, super, type);
      return (1);
    }
  }
  else if ((src = mimeFileType(mime, infile, infile, &compression)) == NULL)
  {
    _cupsLangPrintf(stderr,
                    _("%s: Unable to determine MIME type of \"%s\"."),
		    command, infile);
    return (1);
  }

 /* sscanf return value already checked above */
  sscanf(dsttype, "%15[^/]/%255s", super, type);
  if (!_cups_strcasecmp(super, "printer"))
    dst = printer_type;
  else if ((dst = mimeType(mime, super, type)) == NULL)
  {
    _cupsLangPrintf(stderr,
                    _("%s: Unknown destination MIME type %s/%s."),
		    command, super, type);
    return (1);
  }

 /*
  * Figure out how to filter the file...
  */

  if (src == dst)
  {
   /*
    * Special case - no filtering needed...
    */

    filters = cupsArrayNew(NULL, NULL);
    cupsArrayAdd(filters, &GZIPFilter);
    GZIPFilter.src = src;
    GZIPFilter.dst = dst;
  }
  else if ((filters = mimeFilter(mime, src, dst, &cost)) == NULL)
  {
    _cupsLangPrintf(stderr,
                    _("%s: No filter to convert from %s/%s to %s/%s."),
		    command, src->super, src->type, dst->super, dst->type);
    return (1);
  }
  else if (compression)
    cupsArrayInsert(filters, &GZIPFilter);

  if (prefilter_type)
  {
   /*
    * Add pre-filters...
    */

    mime_filter_t	*filter,	/* Current filter */
			*prefilter;	/* Current pre-filter */
    cups_array_t	*prefilters = cupsArrayNew(NULL, NULL);
					/* New filters array */


    for (filter = (mime_filter_t *)cupsArrayFirst(filters);
	 filter;
	 filter = (mime_filter_t *)cupsArrayNext(filters))
    {
      if ((prefilter = mimeFilterLookup(mime, filter->src,
                                        prefilter_type)) != NULL)
	cupsArrayAdd(prefilters, prefilter);

      cupsArrayAdd(prefilters, filter);
    }

    cupsArrayDelete(filters);
    filters = prefilters;
  }

  if (list_filters)
  {
   /*
    * List filters...
    */

    mime_filter_t	*filter;	/* Current filter */

    for (filter = (mime_filter_t *)cupsArrayFirst(filters);
	 filter;
	 filter = (mime_filter_t *)cupsArrayNext(filters))
      if (strcmp(filter->filter, "-"))
        _cupsLangPuts(stdout, filter->filter);

    status = 0;
  }
  else
  {
   /*
    * Run filters...
    */

    status = exec_filters(src, filters, infile, outfile, ppdfile, printer, user,
			  title, num_options, options);
  }

 /*
  * Remove files as needed, then exit...
  */

  if (TempFile[0])
    unlink(TempFile);

  if (removeppd && ppdfile)
    unlink(ppdfile);

  if (removeinfile && infile)
    unlink(infile);

  return (status);
}


/*
 * 'add_printer_filter()' - Add a single filters from a PPD file.
 */

static void
add_printer_filter(
    const char  *command,		/* I - Command name */
    mime_t      *mime,			/* I - MIME database */
    mime_type_t *filtertype,		/* I - Printer or prefilter MIME type */
    const char  *filter)		/* I - Filter to add */
{
  char		super[MIME_MAX_SUPER],	/* Super-type for filter */
		type[MIME_MAX_TYPE],	/* Type for filter */
		dsuper[MIME_MAX_SUPER],	/* Destination super-type for filter */
		dtype[MIME_MAX_TYPE],	/* Destination type for filter */
		dest[MIME_MAX_SUPER + MIME_MAX_TYPE + 2],
					/* Destination super/type */
		program[1024];		/* Program/filter name */
  int		cost;			/* Cost of filter */
  size_t	maxsize = 0;		/* Maximum supported file size */
  mime_type_t	*temptype,		/* MIME type looping var */
		*desttype;		/* Destination MIME type */
  mime_filter_t	*filterptr;		/* MIME filter */


 /*
  * Parse the filter string; it should be in one of the following formats:
  *
  *     source/type cost program
  *     source/type cost maxsize(nnnn) program
  *     source/type dest/type cost program
  *     source/type dest/type cost maxsize(nnnn) program
  */

  if (sscanf(filter, "%15[^/]/%255s%*[ \t]%15[^/]/%255s%d%*[ \t]%1023[^\n]",
             super, type, dsuper, dtype, &cost, program) == 6)
  {
    snprintf(dest, sizeof(dest), "%s/%s/%s", filtertype->type, dsuper, dtype);

    if ((desttype = mimeType(mime, "printer", dest)) == NULL)
      desttype = mimeAddType(mime, "printer", dest);
  }
  else
  {
    if (sscanf(filter, "%15[^/]/%255s%d%*[ \t]%1023[^\n]", super, type, &cost,
               program) == 4)
    {
      desttype = filtertype;
    }
    else
    {
      _cupsLangPrintf(stderr, _("%s: Invalid filter string \"%s\"."), command,
		      filter);
      return;
    }
  }

  if (!strncmp(program, "maxsize(", 8))
  {
    char	*ptr;			/* Pointer into maxsize(nnnn) program */

    maxsize = (size_t)strtoll(program + 8, &ptr, 10);

    if (*ptr != ')')
    {
      printf("testmime: Invalid filter string \"%s\".\n", filter);
      return;
    }

    ptr ++;
    while (_cups_isspace(*ptr))
      ptr ++;

    _cups_strcpy(program, ptr);
  }

 /*
  * See if the filter program exists; if not, stop the printer and flag
  * the error!
  */

  if (strcmp(program, "-"))
  {
    char filename[1024];		/* Full path to program */

    if (program[0] == '/')
      strlcpy(filename, program, sizeof(filename));
    else
      snprintf(filename, sizeof(filename), "%s/filter/%s", ServerBin, program);

    if (_cupsFileCheck(filename, _CUPS_FILE_CHECK_PROGRAM, !geteuid(), check_cb,
                       (void *)command))
      return;
  }

 /*
  * Add the filter to the MIME database, supporting wildcards as needed...
  */

  for (temptype = mimeFirstType(mime);
       temptype;
       temptype = mimeNextType(mime))
    if (((super[0] == '*' && _cups_strcasecmp(temptype->super, "printer")) ||
         !_cups_strcasecmp(temptype->super, super)) &&
        (type[0] == '*' || !_cups_strcasecmp(temptype->type, type)))
    {
      if (desttype != filtertype)
      {
        filterptr = mimeAddFilter(mime, temptype, desttype, cost, program);

        if (!mimeFilterLookup(mime, desttype, filtertype))
          mimeAddFilter(mime, desttype, filtertype, 0, "-");
      }
      else
        filterptr = mimeAddFilter(mime, temptype, filtertype, cost, program);

      if (filterptr)
	filterptr->maxsize = maxsize;
    }
}


/*
 * 'add_printer_filters()' - Add filters from a PPD file.
 */

static mime_type_t *			/* O - Printer type or NULL on error */
add_printer_filters(
    const char  *command,		/* I - Command name */
    mime_t      *mime,			/* I - MIME database */
    const char  *printer,		/* I - Printer name */
    const char  *ppdfile,		/* I - PPD file */
    mime_type_t **prefilter_type)	/* O - Prefilter type */
{
  ppd_file_t	*ppd;			/* PPD file data */
  _ppd_cache_t	*pc;			/* Cache data for PPD */
  const char	*value;			/* Filter definition value */
  mime_type_t	*printer_type;		/* Printer filter type */


  if ((ppd = _ppdOpenFile(ppdfile, _PPD_LOCALIZATION_NONE)) == NULL)
  {
    ppd_status_t	status;		/* PPD load status */
    int			linenum;	/* Line number */

    status = ppdLastError(&linenum);
    _cupsLangPrintf(stderr, _("%s: Unable to open PPD file: %s on line %d."),
                    command, ppdErrorString(status), linenum);
    return (NULL);
  }

  pc = _ppdCacheCreateWithPPD(ppd);
  if (!pc)
    return (NULL);

  printer_type    = mimeAddType(mime, "printer", printer);
  *prefilter_type = NULL;

  if (pc->filters)
  {
    for (value = (const char *)cupsArrayFirst(pc->filters);
         value;
         value = (const char *)cupsArrayNext(pc->filters))
      add_printer_filter(command, mime, printer_type, value);
  }
  else
  {
    add_printer_filter(command, mime, printer_type,
                       "application/vnd.cups-raw 0 -");
    add_printer_filter(command, mime, printer_type,
                       "application/vnd.cups-postscript 0 -");
  }

  if (pc->prefilters)
  {
    *prefilter_type = mimeAddType(mime, "prefilter", printer);

    for (value = (const char *)cupsArrayFirst(pc->prefilters);
         value;
         value = (const char *)cupsArrayNext(pc->prefilters))
      add_printer_filter(command, mime, *prefilter_type, value);
  }

  return (printer_type);
}


/*
 * 'check_cb()' - Callback function for _cupsFileCheck.
 */

static void
check_cb(void              *context,	/* I - Context (command name) */
         _cups_fc_result_t result,	/* I - Result of check */
	 const char        *message)	/* I - Localized message */
{
  (void)result;

  _cupsLangPrintf(stderr, _("%s: %s"), (char *)context, message);
}


/*
 * 'compare_pids()' - Compare two filter PIDs...
 */

static int				/* O - Result of comparison */
compare_pids(mime_filter_t *a,		/* I - First filter */
             mime_filter_t *b)		/* I - Second filter */
{
 /*
  * Because we're particularly lazy, we store the process ID in the "cost"
  * variable...
  */

  return (a->cost - b->cost);
}


/*
 * 'escape_options()' - Convert an options array to a string.
 */

static char *				/* O - Option string */
escape_options(
    int           num_options,		/* I - Number of options */
    cups_option_t *options)		/* I - Options */
{
  int		i;			/* Looping var */
  cups_option_t	*option;		/* Current option */
  size_t	bytes;			/* Number of bytes needed */
  char		*s,			/* Option string */
		*sptr,			/* Pointer into string */
		*vptr;			/* Pointer into value */


 /*
  * Figure out the worst-case number of bytes we need for the option string.
  */

  for (i = num_options, option = options, bytes = 1; i > 0; i --, option ++)
    bytes += 2 * (strlen(option->name) + strlen(option->value)) + 2;

  if ((s = malloc(bytes)) == NULL)
    return (NULL);

 /*
  * Copy the options to the string...
  */

  for (i = num_options, option = options, sptr = s; i > 0; i --, option ++)
  {
    if (!strcmp(option->name, "copies"))
      continue;

    if (sptr > s)
      *sptr++ = ' ';

    strlcpy(sptr, option->name, bytes - (size_t)(sptr - s));
    sptr += strlen(sptr);
    *sptr++ = '=';

    for (vptr = option->value; *vptr;)
    {
      if (strchr("\\ \t\n", *vptr))
        *sptr++ = '\\';

      *sptr++ = *vptr++;
    }
  }

  *sptr = '\0';

  return (s);
}


/*
 * 'exec_filter()' - Execute a single filter.
 */

static int				/* O - Process ID or -1 on error */
exec_filter(const char *filter,		/* I - Filter to execute */
            char       **argv,		/* I - Argument list */
	    char       **envp,		/* I - Environment list */
	    int        infd,		/* I - Stdin file descriptor */
	    int        outfd)		/* I - Stdout file descriptor */
{
  int		pid,			/* Process ID */
		fd;			/* Temporary file descriptor */
#if defined(__APPLE__)
  char		processPath[1024],	/* CFProcessPath environment variable */
		linkpath[1024];		/* Link path for symlinks... */
  int		linkbytes;		/* Bytes for link path */


 /*
  * Add special voodoo magic for macOS - this allows macOS
  * programs to access their bundle resources properly...
  */

  if ((linkbytes = readlink(filter, linkpath, sizeof(linkpath) - 1)) > 0)
  {
   /*
    * Yes, this is a symlink to the actual program, nul-terminate and
    * use it...
    */

    linkpath[linkbytes] = '\0';

    if (linkpath[0] == '/')
      snprintf(processPath, sizeof(processPath), "CFProcessPath=%s",
	       linkpath);
    else
      snprintf(processPath, sizeof(processPath), "CFProcessPath=%s/%s",
	       dirname((char *)filter), linkpath);
  }
  else
    snprintf(processPath, sizeof(processPath), "CFProcessPath=%s", filter);

  envp[0] = processPath;		/* Replace <CFProcessPath> string */
#endif	/* __APPLE__ */

  if ((pid = fork()) == 0)
  {
   /*
    * Child process goes here...
    *
    * Update stdin/stdout/stderr as needed...
    */

    if (infd != 0)
    {
      if (infd < 0)
        infd = open("/dev/null", O_RDONLY);

      if (infd > 0)
      {
        dup2(infd, 0);
	close(infd);
      }
    }

    if (outfd != 1)
    {
      if (outfd < 0)
        outfd = open("/dev/null", O_WRONLY);

      if (outfd > 1)
      {
	dup2(outfd, 1);
	close(outfd);
      }
    }

    if ((fd = open("/dev/null", O_RDWR)) > 3)
    {
      dup2(fd, 3);
      close(fd);
    }
    fcntl(3, F_SETFL, O_NDELAY);

    if ((fd = open("/dev/null", O_RDWR)) > 4)
    {
      dup2(fd, 4);
      close(fd);
    }
    fcntl(4, F_SETFL, O_NDELAY);

   /*
    * Execute command...
    */

    execve(filter, argv, envp);

    perror(filter);

    exit(errno);
  }

  return (pid);
}


/*
 * 'exec_filters()' - Execute filters for the given file and options.
 */

static int				/* O - 0 on success, 1 on error */
exec_filters(mime_type_t   *srctype,	/* I - Source type */
             cups_array_t  *filters,	/* I - Array of filters to run */
             const char    *infile,	/* I - File to filter */
	     const char    *outfile,	/* I - File to create */
	     const char    *ppdfile,	/* I - PPD file, if any */
	     const char    *printer,	/* I - Printer name */
	     const char    *user,	/* I - Username */
	     const char    *title,	/* I - Job title */
             int           num_options,	/* I - Number of filter options */
	     cups_option_t *options)	/* I - Filter options */
{
  int		i;			/* Looping var */
  const char	*argv[8],		/* Command-line arguments */
		*envp[21],		/* Environment variables */
		*temp;			/* Temporary string */
  char		*optstr,		/* Filter options */
		content_type[1024],	/* CONTENT_TYPE */
		cups_datadir[1024],	/* CUPS_DATADIR */
		cups_fontpath[1024],	/* CUPS_FONTPATH */
		cups_serverbin[1024],	/* CUPS_SERVERBIN */
		cups_serverroot[1024],	/* CUPS_SERVERROOT */
		final_content_type[1024] = "",
					/* FINAL_CONTENT_TYPE */
		lang[1024],		/* LANG */
		path[1024],		/* PATH */
		ppd[1024],		/* PPD */
		printer_info[255],	/* PRINTER_INFO env variable */
		printer_location[255],	/* PRINTER_LOCATION env variable */
		printer_name[255],	/* PRINTER env variable */
		userenv[1024],		/* USER */
#if CUPS_SNAP
		fontconfig_file[1024],	/* FONTCONFIG_FILE */
		fontconfig_path[1024],	/* FONTCONFIG_PATH */
		fontconfig_sysroot[1024],
					/* FONTCONFIG_SYSROOT */
		ld_library_path[2048],	/* LD_LIBRARY_PATH */
#endif /* CUPS_SNAP */
		program[1024];		/* Program to run */
  mime_filter_t	*filter,		/* Current filter */
		*next;			/* Next filter */
  int		current,		/* Current filter */
		filterfds[2][2],	/* Pipes for filters */
		pid,			/* Process ID of filter */
		status,			/* Exit status */
		retval;			/* Return value */
  cups_array_t	*pids;			/* Executed filters array */
  mime_filter_t	key;			/* Search key for filters */
  cups_lang_t	*language;		/* Current language */
  cups_dest_t	*dest;			/* Destination information */


 /*
  * Figure out the final content type...
  */

  for (filter = (mime_filter_t *)cupsArrayLast(filters);
       filter && filter->dst;
       filter = (mime_filter_t *)cupsArrayPrev(filters))
    if (strcmp(filter->dst->super, "printer"))
      break;

  if (filter && filter->dst)
  {
    const char *ptr;			/* Pointer in type name */

    if ((ptr = strchr(filter->dst->type, '/')) != NULL)
      snprintf(final_content_type, sizeof(final_content_type),
	       "FINAL_CONTENT_TYPE=%s", ptr + 1);
    else
      snprintf(final_content_type, sizeof(final_content_type),
	       "FINAL_CONTENT_TYPE=%s/%s", filter->dst->super,
	       filter->dst->type);
  }

 /*
  * Remove NULL ("-") filters...
  */

  for (filter = (mime_filter_t *)cupsArrayFirst(filters);
       filter;
       filter = (mime_filter_t *)cupsArrayNext(filters))
    if (!strcmp(filter->filter, "-"))
      cupsArrayRemove(filters, filter);

 /*
  * Setup the filter environment and command-line...
  */

  optstr = escape_options(num_options, options);

  snprintf(content_type, sizeof(content_type), "CONTENT_TYPE=%s/%s",
           srctype->super, srctype->type);
  snprintf(cups_datadir, sizeof(cups_datadir), "CUPS_DATADIR=%s", DataDir);
  snprintf(cups_serverbin, sizeof(cups_serverbin), "CUPS_SERVERBIN=%s",
           ServerBin);
  snprintf(cups_serverroot, sizeof(cups_serverroot), "CUPS_SERVERROOT=%s",
           ServerRoot);
  language = cupsLangDefault();
  snprintf(lang, sizeof(lang), "LANG=%s.UTF8", language->language);
  snprintf(path, sizeof(path), "PATH=%s", Path);
  if (ppdfile)
    snprintf(ppd, sizeof(ppd), "PPD=%s", ppdfile);
  else if ((temp = getenv("PPD")) != NULL)
    snprintf(ppd, sizeof(ppd), "PPD=%s", temp);
  else
#ifdef __APPLE__
  if (!access("/System/Library/Frameworks/ApplicationServices.framework/"
	      "Versions/A/Frameworks/PrintCore.framework/Versions/A/"
	      "Resources/English.lproj/Generic.ppd", 0))
    strlcpy(ppd, "PPD=/System/Library/Frameworks/ApplicationServices.framework/"
                 "Versions/A/Frameworks/PrintCore.framework/Versions/A/"
		 "Resources/English.lproj/Generic.ppd", sizeof(ppd));
  else
    strlcpy(ppd, "PPD=/System/Library/Frameworks/ApplicationServices.framework/"
                 "Versions/A/Frameworks/PrintCore.framework/Versions/A/"
		 "Resources/Generic.ppd", sizeof(ppd));
#else
  snprintf(ppd, sizeof(ppd), "PPD=%s/model/laserjet.ppd", DataDir);
#endif /* __APPLE__ */

  snprintf(userenv, sizeof(userenv), "USER=%s", user);

  if (printer &&
      (dest = cupsGetNamedDest(CUPS_HTTP_DEFAULT, printer, NULL)) != NULL)
  {
    if ((temp = cupsGetOption("printer-info", dest->num_options,
                              dest->options)) != NULL)
      snprintf(printer_info, sizeof(printer_info), "PRINTER_INFO=%s", temp);
    else
      snprintf(printer_info, sizeof(printer_info), "PRINTER_INFO=%s", printer);

    if ((temp = cupsGetOption("printer-location", dest->num_options,
                              dest->options)) != NULL)
      snprintf(printer_location, sizeof(printer_location),
               "PRINTER_LOCATION=%s", temp);
    else
      strlcpy(printer_location, "PRINTER_LOCATION=Unknown",
              sizeof(printer_location));
  }
  else
  {
    snprintf(printer_info, sizeof(printer_info), "PRINTER_INFO=%s",
             printer ? printer : "Unknown");
    strlcpy(printer_location, "PRINTER_LOCATION=Unknown",
            sizeof(printer_location));
  }

  snprintf(printer_name, sizeof(printer_name), "PRINTER=%s",
	   printer ? printer : "Unknown");

  argv[0] = (char *)printer;
  argv[1] = "1";
  argv[2] = user;
  argv[3] = title;
  argv[4] = cupsGetOption("copies", num_options, options);
  argv[5] = optstr;
  argv[6] = infile;
  argv[7] = NULL;

  if (!argv[4])
    argv[4] = "1";

  for (i = 0; argv[i]; i ++)
    fprintf(stderr, "DEBUG: argv[%d]=\"%s\"\n", i, argv[i]);

  i = 0;
#ifdef __APPLE__
  envp[i ++] = "<CFProcessPath>";
#endif /* __APPLE__ */
  envp[i ++] = content_type;
  envp[i ++] = cups_datadir;
  envp[i ++] = cups_fontpath;
  envp[i ++] = cups_serverbin;
  envp[i ++] = cups_serverroot;
  envp[i ++] = lang;
  envp[i ++] = path;
  envp[i ++] = ppd;
  envp[i ++] = printer_info;
  envp[i ++] = printer_location;
  envp[i ++] = printer_name;
  envp[i ++] = userenv;
  envp[i ++] = "CHARSET=utf-8";
  if (final_content_type[0])
    envp[i ++] = final_content_type;

#if CUPS_SNAP
  if ((temp = getenv("FONTCONFIG_FILE")) != NULL)
  {
    snprintf(fontconfig_file, sizeof(fontconfig_file), "FONTCONFIG_FILE=%s", temp);
    envp[i ++] = fontconfig_file;
  }
  if ((temp = getenv("FONTCONFIG_PATH")) != NULL)
  {
    snprintf(fontconfig_path, sizeof(fontconfig_path), "FONTCONFIG_PATH=%s", temp);
    envp[i ++] = fontconfig_path;
  }
  if ((temp = getenv("FONTCONFIG_SYSROOT")) != NULL)
  {
    snprintf(fontconfig_sysroot, sizeof(fontconfig_sysroot), "FONTCONFIG_SYSROOT=%s", temp);
    envp[i ++] = fontconfig_sysroot;
  }
  if ((temp = getenv("LD_LIBRARY_PATH")) != NULL)
  {
    snprintf(ld_library_path, sizeof(ld_library_path), "LD_LIBRARY_PATH=%s", temp);
    envp[i ++] = ld_library_path;
  }
#endif /* CUPS_SNAP */

  envp[i] = NULL;

  for (i = 0; envp[i]; i ++)
    fprintf(stderr, "DEBUG: envp[%d]=\"%s\"\n", i, envp[i]);

 /*
  * Execute all of the filters...
  */

  pids            = cupsArrayNew((cups_array_func_t)compare_pids, NULL);
  current         = 0;
  filterfds[0][0] = -1;
  filterfds[0][1] = -1;
  filterfds[1][0] = -1;
  filterfds[1][1] = -1;

  if (!infile)
    filterfds[0][0] = 0;

  for (filter = (mime_filter_t *)cupsArrayFirst(filters);
       filter;
       filter = next, current = 1 - current)
  {
    next = (mime_filter_t *)cupsArrayNext(filters);

    if (filter->filter[0] == '/')
      strlcpy(program, filter->filter, sizeof(program));
    else
      snprintf(program, sizeof(program), "%s/filter/%s", ServerBin,
	       filter->filter);

    if (filterfds[!current][1] > 1)
    {
      close(filterfds[1 - current][0]);
      close(filterfds[1 - current][1]);

      filterfds[1 - current][0] = -1;
      filterfds[1 - current][0] = -1;
    }

    if (next)
      open_pipe(filterfds[1 - current]);
    else if (outfile)
    {
      filterfds[1 - current][1] = open(outfile, O_CREAT | O_TRUNC | O_WRONLY,
                                       0666);

      if (filterfds[1 - current][1] < 0)
        fprintf(stderr, "ERROR: Unable to create \"%s\" - %s\n", outfile,
	        strerror(errno));
    }
    else
      filterfds[1 - current][1] = 1;

    pid = exec_filter(program, (char **)argv, (char **)envp,
                      filterfds[current][0], filterfds[1 - current][1]);

    if (pid > 0)
    {
      fprintf(stderr, "INFO: %s (PID %d) started.\n", filter->filter, pid);

      filter->cost = pid;
      cupsArrayAdd(pids, filter);
    }
    else
      break;

    argv[6] = NULL;
  }

 /*
  * Close remaining pipes...
  */

  if (filterfds[0][1] > 1)
  {
    close(filterfds[0][0]);
    close(filterfds[0][1]);
  }

  if (filterfds[1][1] > 1)
  {
    close(filterfds[1][0]);
    close(filterfds[1][1]);
  }

 /*
  * Wait for the children to exit...
  */

  retval = 0;

  while (cupsArrayCount(pids) > 0)
  {
    if ((pid = wait(&status)) < 0)
      continue;

    key.cost = pid;
    if ((filter = (mime_filter_t *)cupsArrayFind(pids, &key)) != NULL)
    {
      cupsArrayRemove(pids, filter);

      if (status)
      {
	if (WIFEXITED(status))
	  fprintf(stderr, "ERROR: %s (PID %d) stopped with status %d\n",
		  filter->filter, pid, WEXITSTATUS(status));
	else
	  fprintf(stderr, "ERROR: %s (PID %d) crashed on signal %d\n",
		  filter->filter, pid, WTERMSIG(status));

        retval = 1;
      }
      else
        fprintf(stderr, "INFO: %s (PID %d) exited with no errors.\n",
	        filter->filter, pid);
    }
  }

  cupsArrayDelete(pids);

  return (retval);
}


/*
 * 'get_job_file()' - Get the specified job file.
 */

static void
get_job_file(const char *job)		/* I - Job ID */
{
  long		jobid,			/* Job ID */
		docnum;			/* Document number */
  const char	*jobptr;		/* Pointer into job ID string */
  char		uri[1024];		/* job-uri */
  http_t	*http;			/* Connection to server */
  ipp_t		*request;		/* Request data */
  int		tempfd;			/* Temporary file */


 /*
  * Get the job ID and document number, if any...
  */

  if ((jobptr = strrchr(job, '-')) != NULL)
    jobptr ++;
  else
    jobptr = job;

  jobid = strtol(jobptr, (char **)&jobptr, 10);

  if (*jobptr == ',')
    docnum = strtol(jobptr + 1, NULL, 10);
  else
    docnum = 1;

  if (jobid < 1 || jobid > INT_MAX)
  {
    _cupsLangPrintf(stderr, _("cupsfilter: Invalid job ID %d."), (int)jobid);
    exit(1);
  }

  if (docnum < 1 || docnum > INT_MAX)
  {
    _cupsLangPrintf(stderr, _("cupsfilter: Invalid document number %d."),
                    (int)docnum);
    exit(1);
  }

 /*
  * Ask the server for the document file...
  */

  if ((http = httpConnectEncrypt(cupsServer(), ippPort(),
                                 cupsEncryption())) == NULL)
  {
    _cupsLangPrintf(stderr, _("%s: Unable to connect to server."),
                    "cupsfilter");
    exit(1);
  }

  request = ippNewRequest(CUPS_GET_DOCUMENT);

  snprintf(uri, sizeof(uri), "ipp://localhost/jobs/%d", (int)jobid);

  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri", NULL, uri);
  ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "document-number",
                (int)docnum);

  if ((tempfd = cupsTempFd(TempFile, sizeof(TempFile))) == -1)
  {
    _cupsLangPrintError("ERROR", _("Unable to create temporary file"));
    httpClose(http);
    exit(1);
  }

  signal(SIGTERM, sighandler);

  ippDelete(cupsDoIORequest(http, request, "/", -1, tempfd));

  close(tempfd);

  httpClose(http);

  if (cupsLastError() != IPP_OK)
  {
    _cupsLangPrintf(stderr, _("cupsfilter: Unable to get job file - %s"),
                    cupsLastErrorString());
    unlink(TempFile);
    exit(1);
  }
}


/*
 * 'open_pipe()' - Create a pipe which is closed on exec.
 */

static int				/* O - 0 on success, -1 on error */
open_pipe(int *fds)			/* O - Pipe file descriptors (2) */
{
 /*
  * Create the pipe...
  */

  if (pipe(fds))
  {
    fds[0] = -1;
    fds[1] = -1;

    return (-1);
  }

 /*
  * Set the "close on exec" flag on each end of the pipe...
  */

  if (fcntl(fds[0], F_SETFD, fcntl(fds[0], F_GETFD) | FD_CLOEXEC))
  {
    close(fds[0]);
    close(fds[1]);

    fds[0] = -1;
    fds[1] = -1;

    return (-1);
  }

  if (fcntl(fds[1], F_SETFD, fcntl(fds[1], F_GETFD) | FD_CLOEXEC))
  {
    close(fds[0]);
    close(fds[1]);

    fds[0] = -1;
    fds[1] = -1;

    return (-1);
  }

 /*
  * Return 0 indicating success...
  */

  return (0);
}


/*
 * 'read_cups_files_conf()' - Read the cups-files.conf file to get the filter settings.
 */

static int				/* O - 0 on success, 1 on error */
read_cups_files_conf(
    const char *filename)		/* I - File to read */
{
  cups_file_t	*fp;			/* cups-files.conf file */
  const char	*temp;			/* Temporary string */
  char		line[1024],		/* Line from file */
		*ptr;			/* Pointer into line */
  int		linenum;		/* Current line number */


  if ((temp = getenv("CUPS_DATADIR")) != NULL)
    set_string(&DataDir, temp);
  else
    set_string(&DataDir, CUPS_DATADIR);

  if ((temp = getenv("CUPS_SERVERBIN")) != NULL)
    set_string(&ServerBin, temp);
  else
    set_string(&ServerBin, CUPS_SERVERBIN);

  strlcpy(line, filename, sizeof(line));
  if ((ptr = strrchr(line, '/')) != NULL)
    *ptr = '\0';
  else
    getcwd(line, sizeof(line));

  set_string(&ServerRoot, line);

  if ((fp = cupsFileOpen(filename, "r")) != NULL)
  {
    linenum = 0;

    while (cupsFileGetConf(fp, line, sizeof(line), &ptr, &linenum))
    {
      if (!_cups_strcasecmp(line, "DataDir"))
        set_string(&DataDir, ptr);
      else if (!_cups_strcasecmp(line, "ServerBin"))
        set_string(&ServerBin, ptr);
      else if (!_cups_strcasecmp(line, "ServerRoot"))
        set_string(&ServerRoot, ptr);
    }

    cupsFileClose(fp);
  }

#if CUPS_SNAP
  if ((temp = getenv("PATH")) != NULL)
    snprintf(line, sizeof(line), "%s/filter:" CUPS_BINDIR ":" CUPS_SBINDIR ":%s", ServerBin, temp);
  else
#endif /* CUPS_SNAP */
  snprintf(line, sizeof(line), "%s/filter:" CUPS_BINDIR ":" CUPS_SBINDIR ":/bin:/usr/bin", ServerBin);
  set_string(&Path, line);

  return (0);
}


/*
 * 'set_string()' - Copy and set a string.
 */

static void
set_string(char       **s,		/* O - Copy of string */
           const char *val)		/* I - String to copy */
{
  if (*s)
    free(*s);

  *s = strdup(val);
}


/*
 * 'sighandler()' - Signal catcher for when we print from stdin...
 */

static void
sighandler(int s)			/* I - Signal number */
{
 /*
  * Remove the temporary file we're using to print a job file...
  */

  if (TempFile[0])
    unlink(TempFile);

 /*
  * Exit...
  */

  exit(s);
}


/*
 * 'usage()' - Show program usage...
 */

static void
usage(const char *opt)			/* I - Incorrect option, if any */
{
  if (opt)
    _cupsLangPrintf(stderr, _("%s: Unknown option \"%c\"."), "cupsfilter", *opt);

  _cupsLangPuts(stdout, _("Usage: cupsfilter [ options ] [ -- ] filename"));
  _cupsLangPuts(stdout, _("Options:"));
  _cupsLangPuts(stdout, _("  --list-filters          List filters that will be used."));
  _cupsLangPuts(stdout, _("  -D                      Remove the input file when finished."));
  _cupsLangPuts(stdout, _("  -P filename.ppd         Set PPD file."));
  _cupsLangPuts(stdout, _("  -U username             Specify username."));
  _cupsLangPuts(stdout, _("  -c cups-files.conf      Set cups-files.conf file to use."));
  _cupsLangPuts(stdout, _("  -d printer              Use the named printer."));
  _cupsLangPuts(stdout, _("  -e                      Use every filter from the PPD file."));
  _cupsLangPuts(stdout, _("  -i mime/type            Set input MIME type (otherwise auto-typed)."));
  _cupsLangPuts(stdout, _("  -j job-id[,N]           Filter file N from the specified job (default is file 1)."));
  _cupsLangPuts(stdout, _("  -m mime/type            Set output MIME type (otherwise application/pdf)."));
  _cupsLangPuts(stdout, _("  -n copies               Set number of copies."));
  _cupsLangPuts(stdout, _("  -o name=value           Set option(s)."));
  _cupsLangPuts(stdout, _("  -p filename.ppd         Set PPD file."));
  _cupsLangPuts(stdout, _("  -t title                Set title."));
  _cupsLangPuts(stdout, _("  -u                      Remove the PPD file when finished."));

  exit(1);
}
