]> git.ipfire.org Git - thirdparty/cups.git/blob - scheduler/cups-lpd.c
Remove svn:keywords since they cause svn_load_dirs.pl to complain about every file.
[thirdparty/cups.git] / scheduler / cups-lpd.c
1 /*
2 * "$Id: cups-lpd.c 177 2006-06-21 00:20:03Z jlovell $"
3 *
4 * Line Printer Daemon interface for the Common UNIX Printing System (CUPS).
5 *
6 * Copyright 1997-2006 by Easy Software Products, all rights reserved.
7 *
8 * These coded instructions, statements, and computer programs are the
9 * property of Easy Software Products and are protected by Federal
10 * copyright law. Distribution and use rights are outlined in the file
11 * "LICENSE.txt" which should have been included with this file. If this
12 * file is missing or damaged please contact Easy Software Products
13 * at:
14 *
15 * Attn: CUPS Licensing Information
16 * Easy Software Products
17 * 44141 Airport View Drive, Suite 204
18 * Hollywood, Maryland 20636 USA
19 *
20 * Voice: (301) 373-9600
21 * EMail: cups-info@cups.org
22 * WWW: http://www.cups.org
23 *
24 * Contents:
25 *
26 * main() - Process an incoming LPD request...
27 * create_job() - Create a new print job.
28 * get_printer() - Get the named printer and its options.
29 * print_file() - Add a file to the current job.
30 * recv_print_job() - Receive a print job from the client.
31 * remove_jobs() - Cancel one or more jobs.
32 * send_state() - Send the queue state.
33 * smart_gets() - Get a line of text, removing the trailing CR and/or LF.
34 */
35
36 /*
37 * Include necessary headers...
38 */
39
40 #include <cups/http-private.h>
41 #include <cups/cups.h>
42 #include <cups/string.h>
43 #include <cups/language.h>
44 #include <stdlib.h>
45 #include <errno.h>
46 #include <syslog.h>
47 #include <ctype.h>
48 #include <unistd.h>
49 #include <fcntl.h>
50
51 #include <sys/types.h>
52 #include <sys/socket.h>
53 #include <netinet/in.h>
54 #include <netdb.h>
55
56 #ifdef HAVE_INTTYPES_H
57 # include <inttypes.h>
58 #endif /* HAVE_INTTYPES_H */
59
60 #ifdef HAVE_COREFOUNDATION_H
61 # include <CoreFoundation/CoreFoundation.h>
62 #endif /* HAVE_COREFOUNDATION_H */
63 #ifdef HAVE_CFPRIV_H
64 # include <CoreFoundation/CFPriv.h>
65 #endif /* HAVE_CFPRIV_H */
66
67
68 /*
69 * LPD "mini-daemon" for CUPS. This program must be used in conjunction
70 * with inetd or another similar program that monitors ports and starts
71 * daemons for each client connection. A typical configuration is:
72 *
73 * printer stream tcp nowait lp /usr/lib/cups/daemon/cups-lpd cups-lpd
74 *
75 * This daemon implements most of RFC 1179 (the unofficial LPD specification)
76 * except for:
77 *
78 * - This daemon does not check to make sure that the source port is
79 * between 721 and 731, since it isn't necessary for proper
80 * functioning and port-based security is no security at all!
81 *
82 * - The "Print any waiting jobs" command is a no-op.
83 *
84 * The LPD-to-IPP mapping is as defined in RFC 2569. The report formats
85 * currently match the Solaris LPD mini-daemon.
86 */
87
88 /*
89 * Prototypes...
90 */
91
92 static int create_job(http_t *http, const char *dest, const char *title,
93 const char *user, int num_options,
94 cups_option_t *options);
95 static int get_printer(http_t *http, const char *name, char *dest,
96 int destsize, cups_option_t **options,
97 int *accepting, int *shared, ipp_pstate_t *state);
98 static int print_file(http_t *http, int id, const char *filename,
99 const char *docname, const char *user, int last);
100 static int recv_print_job(const char *name, int num_defaults,
101 cups_option_t *defaults);
102 static int remove_jobs(const char *name, const char *agent,
103 const char *list);
104 static int send_state(const char *name, const char *list,
105 int longstatus);
106 static char *smart_gets(char *s, int len, FILE *fp);
107
108
109 /*
110 * 'main()' - Process an incoming LPD request...
111 */
112
113 int /* O - Exit status */
114 main(int argc, /* I - Number of command-line arguments */
115 char *argv[]) /* I - Command-line arguments */
116 {
117 int i; /* Looping var */
118 int num_defaults; /* Number of default options */
119 cups_option_t *defaults; /* Default options */
120 char line[256], /* Command string */
121 command, /* Command code */
122 *dest, /* Pointer to destination */
123 *list, /* Pointer to list */
124 *agent, /* Pointer to user */
125 status; /* Status for client */
126 socklen_t hostlen; /* Size of client address */
127 http_addr_t hostaddr; /* Address of client */
128 char hostname[256], /* Name of client */
129 hostip[256], /* IP address */
130 *hostfamily; /* Address family */
131 int hostlookups; /* Do hostname lookups? */
132
133
134 /*
135 * Don't buffer the output...
136 */
137
138 setbuf(stdout, NULL);
139
140 /*
141 * Log things using the "cups-lpd" name...
142 */
143
144 openlog("cups-lpd", LOG_PID, LOG_LPR);
145
146 /*
147 * Scan the command-line for options...
148 */
149
150 num_defaults = 0;
151 defaults = NULL;
152 hostlookups = 1;
153 num_defaults = cupsAddOption("job-originating-host-name", hostname,
154 num_defaults, &defaults);
155
156 for (i = 1; i < argc; i ++)
157 if (argv[i][0] == '-')
158 {
159 switch (argv[i][1])
160 {
161 case 'o' : /* Option */
162 if (argv[i][2])
163 num_defaults = cupsParseOptions(argv[i] + 2, num_defaults,
164 &defaults);
165 else
166 {
167 i ++;
168 if (i < argc)
169 num_defaults = cupsParseOptions(argv[i], num_defaults,
170 &defaults);
171 else
172 syslog(LOG_WARNING, "Expected option string after -o option!");
173 }
174 break;
175
176 case 'n' : /* Don't do hostname lookups */
177 hostlookups = 0;
178 break;
179
180 default :
181 syslog(LOG_WARNING, "Unknown option \"%c\" ignored!", argv[i][1]);
182 break;
183 }
184 }
185 else
186 syslog(LOG_WARNING, "Unknown command-line option \"%s\" ignored!",
187 argv[i]);
188
189 /*
190 * Get the address of the client...
191 */
192
193 hostlen = sizeof(hostaddr);
194
195 if (getpeername(0, (struct sockaddr *)&hostaddr, &hostlen))
196 {
197 syslog(LOG_WARNING, "Unable to get client address - %s", strerror(errno));
198 strcpy(hostname, "unknown");
199 }
200 else
201 {
202 httpAddrString(&hostaddr, hostip, sizeof(hostip));
203
204 if (hostlookups)
205 httpAddrLookup(&hostaddr, hostname, sizeof(hostname));
206 else
207 strlcpy(hostname, hostip, sizeof(hostname));
208
209 #ifdef AF_INET6
210 if (hostaddr.addr.sa_family == AF_INET6)
211 hostfamily = "IPv6";
212 else
213 #endif /* AF_INET6 */
214 hostfamily = "IPv4";
215
216 syslog(LOG_INFO, "Connection from %s (%s %s)", hostname, hostfamily,
217 hostip);
218 }
219
220 /*
221 * RFC1179 specifies that only 1 daemon command can be received for
222 * every connection.
223 */
224
225 if (smart_gets(line, sizeof(line), stdin) == NULL)
226 {
227 /*
228 * Unable to get command from client! Send an error status and return.
229 */
230
231 syslog(LOG_ERR, "Unable to get command line from client!");
232 putchar(1);
233 return (1);
234 }
235
236 /*
237 * The first byte is the command byte. After that will be the queue name,
238 * resource list, and/or user name.
239 */
240
241 command = line[0];
242 dest = line + 1;
243
244 if (command == 0x02)
245 list = NULL;
246 else
247 {
248 for (list = dest + 1; *list && !isspace(*list & 255); list ++);
249
250 while (isspace(*list & 255))
251 *list++ = '\0';
252 }
253
254 /*
255 * Do the command...
256 */
257
258 switch (command)
259 {
260 default : /* Unknown command */
261 syslog(LOG_ERR, "Unknown LPD command 0x%02X!", command);
262 syslog(LOG_ERR, "Command line = %s", line + 1);
263 putchar(1);
264
265 status = 1;
266 break;
267
268 case 0x01 : /* Print any waiting jobs */
269 syslog(LOG_INFO, "Print waiting jobs (no-op)");
270 putchar(0);
271
272 status = 0;
273 break;
274
275 case 0x02 : /* Receive a printer job */
276 syslog(LOG_INFO, "Receive print job for %s", dest);
277 /* recv_print_job() sends initial status byte */
278
279 status = recv_print_job(dest, num_defaults, defaults);
280 break;
281
282 case 0x03 : /* Send queue state (short) */
283 syslog(LOG_INFO, "Send queue state (short) for %s %s", dest, list);
284 /* no status byte for this command */
285
286 status = send_state(dest, list, 0);
287 break;
288
289 case 0x04 : /* Send queue state (long) */
290 syslog(LOG_INFO, "Send queue state (long) for %s %s", dest, list);
291 /* no status byte for this command */
292
293 status = send_state(dest, list, 1);
294 break;
295
296 case 0x05 : /* Remove jobs */
297 /*
298 * Grab the agent and skip to the list of users and/or jobs.
299 */
300
301 agent = list;
302
303 for (; *list && !isspace(*list & 255); list ++);
304 while (isspace(*list & 255))
305 *list++ = '\0';
306
307 syslog(LOG_INFO, "Remove jobs %s on %s by %s", list, dest, agent);
308
309 status = remove_jobs(dest, agent, list);
310
311 putchar(status);
312 break;
313 }
314
315 syslog(LOG_INFO, "Closing connection");
316 closelog();
317
318 return (status);
319 }
320
321
322 /*
323 * 'create_job()' - Create a new print job.
324 */
325
326 static int /* O - Job ID or -1 on error */
327 create_job(http_t *http, /* I - HTTP connection */
328 const char *dest, /* I - Destination name */
329 const char *title, /* I - job-name */
330 const char *user, /* I - requesting-user-name */
331 int num_options, /* I - Number of options for job */
332 cups_option_t *options) /* I - Options for job */
333 {
334 ipp_t *request; /* IPP request */
335 ipp_t *response; /* IPP response */
336 ipp_attribute_t *attr; /* IPP attribute */
337 char uri[HTTP_MAX_URI]; /* Printer URI */
338 int id; /* Job ID */
339
340
341 /*
342 * Setup the Create-Job request...
343 */
344
345 request = ippNewRequest(IPP_CREATE_JOB);
346
347 httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
348 "localhost", 0, "/printers/%s", dest);
349
350 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
351 NULL, uri);
352
353 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
354 "requesting-user-name", NULL, user);
355
356 if (title)
357 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "job-name",
358 NULL, title);
359
360 cupsEncodeOptions(request, num_options, options);
361
362 /*
363 * Do the request...
364 */
365
366 snprintf(uri, sizeof(uri), "/printers/%s", dest);
367
368 response = cupsDoRequest(http, request, uri);
369
370 if (!response || cupsLastError() > IPP_OK_CONFLICT)
371 {
372 syslog(LOG_ERR, "Unable to create job - %s", cupsLastErrorString());
373
374 ippDelete(response);
375
376 return (-1);
377 }
378
379 /*
380 * Get the job-id value from the response and return it...
381 */
382
383 if ((attr = ippFindAttribute(response, "job-id", IPP_TAG_INTEGER)) == NULL)
384 {
385 id = -1;
386
387 syslog(LOG_ERR, "No job-id attribute found in response from server!");
388 }
389 else
390 {
391 id = attr->values[0].integer;
392
393 syslog(LOG_INFO, "Print file - job ID = %d", id);
394 }
395
396 ippDelete(response);
397
398 return (id);
399 }
400
401
402 /*
403 * 'get_printer()' - Get the named printer and its options.
404 */
405
406 static int /* O - Number of options or -1 on error */
407 get_printer(http_t *http, /* I - HTTP connection */
408 const char *name, /* I - Printer name from request */
409 char *dest, /* I - Destination buffer */
410 int destsize, /* I - Size of destination buffer */
411 cups_option_t **options, /* O - Printer options */
412 int *accepting, /* O - printer-is-accepting-jobs value */
413 int *shared, /* O - printer-is-shared value */
414 ipp_pstate_t *state) /* O - printer-state value */
415 {
416 int num_options; /* Number of options */
417 cups_file_t *fp; /* lpoptions file */
418 char line[1024], /* Line from lpoptions file */
419 *value, /* Pointer to value on line */
420 *optptr; /* Pointer to options on line */
421 int linenum; /* Line number in file */
422 const char *cups_serverroot; /* CUPS_SERVERROOT env var */
423 ipp_t *request; /* IPP request */
424 ipp_t *response; /* IPP response */
425 ipp_attribute_t *attr; /* IPP attribute */
426 char uri[HTTP_MAX_URI]; /* Printer URI */
427 static const char * const requested[] =
428 { /* Requested attributes */
429 "printer-info",
430 "printer-is-accepting-jobs",
431 "printer-is-shared",
432 "printer-name",
433 "printer-state"
434 };
435
436
437 /*
438 * Initialize everything...
439 */
440
441 if (accepting)
442 *accepting = 0;
443 if (shared)
444 *shared = 0;
445 if (state)
446 *state = IPP_PRINTER_STOPPED;
447 if (options)
448 *options = NULL;
449
450 /*
451 * If the queue name contains a space, lookup the printer-name using
452 * the printer-info value...
453 */
454
455 if (strchr(name, ' '))
456 {
457 /*
458 * Lookup the printer-info...
459 */
460
461 ipp_attribute_t *accepting_attr,/* printer-is-accepting-jobs */
462 *info_attr, /* printer-info */
463 *name_attr, /* printer-name */
464 *shared_attr, /* printer-is-shared */
465 *state_attr; /* printer-state */
466
467
468 /*
469 * Setup the CUPS-Get-Printers request...
470 */
471
472 request = ippNewRequest(CUPS_GET_PRINTERS);
473
474 ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
475 "requested-attributes",
476 (int)(sizeof(requested) / sizeof(requested[0])),
477 NULL, requested);
478
479 /*
480 * Do the request...
481 */
482
483 response = cupsDoRequest(http, request, "/");
484
485 if (!response || cupsLastError() > IPP_OK_CONFLICT)
486 {
487 syslog(LOG_ERR, "Unable to get list of printers - %s",
488 cupsLastErrorString());
489
490 ippDelete(response);
491
492 return (-1);
493 }
494
495 /*
496 * Scan the response for printers...
497 */
498
499 *dest = '\0';
500 attr = response->attrs;
501
502 while (attr)
503 {
504 /*
505 * Skip to the next printer...
506 */
507
508 while (attr && attr->group_tag != IPP_TAG_PRINTER)
509 attr = attr->next;
510
511 if (!attr)
512 break;
513
514 /*
515 * Get all of the attributes for the current printer...
516 */
517
518 accepting_attr = NULL;
519 info_attr = NULL;
520 name_attr = NULL;
521 shared_attr = NULL;
522 state_attr = NULL;
523
524 while (attr && attr->group_tag == IPP_TAG_PRINTER)
525 {
526 if (!strcmp(attr->name, "printer-is-accepting-jobs") &&
527 attr->value_tag == IPP_TAG_BOOLEAN)
528 accepting_attr = attr;
529 else if (!strcmp(attr->name, "printer-info") &&
530 attr->value_tag == IPP_TAG_TEXT)
531 info_attr = attr;
532 else if (!strcmp(attr->name, "printer-name") &&
533 attr->value_tag == IPP_TAG_NAME)
534 name_attr = attr;
535 else if (!strcmp(attr->name, "printer-is-shared") &&
536 attr->value_tag == IPP_TAG_BOOLEAN)
537 shared_attr = attr;
538 else if (!strcmp(attr->name, "printer-state") &&
539 attr->value_tag == IPP_TAG_ENUM)
540 state_attr = attr;
541
542 attr = attr->next;
543 }
544
545 if (info_attr && name_attr &&
546 !strcasecmp(name, info_attr->values[0].string.text))
547 {
548 /*
549 * Found a match, use this one!
550 */
551
552 strlcpy(dest, name_attr->values[0].string.text, destsize);
553
554 if (accepting && accepting_attr)
555 *accepting = accepting_attr->values[0].boolean;
556
557 if (shared && shared_attr)
558 *shared = shared_attr->values[0].boolean;
559
560 if (state && state_attr)
561 *state = (ipp_pstate_t)state_attr->values[0].integer;
562
563 break;
564 }
565 }
566
567 ippDelete(response);
568
569 if (!*dest)
570 {
571 syslog(LOG_ERR, "Unable to find \"%s\" in list of printers!", name);
572
573 return (-1);
574 }
575
576 name = dest;
577 }
578 else
579 {
580 /*
581 * Otherwise treat it as a queue name optionally with an instance name.
582 */
583
584 strlcpy(dest, name, destsize);
585 if ((value = strchr(dest, '/')) != NULL)
586 *value = '\0';
587
588 /*
589 * Setup the Get-Printer-Attributes request...
590 */
591
592 request = ippNewRequest(IPP_GET_PRINTER_ATTRIBUTES);
593
594 httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
595 "localhost", 0, "/printers/%s", dest);
596
597 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
598 NULL, uri);
599
600 ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
601 "requested-attributes",
602 (int)(sizeof(requested) / sizeof(requested[0])),
603 NULL, requested);
604
605 /*
606 * Do the request...
607 */
608
609 response = cupsDoRequest(http, request, "/");
610
611 if (!response || cupsLastError() > IPP_OK_CONFLICT)
612 {
613 syslog(LOG_ERR, "Unable to check printer status - %s",
614 cupsLastErrorString());
615
616 ippDelete(response);
617
618 return (-1);
619 }
620
621 /*
622 * Get values from the response...
623 */
624
625 if (accepting)
626 {
627 if ((attr = ippFindAttribute(response, "printer-is-accepting-jobs",
628 IPP_TAG_BOOLEAN)) == NULL)
629 syslog(LOG_ERR, "No printer-is-accepting-jobs attribute found in "
630 "response from server!");
631 else
632 *accepting = attr->values[0].boolean;
633 }
634
635 if (shared)
636 {
637 if ((attr = ippFindAttribute(response, "printer-is-shared",
638 IPP_TAG_BOOLEAN)) == NULL)
639 {
640 syslog(LOG_ERR, "No printer-is-shared attribute found in "
641 "response from server!");
642 *shared = 1;
643 }
644 else
645 *shared = attr->values[0].boolean;
646 }
647
648 if (state)
649 {
650 if ((attr = ippFindAttribute(response, "printer-state",
651 IPP_TAG_INTEGER)) == NULL)
652 syslog(LOG_ERR, "No printer-state attribute found in "
653 "response from server!");
654 else
655 *state = (ipp_pstate_t)attr->values[0].integer;
656 }
657
658 ippDelete(response);
659 }
660
661 /*
662 * Override shared value for LPD using system-specific APIs...
663 */
664
665 #ifdef HAVE_CFPRIV_H /* MacOS X */
666 if (shared && *shared)
667 {
668 CFURLRef prefsurl; /* */
669 CFDataRef xmldata; /* */
670 CFPropertyListRef plist; /* */
671 CFStringRef queueid; /* */
672 CFArrayRef lprqarray; /* */
673 CFBooleanRef serverflag; /* */
674 Boolean prefsok; /* */
675 static const char printerprefsfile[] =
676 "/Library/Preferences/com.apple.printservice.plist";
677 /* Preferences file */
678
679
680 /*
681 * See if we are running on MacOS X Server...
682 */
683
684 CFDictionaryRef versdict = _CFCopyServerVersionDictionary();
685
686 if (versdict)
687 {
688 /*
689 * Yes, use the LPR sharing preference...
690 */
691
692 CFRelease(versdict);
693
694 *shared = 0;
695
696 prefsurl = CFURLCreateFromFileSystemRepresentation(
697 kCFAllocatorDefault,
698 (const UInt8 *)printerprefsfile,
699 (CFIndex)strlen(printerprefsfile),
700 false);
701 if (prefsurl)
702 {
703 prefsok = CFURLCreateDataAndPropertiesFromResource(
704 kCFAllocatorDefault, prefsurl, &xmldata,
705 NULL, NULL, NULL);
706 if (prefsok)
707 {
708 plist = CFPropertyListCreateFromXMLData(kCFAllocatorDefault, xmldata,
709 kCFPropertyListImmutable, NULL);
710 if (plist)
711 {
712 serverflag = (CFBooleanRef)CFDictionaryGetValue(
713 (CFDictionaryRef)plist, CFSTR("serviceState"));
714
715 if (serverflag && CFBooleanGetValue(serverflag))
716 {
717 lprqarray = (CFArrayRef)CFDictionaryGetValue(
718 (CFDictionaryRef)plist, CFSTR("lprSharedQueues"));
719
720 if (lprqarray)
721 {
722 queueid = CFStringCreateWithCString(CFAllocatorGetDefault(),
723 dest,
724 kCFStringEncodingUTF8);
725
726 if (queueid)
727 {
728 *shared = CFArrayContainsValue(
729 lprqarray,
730 CFRangeMake(0, CFArrayGetCount(lprqarray)),
731 queueid);
732
733 CFRelease(queueid);
734 }
735
736 CFRelease(lprqarray);
737 }
738 }
739
740 if (serverflag)
741 CFRelease(serverflag);
742
743 CFRelease(plist);
744 }
745 }
746
747 CFRelease(prefsurl);
748 }
749
750 if (!shared)
751 syslog(LOG_ERR, "Warning - Print Service sharing disabled for LPD "
752 "on queue: %s", name);
753 }
754 }
755 #endif /* HAVE_CFPRIV_H */
756
757 /*
758 * Next look for the printer in the lpoptions file...
759 */
760
761 num_options = 0;
762
763 if (options && shared && accepting)
764 {
765 if ((cups_serverroot = getenv("CUPS_SERVERROOT")) == NULL)
766 cups_serverroot = CUPS_SERVERROOT;
767
768 snprintf(line, sizeof(line), "%s/lpoptions", cups_serverroot);
769 if ((fp = cupsFileOpen(line, "r")) != NULL)
770 {
771 linenum = 0;
772 while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum))
773 {
774 /*
775 * Make sure we have "Dest name options" or "Default name options"...
776 */
777
778 if ((strcasecmp(line, "Dest") && strcasecmp(line, "Default")) || !value)
779 continue;
780
781 /*
782 * Separate destination name from options...
783 */
784
785 for (optptr = value; *optptr && !isspace(*optptr & 255); optptr ++);
786
787 while (*optptr == ' ')
788 *optptr++ = '\0';
789
790 /*
791 * If this is our destination, parse the options and break out of
792 * the loop - we're done!
793 */
794
795 if (!strcasecmp(value, name))
796 {
797 num_options = cupsParseOptions(optptr, num_options, options);
798 break;
799 }
800 }
801
802 cupsFileClose(fp);
803 }
804 }
805 else if (options)
806 *options = NULL;
807
808 /*
809 * Return the number of options for this destination...
810 */
811
812 return (num_options);
813 }
814
815
816 /*
817 * 'print_file()' - Add a file to the current job.
818 */
819
820 static int /* O - 0 on success, -1 on failure */
821 print_file(http_t *http, /* I - HTTP connection */
822 int id, /* I - Job ID */
823 const char *filename, /* I - File to print */
824 const char *docname, /* I - document-name */
825 const char *user, /* I - requesting-user-name */
826 int last) /* I - 1 = last file in job */
827 {
828 ipp_t *request; /* IPP request */
829 char uri[HTTP_MAX_URI]; /* Printer URI */
830
831
832 /*
833 * Setup the Send-Document request...
834 */
835
836 request = ippNewRequest(IPP_SEND_DOCUMENT);
837
838 snprintf(uri, sizeof(uri), "ipp://localhost/jobs/%d", id);
839 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri", NULL, uri);
840
841 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
842 "requesting-user-name", NULL, user);
843
844 if (docname)
845 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
846 "document-name", NULL, docname);
847
848 if (last)
849 ippAddBoolean(request, IPP_TAG_OPERATION, "last-document", 1);
850
851 /*
852 * Do the request...
853 */
854
855 snprintf(uri, sizeof(uri), "/jobs/%d", id);
856
857 ippDelete(cupsDoFileRequest(http, request, uri, filename));
858
859 if (cupsLastError() > IPP_OK_CONFLICT)
860 {
861 syslog(LOG_ERR, "Unable to send document - %s", cupsLastErrorString());
862
863 return (-1);
864 }
865
866 return (0);
867 }
868
869
870 /*
871 * 'recv_print_job()' - Receive a print job from the client.
872 */
873
874 static int /* O - Command status */
875 recv_print_job(
876 const char *queue, /* I - Printer name */
877 int num_defaults, /* I - Number of default options */
878 cups_option_t *defaults) /* I - Default options */
879 {
880 http_t *http; /* HTTP connection */
881 int i; /* Looping var */
882 int status; /* Command status */
883 int fd; /* Temporary file */
884 FILE *fp; /* File pointer */
885 char filename[1024]; /* Temporary filename */
886 int bytes; /* Bytes received */
887 char line[256], /* Line from file/stdin */
888 command, /* Command from line */
889 *count, /* Number of bytes */
890 *name; /* Name of file */
891 const char *job_sheets; /* Job sheets */
892 int num_data; /* Number of data files */
893 char control[1024], /* Control filename */
894 data[100][256], /* Data files */
895 temp[100][1024]; /* Temporary files */
896 char user[1024], /* User name */
897 title[1024], /* Job title */
898 docname[1024], /* Document name */
899 dest[256]; /* Printer/class queue */
900 int accepting, /* printer-is-accepting */
901 shared, /* printer-is-shared */
902 num_options; /* Number of options */
903 cups_option_t *options; /* Options */
904 int id; /* Job ID */
905 int docnumber, /* Current document number */
906 doccount; /* Count of documents */
907
908
909 /*
910 * Connect to the server...
911 */
912
913 http = httpConnectEncrypt(cupsServer(), ippPort(), cupsEncryption());
914 if (!http)
915 {
916 syslog(LOG_ERR, "Unable to connect to server: %s", strerror(errno));
917
918 putchar(1);
919
920 return (1);
921 }
922
923 /*
924 * See if the printer is available...
925 */
926
927 num_options = get_printer(http, queue, dest, sizeof(dest), &options,
928 &accepting, &shared, NULL);
929
930 if (num_options < 0 || !accepting || !shared)
931 {
932 if (dest[0])
933 syslog(LOG_INFO, "Rejecting job because \"%s\" is not %s", dest,
934 !accepting ? "accepting jobs" : "shared");
935 else
936 syslog(LOG_ERR, "Unable to get printer information for \"%s\"", queue);
937
938 httpClose(http);
939
940 putchar(1);
941
942 return (1);
943 }
944
945 putchar(0); /* OK so far... */
946
947 /*
948 * Read the request...
949 */
950
951 status = 0;
952 num_data = 0;
953 fd = -1;
954
955 control[0] = '\0';
956
957 while (smart_gets(line, sizeof(line), stdin) != NULL)
958 {
959 if (strlen(line) < 2)
960 {
961 status = 1;
962 break;
963 }
964
965 command = line[0];
966 count = line + 1;
967
968 for (name = count + 1; *name && !isspace(*name & 255); name ++);
969 while (isspace(*name & 255))
970 *name++ = '\0';
971
972 switch (command)
973 {
974 default :
975 case 0x01 : /* Abort */
976 status = 1;
977 break;
978
979 case 0x02 : /* Receive control file */
980 if (strlen(name) < 2)
981 {
982 syslog(LOG_ERR, "Bad control file name \"%s\"", name);
983 putchar(1);
984 status = 1;
985 break;
986 }
987
988 if (control[0])
989 {
990 /*
991 * Append to the existing control file - the LPD spec is
992 * not entirely clear, but at least the OS/2 LPD code sends
993 * multiple control files per connection...
994 */
995
996 if ((fd = open(control, O_WRONLY)) < 0)
997 {
998 syslog(LOG_ERR,
999 "Unable to append to temporary control file \"%s\" - %s",
1000 control, strerror(errno));
1001 putchar(1);
1002 status = 1;
1003 break;
1004 }
1005
1006 lseek(fd, 0, SEEK_END);
1007 }
1008 else
1009 {
1010 if ((fd = cupsTempFd(control, sizeof(control))) < 0)
1011 {
1012 syslog(LOG_ERR, "Unable to open temporary control file \"%s\" - %s",
1013 control, strerror(errno));
1014 putchar(1);
1015 status = 1;
1016 break;
1017 }
1018
1019 strcpy(filename, control);
1020 }
1021 break;
1022
1023 case 0x03 : /* Receive data file */
1024 if (strlen(name) < 2)
1025 {
1026 syslog(LOG_ERR, "Bad data file name \"%s\"", name);
1027 putchar(1);
1028 status = 1;
1029 break;
1030 }
1031
1032 if (num_data >= (int)(sizeof(data) / sizeof(data[0])))
1033 {
1034 /*
1035 * Too many data files...
1036 */
1037
1038 syslog(LOG_ERR, "Too many data files (%d)", num_data);
1039 putchar(1);
1040 status = 1;
1041 break;
1042 }
1043
1044 strlcpy(data[num_data], name, sizeof(data[0]));
1045
1046 if ((fd = cupsTempFd(temp[num_data], sizeof(temp[0]))) < 0)
1047 {
1048 syslog(LOG_ERR, "Unable to open temporary data file \"%s\" - %s",
1049 temp[num_data], strerror(errno));
1050 putchar(1);
1051 status = 1;
1052 break;
1053 }
1054
1055 strcpy(filename, temp[num_data]);
1056
1057 num_data ++;
1058 break;
1059 }
1060
1061 putchar(status);
1062
1063 if (status)
1064 break;
1065
1066 /*
1067 * Copy the data or control file from the client...
1068 */
1069
1070 for (i = atoi(count); i > 0; i -= bytes)
1071 {
1072 if (i > sizeof(line))
1073 bytes = sizeof(line);
1074 else
1075 bytes = i;
1076
1077 if ((bytes = fread(line, 1, bytes, stdin)) > 0)
1078 bytes = write(fd, line, bytes);
1079
1080 if (bytes < 1)
1081 {
1082 syslog(LOG_ERR, "Error while reading file - %s",
1083 strerror(errno));
1084 status = 1;
1085 break;
1086 }
1087 }
1088
1089 /*
1090 * Read trailing nul...
1091 */
1092
1093 if (!status)
1094 {
1095 if (fread(line, 1, 1, stdin) < 1)
1096 {
1097 status = 1;
1098 syslog(LOG_ERR, "Error while reading trailing nul - %s",
1099 strerror(errno));
1100 }
1101 else if (line[0])
1102 {
1103 status = 1;
1104 syslog(LOG_ERR, "Trailing character after file is not nul (%02X)!",
1105 line[0]);
1106 }
1107 }
1108
1109 /*
1110 * Close the file and send an acknowledgement...
1111 */
1112
1113 close(fd);
1114
1115 putchar(status);
1116
1117 if (status)
1118 break;
1119 }
1120
1121 if (!status)
1122 {
1123 /*
1124 * Process the control file and print stuff...
1125 */
1126
1127 if ((fp = fopen(control, "rb")) == NULL)
1128 status = 1;
1129 else
1130 {
1131 /*
1132 * Copy the default options...
1133 */
1134
1135 for (i = 0; i < num_defaults; i ++)
1136 num_options = cupsAddOption(defaults[i].name,
1137 defaults[i].value,
1138 num_options, &options);
1139
1140 /*
1141 * Grab the job information...
1142 */
1143
1144 title[0] = '\0';
1145 user[0] = '\0';
1146 doccount = 0;
1147
1148 while (smart_gets(line, sizeof(line), fp) != NULL)
1149 {
1150 /*
1151 * Process control lines...
1152 */
1153
1154 switch (line[0])
1155 {
1156 case 'J' : /* Job name */
1157 strlcpy(title, line + 1, sizeof(title));
1158 break;
1159
1160 case 'P' : /* User identification */
1161 strlcpy(user, line + 1, sizeof(user));
1162 break;
1163
1164 case 'L' : /* Print banner page */
1165 /*
1166 * If a banner was requested and it's not overridden by a
1167 * command line option and the destination's default is none
1168 * then add the standard banner...
1169 */
1170
1171 if (cupsGetOption("job-sheets", num_defaults, defaults) == NULL &&
1172 ((job_sheets = cupsGetOption("job-sheets", num_options,
1173 options)) == NULL ||
1174 !strcmp(job_sheets, "none,none")))
1175 {
1176 num_options = cupsAddOption("job-sheets", "standard",
1177 num_options, &options);
1178 }
1179 break;
1180
1181 case 'c' : /* Plot CIF file */
1182 case 'd' : /* Print DVI file */
1183 case 'f' : /* Print formatted file */
1184 case 'g' : /* Plot file */
1185 case 'l' : /* Print file leaving control characters (raw) */
1186 case 'n' : /* Print ditroff output file */
1187 case 'o' : /* Print PostScript output file */
1188 case 'p' : /* Print file with 'pr' format (prettyprint) */
1189 case 'r' : /* File to print with FORTRAN carriage control */
1190 case 't' : /* Print troff output file */
1191 case 'v' : /* Print raster file */
1192 doccount ++;
1193
1194 if (line[0] == 'l' &&
1195 !cupsGetOption("document-format", num_options, options))
1196 num_options = cupsAddOption("raw", "", num_options, &options);
1197
1198 if (line[0] == 'p')
1199 num_options = cupsAddOption("prettyprint", "", num_options,
1200 &options);
1201 break;
1202 }
1203
1204 if (status)
1205 break;
1206 }
1207
1208 /*
1209 * Check that we have a username...
1210 */
1211
1212 if (!user[0])
1213 {
1214 syslog(LOG_WARNING, "No username specified by client! "
1215 "Using \"anonymous\"...");
1216 strcpy(user, "anonymous");
1217 }
1218
1219 /*
1220 * Create the job...
1221 */
1222
1223 if ((id = create_job(http, dest, title, user, num_options, options)) < 0)
1224 status = 1;
1225 else
1226 {
1227 /*
1228 * Then print the job files...
1229 */
1230
1231 rewind(fp);
1232
1233 docname[0] = '\0';
1234 docnumber = 0;
1235
1236 while (smart_gets(line, sizeof(line), fp) != NULL)
1237 {
1238 /*
1239 * Process control lines...
1240 */
1241
1242 switch (line[0])
1243 {
1244 case 'N' : /* Document name */
1245 strlcpy(docname, line + 1, sizeof(docname));
1246 break;
1247
1248 case 'c' : /* Plot CIF file */
1249 case 'd' : /* Print DVI file */
1250 case 'f' : /* Print formatted file */
1251 case 'g' : /* Plot file */
1252 case 'l' : /* Print file leaving control characters (raw) */
1253 case 'n' : /* Print ditroff output file */
1254 case 'o' : /* Print PostScript output file */
1255 case 'p' : /* Print file with 'pr' format (prettyprint) */
1256 case 'r' : /* File to print with FORTRAN carriage control */
1257 case 't' : /* Print troff output file */
1258 case 'v' : /* Print raster file */
1259 /*
1260 * Figure out which file we are printing...
1261 */
1262
1263 for (i = 0; i < num_data; i ++)
1264 if (!strcmp(data[i], line + 1))
1265 break;
1266
1267 if (i >= num_data)
1268 {
1269 status = 1;
1270 break;
1271 }
1272
1273 /*
1274 * Send the print file...
1275 */
1276
1277 docnumber ++;
1278
1279 if (print_file(http, id, temp[i], docname, user,
1280 docnumber == doccount))
1281 status = 1;
1282 else
1283 status = 0;
1284
1285 break;
1286 }
1287
1288 if (status)
1289 break;
1290 }
1291
1292 fclose(fp);
1293 }
1294 }
1295 }
1296
1297 cupsFreeOptions(num_options, options);
1298
1299 httpClose(http);
1300
1301 /*
1302 * Clean up all temporary files and return...
1303 */
1304
1305 unlink(control);
1306
1307 for (i = 0; i < num_data; i ++)
1308 unlink(temp[i]);
1309
1310 return (status);
1311 }
1312
1313
1314 /*
1315 * 'remove_jobs()' - Cancel one or more jobs.
1316 */
1317
1318 static int /* O - Command status */
1319 remove_jobs(const char *dest, /* I - Destination */
1320 const char *agent, /* I - User agent */
1321 const char *list) /* I - List of jobs or users */
1322 {
1323 int id; /* Job ID */
1324 http_t *http; /* HTTP server connection */
1325 ipp_t *request; /* IPP Request */
1326 char uri[HTTP_MAX_URI]; /* Job URI */
1327
1328
1329 (void)dest; /* Suppress compiler warnings... */
1330
1331 /*
1332 * Try connecting to the local server...
1333 */
1334
1335 if ((http = httpConnectEncrypt(cupsServer(), ippPort(),
1336 cupsEncryption())) == NULL)
1337 {
1338 syslog(LOG_ERR, "Unable to connect to server %s: %s", cupsServer(),
1339 strerror(errno));
1340 return (1);
1341 }
1342
1343 /*
1344 * Loop for each job...
1345 */
1346
1347 while ((id = atoi(list)) > 0)
1348 {
1349 /*
1350 * Skip job ID in list...
1351 */
1352
1353 while (isdigit(*list & 255))
1354 list ++;
1355 while (isspace(*list & 255))
1356 list ++;
1357
1358 /*
1359 * Build an IPP_CANCEL_JOB request, which requires the following
1360 * attributes:
1361 *
1362 * attributes-charset
1363 * attributes-natural-language
1364 * job-uri
1365 * requesting-user-name
1366 */
1367
1368 request = ippNewRequest(IPP_CANCEL_JOB);
1369
1370 sprintf(uri, "ipp://localhost/jobs/%d", id);
1371 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri", NULL, uri);
1372
1373 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
1374 "requesting-user-name", NULL, agent);
1375
1376 /*
1377 * Do the request and get back a response...
1378 */
1379
1380 ippDelete(cupsDoRequest(http, request, "/jobs"));
1381
1382 if (cupsLastError() > IPP_OK_CONFLICT)
1383 {
1384 syslog(LOG_WARNING, "Cancel of job ID %d failed: %s\n", id,
1385 cupsLastErrorString());
1386 httpClose(http);
1387 return (1);
1388 }
1389 else
1390 syslog(LOG_INFO, "Job ID %d cancelled", id);
1391 }
1392
1393 httpClose(http);
1394
1395 return (0);
1396 }
1397
1398
1399 /*
1400 * 'send_state()' - Send the queue state.
1401 */
1402
1403 static int /* O - Command status */
1404 send_state(const char *queue, /* I - Destination */
1405 const char *list, /* I - Job or user */
1406 int longstatus) /* I - List of jobs or users */
1407 {
1408 int id; /* Job ID from list */
1409 http_t *http; /* HTTP server connection */
1410 ipp_t *request, /* IPP Request */
1411 *response; /* IPP Response */
1412 ipp_attribute_t *attr; /* Current attribute */
1413 ipp_pstate_t state; /* Printer state */
1414 const char *jobdest, /* Pointer into job-printer-uri */
1415 *jobuser, /* Pointer to job-originating-user-name */
1416 *jobname; /* Pointer to job-name */
1417 ipp_jstate_t jobstate; /* job-state */
1418 int jobid, /* job-id */
1419 jobsize, /* job-k-octets */
1420 jobcount, /* Number of jobs */
1421 jobcopies, /* Number of copies */
1422 rank; /* Rank of job */
1423 char rankstr[255]; /* Rank string */
1424 char namestr[1024]; /* Job name string */
1425 char uri[HTTP_MAX_URI]; /* Printer URI */
1426 char dest[256]; /* Printer/class queue */
1427 static const char * const ranks[10] = /* Ranking strings */
1428 {
1429 "th",
1430 "st",
1431 "nd",
1432 "rd",
1433 "th",
1434 "th",
1435 "th",
1436 "th",
1437 "th",
1438 "th"
1439 };
1440 static const char * const requested[] =
1441 { /* Requested attributes */
1442 "job-id",
1443 "job-k-octets",
1444 "job-state",
1445 "job-printer-uri",
1446 "job-originating-user-name",
1447 "job-name",
1448 "copies"
1449 };
1450
1451
1452 /*
1453 * Try connecting to the local server...
1454 */
1455
1456 if ((http = httpConnectEncrypt(cupsServer(), ippPort(),
1457 cupsEncryption())) == NULL)
1458 {
1459 syslog(LOG_ERR, "Unable to connect to server %s: %s", cupsServer(),
1460 strerror(errno));
1461 printf("Unable to connect to server %s: %s", cupsServer(), strerror(errno));
1462 return (1);
1463 }
1464
1465 /*
1466 * Get the actual destination name and printer state...
1467 */
1468
1469 if (get_printer(http, queue, dest, sizeof(dest), NULL, NULL, NULL, &state))
1470 {
1471 syslog(LOG_ERR, "Unable to get printer %s: %s", queue,
1472 cupsLastErrorString());
1473 printf("Unable to get printer %s: %s", queue, cupsLastErrorString());
1474 return (1);
1475 }
1476
1477 /*
1478 * Show the queue state...
1479 */
1480
1481 switch (state)
1482 {
1483 case IPP_PRINTER_IDLE :
1484 printf("%s is ready\n", dest);
1485 break;
1486 case IPP_PRINTER_PROCESSING :
1487 printf("%s is ready and printing\n", dest);
1488 break;
1489 case IPP_PRINTER_STOPPED :
1490 printf("%s is not ready\n", dest);
1491 break;
1492 }
1493
1494 /*
1495 * Build an IPP_GET_JOBS or IPP_GET_JOB_ATTRIBUTES request, which requires
1496 * the following attributes:
1497 *
1498 * attributes-charset
1499 * attributes-natural-language
1500 * job-uri or printer-uri
1501 */
1502
1503 id = atoi(list);
1504
1505 request = ippNewRequest(id ? IPP_GET_JOB_ATTRIBUTES : IPP_GET_JOBS);
1506
1507 httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
1508 "localhost", 0, "/printers/%s", dest);
1509
1510 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
1511 NULL, uri);
1512
1513 if (id)
1514 ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id", id);
1515 else
1516 {
1517 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
1518 "requesting-user-name", NULL, list);
1519 ippAddBoolean(request, IPP_TAG_OPERATION, "my-jobs", 1);
1520 }
1521
1522 ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
1523 "requested-attributes",
1524 sizeof(requested) / sizeof(requested[0]),
1525 NULL, requested);
1526
1527 /*
1528 * Do the request and get back a response...
1529 */
1530
1531 jobcount = 0;
1532 response = cupsDoRequest(http, request, "/");
1533
1534 if (cupsLastError() > IPP_OK_CONFLICT)
1535 {
1536 printf("get-jobs failed: %s\n", cupsLastErrorString());
1537 ippDelete(response);
1538 return (1);
1539 }
1540
1541 /*
1542 * Loop through the job list and display them...
1543 */
1544
1545 for (attr = response->attrs, rank = 1; attr; attr = attr->next)
1546 {
1547 /*
1548 * Skip leading attributes until we hit a job...
1549 */
1550
1551 while (attr && (attr->group_tag != IPP_TAG_JOB || !attr->name))
1552 attr = attr->next;
1553
1554 if (!attr)
1555 break;
1556
1557 /*
1558 * Pull the needed attributes from this job...
1559 */
1560
1561 jobid = 0;
1562 jobsize = 0;
1563 jobstate = IPP_JOB_PENDING;
1564 jobname = "untitled";
1565 jobuser = NULL;
1566 jobdest = NULL;
1567 jobcopies = 1;
1568
1569 while (attr && attr->group_tag == IPP_TAG_JOB)
1570 {
1571 if (!strcmp(attr->name, "job-id") &&
1572 attr->value_tag == IPP_TAG_INTEGER)
1573 jobid = attr->values[0].integer;
1574
1575 if (!strcmp(attr->name, "job-k-octets") &&
1576 attr->value_tag == IPP_TAG_INTEGER)
1577 jobsize = attr->values[0].integer;
1578
1579 if (!strcmp(attr->name, "job-state") &&
1580 attr->value_tag == IPP_TAG_ENUM)
1581 jobstate = (ipp_jstate_t)attr->values[0].integer;
1582
1583 if (!strcmp(attr->name, "job-printer-uri") &&
1584 attr->value_tag == IPP_TAG_URI)
1585 if ((jobdest = strrchr(attr->values[0].string.text, '/')) != NULL)
1586 jobdest ++;
1587
1588 if (!strcmp(attr->name, "job-originating-user-name") &&
1589 attr->value_tag == IPP_TAG_NAME)
1590 jobuser = attr->values[0].string.text;
1591
1592 if (!strcmp(attr->name, "job-name") &&
1593 attr->value_tag == IPP_TAG_NAME)
1594 jobname = attr->values[0].string.text;
1595
1596 if (!strcmp(attr->name, "copies") &&
1597 attr->value_tag == IPP_TAG_INTEGER)
1598 jobcopies = attr->values[0].integer;
1599
1600 attr = attr->next;
1601 }
1602
1603 /*
1604 * See if we have everything needed...
1605 */
1606
1607 if (!jobdest || !jobid)
1608 {
1609 if (!attr)
1610 break;
1611 else
1612 continue;
1613 }
1614
1615 if (!longstatus && jobcount == 0)
1616 puts("Rank Owner Job File(s) Total Size");
1617
1618 jobcount ++;
1619
1620 /*
1621 * Display the job...
1622 */
1623
1624 if (jobstate == IPP_JOB_PROCESSING)
1625 strcpy(rankstr, "active");
1626 else
1627 {
1628 snprintf(rankstr, sizeof(rankstr), "%d%s", rank, ranks[rank % 10]);
1629 rank ++;
1630 }
1631
1632 if (longstatus)
1633 {
1634 puts("");
1635
1636 if (jobcopies > 1)
1637 snprintf(namestr, sizeof(namestr), "%d copies of %s", jobcopies,
1638 jobname);
1639 else
1640 strlcpy(namestr, jobname, sizeof(namestr));
1641
1642 printf("%s: %-33.33s [job %d localhost]\n", jobuser, rankstr, jobid);
1643 printf(" %-39.39s %.0f bytes\n", namestr, 1024.0 * jobsize);
1644 }
1645 else
1646 printf("%-7s %-7.7s %-7d %-31.31s %.0f bytes\n", rankstr, jobuser,
1647 jobid, jobname, 1024.0 * jobsize);
1648
1649 if (!attr)
1650 break;
1651 }
1652
1653 ippDelete(response);
1654
1655 if (jobcount == 0)
1656 puts("no entries");
1657
1658 httpClose(http);
1659
1660 return (0);
1661 }
1662
1663
1664 /*
1665 * 'smart_gets()' - Get a line of text, removing the trailing CR and/or LF.
1666 */
1667
1668 static char * /* O - Line read or NULL */
1669 smart_gets(char *s, /* I - Pointer to line buffer */
1670 int len, /* I - Size of line buffer */
1671 FILE *fp) /* I - File to read from */
1672 {
1673 char *ptr, /* Pointer into line */
1674 *end; /* End of line */
1675 int ch; /* Character from file */
1676
1677
1678 /*
1679 * Read the line; unlike fgets(), we read the entire line but dump
1680 * characters that go past the end of the buffer. Also, we accept
1681 * CR, LF, or CR LF for the line endings to be "safe", although
1682 * RFC 1179 specifically says "just use LF".
1683 */
1684
1685 ptr = s;
1686 end = s + len - 1;
1687
1688 while ((ch = getc(fp)) != EOF)
1689 {
1690 if (ch == '\n')
1691 break;
1692 else if (ch == '\r')
1693 {
1694 /*
1695 * See if a LF follows...
1696 */
1697
1698 ch = getc(fp);
1699
1700 if (ch != '\n')
1701 ungetc(ch, fp);
1702
1703 break;
1704 }
1705 else if (ptr < end)
1706 *ptr++ = ch;
1707 }
1708
1709 *ptr = '\0';
1710
1711 if (ch == EOF && ptr == s)
1712 return (NULL);
1713 else
1714 return (s);
1715 }
1716
1717
1718 /*
1719 * End of "$Id: cups-lpd.c 177 2006-06-21 00:20:03Z jlovell $".
1720 */