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