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