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