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