• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * PPD utilities for CUPS.
3  *
4  * Copyright © 2020-2024 by OpenPrinting.
5  * Copyright © 2007-2018 by Apple Inc.
6  * Copyright © 1997-2006 by Easy Software Products.
7  *
8  * Licensed under Apache License v2.0.  See the file "LICENSE" for more
9  * information.
10  */
11 
12 /*
13  * Include necessary headers...
14  */
15 
16 #include "cups-private.h"
17 #include "ppd-private.h"
18 #include "debug-internal.h"
19 #include <fcntl.h>
20 #include <sys/stat.h>
21 #if defined(_WIN32) || defined(__EMX__)
22 #  include <io.h>
23 #else
24 #  include <unistd.h>
25 #endif /* _WIN32 || __EMX__ */
26 
27 
28 /*
29  * Local functions...
30  */
31 
32 static int	cups_get_printer_uri(http_t *http, const char *name,
33 		                     char *host, int hostsize, int *port,
34 				     char *resource, int resourcesize);
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 #ifdef _CS_DARWIN_USER_TEMP_DIR
230         char		tmppath[1024];	/* Temporary directory */
231 #endif /* _CS_DARWIN_USER_TEMP_DIR */
232 
233 	if ((tmpdir = getenv("TMPDIR")) != NULL && access(tmpdir, W_OK))
234 	  tmpdir = NULL;
235 
236 	if (!tmpdir)
237 	{
238 #ifdef _CS_DARWIN_USER_TEMP_DIR
239 	  if (confstr(_CS_DARWIN_USER_TEMP_DIR, tmppath, sizeof(tmppath)))
240 	    tmpdir = tmppath;
241 	  else
242 #endif /* _CS_DARWIN_USER_TEMP_DIR */
243 	    tmpdir = "/private/tmp";		/* OS X 10.4 and earlier */
244 	}
245 #else
246        /*
247 	* Previously we put root temporary files in the default CUPS temporary
248 	* directory under /var/spool/cups.  However, since the scheduler cleans
249 	* out temporary files there and runs independently of the user apps, we
250 	* don't want to use it unless specifically told to by cupsd.
251 	*/
252 
253 	if ((tmpdir = getenv("TMPDIR")) == NULL)
254 	  tmpdir = "/tmp";
255 #endif /* __APPLE__ */
256 
257         DEBUG_printf(("2cupsGetPPD3: tmpdir=\"%s\".", tmpdir));
258 
259        /*
260 	* Make the temporary name using the specified directory...
261 	*/
262 
263 	tries = 0;
264 
265 	do
266 	{
267 	 /*
268 	  * Get the current time of day...
269 	  */
270 
271 	  gettimeofday(&curtime, NULL);
272 
273 	 /*
274 	  * Format a string using the hex time values...
275 	  */
276 
277 	  snprintf(buffer, bufsize, "%s/%08lx%05lx", tmpdir,
278 		   (unsigned long)curtime.tv_sec,
279 		   (unsigned long)curtime.tv_usec);
280 
281 	 /*
282 	  * Try to make a symlink...
283 	  */
284 
285 	  if (!symlink(ppdname, buffer))
286 	    break;
287 
288 	  DEBUG_printf(("2cupsGetPPD3: Symlink \"%s\" to \"%s\" failed: %s", ppdname, buffer, strerror(errno)));
289 
290 	  tries ++;
291 	}
292 	while (tries < 1000);
293 
294         if (tries >= 1000)
295 	{
296 	  DEBUG_puts("2cupsGetPPD3: Unable to symlink after 1000 tries, returning error.");
297 
298           _cupsSetError(IPP_STATUS_ERROR_INTERNAL, NULL, 0);
299 
300 	  return (HTTP_STATUS_SERVER_ERROR);
301 	}
302       }
303 
304       if (*modtime >= ppdinfo.st_mtime)
305       {
306         DEBUG_printf(("2cupsGetPPD3: Returning not-modified, filename=\"%s\".", buffer));
307         return (HTTP_STATUS_NOT_MODIFIED);
308       }
309       else
310       {
311         DEBUG_printf(("2cupsGetPPD3: Returning ok, filename=\"%s\", modtime=%ld.", buffer, (long)ppdinfo.st_mtime));
312         *modtime = ppdinfo.st_mtime;
313 	return (HTTP_STATUS_OK);
314       }
315     }
316   }
317 #endif /* !_WIN32 */
318 
319  /*
320   * Try finding a printer URI for this printer...
321   */
322 
323   DEBUG_puts("2cupsGetPPD3: Unable to access local file, copying...");
324 
325   if (!http)
326   {
327     if ((http = _cupsConnect()) == NULL)
328     {
329       DEBUG_puts("2cupsGetPPD3: Unable to connect to scheduler.");
330       return (HTTP_STATUS_SERVICE_UNAVAILABLE);
331     }
332   }
333 
334   if (!cups_get_printer_uri(http, name, hostname, sizeof(hostname), &port, resource, sizeof(resource)))
335   {
336     DEBUG_puts("2cupsGetPPD3: Unable to get printer URI.");
337     return (HTTP_STATUS_NOT_FOUND);
338   }
339 
340   DEBUG_printf(("2cupsGetPPD3: Printer hostname=\"%s\", port=%d", hostname, port));
341 
342   if (cupsServer()[0] == '/' && !_cups_strcasecmp(hostname, "localhost") && port == ippPort())
343   {
344    /*
345     * Redirect localhost to domain socket...
346     */
347 
348     strlcpy(hostname, cupsServer(), sizeof(hostname));
349     port = 0;
350 
351     DEBUG_printf(("2cupsGetPPD3: Redirecting to \"%s\".", hostname));
352   }
353 
354  /*
355   * Remap local hostname to localhost...
356   */
357 
358   httpGetHostname(NULL, localhost, sizeof(localhost));
359 
360   DEBUG_printf(("2cupsGetPPD3: Local hostname=\"%s\"", localhost));
361 
362   if (!_cups_strcasecmp(localhost, hostname))
363     strlcpy(hostname, "localhost", sizeof(hostname));
364 
365  /*
366   * Get the hostname and port number we are connected to...
367   */
368 
369   httpGetHostname(http, http_hostname, sizeof(http_hostname));
370   http_port = httpAddrPort(http->hostaddr);
371 
372   DEBUG_printf(("2cupsGetPPD3: Connection hostname=\"%s\", port=%d",
373                 http_hostname, http_port));
374 
375  /*
376   * Reconnect to the correct server as needed...
377   */
378 
379   if (!_cups_strcasecmp(http_hostname, hostname) && port == http_port)
380     http2 = http;
381   else if ((http2 = httpConnect2(hostname, port, NULL, AF_UNSPEC,
382 				 cupsEncryption(), 1, 30000, NULL)) == NULL)
383   {
384     DEBUG_puts("2cupsGetPPD3: Unable to connect to server");
385 
386     return (HTTP_STATUS_SERVICE_UNAVAILABLE);
387   }
388 
389  /*
390   * Get a temp file...
391   */
392 
393   if (buffer[0])
394     fd = open(buffer, O_CREAT | O_TRUNC | O_WRONLY, 0600);
395   else
396     fd = cupsTempFd(tempfile, sizeof(tempfile));
397 
398   if (fd < 0)
399   {
400    /*
401     * Can't open file; close the server connection and return NULL...
402     */
403 
404     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, NULL, 0);
405 
406     if (http2 != http)
407       httpClose(http2);
408 
409     return (HTTP_STATUS_SERVER_ERROR);
410   }
411 
412  /*
413   * And send a request to the HTTP server...
414   */
415 
416   strlcat(resource, ".ppd", sizeof(resource));
417 
418   if (*modtime > 0)
419     httpSetField(http2, HTTP_FIELD_IF_MODIFIED_SINCE,
420                  httpGetDateString(*modtime));
421 
422   status = cupsGetFd(http2, resource, fd);
423 
424   close(fd);
425 
426  /*
427   * See if we actually got the file or an error...
428   */
429 
430   if (status == HTTP_STATUS_OK)
431   {
432     *modtime = httpGetDateTime(httpGetField(http2, HTTP_FIELD_DATE));
433 
434     if (tempfile[0])
435       strlcpy(buffer, tempfile, bufsize);
436   }
437   else if (status != HTTP_STATUS_NOT_MODIFIED)
438   {
439     _cupsSetHTTPError(http2, status);
440 
441     if (buffer[0])
442       unlink(buffer);
443     else if (tempfile[0])
444       unlink(tempfile);
445   }
446   else if (tempfile[0])
447     unlink(tempfile);
448 
449   if (http2 != http)
450     httpClose(http2);
451 
452  /*
453   * Return the PPD file...
454   */
455 
456   DEBUG_printf(("2cupsGetPPD3: Returning status %d", status));
457 
458   return (status);
459 }
460 
461 
462 /*
463  * 'cupsGetServerPPD()' - Get an available PPD file from the server.
464  *
465  * This function returns the named PPD file from the server.  The
466  * list of available PPDs is provided by the IPP @code CUPS_GET_PPDS@
467  * operation.
468  *
469  * You must remove (unlink) the PPD file when you are finished with
470  * it. The PPD filename is stored in a static location that will be
471  * overwritten on the next call to @link cupsGetPPD@, @link cupsGetPPD2@,
472  * or @link cupsGetServerPPD@.
473  *
474  * @since CUPS 1.3/macOS 10.5@
475  */
476 
477 char *					/* O - Name of PPD file or @code NULL@ on error */
cupsGetServerPPD(http_t * http,const char * name)478 cupsGetServerPPD(http_t     *http,	/* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */
479                  const char *name)	/* I - Name of PPD file ("ppd-name") */
480 {
481   int			fd;		/* PPD file descriptor */
482   ipp_t			*request;	/* IPP request */
483   _ppd_globals_t	*pg = _ppdGlobals();
484 					/* Pointer to library globals */
485 
486 
487  /*
488   * Range check input...
489   */
490 
491   if (!name)
492   {
493     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("No PPD name"), 1);
494 
495     return (NULL);
496   }
497 
498   if (!http)
499     if ((http = _cupsConnect()) == NULL)
500       return (NULL);
501 
502  /*
503   * Get a temp file...
504   */
505 
506   if ((fd = cupsTempFd(pg->ppd_filename, sizeof(pg->ppd_filename))) < 0)
507   {
508    /*
509     * Can't open file; close the server connection and return NULL...
510     */
511 
512     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, NULL, 0);
513 
514     return (NULL);
515   }
516 
517  /*
518   * Get the PPD file...
519   */
520 
521   request = ippNewRequest(IPP_OP_CUPS_GET_PPD);
522   ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "ppd-name", NULL,
523                name);
524 
525   ippDelete(cupsDoIORequest(http, request, "/", -1, fd));
526 
527   close(fd);
528 
529   if (cupsLastError() != IPP_STATUS_OK)
530   {
531     unlink(pg->ppd_filename);
532     return (NULL);
533   }
534   else
535     return (pg->ppd_filename);
536 }
537 
538 
539 /*
540  * 'cups_get_printer_uri()' - Get the printer-uri-supported attribute for the
541  *                            first printer in a class.
542  */
543 
544 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)545 cups_get_printer_uri(
546     http_t     *http,			/* I - Connection to server */
547     const char *name,			/* I - Name of printer or class */
548     char       *host,			/* I - Hostname buffer */
549     int        hostsize,		/* I - Size of hostname buffer */
550     int        *port,			/* O - Port number */
551     char       *resource,		/* I - Resource buffer */
552     int        resourcesize)		/* I - Size of resource buffer */
553 {
554   int		i;			/* Looping var */
555   ipp_t		*request,		/* IPP request */
556 		*response;		/* IPP response */
557   ipp_attribute_t *attr;		/* Current attribute */
558   char		uri[HTTP_MAX_URI],	/* printer-uri attribute */
559 		scheme[HTTP_MAX_URI],	/* Scheme name */
560 		username[HTTP_MAX_URI];	/* Username:password */
561   static const char * const requested_attrs[] =
562 		{			/* Requested attributes */
563 		  "member-uris",
564 		  "printer-uri-supported"
565 		};
566 
567 
568   DEBUG_printf(("4cups_get_printer_uri(http=%p, name=\"%s\", host=%p, hostsize=%d, resource=%p, resourcesize=%d)", http, name, host, hostsize, resource, resourcesize));
569 
570  /*
571   * Setup the printer URI...
572   */
573 
574   if (httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL, "localhost", 0, "/printers/%s", name) < HTTP_URI_STATUS_OK)
575   {
576     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to create printer-uri"), 1);
577 
578     *host     = '\0';
579     *resource = '\0';
580 
581     return (0);
582   }
583 
584   DEBUG_printf(("5cups_get_printer_uri: printer-uri=\"%s\"", uri));
585 
586  /*
587   * Build an IPP_GET_PRINTER_ATTRIBUTES request, which requires the following
588   * attributes:
589   *
590   *    attributes-charset
591   *    attributes-natural-language
592   *    printer-uri
593   *    requested-attributes
594   */
595 
596   request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES);
597 
598   ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri);
599 
600   ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", sizeof(requested_attrs) / sizeof(requested_attrs[0]), NULL, requested_attrs);
601 
602  /*
603   * Do the request and get back a response...
604   */
605 
606   snprintf(resource, (size_t)resourcesize, "/printers/%s", name);
607 
608   if ((response = cupsDoRequest(http, request, resource)) != NULL)
609   {
610     if ((attr = ippFindAttribute(response, "member-uris", IPP_TAG_URI)) != NULL)
611     {
612      /*
613       * Get the first actual printer name in the class...
614       */
615 
616       DEBUG_printf(("5cups_get_printer_uri: Got member-uris with %d values.", ippGetCount(attr)));
617 
618       for (i = 0; i < attr->num_values; i ++)
619       {
620         DEBUG_printf(("5cups_get_printer_uri: member-uris[%d]=\"%s\"", i, ippGetString(attr, i, NULL)));
621 
622 	httpSeparateURI(HTTP_URI_CODING_ALL, attr->values[i].string.text, scheme, sizeof(scheme), username, sizeof(username), host, hostsize, port, resource, resourcesize);
623 	if (!strncmp(resource, "/printers/", 10))
624 	{
625 	 /*
626 	  * Found a printer!
627 	  */
628 
629           ippDelete(response);
630 
631 	  DEBUG_printf(("5cups_get_printer_uri: Found printer member with host=\"%s\", port=%d, resource=\"%s\"", host, *port, resource));
632 	  return (1);
633 	}
634       }
635     }
636     else if ((attr = ippFindAttribute(response, "printer-uri-supported", IPP_TAG_URI)) != NULL)
637     {
638       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);
639       ippDelete(response);
640 
641       DEBUG_printf(("5cups_get_printer_uri: Resolved to host=\"%s\", port=%d, resource=\"%s\"", host, *port, resource));
642 
643       if (!strncmp(resource, "/classes/", 9))
644       {
645         _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("No printer-uri found for class"), 1);
646 
647 	*host     = '\0';
648 	*resource = '\0';
649 
650         DEBUG_puts("5cups_get_printer_uri: Not returning class.");
651 	return (0);
652       }
653 
654       return (1);
655     }
656 
657     ippDelete(response);
658   }
659 
660   if (cupsLastError() != IPP_STATUS_ERROR_NOT_FOUND)
661     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("No printer-uri found"), 1);
662 
663   *host     = '\0';
664   *resource = '\0';
665 
666   DEBUG_puts("5cups_get_printer_uri: Printer URI not found.");
667   return (0);
668 }
669