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