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