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