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