Home | History | Annotate | Download | only in filter
      1 /*
      2  * PostScript command filter for CUPS.
      3  *
      4  * Copyright 2008-2014 by Apple Inc.
      5  *
      6  * These coded instructions, statements, and computer programs are the
      7  * property of Apple Inc. and are protected by Federal copyright
      8  * law.  Distribution and use rights are outlined in the file "LICENSE.txt"
      9  * which should have been included with this file.  If this file is
     10  * missing or damaged, see the license at "http://www.cups.org/".
     11  */
     12 
     13 /*
     14  * Include necessary headers...
     15  */
     16 
     17 #include <cups/cups-private.h>
     18 #include <cups/ppd.h>
     19 #include <cups/sidechannel.h>
     20 
     21 
     22 /*
     23  * Local functions...
     24  */
     25 
     26 static int	auto_configure(ppd_file_t *ppd, const char *user);
     27 static void	begin_ps(ppd_file_t *ppd, const char *user);
     28 static void	end_ps(ppd_file_t *ppd);
     29 static void	print_self_test_page(ppd_file_t *ppd, const char *user);
     30 static void	report_levels(ppd_file_t *ppd, const char *user);
     31 
     32 
     33 /*
     34  * 'main()' - Process a CUPS command file.
     35  */
     36 
     37 int					/* O - Exit status */
     38 main(int  argc,				/* I - Number of command-line arguments */
     39      char *argv[])			/* I - Command-line arguments */
     40 {
     41   int		status = 0;		/* Exit status */
     42   cups_file_t	*fp;			/* Command file */
     43   char		line[1024],		/* Line from file */
     44 		*value;			/* Value on line */
     45   int		linenum;		/* Line number in file */
     46   ppd_file_t	*ppd;			/* PPD file */
     47 
     48 
     49  /*
     50   * Check for valid arguments...
     51   */
     52 
     53   if (argc < 6 || argc > 7)
     54   {
     55    /*
     56     * We don't have the correct number of arguments; write an error message
     57     * and return.
     58     */
     59 
     60     _cupsLangPrintf(stderr,
     61                     _("Usage: %s job-id user title copies options [file]"),
     62                     argv[0]);
     63     return (1);
     64   }
     65 
     66  /*
     67   * Open the PPD file...
     68   */
     69 
     70   if ((ppd = ppdOpenFile(getenv("PPD"))) == NULL)
     71   {
     72     fputs("ERROR: Unable to open PPD file!\n", stderr);
     73     return (1);
     74   }
     75 
     76  /*
     77   * Open the command file as needed...
     78   */
     79 
     80   if (argc == 7)
     81   {
     82     if ((fp = cupsFileOpen(argv[6], "r")) == NULL)
     83     {
     84       perror("ERROR: Unable to open command file - ");
     85       return (1);
     86     }
     87   }
     88   else
     89     fp = cupsFileStdin();
     90 
     91  /*
     92   * Read the commands from the file and send the appropriate commands...
     93   */
     94 
     95   linenum = 0;
     96 
     97   while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum))
     98   {
     99    /*
    100     * Parse the command...
    101     */
    102 
    103     if (!_cups_strcasecmp(line, "AutoConfigure"))
    104       status |= auto_configure(ppd, argv[2]);
    105     else if (!_cups_strcasecmp(line, "PrintSelfTestPage"))
    106       print_self_test_page(ppd, argv[2]);
    107     else if (!_cups_strcasecmp(line, "ReportLevels"))
    108       report_levels(ppd, argv[2]);
    109     else
    110     {
    111       _cupsLangPrintFilter(stderr, "ERROR",
    112                            _("Invalid printer command \"%s\"."), line);
    113       status = 1;
    114     }
    115   }
    116 
    117   return (status);
    118 }
    119 
    120 
    121 /*
    122  * 'auto_configure()' - Automatically configure the printer using PostScript
    123  *                      query commands and/or SNMP lookups.
    124  */
    125 
    126 static int				/* O - Exit status */
    127 auto_configure(ppd_file_t *ppd,		/* I - PPD file */
    128                const char *user)	/* I - Printing user */
    129 {
    130   int		status = 0;		/* Exit status */
    131   ppd_option_t	*option;		/* Current option in PPD */
    132   ppd_attr_t	*attr;			/* Query command attribute */
    133   const char	*valptr;		/* Pointer into attribute value */
    134   char		buffer[1024],		/* String buffer */
    135 		*bufptr;		/* Pointer into buffer */
    136   ssize_t	bytes;			/* Number of bytes read */
    137   int		datalen;		/* Side-channel data length */
    138 
    139 
    140  /*
    141   * See if the backend supports bidirectional I/O...
    142   */
    143 
    144   datalen = 1;
    145   if (cupsSideChannelDoRequest(CUPS_SC_CMD_GET_BIDI, buffer, &datalen,
    146                                30.0) != CUPS_SC_STATUS_OK ||
    147       buffer[0] != CUPS_SC_BIDI_SUPPORTED)
    148   {
    149     fputs("DEBUG: Unable to auto-configure PostScript Printer - no "
    150           "bidirectional I/O available!\n", stderr);
    151     return (1);
    152   }
    153 
    154  /*
    155   * Put the printer in PostScript mode...
    156   */
    157 
    158   begin_ps(ppd, user);
    159 
    160  /*
    161   * (STR #4028)
    162   *
    163   * As a lot of PPDs contain bad PostScript query code, we need to prevent one
    164   * bad query sequence from affecting all auto-configuration.  The following
    165   * error handler allows us to log PostScript errors to cupsd.
    166   */
    167 
    168   puts("/cups_handleerror {\n"
    169        "  $error /newerror false put\n"
    170        "  (:PostScript error in \") print cups_query_keyword print (\": ) "
    171        "print\n"
    172        "  $error /errorname get 128 string cvs print\n"
    173        "  (; offending command:) print $error /command get 128 string cvs "
    174        "print (\n) print flush\n"
    175        "} bind def\n"
    176        "errordict /timeout {} put\n"
    177        "/cups_query_keyword (?Unknown) def\n");
    178   fflush(stdout);
    179 
    180  /*
    181   * Wait for the printer to become connected...
    182   */
    183 
    184   do
    185   {
    186     sleep(1);
    187     datalen = 1;
    188   }
    189   while (cupsSideChannelDoRequest(CUPS_SC_CMD_GET_CONNECTED, buffer, &datalen,
    190                                   5.0) == CUPS_SC_STATUS_OK && !buffer[0]);
    191 
    192  /*
    193   * Then loop through every option in the PPD file and ask for the current
    194   * value...
    195   */
    196 
    197   fputs("DEBUG: Auto-configuring PostScript printer...\n", stderr);
    198 
    199   for (option = ppdFirstOption(ppd); option; option = ppdNextOption(ppd))
    200   {
    201    /*
    202     * See if we have a query command for this option...
    203     */
    204 
    205     snprintf(buffer, sizeof(buffer), "?%s", option->keyword);
    206 
    207     if ((attr = ppdFindAttr(ppd, buffer, NULL)) == NULL || !attr->value)
    208     {
    209       fprintf(stderr, "DEBUG: Skipping %s option...\n", option->keyword);
    210       continue;
    211     }
    212 
    213    /*
    214     * Send the query code to the printer...
    215     */
    216 
    217     fprintf(stderr, "DEBUG: Querying %s...\n", option->keyword);
    218 
    219     for (bufptr = buffer, valptr = attr->value; *valptr; valptr ++)
    220     {
    221      /*
    222       * Log the query code, breaking at newlines...
    223       */
    224 
    225       if (*valptr == '\n')
    226       {
    227         *bufptr = '\0';
    228         fprintf(stderr, "DEBUG: %s\\n\n", buffer);
    229         bufptr = buffer;
    230       }
    231       else if (*valptr < ' ')
    232       {
    233         if (bufptr >= (buffer + sizeof(buffer) - 4))
    234         {
    235 	  *bufptr = '\0';
    236 	  fprintf(stderr, "DEBUG: %s\n", buffer);
    237 	  bufptr = buffer;
    238         }
    239 
    240         if (*valptr == '\r')
    241         {
    242           *bufptr++ = '\\';
    243           *bufptr++ = 'r';
    244         }
    245         else if (*valptr == '\t')
    246         {
    247           *bufptr++ = '\\';
    248           *bufptr++ = 't';
    249         }
    250         else
    251         {
    252           *bufptr++ = '\\';
    253           *bufptr++ = '0' + ((*valptr / 64) & 7);
    254           *bufptr++ = '0' + ((*valptr / 8) & 7);
    255           *bufptr++ = '0' + (*valptr & 7);
    256         }
    257       }
    258       else
    259       {
    260         if (bufptr >= (buffer + sizeof(buffer) - 1))
    261         {
    262 	  *bufptr = '\0';
    263 	  fprintf(stderr, "DEBUG: %s\n", buffer);
    264 	  bufptr = buffer;
    265         }
    266 
    267 	*bufptr++ = *valptr;
    268       }
    269     }
    270 
    271     if (bufptr > buffer)
    272     {
    273       *bufptr = '\0';
    274       fprintf(stderr, "DEBUG: %s\n", buffer);
    275     }
    276 
    277     printf("/cups_query_keyword (?%s) def\n", option->keyword);
    278 					/* Set keyword for error reporting */
    279     fputs("{ (", stdout);
    280     for (valptr = attr->value; *valptr; valptr ++)
    281     {
    282       if (*valptr == '(' || *valptr == ')' || *valptr == '\\')
    283         putchar('\\');
    284       putchar(*valptr);
    285     }
    286     fputs(") cvx exec } stopped { cups_handleerror } if clear\n", stdout);
    287     					/* Send query code */
    288     fflush(stdout);
    289 
    290     datalen = 0;
    291     cupsSideChannelDoRequest(CUPS_SC_CMD_DRAIN_OUTPUT, buffer, &datalen, 5.0);
    292 
    293    /*
    294     * Read the response data...
    295     */
    296 
    297     bufptr    = buffer;
    298     buffer[0] = '\0';
    299     while ((bytes = cupsBackChannelRead(bufptr, sizeof(buffer) - (size_t)(bufptr - buffer) - 1, 10.0)) > 0)
    300     {
    301      /*
    302       * No newline at the end? Go on reading ...
    303       */
    304 
    305       bufptr += bytes;
    306       *bufptr = '\0';
    307 
    308       if (bytes == 0 ||
    309           (bufptr > buffer && bufptr[-1] != '\r' && bufptr[-1] != '\n'))
    310 	continue;
    311 
    312      /*
    313       * Trim whitespace and control characters from both ends...
    314       */
    315 
    316       bytes = bufptr - buffer;
    317 
    318       for (bufptr --; bufptr >= buffer; bufptr --)
    319         if (isspace(*bufptr & 255) || iscntrl(*bufptr & 255))
    320 	  *bufptr = '\0';
    321 	else
    322 	  break;
    323 
    324       for (bufptr = buffer; isspace(*bufptr & 255) || iscntrl(*bufptr & 255);
    325 	   bufptr ++);
    326 
    327       if (bufptr > buffer)
    328       {
    329         _cups_strcpy(buffer, bufptr);
    330 	bufptr = buffer;
    331       }
    332 
    333       fprintf(stderr, "DEBUG: Got %d bytes.\n", (int)bytes);
    334 
    335      /*
    336       * Skip blank lines...
    337       */
    338 
    339       if (!buffer[0])
    340         continue;
    341 
    342      /*
    343       * Check the response...
    344       */
    345 
    346       if ((bufptr = strchr(buffer, ':')) != NULL)
    347       {
    348        /*
    349         * PostScript code for this option in the PPD is broken; show the
    350         * interpreter's error message that came back...
    351         */
    352 
    353 	fprintf(stderr, "DEBUG%s\n", bufptr);
    354 	break;
    355       }
    356 
    357      /*
    358       * Verify the result is a valid option choice...
    359       */
    360 
    361       if (!ppdFindChoice(option, buffer))
    362       {
    363 	if (!strcasecmp(buffer, "Unknown"))
    364 	  break;
    365 
    366 	bufptr    = buffer;
    367 	buffer[0] = '\0';
    368         continue;
    369       }
    370 
    371      /*
    372       * Write out the result and move on to the next option...
    373       */
    374 
    375       fprintf(stderr, "PPD: Default%s=%s\n", option->keyword, buffer);
    376       break;
    377     }
    378 
    379    /*
    380     * Printer did not answer this option's query
    381     */
    382 
    383     if (bytes <= 0)
    384     {
    385       fprintf(stderr,
    386 	      "DEBUG: No answer to query for option %s within 10 seconds.\n",
    387 	      option->keyword);
    388       status = 1;
    389     }
    390   }
    391 
    392  /*
    393   * Finish the job...
    394   */
    395 
    396   fflush(stdout);
    397   end_ps(ppd);
    398 
    399  /*
    400   * Return...
    401   */
    402 
    403   if (status)
    404     _cupsLangPrintFilter(stderr, "WARNING",
    405                          _("Unable to configure printer options."));
    406 
    407   return (0);
    408 }
    409 
    410 
    411 /*
    412  * 'begin_ps()' - Send the standard PostScript prolog.
    413  */
    414 
    415 static void
    416 begin_ps(ppd_file_t *ppd,		/* I - PPD file */
    417          const char *user)		/* I - Username */
    418 {
    419   (void)user;
    420 
    421   if (ppd->jcl_begin)
    422   {
    423     fputs(ppd->jcl_begin, stdout);
    424     fputs(ppd->jcl_ps, stdout);
    425   }
    426 
    427   puts("%!");
    428   puts("userdict dup(\\004)cvn{}put (\\004\\004)cvn{}put\n");
    429 
    430   fflush(stdout);
    431 }
    432 
    433 
    434 /*
    435  * 'end_ps()' - Send the standard PostScript trailer.
    436  */
    437 
    438 static void
    439 end_ps(ppd_file_t *ppd)			/* I - PPD file */
    440 {
    441   if (ppd->jcl_end)
    442     fputs(ppd->jcl_end, stdout);
    443   else
    444     putchar(0x04);
    445 
    446   fflush(stdout);
    447 }
    448 
    449 
    450 /*
    451  * 'print_self_test_page()' - Print a self-test page.
    452  */
    453 
    454 static void
    455 print_self_test_page(ppd_file_t *ppd,	/* I - PPD file */
    456                      const char *user)	/* I - Printing user */
    457 {
    458  /*
    459   * Put the printer in PostScript mode...
    460   */
    461 
    462   begin_ps(ppd, user);
    463 
    464  /*
    465   * Send a simple file the draws a box around the imageable area and shows
    466   * the product/interpreter information...
    467   */
    468 
    469   puts("\r%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%"
    470        "%%%%%%%%%%%%%\n"
    471        "\r%%%% If you can read this, you are using the wrong driver for your "
    472        "printer. %%%%\n"
    473        "\r%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%"
    474        "%%%%%%%%%%%%%\n"
    475        "0 setgray\n"
    476        "2 setlinewidth\n"
    477        "initclip newpath clippath gsave stroke grestore pathbbox\n"
    478        "exch pop exch pop exch 9 add exch 9 sub moveto\n"
    479        "/Courier findfont 12 scalefont setfont\n"
    480        "0 -12 rmoveto gsave product show grestore\n"
    481        "0 -12 rmoveto gsave version show ( ) show revision 20 string cvs show "
    482        "grestore\n"
    483        "0 -12 rmoveto gsave serialnumber 20 string cvs show grestore\n"
    484        "showpage");
    485 
    486  /*
    487   * Finish the job...
    488   */
    489 
    490   end_ps(ppd);
    491 }
    492 
    493 
    494 /*
    495  * 'report_levels()' - Report supply levels.
    496  */
    497 
    498 static void
    499 report_levels(ppd_file_t *ppd,		/* I - PPD file */
    500               const char *user)		/* I - Printing user */
    501 {
    502  /*
    503   * Put the printer in PostScript mode...
    504   */
    505 
    506   begin_ps(ppd, user);
    507 
    508  /*
    509   * Don't bother sending any additional PostScript commands, since we just
    510   * want the backend to have enough time to collect the supply info.
    511   */
    512 
    513  /*
    514   * Finish the job...
    515   */
    516 
    517   end_ps(ppd);
    518 }
    519