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