1 /*
2  * PPD utilities for CUPS.
3  *
4  * Copyright © 2007-2018 by Apple Inc.
5  * Copyright © 1997-2006 by Easy Software Products.
6  *
7  * Licensed under Apache License v2.0.  See the file "LICENSE" for more
8  * information.
9  */
10 
11 /*
12  * Include necessary headers...
13  */
14 
15 #include "cups-private.h"
16 #include "ppd-private.h"
17 #include "debug-internal.h"
18 #include <fcntl.h>
19 #include <sys/stat.h>
20 #if defined(_WIN32) || defined(__EMX__)
21 #  include <io.h>
22 #else
23 #  include <unistd.h>
24 #endif /* _WIN32 || __EMX__ */
25 
26 
27 /*
28  * Local functions...
29  */
30 
31 static int	cups_get_printer_uri(http_t *http, const char *name,
32 		                     char *host, int hostsize, int *port,
33 				     char *resource, int resourcesize,
34 				     int depth);
35 
36 
37 /*
38  * 'cupsGetPPD()' - Get the PPD file for a printer on the default server.
39  *
40  * For classes, @code cupsGetPPD@ returns the PPD file for the first printer
41  * in the class.
42  *
43  * The returned filename is stored in a static buffer and is overwritten with
44  * each call to @code cupsGetPPD@ or @link cupsGetPPD2@.  The caller "owns" the
45  * file that is created and must @code unlink@ the returned filename.
46  */
47 
48 const char *				/* O - Filename for PPD file */
cupsGetPPD(const char * name)49 cupsGetPPD(const char *name)		/* I - Destination name */
50 {
51   _ppd_globals_t *pg = _ppdGlobals();	/* Pointer to library globals */
52   time_t	modtime = 0;		/* Modification time */
53 
54 
55  /*
56   * Return the PPD file...
57   */
58 
59   pg->ppd_filename[0] = '\0';
60 
61   if (cupsGetPPD3(CUPS_HTTP_DEFAULT, name, &modtime, pg->ppd_filename,
62                   sizeof(pg->ppd_filename)) == HTTP_STATUS_OK)
63     return (pg->ppd_filename);
64   else
65     return (NULL);
66 }
67 
68 
69 /*
70  * 'cupsGetPPD2()' - Get the PPD file for a printer from the specified server.
71  *
72  * For classes, @code cupsGetPPD2@ returns the PPD file for the first printer
73  * in the class.
74  *
75  * The returned filename is stored in a static buffer and is overwritten with
76  * each call to @link cupsGetPPD@ or @code cupsGetPPD2@.  The caller "owns" the
77  * file that is created and must @code unlink@ the returned filename.
78  *
79  * @since CUPS 1.1.21/macOS 10.4@
80  */
81 
82 const char *				/* O - Filename for PPD file */
cupsGetPPD2(http_t * http,const char * name)83 cupsGetPPD2(http_t     *http,		/* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */
84             const char *name)		/* I - Destination name */
85 {
86   _ppd_globals_t *pg = _ppdGlobals();	/* Pointer to library globals */
87   time_t	modtime = 0;		/* Modification time */
88 
89 
90   pg->ppd_filename[0] = '\0';
91 
92   if (cupsGetPPD3(http, name, &modtime, pg->ppd_filename,
93                   sizeof(pg->ppd_filename)) == HTTP_STATUS_OK)
94     return (pg->ppd_filename);
95   else
96     return (NULL);
97 }
98 
99 
100 /*
101  * 'cupsGetPPD3()' - Get the PPD file for a printer on the specified
102  *                   server if it has changed.
103  *
104  * The "modtime" parameter contains the modification time of any
105  * locally-cached content and is updated with the time from the PPD file on
106  * the server.
107  *
108  * The "buffer" parameter contains the local PPD filename.  If it contains
109  * the empty string, a new temporary file is created, otherwise the existing
110  * file will be overwritten as needed.  The caller "owns" the file that is
111  * created and must @code unlink@ the returned filename.
112  *
113  * On success, @code HTTP_STATUS_OK@ is returned for a new PPD file and
114  * @code HTTP_STATUS_NOT_MODIFIED@ if the existing PPD file is up-to-date.  Any other
115  * status is an error.
116  *
117  * For classes, @code cupsGetPPD3@ returns the PPD file for the first printer
118  * in the class.
119  *
120  * @since CUPS 1.4/macOS 10.6@
121  */
122 
123 http_status_t				/* O  - HTTP status */
cupsGetPPD3(http_t * http,const char * name,time_t * modtime,char * buffer,size_t bufsize)124 cupsGetPPD3(http_t     *http,		/* I  - HTTP connection or @code CUPS_HTTP_DEFAULT@ */
125             const char *name,		/* I  - Destination name */
126 	    time_t     *modtime,	/* IO - Modification time */
127 	    char       *buffer,		/* I  - Filename buffer */
128 	    size_t     bufsize)		/* I  - Size of filename buffer */
129 {
130   int		http_port;		/* Port number */
131   char		http_hostname[HTTP_MAX_HOST];
132 					/* Hostname associated with connection */
133   http_t	*http2;			/* Alternate HTTP connection */
134   int		fd;			/* PPD file */
135   char		localhost[HTTP_MAX_URI],/* Local hostname */
136 		hostname[HTTP_MAX_URI],	/* Hostname */
137 		resource[HTTP_MAX_URI];	/* Resource name */
138   int		port;			/* Port number */
139   http_status_t	status;			/* HTTP status from server */
140   char		tempfile[1024] = "";	/* Temporary filename */
141   _cups_globals_t *cg = _cupsGlobals();	/* Pointer to library globals */
142 
143 
144  /*
145   * Range check input...
146   */
147 
148   DEBUG_printf(("cupsGetPPD3(http=%p, name=\"%s\", modtime=%p(%d), buffer=%p, "
149                 "bufsize=%d)", http, name, modtime,
150 		modtime ? (int)*modtime : 0, buffer, (int)bufsize));
151 
152   if (!name)
153   {
154     DEBUG_puts("2cupsGetPPD3: No printer name, returning NULL.");
155     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("No printer name"), 1);
156     return (HTTP_STATUS_NOT_ACCEPTABLE);
157   }
158 
159   if (!modtime)
160   {
161     DEBUG_puts("2cupsGetPPD3: No modtime, returning NULL.");
162     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("No modification time"), 1);
163     return (HTTP_STATUS_NOT_ACCEPTABLE);
164   }
165 
166   if (!buffer || bufsize <= 1)
167   {
168     DEBUG_puts("2cupsGetPPD3: No filename buffer, returning NULL.");
169     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad filename buffer"), 1);
170     return (HTTP_STATUS_NOT_ACCEPTABLE);
171   }
172 
173 #ifndef _WIN32
174  /*
175   * See if the PPD file is available locally...
176   */
177 
178   if (http)
179     httpGetHostname(http, hostname, sizeof(hostname));
180   else
181   {
182     strlcpy(hostname, cupsServer(), sizeof(hostname));
183     if (hostname[0] == '/')
184       strlcpy(hostname, "localhost", sizeof(hostname));
185   }
186 
187   if (!_cups_strcasecmp(hostname, "localhost"))
188   {
189     char	ppdname[1024];		/* PPD filename */
190     struct stat	ppdinfo;		/* PPD file information */
191 
192 
193     snprintf(ppdname, sizeof(ppdname), "%s/ppd/%s.ppd", cg->cups_serverroot,
194              name);
195     if (!stat(ppdname, &ppdinfo) && !access(ppdname, R_OK))
196     {
197      /*
198       * OK, the file exists and is readable, use it!
199       */
200 
201       if (buffer[0])
202       {
203         DEBUG_printf(("2cupsGetPPD3: Using filename \"%s\".", buffer));
204 
205         unlink(buffer);
206 
207 	if (symlink(ppdname, buffer) && errno != EEXIST)
208         {
209           _cupsSetError(IPP_STATUS_ERROR_INTERNAL, NULL, 0);
210 
211 	  return (HTTP_STATUS_SERVER_ERROR);
212 	}
213       }
214       else
215       {
216         int		tries;		/* Number of tries */
217         const char	*tmpdir;	/* TMPDIR environment variable */
218 	struct timeval	curtime;	/* Current time */
219 
220 
221 #ifdef __APPLE__
222        /*
223 	* On macOS and iOS, the TMPDIR environment variable is not always the
224 	* best location to place temporary files due to sandboxing.  Instead,
225 	* the confstr function should be called to get the proper per-user,
226 	* per-process TMPDIR value.
227 	*/
228 
229         char		tmppath[1024];	/* Temporary directory */
230 
231 	if ((tmpdir = getenv("TMPDIR")) != NULL && access(tmpdir, W_OK))
232 	  tmpdir = NULL;
233 
234 	if (!tmpdir)
235 	{
236 	  if (confstr(_CS_DARWIN_USER_TEMP_DIR, tmppath, sizeof(tmppath)))
237 	    tmpdir = tmppath;
238 	  else
239 	    tmpdir = "/private/tmp";		/* This should never happen */
240 	}
241 #else
242        /*
243 	* Previously we put root temporary files in the default CUPS temporary
244 	* directory under /var/spool/cups.  However, since the scheduler cleans
245 	* out temporary files there and runs independently of the user apps, we
246 	* don't want to use it unless specifically told to by cupsd.
247 	*/
248 
249 	if ((tmpdir = getenv("TMPDIR")) == NULL)
250 	  tmpdir = "/tmp";
251 #endif /* __APPLE__ */
252 
253         DEBUG_printf(("2cupsGetPPD3: tmpdir=\"%s\".", tmpdir));
254 
255        /*
256 	* Make the temporary name using the specified directory...
257 	*/
258 
259 	tries = 0;
260 
261 	do
262 	{
263 	 /*
264 	  * Get the current time of day...
265 	  */
266 
267 	  gettimeofday(&curtime, NULL);
268 
269 	 /*
270 	  * Format a string using the hex time values...
271 	  */
272 
273 	  snprintf(buffer, bufsize, "%s/%08lx%05lx", tmpdir,
274 		   (unsigned long)curtime.tv_sec,
275 		   (unsigned long)curtime.tv_usec);
276 
277 	 /*
278 	  * Try to make a symlink...
279 	  */
280 
281 	  if (!symlink(ppdname, buffer))
282 	    break;
283 
284 	  DEBUG_printf(("2cupsGetPPD3: Symlink \"%s\" to \"%s\" failed: %s", ppdname, buffer, strerror(errno)));
285 
286 	  tries ++;
287 	}
288 	while (tries < 1000);
289 
290         if (tries >= 1000)
291 	{
292 	  DEBUG_puts("2cupsGetPPD3: Unable to symlink after 1000 tries, returning error.");
293 
294           _cupsSetError(IPP_STATUS_ERROR_INTERNAL, NULL, 0);
295 
296 	  return (HTTP_STATUS_SERVER_ERROR);
297 	}
298       }
299 
300       if (*modtime >= ppdinfo.st_mtime)
301       {
302         DEBUG_printf(("2cupsGetPPD3: Returning not-modified, filename=\"%s\".", buffer));
303         return (HTTP_STATUS_NOT_MODIFIED);
304       }
305       else
306       {
307         DEBUG_printf(("2cupsGetPPD3: Returning ok, filename=\"%s\", modtime=%ld.", buffer, (long)ppdinfo.st_mtime));
308         *modtime = ppdinfo.st_mtime;
309 	return (HTTP_STATUS_OK);
310       }
311     }
312   }
313 #endif /* !_WIN32 */
314 
315  /*
316   * Try finding a printer URI for this printer...
317   */
318 
319   DEBUG_puts("2cupsGetPPD3: Unable to access local file, copying...");
320 
321   if (!http)
322   {
323     if ((http = _cupsConnect()) == NULL)
324     {
325       DEBUG_puts("2cupsGetPPD3: Unable to connect to scheduler.");
326       return (HTTP_STATUS_SERVICE_UNAVAILABLE);
327     }
328   }
329 
330   if (!cups_get_printer_uri(http, name, hostname, sizeof(hostname), &port, resource, sizeof(resource), 0))
331   {
332     DEBUG_puts("2cupsGetPPD3: Unable to get printer URI.");
333     return (HTTP_STATUS_NOT_FOUND);
334   }
335 
336   DEBUG_printf(("2cupsGetPPD3: Printer hostname=\"%s\", port=%d", hostname, port));
337 
338   if (cupsServer()[0] == '/' && !_cups_strcasecmp(hostname, "localhost") && port == ippPort())
339   {
340    /*
341     * Redirect localhost to domain socket...
342     */
343 
344     strlcpy(hostname, cupsServer(), sizeof(hostname));
345     port = 0;
346 
347     DEBUG_printf(("2cupsGetPPD3: Redirecting to \"%s\".", hostname));
348   }
349 
350  /*
351   * Remap local hostname to localhost...
352   */
353 
354   httpGetHostname(NULL, localhost, sizeof(localhost));
355 
356   DEBUG_printf(("2cupsGetPPD3: Local hostname=\"%s\"", localhost));
357 
358   if (!_cups_strcasecmp(localhost, hostname))
359     strlcpy(hostname, "localhost", sizeof(hostname));
360 
361  /*
362   * Get the hostname and port number we are connected to...
363   */
364 
365   httpGetHostname(http, http_hostname, sizeof(http_hostname));
366   http_port = httpAddrPort(http->hostaddr);
367 
368   DEBUG_printf(("2cupsGetPPD3: Connection hostname=\"%s\", port=%d",
369                 http_hostname, http_port));
370 
371  /*
372   * Reconnect to the correct server as needed...
373   */
374 
375   if (!_cups_strcasecmp(http_hostname, hostname) && port == http_port)
376     http2 = http;
377   else if ((http2 = httpConnect2(hostname, port, NULL, AF_UNSPEC,
378 				 cupsEncryption(), 1, 30000, NULL)) == NULL)
379   {
380     DEBUG_puts("2cupsGetPPD3: Unable to connect to server");
381 
382     return (HTTP_STATUS_SERVICE_UNAVAILABLE);
383   }
384 
385  /*
386   * Get a temp file...
387   */
388 
389   if (buffer[0])
390     fd = open(buffer, O_CREAT | O_TRUNC | O_WRONLY, 0600);
391   else
392     fd = cupsTempFd(tempfile, sizeof(tempfile));
393 
394   if (fd < 0)
395   {
396    /*
397     * Can't open file; close the server connection and return NULL...
398     */
399 
400     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, NULL, 0);
401 
402     if (http2 != http)
403       httpClose(http2);
404 
405     return (HTTP_STATUS_SERVER_ERROR);
406   }
407 
408  /*
409   * And send a request to the HTTP server...
410   */
411 
412   strlcat(resource, ".ppd", sizeof(resource));
413 
414   if (*modtime > 0)
415     httpSetField(http2, HTTP_FIELD_IF_MODIFIED_SINCE,
416                  httpGetDateString(*modtime));
417 
418   status = cupsGetFd(http2, resource, fd);
419 
420   close(fd);
421 
422  /*
423   * See if we actually got the file or an error...
424   */
425 
426   if (status == HTTP_STATUS_OK)
427   {
428     *modtime = httpGetDateTime(httpGetField(http2, HTTP_FIELD_DATE));
429 
430     if (tempfile[0])
431       strlcpy(buffer, tempfile, bufsize);
432   }
433   else if (status != HTTP_STATUS_NOT_MODIFIED)
434   {
435     _cupsSetHTTPError(status);
436 
437     if (buffer[0])
438       unlink(buffer);
439     else if (tempfile[0])
440       unlink(tempfile);
441   }
442   else if (tempfile[0])
443     unlink(tempfile);
444 
445   if (http2 != http)
446     httpClose(http2);
447 
448  /*
449   * Return the PPD file...
450   */
451 
452   DEBUG_printf(("2cupsGetPPD3: Returning status %d", status));
453 
454   return (status);
455 }
456 
457 
458 /*
459  * 'cupsGetServerPPD()' - Get an available PPD file from the server.
460  *
461  * This function returns the named PPD file from the server.  The
462  * list of available PPDs is provided by the IPP @code CUPS_GET_PPDS@
463  * operation.
464  *
465  * You must remove (unlink) the PPD file when you are finished with
466  * it. The PPD filename is stored in a static location that will be
467  * overwritten on the next call to @link cupsGetPPD@, @link cupsGetPPD2@,
468  * or @link cupsGetServerPPD@.
469  *
470  * @since CUPS 1.3/macOS 10.5@
471  */
472 
473 char *					/* O - Name of PPD file or @code NULL@ on error */
cupsGetServerPPD(http_t * http,const char * name)474 cupsGetServerPPD(http_t     *http,	/* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */
475                  const char *name)	/* I - Name of PPD file ("ppd-name") */
476 {
477   int			fd;		/* PPD file descriptor */
478   ipp_t			*request;	/* IPP request */
479   _ppd_globals_t	*pg = _ppdGlobals();
480 					/* Pointer to library globals */
481 
482 
483  /*
484   * Range check input...
485   */
486 
487   if (!name)
488   {
489     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("No PPD name"), 1);
490 
491     return (NULL);
492   }
493 
494   if (!http)
495     if ((http = _cupsConnect()) == NULL)
496       return (NULL);
497 
498  /*
499   * Get a temp file...
500   */
501 
502   if ((fd = cupsTempFd(pg->ppd_filename, sizeof(pg->ppd_filename))) < 0)
503   {
504    /*
505     * Can't open file; close the server connection and return NULL...
506     */
507 
508     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, NULL, 0);
509 
510     return (NULL);
511   }
512 
513  /*
514   * Get the PPD file...
515   */
516 
517   request = ippNewRequest(IPP_OP_CUPS_GET_PPD);
518   ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "ppd-name", NULL,
519                name);
520 
521   ippDelete(cupsDoIORequest(http, request, "/", -1, fd));
522 
523   close(fd);
524 
525   if (cupsLastError() != IPP_STATUS_OK)
526   {
527     unlink(pg->ppd_filename);
528     return (NULL);
529   }
530   else
531     return (pg->ppd_filename);
532 }
533 
534 
535 /*
536  * 'cups_get_printer_uri()' - Get the printer-uri-supported attribute for the
537  *                            first printer in a class.
538  */
539 
540 static int				/* O - 1 on success, 0 on failure */
cups_get_printer_uri(http_t * http,const char * name,char * host,int hostsize,int * port,char * resource,int resourcesize,int depth)541 cups_get_printer_uri(
542     http_t     *http,			/* I - Connection to server */
543     const char *name,			/* I - Name of printer or class */
544     char       *host,			/* I - Hostname buffer */
545     int        hostsize,		/* I - Size of hostname buffer */
546     int        *port,			/* O - Port number */
547     char       *resource,		/* I - Resource buffer */
548     int        resourcesize,		/* I - Size of resource buffer */
549     int        depth)			/* I - Depth of query */
550 {
551   int		i;			/* Looping var */
552   ipp_t		*request,		/* IPP request */
553 		*response;		/* IPP response */
554   ipp_attribute_t *attr;		/* Current attribute */
555   char		uri[HTTP_MAX_URI],	/* printer-uri attribute */
556 		scheme[HTTP_MAX_URI],	/* Scheme name */
557 		username[HTTP_MAX_URI];	/* Username:password */
558   static const char * const requested_attrs[] =
559 		{			/* Requested attributes */
560 		  "member-uris",
561 		  "printer-uri-supported"
562 		};
563 
564 
565   DEBUG_printf(("4cups_get_printer_uri(http=%p, name=\"%s\", host=%p, hostsize=%d, resource=%p, resourcesize=%d, depth=%d)", http, name, host, hostsize, resource, resourcesize, depth));
566 
567  /*
568   * Setup the printer URI...
569   */
570 
571   if (httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL, "localhost", 0, "/printers/%s", name) < HTTP_URI_STATUS_OK)
572   {
573     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to create printer-uri"), 1);
574 
575     *host     = '\0';
576     *resource = '\0';
577 
578     return (0);
579   }
580 
581   DEBUG_printf(("5cups_get_printer_uri: printer-uri=\"%s\"", uri));
582 
583  /*
584   * Build an IPP_GET_PRINTER_ATTRIBUTES request, which requires the following
585   * attributes:
586   *
587   *    attributes-charset
588   *    attributes-natural-language
589   *    printer-uri
590   *    requested-attributes
591   */
592 
593   request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES);
594 
595   ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri);
596 
597   ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", sizeof(requested_attrs) / sizeof(requested_attrs[0]), NULL, requested_attrs);
598 
599  /*
600   * Do the request and get back a response...
601   */
602 
603   snprintf(resource, (size_t)resourcesize, "/printers/%s", name);
604 
605   if ((response = cupsDoRequest(http, request, resource)) != NULL)
606   {
607     if ((attr = ippFindAttribute(response, "member-uris", IPP_TAG_URI)) != NULL)
608     {
609      /*
610       * Get the first actual printer name in the class...
611       */
612 
613       DEBUG_printf(("5cups_get_printer_uri: Got member-uris with %d values.", ippGetCount(attr)));
614 
615       for (i = 0; i < attr->num_values; i ++)
616       {
617         DEBUG_printf(("5cups_get_printer_uri: member-uris[%d]=\"%s\"", i, ippGetString(attr, i, NULL)));
618 
619 	httpSeparateURI(HTTP_URI_CODING_ALL, attr->values[i].string.text, scheme, sizeof(scheme), username, sizeof(username), host, hostsize, port, resource, resourcesize);
620 	if (!strncmp(resource, "/printers/", 10))
621 	{
622 	 /*
623 	  * Found a printer!
624 	  */
625 
626           ippDelete(response);
627 
628 	  DEBUG_printf(("5cups_get_printer_uri: Found printer member with host=\"%s\", port=%d, resource=\"%s\"", host, *port, resource));
629 	  return (1);
630 	}
631       }
632     }
633     else if ((attr = ippFindAttribute(response, "printer-uri-supported", IPP_TAG_URI)) != NULL)
634     {
635       httpSeparateURI(HTTP_URI_CODING_ALL, _httpResolveURI(attr->values[0].string.text, uri, sizeof(uri), _HTTP_RESOLVE_DEFAULT, NULL, NULL), scheme, sizeof(scheme), username, sizeof(username), host, hostsize, port, resource, resourcesize);
636       ippDelete(response);
637 
638       DEBUG_printf(("5cups_get_printer_uri: Resolved to host=\"%s\", port=%d, resource=\"%s\"", host, *port, resource));
639 
640       if (!strncmp(resource, "/classes/", 9))
641       {
642         _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("No printer-uri found for class"), 1);
643 
644 	*host     = '\0';
645 	*resource = '\0';
646 
647         DEBUG_puts("5cups_get_printer_uri: Not returning class.");
648 	return (0);
649       }
650 
651       return (1);
652     }
653 
654     ippDelete(response);
655   }
656 
657   if (cupsLastError() != IPP_STATUS_ERROR_NOT_FOUND)
658     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("No printer-uri found"), 1);
659 
660   *host     = '\0';
661   *resource = '\0';
662 
663   DEBUG_puts("5cups_get_printer_uri: Printer URI not found.");
664   return (0);
665 }
666