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