]> git.ipfire.org Git - thirdparty/cups.git/blob - backend/pap.c
Merge changes from CUPS 1.4svn-r7961.
[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 void
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 {
1293 fputs(_("WARNING: Failed to read side-channel request!\n"), stderr);
1294 return;
1295 }
1296
1297 switch (command)
1298 {
1299 case CUPS_SC_CMD_GET_BIDI: /* Is the connection bidirectional? */
1300 data[0] = 1;
1301 cupsSideChannelWrite(command, CUPS_SC_STATUS_OK, data, 1, 1.0);
1302 break;
1303
1304 case CUPS_SC_CMD_GET_STATE: /* Return device state */
1305 data[0] = CUPS_SC_STATE_ONLINE;
1306 cupsSideChannelWrite(command, CUPS_SC_STATUS_OK, data, 1, 1.0);
1307 break;
1308
1309 case CUPS_SC_CMD_DRAIN_OUTPUT: /* Drain all pending output */
1310 case CUPS_SC_CMD_SOFT_RESET: /* Do a soft reset */
1311 case CUPS_SC_CMD_GET_DEVICE_ID: /* Return IEEE-1284 device ID */
1312 default:
1313 cupsSideChannelWrite(command, CUPS_SC_STATUS_NOT_IMPLEMENTED,
1314 NULL, 0, 1.0);
1315 break;
1316 }
1317 return;
1318 }
1319
1320
1321 #pragma mark -
1322 /*!
1323 * @function statusUpdate
1324 * @abstract Format and print a PAP status response to stderr.
1325 *
1326 * @param status The status response string
1327 * @param statusLen The length of the status response string
1328 */
1329 void statusUpdate(char* status, u_char statusLen)
1330 {
1331 static char status_str[255];
1332 static u_char last_statusLen = 0xFF;
1333
1334 /* Only send this if the status has changed */
1335 if (statusLen != last_statusLen || memcmp(status, status_str, statusLen) != 0)
1336 {
1337 if (statusLen > sizeof(status_str)-1)
1338 statusLen = sizeof(status_str)-1;
1339 last_statusLen = statusLen;
1340 memcpy(status_str, status, statusLen);
1341 status_str[(int)statusLen] = '\0';
1342
1343 /*
1344 * Make sure the status string is in the form of a PostScript comment.
1345 */
1346
1347 if (statusLen > 3 && memcmp(status, "%%[", 3) == 0)
1348 fprintf(stderr, "INFO: %s\n", status_str);
1349 else
1350 fprintf(stderr, "INFO: %%%%[ %s ]%%%%\n", status_str);
1351 }
1352 return;
1353 }
1354
1355
1356 /*!
1357 * @function parseUri
1358 * @abstract Parse a PAP URI into it's NBP components.
1359 *
1360 * @param argv0 The PAP URI to parse
1361 * @param name NBP name
1362 * @param zone NBP zone
1363 * @param type NBP type
1364 *
1365 * @result A non-zero return value for errors
1366 */
1367 static int parseUri(const char* argv0, char* name, char* type, char* zone)
1368 {
1369 char method[255], /* Method in URI */
1370 hostname[1024], /* Hostname */
1371 username[255], /* Username info (not used) */
1372 resource[1024], /* Resource info (device and options) */
1373 *resourcePtr,
1374 *typePtr,
1375 *options, /* Pointer to options */
1376 *optionName, /* Name of option */
1377 *value, /* Value of option */
1378 sep; /* Separator character */
1379 int port; /* Port number (not used) */
1380 int statusInterval; /* */
1381
1382 /*
1383 * Extract the device name and options from the URI...
1384 */
1385 method[0] = username[0] = hostname[0] = resource[0] = '\0';
1386 port = 0;
1387
1388 httpSeparateURI(HTTP_URI_CODING_NONE, argv0, method, sizeof(method),
1389 username, sizeof(username),
1390 hostname, sizeof(hostname), &port,
1391 resource, sizeof(resource));
1392
1393 /*
1394 * See if there are any options...
1395 */
1396 if ((options = strchr(resource, '?')) != NULL)
1397 {
1398 /*
1399 * Yup, terminate the device name string and move to the first
1400 * character of the options...
1401 */
1402 *options++ = '\0';
1403
1404 while (*options != '\0')
1405 {
1406 /*
1407 * Get the name...
1408 */
1409
1410 optionName = options;
1411
1412 while (*options && *options != '=' && *options != '+' && *options != '&')
1413 options ++;
1414
1415 if ((sep = *options) != '\0')
1416 *options++ = '\0';
1417
1418 if (sep == '=')
1419 {
1420 /*
1421 * Get the value...
1422 */
1423
1424 value = options;
1425
1426 while (*options && *options != '+' && *options != '&')
1427 options ++;
1428
1429 if (*options)
1430 *options++ = '\0';
1431 }
1432 else
1433 value = (char *)"";
1434
1435 /*
1436 * Process the option...
1437 */
1438
1439 if (!strcasecmp(optionName, "waiteof"))
1440 {
1441 /*
1442 * Wait for the end of the print file?
1443 */
1444
1445 if (!strcasecmp(value, "on") ||
1446 !strcasecmp(value, "yes") ||
1447 !strcasecmp(value, "true"))
1448 {
1449 gWaitEOF = true;
1450 }
1451 else if (!strcasecmp(value, "off") ||
1452 !strcasecmp(value, "no") ||
1453 !strcasecmp(value, "false"))
1454 {
1455 gWaitEOF = false;
1456 }
1457 else
1458 {
1459 _cupsLangPrintf(stderr,
1460 _("WARNING: Boolean expected for waiteof option \"%s\"\n"),
1461 value);
1462 }
1463 }
1464 else if (!strcasecmp(optionName, "status"))
1465 {
1466 /*
1467 * Set status reporting interval...
1468 */
1469
1470 statusInterval = atoi(value);
1471 if (value[0] < '0' || value[0] > '9' || statusInterval < 0)
1472 {
1473 _cupsLangPrintf(stderr,
1474 _("WARNING: number expected for status option \"%s\"\n"),
1475 value);
1476 }
1477 else
1478 {
1479 gStatusInterval = statusInterval;
1480 }
1481 }
1482 }
1483 }
1484
1485 resourcePtr = resource;
1486
1487 if (*resourcePtr == '/')
1488 resourcePtr++;
1489
1490 /* If the resource has a slash we assume the slash seperates the AppleTalk object
1491 * name from the AppleTalk type. If the slash is not present we assume the AppleTalk
1492 * type is LaserWriter.
1493 */
1494
1495 typePtr = strchr(resourcePtr, '/');
1496 if (typePtr != NULL)
1497 {
1498 *typePtr++ = '\0';
1499 }
1500 else
1501 {
1502 typePtr = "LaserWriter";
1503 }
1504
1505 removePercentEscapes(hostname, zone, NBP_NVE_STR_SIZE + 1);
1506 removePercentEscapes(resourcePtr, name, NBP_NVE_STR_SIZE + 1);
1507 removePercentEscapes(typePtr, type, NBP_NVE_STR_SIZE + 1);
1508
1509 return 0;
1510 }
1511
1512
1513 /*!
1514 * @function addPercentEscapes
1515 * @abstract Encode a string with percent escapes
1516 *
1517 * @param src The source C string
1518 * @param dst Desination buffer
1519 * @param dstMax Size of desination buffer
1520 *
1521 * @result A non-zero return value for errors
1522 */
1523 static int addPercentEscapes(const char* src, char* dst, int dstMax)
1524 {
1525 char c;
1526 char *dstEnd = dst + dstMax - 1; /* -1 to leave room for the NUL */
1527
1528 while (*src)
1529 {
1530 c = *src++;
1531
1532 if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
1533 (c >= '0' && c <= '9') || (c == '.' || c == '-' || c == '*' || c == '_'))
1534 {
1535 if (dst >= dstEnd)
1536 return -1;
1537
1538 *dst++ = c;
1539 }
1540 else
1541 {
1542 if (dst >= dstEnd - 2)
1543 return -1;
1544
1545 snprintf(dst, dstEnd - dst, "%%%02x", c);
1546 dst += 3;
1547 }
1548 }
1549
1550 *dst = '\0';
1551 return 0;
1552 }
1553
1554
1555 /*!
1556 * @function removePercentEscapes
1557 * @abstract Returns a string with any percent escape sequences replaced with their equivalent character
1558 *
1559 * @param src Source buffer
1560 * @param srclen Number of bytes in source buffer
1561 * @param dst Desination buffer
1562 * @param dstMax Size of desination buffer
1563 *
1564 * @result A non-zero return value for errors
1565 */
1566 static int removePercentEscapes(const char* src, char* dst, int dstMax)
1567 {
1568 int c;
1569 const char *dstEnd = dst + dstMax;
1570
1571 while (*src && dst < dstEnd)
1572 {
1573 c = *src++;
1574
1575 if (c == '%')
1576 {
1577 sscanf(src, "%02x", &c);
1578 src += 2;
1579 }
1580 *dst++ = (char)c;
1581 }
1582
1583 if (dst >= dstEnd)
1584 return -1;
1585
1586 *dst = '\0';
1587 return 0;
1588 }
1589
1590
1591 /*!
1592 * @function nbptuple_compare
1593 * @abstract An NBP comparator for qsort.
1594 *
1595 * @result p1<p2: -1, p1=p2: 0, p1>p2: 1
1596 */
1597 int nbptuple_compare(const void *p1, const void *p2)
1598 {
1599 int result;
1600 int len = MIN(((at_nbptuple_t*)p1)->enu_entity.object.len,
1601 ((at_nbptuple_t*)p2)->enu_entity.object.len);
1602
1603 if ((result = memcmp(((at_nbptuple_t*)p1)->enu_entity.object.str, ((at_nbptuple_t*)p2)->enu_entity.object.str, len)) == 0)
1604 {
1605 if (((at_nbptuple_t*)p1)->enu_entity.object.len < ((at_nbptuple_t*)p2)->enu_entity.object.len)
1606 result = -1;
1607 else if (((at_nbptuple_t*)p1)->enu_entity.object.len > ((at_nbptuple_t*)p2)->enu_entity.object.len)
1608 result = 1;
1609 else
1610 result = 0;
1611 }
1612 return result;
1613 }
1614
1615
1616 /*!
1617 * @function okayToUseAppleTalk
1618 * @abstract Returns true if AppleTalk is available and enabled.
1619 *
1620 * @result non-zero if AppleTalk is enabled
1621 */
1622 static int okayToUseAppleTalk()
1623 {
1624 int atStatus = checkATStack();
1625
1626 /* I think the test should be:
1627 * return atStatus == RUNNING || atStatus == LOADED;
1628 * but when I disable AppleTalk from the network control panel and
1629 * reboot, AppleTalk shows up as loaded. The test empirically becomes
1630 * the following:
1631 */
1632 return atStatus == RUNNING;
1633 }
1634
1635
1636 /*!
1637 * @function packet_name
1638 * @abstract Returns packet name string.
1639 *
1640 * @result A string
1641 */
1642 static const char *packet_name(u_char x)
1643 {
1644 switch (x)
1645 {
1646 case AT_PAP_TYPE_OPEN_CONN: return "PAP_OPEN_CONN";
1647 case AT_PAP_TYPE_OPEN_CONN_REPLY: return "PAP_OPEN_CONN_REPLY";
1648 case AT_PAP_TYPE_SEND_DATA: return "PAP_SEND_DATA";
1649 case AT_PAP_TYPE_DATA: return "PAP_DATA";
1650 case AT_PAP_TYPE_TICKLE: return "PAP_TICKLE";
1651 case AT_PAP_TYPE_CLOSE_CONN: return "PAP_CLOSE_CONN";
1652 case AT_PAP_TYPE_CLOSE_CONN_REPLY: return "PAP_CLOSE_CONN_REPLY";
1653 case AT_PAP_TYPE_SEND_STATUS: return "PAP_SEND_STATUS";
1654 case AT_PAP_TYPE_SEND_STS_REPLY: return "PAP_SEND_STS_REPLY";
1655 case AT_PAP_TYPE_READ_LW: return "PAP_READ_LW";
1656 }
1657 return "<Unknown>";
1658 }
1659
1660
1661 /*!
1662 * @function connectTimeout
1663 * @abstract Returns the connect timeout preference value.
1664 */
1665 static int connectTimeout()
1666 {
1667 CFPropertyListRef value;
1668 SInt32 connect_timeout = (7 * 24 * 60 * 60); /* Default timeout is one week... */
1669
1670 value = CFPreferencesCopyValue(CFSTR("timeout"), CFSTR("com.apple.print.backends"),
1671 kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
1672 if (value != NULL)
1673 {
1674 if (CFGetTypeID(value) == CFNumberGetTypeID())
1675 CFNumberGetValue(value, kCFNumberSInt32Type, &connect_timeout);
1676
1677 CFRelease(value);
1678 }
1679
1680 return connect_timeout;
1681 }
1682
1683
1684 /*!
1685 * @function signalHandler
1686 * @abstract A signal handler so we can clean up the pap session before exiting.
1687 *
1688 * @param sigraised The signal raised
1689 *
1690 * @result Never returns
1691 */
1692 static void signalHandler(int sigraised)
1693 {
1694 _cupsLangPuts(stderr, _("ERROR: There was a timeout error while sending data to the printer\n"));
1695
1696 papClose();
1697
1698 _exit(1);
1699 }