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