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