Re: [MOD] STR #4028: commandtops not robust against broken PostScript in PPD file

Till Kamppeter <[email protected]>
Newsgroups gmane.comp.printing.cups.bugs
Message-ID <[email protected]>
DO NOT REPLY TO THIS MESSAGE.  INSTEAD, POST ANY RESPONSES TO THE LINK BELOW.

[STR New]

Attached file "commandtops_v3.c"...

Link: http://www.cups.org/str.php?L4028
Version: 1.5.2

_______________________________________________
cups-bugs mailing list
[email protected]
http://lists.easysw.com/mailman/listinfo/cups-bugs
commandtops_v3.c (text/plain, 10.8 KB)
/*
 * "$Id: commandtops.c 9793 2011-05-20 03:49:49Z mike $"
 *
 *   PostScript command filter for CUPS.
 *
 *   Copyright 2008-2011 by Apple Inc.
 *
 *   These coded instructions, statements, and computer programs are the
 *   property of Apple Inc. and are protected by Federal copyright
 *   law.  Distribution and use rights are outlined in the file "LICENSE.txt"
 *   which should have been included with this file.  If this file is
 *   file is missing or damaged, see the license at "http://www.cups.org/".
 *
 *
 * Contents:
 *
 *   main()                 - Process a CUPS command file.
 *   auto_configure()       - Automatically configure the printer using
 *                            PostScript query commands and/or SNMP lookups.
 *   begin_ps()             - Send the standard PostScript prolog.
 *   end_ps()               - Send the standard PostScript trailer.
 *   print_self_test_page() - Print a self-test page.
 *   report_levels()        - Report supply levels.
 */

/*
 * Include necessary headers...
 */

#include <cups/cups-private.h>
#include <cups/ppd.h>
#include <cups/sidechannel.h>


/*
 * Local functions...
 */

static void	auto_configure(ppd_file_t *ppd, const char *user);
static void	begin_ps(ppd_file_t *ppd, const char *user);
static void	end_ps(ppd_file_t *ppd);
static void	print_self_test_page(ppd_file_t *ppd, const char *user);
static void	report_levels(ppd_file_t *ppd, const char *user);


/*
 * 'main()' - Process a CUPS command file.
 */

int					/* O - Exit status */
main(int  argc,				/* I - Number of command-line arguments */
     char *argv[])			/* I - Command-line arguments */
{
  cups_file_t	*fp;			/* Command file */
  char		line[1024],		/* Line from file */
		*value;			/* Value on line */
  int		linenum;		/* Line number in file */
  ppd_file_t	*ppd;			/* PPD file */


 /*
  * Check for valid arguments...
  */

  if (argc < 6 || argc > 7)
  {
   /*
    * We don't have the correct number of arguments; write an error message
    * and return.
    */

    _cupsLangPrintf(stderr,
                    _("Usage: %s job-id user title copies options file"),
                    argv[0]);
    return (1);
  }

 /*
  * Open the PPD file...
  */

  if ((ppd = ppdOpenFile(getenv("PPD"))) == NULL)
  {
    fputs("ERROR: Unable to open PPD file!\n", stderr);
    return (1);
  }

 /*
  * Open the command file as needed...
  */

  if (argc == 7)
  {
    if ((fp = cupsFileOpen(argv[6], "r")) == NULL)
    {
      perror("ERROR: Unable to open command file - ");
      return (1);
    }
  }
  else
    fp = cupsFileStdin();

 /*
  * Read the commands from the file and send the appropriate commands...
  */

  linenum = 0;

  while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum))
  {
   /*
    * Parse the command...
    */

    if (!_cups_strcasecmp(line, "AutoConfigure"))
      auto_configure(ppd, argv[2]);
    else if (!_cups_strcasecmp(line, "PrintSelfTestPage"))
      print_self_test_page(ppd, argv[2]);
    else if (!_cups_strcasecmp(line, "ReportLevels"))
      report_levels(ppd, argv[2]);
    else
      fprintf(stderr, "ERROR: Invalid printer command \"%s\"!\n", line);
  }

  return (0);
}


/*
 * 'auto_configure()' - Automatically configure the printer using PostScript
 *                      query commands and/or SNMP lookups.
 */

static void
auto_configure(ppd_file_t *ppd,		/* I - PPD file */
               const char *user)	/* I - Printing user */
{
  ppd_option_t	*option;		/* Current option in PPD */
  ppd_attr_t	*attr;			/* Query command attribute */
  char		buffer[1024],		/* String buffer */
		*bufptr;		/* Pointer into buffer */
  ssize_t	bytes;			/* Number of bytes read */
  int		datalen;		/* Side-channel data length */
  int		ch;			/* single character equivalent */
  char		*tempptr;		/* Pointer into buffer */


 /*
  * See if the backend supports bidirectional I/O...
  */

  datalen = 1;
  if (cupsSideChannelDoRequest(CUPS_SC_CMD_GET_BIDI, buffer, &datalen,
                               30.0) != CUPS_SC_STATUS_OK ||
      buffer[0] != CUPS_SC_BIDI_SUPPORTED)
  {
    fputs("DEBUG: Unable to auto-configure PostScript Printer - no "
          "bidirectional I/O available!\n", stderr);
    return;
  }

 /*
  * Put the printer in PostScript mode...
  */

  begin_ps(ppd, user);
  fflush(stdout);

 /*
  * Wait for the printer to become connected...
  */

  do
  {
    sleep(1);
    datalen = 1;
  }
  while (cupsSideChannelDoRequest(CUPS_SC_CMD_GET_CONNECTED, buffer, &datalen,
                                  5.0) == CUPS_SC_STATUS_OK && !buffer[0]);
 /*
  * As a lot of printer vendor supplied PPDs have exhibited erroneous PostScript
  * constructs in query code sequences, we make auto configuring (almost) bomb proof even 
  * against syntax errors in PostScript code.
  * As a first step, we establish our own PS error handler.
  */
  puts("/my_handleerror\n"
	"{\n"
	" $error/newerror false put\n"
	" (:PostScript error in \")print query print (\": )print\n"
	" $error/errorname get 128 string cvs print\n"
	" (; offending command:)print $error/command get 128 string cvs print (\n)print flush\n"
	"}bind def\n"
	"errordict/timeout{}put\n"
	"/query (?Unknown) def\n");
  fflush(stdout);
 /*
  * Then loop through every option in the PPD file and ask for the current
  * value...
  */

  fputs("DEBUG: Auto-configuring PostScript printer...\n", stderr);

  for (option = ppdFirstOption(ppd); option; option = ppdNextOption(ppd))
  {
   /*
    * See if we have a query command for this option...
    */

    snprintf(buffer, sizeof(buffer), "?%s", option->keyword);

    if ((attr = ppdFindAttr(ppd, buffer, NULL)) == NULL || !attr->value)
    {
      fprintf(stderr, "DEBUG: Skipping %s option...\n", option->keyword);
      continue;
    }

   /*
    * Send the query code to the printer...
    */

    fprintf(stderr, "DEBUG: Querying %s...\n", option->keyword);
    fprintf(stdout, "/query (?%s)def\n", option->keyword); /* pass the keyword for error reporting */
    puts("/this_query(");
   /*
    * To prevent our own construct from failing due to unpaired parantheses
    * within the query code, we take pains to escape thr "(" and ")".
    */
    fprintf(stderr, "DEBUG: %s\n", attr->value);
    bufptr = attr->value;
    while (*bufptr)
    {
	ch = *bufptr++;
	if (ch == '(' || ch == ')') putchar('\\');
	putchar(ch);
    }
    puts(")def\n{this_query cvx exec}stopped{userdict/my_handleerror get exec}if clear\n");
    fflush(stdout);

    datalen = 0;
    cupsSideChannelDoRequest(CUPS_SC_CMD_DRAIN_OUTPUT, buffer, &datalen, 5.0);

   /*
    * Read the response data...
    */

    bufptr = buffer;
    buffer[0] = '\0';
    while ((bytes = cupsBackChannelRead(bufptr,
					sizeof(buffer) - (bufptr - buffer) - 1,
					10.0)) > 0)
    {
      *(bufptr + bytes) = '\0';

     /*
      * No newline at the end? Go on reading ...
      */

      bufptr += bytes;
      if (bytes == 0 ||
	  (bufptr - buffer < sizeof(buffer) - 1 && 
	   *(bufptr - 1) != '\r' && *(bufptr - 1) != '\n'))
	continue;

      bytes = bufptr - buffer;

     /*
      * Trim whitespace and control characters from both ends...
      */

      for (bufptr --; bufptr >= buffer; bufptr --)
        if (isspace(*bufptr & 255) || iscntrl(*bufptr & 255))
	  *bufptr = '\0';
	else
	  break;

      for (bufptr = buffer; isspace(*bufptr & 255) || iscntrl(*bufptr & 255);
	   bufptr ++);

      fprintf(stderr, "DEBUG: Got \"%s\" (%d bytes)\n", bufptr, (int)bytes);

     /*
      * Skip blank lines...
      */

      if (!*bufptr)
      {
	bufptr = buffer;
	buffer[0] = '\0';
        continue;
      }

     /*
      * PostScript code for this option in the PPD is broken...
      */
      if ((tempptr = strchr(bufptr, ':')) != NULL)
      {
	fprintf(stderr, "DEBUG%s\n", tempptr); /* the interpreter's error message */
	fprintf(stderr, "ERROR: PostScript error in query for option %s, default for this option not set.\n", option->keyword);
	break;
      }

     /*
      * Verify the result is a valid option choice...
      */

      if (!ppdFindChoice(option, bufptr))
      {
	/* Consider "Unknown" as not configured in the printer */
	if (!strcasecmp(bufptr, "Unknown"))
	{
	  fprintf(stderr,
		  "WARNING: Printer answered query for the setting of option %s with \"Unknown\", default for this option not set.\n",
		  option->keyword);
	  break;
	}
	bufptr = buffer;
	buffer[0] = '\0';
        continue;
      }

     /*
      * Write out the result and move on to the next option...
      */

      fprintf(stderr, "DEBUG: Default%s=%s\n", option->keyword, bufptr);
      fprintf(stderr, "PPD: Default%s=%s\n", option->keyword, bufptr);
      break;
    }

   /*
    * Printer did not answer this option's query
    */

    if (bytes <= 0)
      fprintf(stderr,
	      "ERROR: Printer timed out on option %s, default for this option not set.\n",
	      option->keyword);
  }

 /*
  * Finish the job...
  */

  fflush(stdout);
  end_ps(ppd);
}


/*
 * 'begin_ps()' - Send the standard PostScript prolog.
 */

static void
begin_ps(ppd_file_t *ppd,		/* I - PPD file */
         const char *user)		/* I - Username */
{
  (void)user;

  if (ppd->jcl_begin)
  {
    fputs(ppd->jcl_begin, stdout);
    fputs(ppd->jcl_ps, stdout);
  }

  puts("%!");
  puts("userdict dup(\\004)cvn{}put (\\004\\004)cvn{}put\n");
  fflush(stdout);
}


/*
 * 'end_ps()' - Send the standard PostScript trailer.
 */

static void
end_ps(ppd_file_t *ppd)			/* I - PPD file */
{
  if (ppd->jcl_end)
    fputs(ppd->jcl_end, stdout);
  else
    putchar(0x04);

  fflush(stdout);
}


/*
 * 'print_self_test_page()' - Print a self-test page.
 */

static void
print_self_test_page(ppd_file_t *ppd,	/* I - PPD file */
                     const char *user)	/* I - Printing user */
{
 /*
  * Put the printer in PostScript mode...
  */

  begin_ps(ppd, user);

 /*
  * Send a simple file the draws a box around the imageable area and shows
  * the product/interpreter information...
  */

  puts("% You are using the wrong driver for your printer!\n"
       "0 setgray\n"
       "2 setlinewidth\n"
       "initclip newpath clippath gsave stroke grestore pathbbox\n"
       "exch pop exch pop exch 9 add exch 9 sub moveto\n"
       "/Courier findfont 12 scalefont setfont\n"
       "0 -12 rmoveto gsave product show grestore\n"
       "0 -12 rmoveto gsave version show ( ) show revision 20 string cvs show "
       "grestore\n"
       "0 -12 rmoveto gsave serialnumber 20 string cvs show grestore\n"
       "showpage");

 /*
  * Finish the job...
  */

  end_ps(ppd);
}


/*
 * 'report_levels()' - Report supply levels.
 */

static void
report_levels(ppd_file_t *ppd,		/* I - PPD file */
              const char *user)		/* I - Printing user */
{
 /*
  * Put the printer in PostScript mode...
  */

  begin_ps(ppd, user);

 /*
  * Don't bother sending any additional PostScript commands, since we just
  * want the backend to have enough time to collect the supply info.
  */

 /*
  * Finish the job...
  */

  end_ps(ppd);
}


/*
 * End of "$Id: commandtops.c 9793 2011-05-20 03:49:49Z mike $".
 */
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.