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