]> git.ipfire.org Git - thirdparty/cups.git/blob - backend/pap.c
Merge changes from CUPS 1.3.1.
[thirdparty/cups.git] / backend / pap.c
1 /*
2 * "$Id: pap.c 6911 2007-09-04 20:35:08Z mike $"
3 *
4 * © Copyright 2004 Apple Computer, Inc. All rights reserved.
5 *
6 * IMPORTANT: This Apple software is supplied to you by Apple Computer,
7 * Inc. ("Apple") in consideration of your agreement to the following
8 * terms, and your use, installation, modification or redistribution of
9 * this Apple software constitutes acceptance of these terms. If you do
10 * not agree with these terms, please do not use, install, modify or
11 * redistribute this Apple software.
12 *
13 * In consideration of your agreement to abide by the following terms, and
14 * subject to these terms, Apple grants you a personal, non-exclusive
15 * license, under AppleÕs copyrights in this original Apple software (the
16 * "Apple Software"), to use, reproduce, modify and redistribute the Apple
17 * Software, with or without modifications, in source and/or binary forms;
18 * provided that if you redistribute the Apple Software in its entirety and
19 * without modifications, you must retain this notice and the following
20 * text and disclaimers in all such redistributions of the Apple Software.
21 * Neither the name, trademarks, service marks or logos of Apple Computer,
22 * Inc. may be used to endorse or promote products derived from the Apple
23 * Software without specific prior written permission from Apple. Except
24 * as expressly stated in this notice, no other rights or licenses, express
25 * or implied, are granted by Apple herein, including but not limited to
26 * any patent rights that may be infringed by your derivative works or by
27 * other works in which the Apple Software may be incorporated.
28 *
29 * The Apple Software is provided by Apple on an "AS IS" basis. APPLE
30 * MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
31 * THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
32 * FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
33 * OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
34 *
35 * IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
36 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
37 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
38 * INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
39 * MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
40 * AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
41 * STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
42 * POSSIBILITY OF SUCH DAMAGE.
43 *
44 *
45 * This program implements the Printer Access Protocol (PAP) on top of AppleTalk
46 * Transaction Protocol (ATP). If it were to use the blocking pap functions of
47 * the AppleTalk library it would need seperate threads for reading, writing
48 * and status.
49 *
50 * Contents:
51 *
52 * main() - Send a file to the specified Appletalk printer.
53 * listDevices() - List all LaserWriter printers in the local zone.
54 * printFile() - Print file.
55 * papOpen() - Open a pap session to a printer.
56 * papClose() - Close a pap session.
57 * papWrite() - Write bytes to a printer.
58 * papCloseResp() - Send a pap close response.
59 * papSendRequest() - Fomrat and send a pap packet.
60 * papCancelRequest() - Cancel a pending pap request.
61 * sidechannel_request() - Handle side-channel requests.
62 * statusUpdate() - Print printer status to stderr.
63 * parseUri() - Extract the print name and zone from a uri.
64 * addPercentEscapes() - Encode a string with percent escapes.
65 * removePercentEscapes - Remove percent escape sequences from a string.
66 * nbptuple_compare() - Compare routine for qsort.
67 * okayToUseAppleTalk() - Returns true if AppleTalk is available and enabled.
68 * packet_name() - Returns packet name string.
69 * connectTimeout() - Returns the connect timeout preference value.
70 * signalHandler() - handle SIGINT to close the session before quiting.
71 */
72
73 #include <config.h>
74
75 #include <stdio.h>
76 #include <stdlib.h>
77 #include <ctype.h>
78 #include <termios.h>
79 #include <unistd.h>
80 #include <assert.h>
81 #include <signal.h>
82
83 #include <sys/fcntl.h>
84 #include <sys/param.h>
85 #include <sys/time.h>
86 #include <sys/errno.h>
87
88 #include <netat/appletalk.h>
89 #include <netat/atp.h>
90 #include <netat/ddp.h>
91 #include <netat/nbp.h>
92 #include <netat/pap.h>
93
94 #include <cups/cups.h>
95 #include <cups/backend.h>
96 #include <cups/sidechannel.h>
97 #include <cups/i18n.h>
98
99 #include <libkern/OSByteOrder.h>
100
101 #ifdef HAVE_APPLETALK_AT_PROTO_H
102 # include <AppleTalk/at_proto.h>
103 #else
104 /* These definitions come from at_proto.h... */
105 # define ZIP_DEF_INTERFACE NULL
106 enum { RUNNING, NOTLOADED, LOADED, OTHERERROR };
107
108 extern int atp_abort(int fd, at_inet_t *dest, u_short tid);
109 extern int atp_close(int fd);
110 extern int atp_getreq(int fd, at_inet_t *src, char *buf, int *len, int *userdata,
111 int *xo, u_short *tid, u_char *bitmap, int nowait);
112 extern int atp_getresp(int fd, u_short *tid, at_resp_t *resp);
113 extern int atp_look(int fd);
114 extern int atp_open(at_socket *sock);
115 extern int atp_sendreq(int fd, at_inet_t *dest, char *buf, int len,
116 int userdata, int xo, int xo_relt, u_short *tid,
117 at_resp_t *resp, at_retry_t *retry, int nowait);
118 extern int atp_sendrsp(int fd, at_inet_t *dest, int xo, u_short tid,
119 at_resp_t *resp);
120 extern int checkATStack();
121 extern int nbp_lookup(at_entity_t *entity, at_nbptuple_t *buf, int max,
122 at_retry_t *retry);
123 extern int nbp_make_entity(at_entity_t *entity, char *obj, char *type,
124 char *zone);
125 extern int zip_getmyzone(char *ifName, at_nvestr_t *zone);
126 #endif /* HAVE_APPLETALK_AT_PROTO_H */
127
128 #include <CoreFoundation/CFURL.h>
129 #include <CoreFoundation/CFNumber.h>
130 #include <CoreFoundation/CFPreferences.h>
131
132 /* Defines */
133 #define MAX_PRINTERS 500 /* Max number of printers we can lookup */
134 #define PAP_CONNID 0
135 #define PAP_TYPE 1
136 #define PAP_EOF 2
137
138 #define CONNID_OF(p) (((u_char *)&p)[0])
139 #define TYPE_OF(p) (((u_char *)&p)[1])
140 #define SEQUENCE_NUM(p) (((u_char *)&p)[2])
141 #define IS_PAP_EOF(p) (((u_char *)&p)[2])
142
143 #ifndef true
144 #define true 1
145 #define false 0
146 #endif
147
148 /* Globals */
149 int gSockfd = 0; /* Socket descriptor */
150 at_inet_t gSessionAddr = { 0 }; /* Address of the session responding socket */
151 u_char gConnID = 0; /* PAP session connection id */
152 u_short gSendDataID = 0; /* Transaction id of pending send-data request */
153 u_short gTickleID = 0; /* Transaction id of outstanding tickle request*/
154 int gWaitEOF = false; /* Option: wait for a remote's EOF */
155 int gStatusInterval= 5; /* Option: 0=off else seconds between status requests*/
156 int gErrorlogged = false; /* If an error was logged don't send any more INFO messages */
157 int gDebug = 0; /* Option: emit debugging info */
158
159 /* Local functions */
160 static int listDevices(void);
161 static int printFile(char* name, char* type, char* zone, int fdin, int fdout,
162 int fderr, int copies, int argc);
163 static int papOpen(at_nbptuple_t* tuple, u_char* connID, int* fd,
164 at_inet_t* pap_to, u_char* flowQuantum);
165 static int papClose();
166 static int papWrite(int sockfd, at_inet_t* dest, u_short tid, u_char connID,
167 u_char flowQuantum, char* data, int len, int eof);
168 static int papCloseResp(int sockfd, at_inet_t* dest, int xo, u_short tid,
169 u_char connID);
170 static int papSendRequest(int sockfd, at_inet_t* dest, u_char connID,
171 int function, u_char bitmap, int xo, int seqno);
172 static int papCancelRequest(int sockfd, u_short tid);
173 static void sidechannel_request();
174 static void statusUpdate(char* status, u_char statusLen);
175 static int parseUri(const char* argv0, char* name, char* type, char* zone);
176 static int addPercentEscapes(const char* src, char* dst, int dstMax);
177 static int removePercentEscapes(const char* src, char* dst, int dstMax);
178 static int nbptuple_compare(const void *p1, const void *p2);
179 static int okayToUseAppleTalk(void);
180 static const char *packet_name(u_char x);
181 static int connectTimeout(void);
182 static void signalHandler(int sigraised);
183
184
185 /*!
186 * @function main
187 * @abstract Send a file to the specified AppleTalk PAP address.
188 *
189 * Usage: printer-uri job-id user title copies options [file]
190 *
191 * @param argc # of arguments
192 * @param argv array of arguments
193 *
194 * @result A non-zero return value for errors
195 */
196 int main (int argc, const char * argv[])
197 {
198 int err = 0;
199 FILE *fp; /* Print file */
200 int copies; /* Number of copies to print */
201 char name[NBP_NVE_STR_SIZE + 1]; /* +1 for a nul */
202 char type[NBP_NVE_STR_SIZE + 1]; /* +1 for a nul */
203 char zone[NBP_NVE_STR_SIZE + 1]; /* +1 for a nul */
204
205 /* Make sure status messages are not buffered... */
206 setbuf(stderr, NULL);
207
208 if (argc == 1 || (argc == 2 && strcmp(argv[1], "-discover") == 0))
209 {
210 /* If listDevices() didn't find any devices or returns an error output a
211 * legacy style announcement.
212 *
213 */
214 if (listDevices() <= 0)
215 puts("network pap \"Unknown\" \"AppleTalk Printer Access Protocol (pap)\"");
216
217 return 0;
218 }
219
220 if (argc < 6 || argc > 7)
221 {
222 fprintf(stderr, "argc = %d\n", argc);
223 for (err = 0; err < argc; err++) {
224 fprintf(stderr, "%02d:%s\n", err, argv[err]);
225 }
226 fprintf(stderr, "Usage: pap job-id user title copies options [file]\n");
227 exit(EINVAL);
228 }
229
230 /* If we have 7 arguments, print the file named on the command-line.
231 * Otherwise, send stdin instead...
232 */
233 if (argc == 6)
234 {
235 fp = stdin;
236 copies = 1;
237 }
238 else
239 {
240 fprintf(stderr, "DEBUG: opening print file \"%s\"\n", argv[6]);
241
242 /* Try to open the print file... */
243 if ((fp = fopen(argv[6], "rb")) == NULL)
244 {
245 _cupsLangPrintf(stderr,
246 _("ERROR: Unable to open print file \"%s\": %s\n"),
247 argv[6], strerror(errno));
248 return (1);
249 }
250
251 copies = atoi(argv[4]);
252 }
253
254 /* Extract the device name and options from the URI... */
255 parseUri(cupsBackendDeviceURI((char **)argv), name, type, zone);
256
257 err = printFile(name, type, zone, fileno(fp), STDOUT_FILENO, STDERR_FILENO, copies, argc);
258
259 if (fp != stdin)
260 fclose(fp);
261
262 /* Only clear the last status if there wasn't an error */
263 if (err == noErr && !gErrorlogged)
264 fprintf(stderr, "INFO:\n");
265
266 return err;
267 }
268
269
270 /*!
271 * @function listDevices
272 * @abstract Print a list of all LaserWriter type devices registered in the default zone.
273 *
274 * @result A non-zero return value for errors
275 */
276 static int listDevices(void)
277 {
278 int err = noErr;
279 int i;
280 int numberFound;
281
282 at_nvestr_t at_zone;
283 at_entity_t entity;
284 at_nbptuple_t buf[MAX_PRINTERS];
285 at_retry_t retry;
286 char name[NBP_NVE_STR_SIZE+1];
287 char encodedName[(3 * NBP_NVE_STR_SIZE) + 1];
288 char zone[NBP_NVE_STR_SIZE+1];
289 char encodedZone[(3 * NBP_NVE_STR_SIZE) + 1];
290
291 /* Make sure it's okay to use appletalk */
292 if (!okayToUseAppleTalk())
293 {
294 fprintf(stderr, "ERROR: AppleTalk disabled in System Preferences\n");
295 return -1; /* Network is down */
296 }
297
298 if ((err = zip_getmyzone(ZIP_DEF_INTERFACE, &at_zone)) != 0)
299 {
300 perror("ERROR: Unable to get default AppleTalk zone");
301 return -2;
302 }
303
304 memcpy(zone, at_zone.str, MIN(at_zone.len, sizeof(zone)-1));
305 zone[MIN(at_zone.len, sizeof(zone)-1)] = '\0';
306
307 fprintf(stderr, "INFO: Using default AppleTalk zone \"%s\"\n", zone);
308
309 addPercentEscapes(zone, encodedZone, sizeof(encodedZone));
310
311 /* Look up all the printers in our zone */
312 nbp_make_entity(&entity, "=", "LaserWriter", zone);
313 retry.retries = 1;
314 retry.interval = 1;
315 retry.backoff = 1;
316
317 if ((numberFound = nbp_lookup(&entity, buf, MAX_PRINTERS, &retry)) < 0)
318 {
319 perror("ERROR: Unable to lookup AppleTalk printers");
320 return numberFound;
321 }
322
323 if (numberFound >= MAX_PRINTERS)
324 fprintf(stderr, "WARNING: Adding only the first %d printers found", MAX_PRINTERS);
325
326 /* Not required but sort them so they look nice */
327 qsort(buf, numberFound, sizeof(at_nbptuple_t), nbptuple_compare);
328
329 for (i = 0; i < numberFound; i++)
330 {
331 memcpy(name, buf[i].enu_entity.object.str, MIN(buf[i].enu_entity.object.len, sizeof(name)-1));
332 name[MIN(buf[i].enu_entity.object.len, sizeof(name)-1)] = '\0';
333
334 if (addPercentEscapes(name, encodedName, sizeof(encodedName)) == 0)
335 {
336 /* Each line is of the form: "class URI "make model" "info" */
337 char make_model[128], /* Make and model */
338 *ptr;
339
340
341 if ((ptr = strchr(name, ' ')) != NULL)
342 {
343 /*
344 * If the printer name contains spaces, it is probably a make and
345 * model...
346 */
347
348 if (!strncmp(name, "ET00", 4))
349 {
350 /*
351 * Drop leading ethernet address info...
352 */
353
354 strlcpy(make_model, ptr + 1, sizeof(make_model));
355 }
356 else
357 strlcpy(make_model, name, sizeof(make_model));
358 }
359 else
360 strcpy(make_model, "Unknown");
361
362 printf("network pap://%s/%s/LaserWriter \"%s\" \"%s AppleTalk\"\n",
363 encodedZone, encodedName, make_model, name);
364 }
365 }
366 return numberFound;
367 }
368
369
370 /*!
371 * @function printFile
372 * @abstract Open a PAP session and send the data from the input socket to the printer.
373 *
374 * @param name NBP name
375 * @param zone NBP zone
376 * @param type NBP type
377 * @param fdin File descriptor to read data from
378 * @param fdout File descriptor to write printer responses to
379 * @param fderr File descriptor to write printer status to
380 * @param copies # of copies to send (in case in the converter couldn't handle this for us).
381 * @param argc # of command line arguments.
382 *
383 * @result A non-zero return value for errors
384 */
385 static int printFile(char* name, char* type, char* zone, int fdin, int fdout, int fderr, int copies, int argc)
386 {
387 int err;
388 int rc;
389 int val;
390 int len, i;
391
392 char fileBuffer[4096]; /* File buffer */
393 int fileBufferNbytes;
394 off_t fileTbytes;
395 int fileEOFRead;
396 int fileEOFSent;
397
398 char sockBuffer[4096 + 1]; /* Socket buffer with room for nul */
399 char atpReqBuf[AT_PAP_DATA_SIZE];
400 fd_set readSet;
401 int use_sidechannel; /* Use side channel? */
402
403 at_nbptuple_t tuple;
404 at_inet_t sendDataAddr;
405 at_inet_t src;
406 at_resp_t resp;
407 int userdata, xo, reqlen;
408 u_short tid;
409 u_char bitmap;
410 int maxfdp1,
411 nbp_failures = 0;
412 struct timeval timeout, *timeoutPtr;
413 u_char flowQuantum = 1;
414 u_short recvSequence = 0;
415 time_t now,
416 start_time,
417 elasped_time,
418 sleep_time,
419 connect_timeout = -1,
420 nextStatusTime = 0;
421 at_entity_t entity;
422 at_retry_t retry;
423
424 #if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
425 struct sigaction action; /* Actions for POSIX signals */
426 #endif /* HAVE_SIGACTION && !HAVE_SIGSET */
427
428 /*
429 * Test the side channel descriptor before calling papOpen() since it may open
430 * an unused fd 4 (a.k.a. CUPS_SC_FD)...
431 */
432
433 FD_ZERO(&readSet);
434 FD_SET(CUPS_SC_FD, &readSet);
435
436 timeout.tv_sec = 0;
437 timeout.tv_usec = 0;
438
439 if ((select(CUPS_SC_FD+1, &readSet, NULL, NULL, &timeout)) >= 0)
440 use_sidechannel = 1;
441 else
442 use_sidechannel = 0;
443
444 /* try to find our printer */
445 if ((err = nbp_make_entity(&entity, name, type, zone)) != noErr)
446 {
447 fprintf(stderr, "ERROR: Unable to make AppleTalk address: %s\n", strerror(errno));
448 goto Exit;
449 }
450
451 /*
452 * Remember when we started looking for the printer.
453 */
454
455 start_time = time(NULL);
456
457 retry.interval = 1;
458 retry.retries = 5;
459 retry.backoff = 0;
460
461 fprintf(stderr, "STATE: +connecting-to-device\n");
462
463 /* Loop forever trying to get an open session with the printer. */
464 for (;;)
465 {
466 /* Make sure it's okay to use appletalk */
467 if (okayToUseAppleTalk())
468 {
469 /* Clear this printer-state-reason in case we've set it */
470 fprintf(stderr, "STATE: -apple-appletalk-disabled-warning\n");
471
472 /* Resolve the name into an address. Returns the number found or an error */
473 if ((err = nbp_lookup(&entity, &tuple, 1, &retry)) > 0)
474 {
475 if (err > 1)
476 fprintf(stderr, "DEBUG: Found more than one printer with the name \"%s\"\n", name);
477
478 if (nbp_failures)
479 {
480 fprintf(stderr, "STATE: -apple-nbp-lookup-warning\n");
481 nbp_failures = 0;
482 }
483
484 /* Open a connection to the device */
485 if ((err = papOpen(&tuple, &gConnID, &gSockfd, &gSessionAddr, &flowQuantum)) == 0)
486 break;
487
488 fprintf(stderr, "WARNING: Unable to open \"%s:%s\": %s\n", name, zone, strerror(err));
489 }
490 else
491 {
492 /* It's not unusual to have to call nbp_lookup() twice before it's sucessful... */
493 if (++nbp_failures > 2)
494 {
495 retry.interval = 2;
496 retry.retries = 3;
497 fprintf(stderr, "STATE: +apple-nbp-lookup-warning\n");
498 fprintf(stderr, "WARNING: Printer not responding\n");
499 }
500 }
501 }
502 else
503 {
504 fprintf(stderr, "STATE: +apple-appletalk-disabled-warning\n");
505 fprintf(stderr, "INFO: AppleTalk disabled in System Preferences.\n");
506 }
507
508 elasped_time = time(NULL) - start_time;
509
510 if (connect_timeout == -1)
511 connect_timeout = connectTimeout();
512
513 if (connect_timeout && elasped_time > connect_timeout)
514 {
515 fprintf(stderr, "ERROR: Printer not responding\n");
516 err = ETIMEDOUT;
517 goto Exit; /* Waiting too long... */
518 }
519 else if (elasped_time < (30 * 60))
520 sleep_time = 10; /* Waiting < 30 minutes */
521 else if (elasped_time < (24 * 60 * 60))
522 sleep_time = 30; /* Waiting < 24 hours */
523 else
524 sleep_time = 60; /* Waiting > 24 hours */
525
526 fprintf(stderr, "DEBUG: sleeping %d seconds...\n", (int)sleep_time);
527 sleep(sleep_time);
528 }
529
530 fprintf(stderr, "STATE: -connecting-to-device\n");
531
532 /*
533 * Now that we are connected to the printer ignore SIGTERM so that we
534 * can finish out any page data the driver sends (e.g. to eject the
535 * current page... if we are printing data from a file then catch the
536 * signal so we can send a PAP Close packet (otherwise you can't cancel
537 * raw jobs...)
538 */
539
540 #ifdef HAVE_SIGSET /* Use System V signals over POSIX to avoid bugs */
541 sigset(SIGTERM, (argc < 7) ? SIG_IGN : signalHandler);
542 #elif defined(HAVE_SIGACTION)
543 memset(&action, 0, sizeof(action));
544
545 sigemptyset(&action.sa_mask);
546 action.sa_handler = (argc < 7) ? SIG_IGN : signalHandler;
547 sigaction(SIGTERM, &action, NULL);
548 #else
549 signal(SIGTERM, (argc < 7) ? SIG_IGN : signalHandler);
550
551 #ifdef DEBUG
552 /* Makes debugging easier; otherwise printer will be busy for several minutes */
553 signal(SIGINT, signalHandler);
554 #endif /* DEBUG */
555
556 #endif /* HAVE_SIGSET */
557
558 fprintf(stderr, "INFO: Sending data\n");
559
560 sendDataAddr = tuple.enu_addr;
561
562 /* Start the tickle packets and set a timeout alarm */
563 if ((err = papSendRequest(gSockfd, &gSessionAddr, gConnID, AT_PAP_TYPE_TICKLE, 0, false, false)) < 0)
564 {
565 perror("ERROR: Unable to send PAP tickle request");
566 goto Exit;
567 }
568 signal(SIGALRM, signalHandler);
569 alarm(PAP_TIMEOUT);
570
571 /* Prime the pump with an initial send-data packet */
572 if ((err = papSendRequest(gSockfd, &gSessionAddr, gConnID, AT_PAP_TYPE_SEND_DATA, 0xFF, true, true)) < 0)
573 {
574 perror("ERROR: Unable to send initial PAP send data request");
575 goto Exit;
576 }
577
578 /* Set non-blocking mode on our data source descriptor */
579 val = fcntl(fdin, F_GETFL, 0);
580 fcntl(fdin, F_SETFL, val | O_NONBLOCK);
581
582 fileBufferNbytes = 0;
583 fileTbytes = 0;
584 fileEOFRead = fileEOFSent = false;
585
586 maxfdp1 = MAX(fdin, gSockfd) + 1;
587
588 if (use_sidechannel && CUPS_SC_FD >= maxfdp1)
589 maxfdp1 = CUPS_SC_FD + 1;
590
591 if (gStatusInterval != 0)
592 {
593 timeout.tv_usec = 0;
594 nextStatusTime = time(NULL) + gStatusInterval;
595 timeoutPtr = &timeout;
596 }
597 else
598 timeoutPtr = NULL;
599
600
601 for (;;)
602 {
603 /* Set up our descriptors for the select */
604 FD_ZERO(&readSet);
605 FD_SET(gSockfd, &readSet);
606
607 if (fileBufferNbytes == 0 && fileEOFRead == false)
608 FD_SET(fdin, &readSet);
609
610 if (use_sidechannel)
611 FD_SET(CUPS_SC_FD, &readSet);
612
613 /* Set the select timeout value based on the next status interval */
614 if (gStatusInterval != 0)
615 {
616 now = time(NULL);
617 timeout.tv_sec = (nextStatusTime > now) ? nextStatusTime - now : 1;
618 }
619
620 /* Wait here for something interesting to happen */
621 if ((err = select(maxfdp1, &readSet, 0, 0, timeoutPtr)) < 0)
622 {
623 perror("ERROR: select");
624 break;
625 }
626
627 if (err == 0 || (gStatusInterval != 0 && time(NULL) >= nextStatusTime))
628 {
629 /* Time to send a status request */
630 if ((err = papSendRequest(gSockfd, &tuple.enu_addr, 0, AT_PAP_TYPE_SEND_STATUS, 0x01, false, false)) < 0)
631 perror("WARNING: Unable to send PAP status request");
632
633 if (gStatusInterval)
634 nextStatusTime = time(NULL) + gStatusInterval;
635 }
636
637 /*
638 * Check if we have a side-channel request ready...
639 */
640
641 if (use_sidechannel && FD_ISSET(CUPS_SC_FD, &readSet))
642 sidechannel_request();
643
644 /* Was there an event on the input stream? */
645 if (FD_ISSET(fdin, &readSet))
646 {
647 FD_CLR(fdin, &readSet);
648
649 assert(fileBufferNbytes == 0);
650 fileBufferNbytes = read(fdin, fileBuffer, MIN(sizeof(fileBuffer), AT_PAP_DATA_SIZE * flowQuantum));
651 if (fileBufferNbytes == 0)
652 fileEOFRead = true;
653
654 if (fileEOFSent == false && fileBufferNbytes >= 0 && gSendDataID != 0)
655 {
656 fprintf(stderr, "DEBUG: -> PAP_DATA %d bytes %s\n", fileBufferNbytes, fileEOFRead ? "with EOF" : "");
657 papWrite(gSockfd, &sendDataAddr, gSendDataID, gConnID, flowQuantum, fileBuffer, fileBufferNbytes, fileEOFRead);
658
659 fileTbytes += fileBufferNbytes;
660 if (argc > 6 && !gErrorlogged)
661 fprintf(stderr, "DEBUG: Sending print file, %qd bytes\n", (off_t)fileTbytes);
662
663 fileBufferNbytes = 0;
664 gSendDataID = 0;
665 if (fileEOFRead)
666 {
667 fileEOFSent = true;
668 if (gWaitEOF == false || fileTbytes == 0)
669 {
670 err = 0;
671 goto Exit;
672 }
673 }
674 }
675 }
676
677 /* Was there an event on the output stream? */
678 if (FD_ISSET(gSockfd, &readSet))
679 {
680 if ((rc = atp_look(gSockfd)) < 0)
681 {
682 perror("ERROR: Unable to look for PAP response");
683 break;
684 }
685
686 if (rc > 0)
687 {
688 /* It's an ATP response */
689 resp.resp[0].iov_base = sockBuffer;
690 resp.resp[0].iov_len = sizeof(sockBuffer) - 1;
691 resp.bitmap = 0x01;
692
693 if ((err = atp_getresp(gSockfd, &tid, &resp)) < 0)
694 {
695 perror("ERROR: Unable to get PAP response");
696 break;
697 }
698 userdata = resp.userdata[0];
699 }
700 else
701 {
702 /* It's an ATP request */
703 reqlen = sizeof(atpReqBuf);
704 if ((err = atp_getreq(gSockfd, &src, atpReqBuf, &reqlen, &userdata, &xo, &tid, &bitmap, 0)) < 0)
705 {
706 perror("ERROR: Unable to get PAP request");
707 break;
708 }
709 }
710
711 fprintf(stderr, "DEBUG: <- %s\n", packet_name(TYPE_OF(userdata)));
712
713 switch (TYPE_OF(userdata))
714 {
715 case AT_PAP_TYPE_SEND_STS_REPLY: /* Send-Status-Reply packet */
716 if (resp.bitmap & 1)
717 {
718 char *iov_base = (char *)resp.resp[0].iov_base;
719 statusUpdate(&iov_base[5], iov_base[4]);
720 }
721 break;
722
723 case AT_PAP_TYPE_SEND_DATA: /* Send-Data packet */
724 sendDataAddr.socket = src.socket;
725 gSendDataID = tid;
726 recvSequence = OSReadBigInt16(&SEQUENCE_NUM(userdata), 0);
727
728 if ((fileBufferNbytes > 0 || fileEOFRead) && fileEOFSent == false)
729 {
730 fprintf(stderr, "DEBUG: -> PAP_DATA %d bytes %s\n", fileBufferNbytes, fileEOFRead ? "with EOF" : "");
731 papWrite(gSockfd, &sendDataAddr, gSendDataID, gConnID, flowQuantum, fileBuffer, fileBufferNbytes, fileEOFRead);
732
733 fileTbytes += fileBufferNbytes;
734 if (argc > 6 && !gErrorlogged)
735 fprintf(stderr, "DEBUG: Sending print file, %qd bytes\n", (off_t)fileTbytes);
736
737 fileBufferNbytes = 0;
738 gSendDataID = 0;
739 if (fileEOFRead)
740 {
741 fileEOFSent = true;
742 if (gWaitEOF == false)
743 {
744 err = 0;
745 goto Exit;
746 }
747 }
748 }
749 break;
750
751 case AT_PAP_TYPE_DATA: /* Data packet */
752 for (len=0, i=0; i < ATP_TRESP_MAX; i++)
753 {
754 if (resp.bitmap & (1 << i))
755 len += resp.resp[i].iov_len;
756 }
757
758 fprintf(stderr, "DEBUG: <- PAP_DATA %d bytes %s\n", len, IS_PAP_EOF(userdata) ? "with EOF" : "");
759
760 if (len > 0)
761 {
762 char *pLineBegin, *pCommentEnd, *pChar;
763 char *logLevel;
764 char logstr[512];
765 int logstrlen;
766
767 cupsBackChannelWrite(sockBuffer, len, 1.0);
768
769 sockBuffer[len] = '\0'; /* We always reserve room for the nul so we can use strstr() below*/
770 pLineBegin = sockBuffer;
771
772 /* If there are PostScript status comments in the buffer log them.
773 *
774 * This logic shouldn't be in the backend but until we get backchannel
775 * data in CUPS 1.2 it has to live here.
776 */
777 while (pLineBegin < sockBuffer + len &&
778 (pLineBegin = strstr(pLineBegin, "%%[")) != NULL &&
779 (pCommentEnd = strstr(pLineBegin, "]%%")) != NULL)
780 {
781 pCommentEnd += 3; /* Skip past "]%%" */
782 *pCommentEnd = '\0'; /* There's always room for the nul */
783
784 /* Strip the CRs & LFs before writing it to stderr */
785 for (pChar = pLineBegin; pChar < pCommentEnd; pChar++)
786 if (*pChar == '\r' || *pChar == '\n')
787 *pChar = ' ';
788
789 if (strncasecmp(pLineBegin, "%%[ Error:", 10) == 0)
790 {
791 /* logLevel should be "ERROR" here but this causes PrintCenter
792 * to pause the queue which in turn clears this error, which
793 * restarts the job. So the job ends up in an infinite loop with
794 * the queue being held/un-held. Just make it DEBUG for now until
795 * we fix notifications later.
796 */
797 logLevel = "DEBUG";
798 gErrorlogged = true;
799 }
800 else if (strncasecmp(pLineBegin, "%%[ Flushing", 12) == 0)
801 logLevel = "DEBUG";
802 else
803 logLevel = "INFO";
804
805 if ((logstrlen = snprintf(logstr, sizeof(logstr), "%s: %s\n", logLevel, pLineBegin)) >= sizeof(logstr))
806 {
807 /* If the string was trucnated make sure it has a linefeed before the nul */
808 logstrlen = sizeof(logstr) - 1;
809 logstr[logstrlen - 1] = '\n';
810 }
811
812 write(fderr, logstr, logstrlen);
813
814 pLineBegin = pCommentEnd + 1;
815 }
816 }
817
818 if (IS_PAP_EOF(userdata) != 0)
819 {
820 /* If this is EOF then were we expecting it? */
821 if (fileEOFSent == true)
822 goto Exit;
823 else
824 {
825 fprintf(stderr, "WARNING: Printer sent unexpected EOF\n");
826 }
827 }
828
829 if ((err = papSendRequest(gSockfd, &gSessionAddr, gConnID, AT_PAP_TYPE_SEND_DATA, 0xFF, true, true)) < 0)
830 {
831 fprintf(stderr, "ERROR: Error %d sending PAPSendData resuest: %s\n", err, strerror(errno));
832 goto Exit;
833 }
834 break;
835
836 case AT_PAP_TYPE_TICKLE: /* Tickle packet */
837 break;
838
839 case AT_PAP_TYPE_CLOSE_CONN: /* Close-Connection packet */
840 /* We shouldn't normally see this. */
841 papCloseResp(gSockfd, &gSessionAddr, xo, tid, gConnID);
842
843 /* If this is EOF then were we expecting it? */
844 if (fileEOFSent == true)
845 {
846 fprintf(stderr, "WARNING: Printer sent unexpected EOF\n");
847 }
848 else
849 {
850 fprintf(stderr, "ERROR: Printer sent unexpected EOF\n");
851 }
852 goto Exit;
853 break;
854
855 case AT_PAP_TYPE_OPEN_CONN: /* Open-Connection packet */
856 case AT_PAP_TYPE_OPEN_CONN_REPLY: /* Open-Connection-Reply packet */
857 case AT_PAP_TYPE_SEND_STATUS: /* Send-Status packet */
858 case AT_PAP_TYPE_CLOSE_CONN_REPLY: /* Close-Connection-Reply packet */
859 fprintf(stderr, "WARNING: Unexpected PAP packet of type %d\n", TYPE_OF(userdata));
860 break;
861
862 default:
863 fprintf(stderr, "WARNING: Unknown PAP packet of type %d\n", TYPE_OF(userdata));
864 break;
865 }
866
867 if (CONNID_OF(userdata) == gConnID)
868 {
869 /* Reset tickle timer */
870 alarm(0);
871 alarm(PAP_TIMEOUT);
872 }
873 }
874 }
875
876 Exit:
877 /*
878 * Close the socket and return...
879 */
880 papClose();
881
882 return err;
883 }
884
885
886 #pragma mark -
887 /*!
888 * @function papOpen
889 * @abstract Open a pap session to a printer.
890 *
891 * @param tuple nbp address of printer
892 * @param connID returned pap connection id
893 * @param fd returned socket descriptor
894 * @param sessionAddr returned session address
895 * @param flowQuantum returned flow quantum (usually 8)
896 *
897 * @result A non-zero return value for errors
898 */
899 static int papOpen(at_nbptuple_t* tuple, u_char* connID, int* fd,
900 at_inet_t* sessionAddr, u_char* flowQuantum)
901 {
902 int result,
903 open_result,
904 userdata,
905 atp_err;
906 time_t tm,
907 waitTime;
908 char data[10],
909 rdata[ATP_DATA_SIZE];
910 u_char *puserdata;
911 at_socket socketfd;
912 at_resp_t resp;
913 at_retry_t retry;
914
915 result = 0;
916 socketfd = 0;
917 puserdata = (u_char *)&userdata;
918
919 fprintf(stderr, "INFO: Opening connection\n");
920
921 if ((*fd = atp_open(&socketfd)) < 0)
922 return -1;
923
924 /*
925 * Build the open connection request packet.
926 */
927
928 tm = time(NULL);
929 srand(tm);
930
931 *connID = (rand()&0xff) | 0x01;
932 puserdata[0] = *connID;
933 puserdata[1] = AT_PAP_TYPE_OPEN_CONN;
934 puserdata[2] = 0;
935 puserdata[3] = 0;
936
937 retry.interval = 2;
938 retry.retries = 5;
939
940 resp.bitmap = 0x01;
941 resp.resp[0].iov_base = rdata;
942 resp.resp[0].iov_len = sizeof(rdata);
943
944 data[0] = socketfd;
945 data[1] = 8;
946
947 for (;;)
948 {
949 waitTime = time(NULL) - tm;
950 OSWriteBigInt16(&data[2], 0, (u_short)waitTime);
951
952 fprintf(stderr, "DEBUG: -> %s\n", packet_name(AT_PAP_TYPE_OPEN_CONN));
953
954 if ((atp_err = atp_sendreq(*fd, &tuple->enu_addr, data, 4, userdata, 1, 0,
955 0, &resp, &retry, 0)) < 0)
956 {
957 statusUpdate("Destination unreachable", 23);
958 result = EHOSTUNREACH;
959 break;
960 }
961
962 puserdata = (u_char *)&resp.userdata[0];
963 open_result = OSReadBigInt16(&rdata[2], 0);
964
965 fprintf(stderr, "DEBUG: <- %s, status %d\n", packet_name(puserdata[1]),
966 open_result);
967
968 /*
969 * Just for the sake of our sanity check the other fields in the packet
970 */
971
972 if (puserdata[1] != AT_PAP_TYPE_OPEN_CONN_REPLY ||
973 (open_result == 0 && (puserdata[0] & 0xff) != *connID))
974 {
975 result = EINVAL;
976 break;
977 }
978
979 statusUpdate(&rdata[5], rdata[4] & 0xff);
980
981 /*
982 * if the connection established okay exit from the loop
983 */
984
985 if (open_result == 0)
986 break;
987
988 sleep(1);
989 }
990
991 if (result == 0)
992 {
993 /* Update the session address
994 */
995 sessionAddr->net = tuple->enu_addr.net;
996 sessionAddr->node = tuple->enu_addr.node;
997 sessionAddr->socket = rdata[0];
998 *flowQuantum = rdata[1];
999 }
1000 else
1001 {
1002 atp_close(*fd);
1003 *fd = 0;
1004 sleep(1);
1005 }
1006
1007 return result;
1008 }
1009
1010
1011 /*!
1012 * @function papClose
1013 * @abstract End a PAP session by canceling outstanding send-data & tickle
1014 * transactions and sending a PAP close request.
1015 *
1016 * @result A non-zero return value for errors
1017 */
1018 static int papClose()
1019 {
1020 int fd;
1021 u_short tmpID;
1022 int result;
1023 unsigned char rdata[ATP_DATA_SIZE];
1024 int userdata;
1025 u_char *puserdata = (u_char *)&userdata;
1026 at_resp_t resp;
1027 at_retry_t retry;
1028
1029 if (gSockfd != 0)
1030 {
1031 fd = gSockfd;
1032 gSockfd = 0;
1033
1034 alarm(0);
1035
1036 /* Cancel the pending send-data and tickle trnsactions
1037 */
1038 if (gSendDataID)
1039 {
1040 tmpID = gSendDataID;
1041 gSendDataID = 0;
1042 papCancelRequest(fd, tmpID);
1043 }
1044
1045 if (gTickleID)
1046 {
1047 tmpID = gTickleID;
1048 gTickleID = 0;
1049 papCancelRequest(fd, tmpID);
1050 }
1051
1052 /* This is a workaround for bug #2735145. The problem is papWrite()
1053 * returns before the ATP TRel arrives for it. If we send the pap close packet
1054 * before this release then the printer can drop the last data packets.
1055 * The effect on an Epson printer is the last page doesn't print, on HP it
1056 * doesn't close the pap session.
1057 */
1058 if (gWaitEOF == false)
1059 sleep(2);
1060
1061 fprintf(stderr, "DEBUG: -> %s\n", packet_name(AT_PAP_TYPE_CLOSE_CONN));
1062
1063 puserdata[0] = gConnID;
1064 puserdata[1] = AT_PAP_TYPE_CLOSE_CONN;
1065 puserdata[2] = 0;
1066 puserdata[3] = 0;
1067
1068 retry.interval = 2;
1069 retry.retries = 5;
1070
1071 resp.bitmap = 0x01;
1072 resp.resp[0].iov_base = rdata;
1073 resp.resp[0].iov_len = sizeof(rdata);
1074
1075 result = atp_sendreq(fd, &gSessionAddr, 0, 0, userdata, 1, 0, 0, &resp, &retry, 0);
1076
1077 result = close(fd);
1078 }
1079 return noErr;
1080 }
1081
1082
1083 /*!
1084 * @function papWrite
1085 * @abstract Write bytes to a printer.
1086 *
1087 * @param sockfd socket descriptor
1088 * @param dest destination address
1089 * @param tid transaction id
1090 * @param connID connection id
1091 * @param flowQuantum returned flow quantum (usually 8)
1092 * @param data pointer to the data
1093 * @param len number of bytes to send
1094 * @param eof pap eof flag
1095 *
1096 * @result A non-zero return value for errors
1097 */
1098 static int papWrite(int sockfd, at_inet_t* dest, u_short tid, u_char connID, u_char flowQuantum, char* data, int len, int eof)
1099 {
1100 int result;
1101 int i;
1102 u_char* puserdata;
1103 at_resp_t resp;
1104
1105 /* fprintf(stderr, "DEBUG: papWrite(%d%s) to %d,%d,%d; %d\n", len, eof ? " EOF":"", dest->net, dest->node, dest->socket, connID); */
1106
1107 if (len > AT_PAP_DATA_SIZE * flowQuantum)
1108 {
1109 fprintf(stderr, "DEBUG: papWrite() len of %d is too big!\n", len);
1110 errno = E2BIG;
1111 return -1;
1112 }
1113
1114 /*
1115 * Break up the outgoing data into a set of
1116 * response packets to reply to an incoming
1117 * PAP 'SENDDATA' request
1118 */
1119 for (i = 0; i < flowQuantum; i++)
1120 {
1121 resp.userdata[i] = 0;
1122 puserdata = (u_char *)&resp.userdata[i];
1123
1124 puserdata[PAP_CONNID] = connID;
1125 puserdata[PAP_TYPE] = AT_PAP_TYPE_DATA;
1126 puserdata[PAP_EOF] = eof ? 1 : 0;
1127
1128 resp.resp[i].iov_base = (caddr_t)data;
1129
1130 if (data)
1131 data += AT_PAP_DATA_SIZE;
1132
1133 resp.resp[i].iov_len = MIN((int)len, (int)AT_PAP_DATA_SIZE);
1134 len -= resp.resp[i].iov_len;
1135 if (len == 0)
1136 break;
1137 }
1138 resp.bitmap = (1 << (i + 1)) - 1;
1139
1140 /*
1141 * Write out the data as a PAP 'DATA' response
1142 */
1143 errno = 0;
1144 if ((result = atp_sendrsp(sockfd, dest, true, tid, &resp)) < 0)
1145 {
1146 fprintf(stderr, "DEBUG: atp_sendrsp() returns %d, errno %d \"%s\"\n", result, errno, strerror(errno));
1147 return -1;
1148 }
1149 return(0);
1150 }
1151
1152
1153 /*!
1154 * @function papCloseResp
1155 * @abstract Send a pap close response in the rare case we receive a close connection request.
1156 *
1157 * @param sockfd socket descriptor
1158 * @param dest destination address
1159 * @param tid transaction id
1160 * @param connID connection id
1161 *
1162 * @result A non-zero return value for errors
1163 */
1164 static int papCloseResp(int sockfd, at_inet_t* dest, int xo, u_short tid, u_char connID)
1165 {
1166 int result;
1167 at_resp_t resp;
1168
1169 resp.bitmap = 1;
1170 resp.userdata[0] = 0;
1171
1172 ((u_char*)&resp.userdata[0])[PAP_CONNID] = connID;
1173 ((u_char*)&resp.userdata[0])[PAP_TYPE] = AT_PAP_TYPE_CLOSE_CONN_REPLY;
1174
1175 resp.resp[0].iov_base = NULL;
1176 resp.resp[0].iov_len = 0;
1177
1178 if ((result = atp_sendrsp(sockfd, dest, xo, tid, &resp)) < 0)
1179 {
1180 fprintf(stderr, "DEBUG: atp_sendrsp() returns %d, errno %d \"%s\"\n", result, errno, strerror(errno));
1181 return -1;
1182 }
1183 return 0;
1184 }
1185
1186
1187 /*!
1188 * @function papSendRequest
1189 * @abstract Send a pap close response in the rare case we receive a close connection request.
1190 *
1191 * @param sockfd socket descriptor
1192 * @param dest destination address
1193 * @param function pap function
1194 * @param bitmap bitmap
1195 * @param xo exactly once
1196 * @param seqno sequence number
1197 *
1198 * @result A non-zero return value for errors
1199 */
1200 static int papSendRequest(int sockfd, at_inet_t* dest, u_char connID, int function, u_char bitmap, int xo, int seqno)
1201 {
1202 u_short tid;
1203 int err;
1204 sigset_t sv, osv;
1205 int userdata;
1206 u_char *puserdata = (u_char *)&userdata;
1207 at_retry_t retry;
1208 at_resp_t resp;
1209 static u_short pap_send_count = 0;
1210
1211 fprintf(stderr, "DEBUG: -> %s\n", packet_name(function));
1212
1213 puserdata[0] = connID;
1214 puserdata[1] = function;
1215 resp.bitmap = bitmap;
1216 retry.interval = 10;
1217 retry.retries = -1; /* was ATP_INFINITE_RETRIES */
1218 if (seqno)
1219 {
1220 pap_send_count++;
1221 if (pap_send_count == 0)
1222 pap_send_count = 1;
1223
1224 OSWriteBigInt16(&puserdata[2], 0, pap_send_count);
1225 }
1226 else
1227 OSWriteBigInt16(&puserdata[2], 0, 0);
1228
1229 sigemptyset(&sv);
1230 sigaddset(&sv, SIGIO);
1231 sigprocmask(SIG_SETMASK, &sv, &osv);
1232
1233 err = atp_sendreq(sockfd, dest, 0, 0, userdata, xo, 0, &tid, &resp, &retry, 1);
1234
1235 sigprocmask(SIG_SETMASK, &osv, NULL);
1236
1237 return err;
1238 }
1239
1240
1241 /*!
1242 * @function papCancelRequest
1243 * @abstract Cancel a pending pap request.
1244 *
1245 * @param sockfd socket descriptor
1246 * @param tid transaction ID
1247 *
1248 * @result A non-zero return value for errors
1249 */
1250 int papCancelRequest(int sockfd, u_short tid)
1251 {
1252 sigset_t sv, osv;
1253
1254 sigemptyset(&sv);
1255 sigaddset(&sv, SIGIO);
1256 sigprocmask(SIG_SETMASK, &sv, &osv);
1257
1258 if (atp_abort(sockfd, NULL, tid) < 0)
1259 {
1260 sigprocmask(SIG_SETMASK, &osv, NULL);
1261 return -1;
1262 }
1263 sigprocmask(SIG_SETMASK, &osv, NULL);
1264
1265 return 0;
1266 }
1267
1268
1269 /*
1270 * 'sidechannel_request()' - Handle side-channel requests.
1271 */
1272
1273 static void
1274 sidechannel_request()
1275 {
1276 cups_sc_command_t command; /* Request command */
1277 cups_sc_status_t status; /* Request/response status */
1278 char data[2048]; /* Request/response data */
1279 int datalen; /* Request/response data size */
1280
1281 datalen = sizeof(data);
1282
1283 if (cupsSideChannelRead(&command, &status, data, &datalen, 1.0))
1284 {
1285 fputs(_("WARNING: Failed to read side-channel request!\n"), stderr);
1286 return;
1287 }
1288
1289 switch (command)
1290 {
1291 case CUPS_SC_CMD_GET_BIDI: /* Is the connection bidirectional? */
1292 data[0] = 1;
1293 cupsSideChannelWrite(command, CUPS_SC_STATUS_OK, data, 1, 1.0);
1294 break;
1295
1296 case CUPS_SC_CMD_GET_STATE: /* Return device state */
1297 data[0] = CUPS_SC_STATE_ONLINE;
1298 cupsSideChannelWrite(command, CUPS_SC_STATUS_OK, data, 1, 1.0);
1299 break;
1300
1301 case CUPS_SC_CMD_DRAIN_OUTPUT: /* Drain all pending output */
1302 case CUPS_SC_CMD_SOFT_RESET: /* Do a soft reset */
1303 case CUPS_SC_CMD_GET_DEVICE_ID: /* Return IEEE-1284 device ID */
1304 default:
1305 cupsSideChannelWrite(command, CUPS_SC_STATUS_NOT_IMPLEMENTED,
1306 NULL, 0, 1.0);
1307 break;
1308 }
1309 return;
1310 }
1311
1312
1313 #pragma mark -
1314 /*!
1315 * @function statusUpdate
1316 * @abstract Format and print a PAP status response to stderr.
1317 *
1318 * @param status The status response string
1319 * @param statusLen The length of the status response string
1320 */
1321 void statusUpdate(char* status, u_char statusLen)
1322 {
1323 static char status_str[255];
1324 static u_char last_statusLen = 0xFF;
1325
1326 /* Only send this if the status has changed */
1327 if (statusLen != last_statusLen || memcmp(status, status_str, statusLen) != 0)
1328 {
1329 if (statusLen > sizeof(status_str)-1)
1330 statusLen = sizeof(status_str)-1;
1331 last_statusLen = statusLen;
1332 memcpy(status_str, status, statusLen);
1333 status_str[(int)statusLen] = '\0';
1334
1335 /*
1336 * Make sure the status string is in the form of a PostScript comment.
1337 */
1338
1339 if (statusLen > 3 && memcmp(status, "%%[", 3) == 0)
1340 fprintf(stderr, "INFO: %s\n", status_str);
1341 else
1342 fprintf(stderr, "INFO: %%%%[ %s ]%%%%\n", status_str);
1343 }
1344 return;
1345 }
1346
1347
1348 /*!
1349 * @function parseUri
1350 * @abstract Parse a PAP URI into it's NBP components.
1351 *
1352 * @param argv0 The PAP URI to parse
1353 * @param name NBP name
1354 * @param zone NBP zone
1355 * @param type NBP type
1356 *
1357 * @result A non-zero return value for errors
1358 */
1359 static int parseUri(const char* argv0, char* name, char* type, char* zone)
1360 {
1361 char method[255], /* Method in URI */
1362 hostname[1024], /* Hostname */
1363 username[255], /* Username info (not used) */
1364 resource[1024], /* Resource info (device and options) */
1365 *resourcePtr,
1366 *typePtr,
1367 *options, /* Pointer to options */
1368 *optionName, /* Name of option */
1369 *value, /* Value of option */
1370 sep; /* Separator character */
1371 int port; /* Port number (not used) */
1372 int statusInterval; /* */
1373
1374 /*
1375 * Extract the device name and options from the URI...
1376 */
1377 method[0] = username[0] = hostname[0] = resource[0] = '\0';
1378 port = 0;
1379
1380 httpSeparateURI(HTTP_URI_CODING_NONE, argv0, method, sizeof(method),
1381 username, sizeof(username),
1382 hostname, sizeof(hostname), &port,
1383 resource, sizeof(resource));
1384
1385 /*
1386 * See if there are any options...
1387 */
1388 if ((options = strchr(resource, '?')) != NULL)
1389 {
1390 /*
1391 * Yup, terminate the device name string and move to the first
1392 * character of the options...
1393 */
1394 *options++ = '\0';
1395
1396 while (*options != '\0')
1397 {
1398 /*
1399 * Get the name...
1400 */
1401
1402 optionName = options;
1403
1404 while (*options && *options != '=' && *options != '+' && *options != '&')
1405 options ++;
1406
1407 if ((sep = *options) != '\0')
1408 *options++ = '\0';
1409
1410 if (sep == '=')
1411 {
1412 /*
1413 * Get the value...
1414 */
1415
1416 value = options;
1417
1418 while (*options && *options != '+' && *options != '&')
1419 options ++;
1420
1421 if (*options)
1422 *options++ = '\0';
1423 }
1424 else
1425 value = (char *)"";
1426
1427 /*
1428 * Process the option...
1429 */
1430
1431 if (!strcasecmp(optionName, "waiteof"))
1432 {
1433 /*
1434 * Wait for the end of the print file?
1435 */
1436
1437 if (!strcasecmp(value, "on") ||
1438 !strcasecmp(value, "yes") ||
1439 !strcasecmp(value, "true"))
1440 {
1441 gWaitEOF = true;
1442 }
1443 else if (!strcasecmp(value, "off") ||
1444 !strcasecmp(value, "no") ||
1445 !strcasecmp(value, "false"))
1446 {
1447 gWaitEOF = false;
1448 }
1449 else
1450 {
1451 fprintf(stderr, "WARNING: Boolean expected for waiteof option \"%s\"\n", value);
1452 }
1453 }
1454 else if (!strcasecmp(optionName, "status"))
1455 {
1456 /*
1457 * Set status reporting interval...
1458 */
1459
1460 statusInterval = atoi(value);
1461 if (value[0] < '0' || value[0] > '9' || statusInterval < 0)
1462 {
1463 fprintf(stderr, "WARNING: number expected for status option \"%s\"\n",
1464 value);
1465 }
1466 else
1467 {
1468 gStatusInterval = statusInterval;
1469 }
1470 }
1471 }
1472 }
1473
1474 resourcePtr = resource;
1475
1476 if (*resourcePtr == '/')
1477 resourcePtr++;
1478
1479 /* If the resource has a slash we assume the slash seperates the AppleTalk object
1480 * name from the AppleTalk type. If the slash is not present we assume the AppleTalk
1481 * type is LaserWriter.
1482 */
1483
1484 typePtr = strchr(resourcePtr, '/');
1485 if (typePtr != NULL)
1486 {
1487 *typePtr++ = '\0';
1488 }
1489 else
1490 {
1491 typePtr = "LaserWriter";
1492 }
1493
1494 removePercentEscapes(hostname, zone, NBP_NVE_STR_SIZE + 1);
1495 removePercentEscapes(resourcePtr, name, NBP_NVE_STR_SIZE + 1);
1496 removePercentEscapes(typePtr, type, NBP_NVE_STR_SIZE + 1);
1497
1498 return 0;
1499 }
1500
1501
1502 /*!
1503 * @function addPercentEscapes
1504 * @abstract Encode a string with percent escapes
1505 *
1506 * @param src The source C string
1507 * @param dst Desination buffer
1508 * @param dstMax Size of desination buffer
1509 *
1510 * @result A non-zero return value for errors
1511 */
1512 static int addPercentEscapes(const char* src, char* dst, int dstMax)
1513 {
1514 char c;
1515 char *dstEnd = dst + dstMax - 1; /* -1 to leave room for the NUL */
1516
1517 while (*src)
1518 {
1519 c = *src++;
1520
1521 if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
1522 (c >= '0' && c <= '9') || (c == '.' || c == '-' || c == '*' || c == '_'))
1523 {
1524 if (dst >= dstEnd)
1525 return -1;
1526
1527 *dst++ = c;
1528 }
1529 else
1530 {
1531 if (dst >= dstEnd - 2)
1532 return -1;
1533
1534 snprintf(dst, dstEnd - dst, "%%%02x", c);
1535 dst += 3;
1536 }
1537 }
1538
1539 *dst = '\0';
1540 return 0;
1541 }
1542
1543
1544 /*!
1545 * @function removePercentEscapes
1546 * @abstract Returns a string with any percent escape sequences replaced with their equivalent character
1547 *
1548 * @param src Source buffer
1549 * @param srclen Number of bytes in source buffer
1550 * @param dst Desination buffer
1551 * @param dstMax Size of desination buffer
1552 *
1553 * @result A non-zero return value for errors
1554 */
1555 static int removePercentEscapes(const char* src, char* dst, int dstMax)
1556 {
1557 int c;
1558 const char *dstEnd = dst + dstMax;
1559
1560 while (*src && dst < dstEnd)
1561 {
1562 c = *src++;
1563
1564 if (c == '%')
1565 {
1566 sscanf(src, "%02x", &c);
1567 src += 2;
1568 }
1569 *dst++ = (char)c;
1570 }
1571
1572 if (dst >= dstEnd)
1573 return -1;
1574
1575 *dst = '\0';
1576 return 0;
1577 }
1578
1579
1580 /*!
1581 * @function nbptuple_compare
1582 * @abstract An NBP comparator for qsort.
1583 *
1584 * @result p1<p2: -1, p1=p2: 0, p1>p2: 1
1585 */
1586 int nbptuple_compare(const void *p1, const void *p2)
1587 {
1588 int result;
1589 int len = MIN(((at_nbptuple_t*)p1)->enu_entity.object.len,
1590 ((at_nbptuple_t*)p2)->enu_entity.object.len);
1591
1592 if ((result = memcmp(((at_nbptuple_t*)p1)->enu_entity.object.str, ((at_nbptuple_t*)p2)->enu_entity.object.str, len)) == 0)
1593 {
1594 if (((at_nbptuple_t*)p1)->enu_entity.object.len < ((at_nbptuple_t*)p2)->enu_entity.object.len)
1595 result = -1;
1596 else if (((at_nbptuple_t*)p1)->enu_entity.object.len > ((at_nbptuple_t*)p2)->enu_entity.object.len)
1597 result = 1;
1598 else
1599 result = 0;
1600 }
1601 return result;
1602 }
1603
1604
1605 /*!
1606 * @function okayToUseAppleTalk
1607 * @abstract Returns true if AppleTalk is available and enabled.
1608 *
1609 * @result non-zero if AppleTalk is enabled
1610 */
1611 static int okayToUseAppleTalk()
1612 {
1613 int atStatus = checkATStack();
1614
1615 /* I think the test should be:
1616 * return atStatus == RUNNING || atStatus == LOADED;
1617 * but when I disable AppleTalk from the network control panel and
1618 * reboot, AppleTalk shows up as loaded. The test empirically becomes
1619 * the following:
1620 */
1621 return atStatus == RUNNING;
1622 }
1623
1624
1625 /*!
1626 * @function packet_name
1627 * @abstract Returns packet name string.
1628 *
1629 * @result A string
1630 */
1631 static const char *packet_name(u_char x)
1632 {
1633 switch (x)
1634 {
1635 case AT_PAP_TYPE_OPEN_CONN: return "PAP_OPEN_CONN";
1636 case AT_PAP_TYPE_OPEN_CONN_REPLY: return "PAP_OPEN_CONN_REPLY";
1637 case AT_PAP_TYPE_SEND_DATA: return "PAP_SEND_DATA";
1638 case AT_PAP_TYPE_DATA: return "PAP_DATA";
1639 case AT_PAP_TYPE_TICKLE: return "PAP_TICKLE";
1640 case AT_PAP_TYPE_CLOSE_CONN: return "PAP_CLOSE_CONN";
1641 case AT_PAP_TYPE_CLOSE_CONN_REPLY: return "PAP_CLOSE_CONN_REPLY";
1642 case AT_PAP_TYPE_SEND_STATUS: return "PAP_SEND_STATUS";
1643 case AT_PAP_TYPE_SEND_STS_REPLY: return "PAP_SEND_STS_REPLY";
1644 case AT_PAP_TYPE_READ_LW: return "PAP_READ_LW";
1645 }
1646 return "<Unknown>";
1647 }
1648
1649
1650 /*!
1651 * @function connectTimeout
1652 * @abstract Returns the connect timeout preference value.
1653 */
1654 static int connectTimeout()
1655 {
1656 CFPropertyListRef value;
1657 SInt32 connect_timeout = (7 * 24 * 60 * 60); /* Default timeout is one week... */
1658
1659 value = CFPreferencesCopyValue(CFSTR("timeout"), CFSTR("com.apple.print.backends"),
1660 kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
1661 if (value != NULL)
1662 {
1663 if (CFGetTypeID(value) == CFNumberGetTypeID())
1664 CFNumberGetValue(value, kCFNumberSInt32Type, &connect_timeout);
1665
1666 CFRelease(value);
1667 }
1668
1669 return connect_timeout;
1670 }
1671
1672
1673 /*!
1674 * @function signalHandler
1675 * @abstract A signal handler so we can clean up the pap session before exiting.
1676 *
1677 * @param sigraised The signal raised
1678 *
1679 * @result Never returns
1680 */
1681 static void signalHandler(int sigraised)
1682 {
1683 fprintf(stderr, "ERROR: There was a timeout error while sending data to the printer\n");
1684
1685 papClose();
1686
1687 _exit(1);
1688 }